[컴][디버그] Breakpoint 의 종류와 작동원리

process 를 멈춰서(halt) 확인할 수 있는 사항
  1. variables
  2. stack
  3. arguments
  4. memory locations

Breakpoints

3가지 breakpoint 가 있다.
  1. soft breakpoints
    1. one-shot breakpoint : 한 번 쓰이고 breakpoint list 에서 사라지는 것
    2. persistent breakpoint : breakpoint list 에서 저장되어 있고, 계속해서 쓰이는 것
  2. hardware breakpoints
  3. memory breakpoints


soft breakpoints


우리가 debugging 할 때 흔히 쓰는 breakpoint 이다.
1-byte 의 instruction 이다.
process 의 실행을 멈추고 control 을 breakpoint exception handler 에 넘겨준다.

instruction 과 opcode 의 차이

instruction 은 MOV, ADD, INC 같은 녀석들이다. 이 녀석 들의 기계어가 바로 opcode(operation code) 이며 이 opcode 는 0x8c, 0x88 등의 숫자로 나타낸다.

댓글에서 꾸지람을 들어서 ㅜ.ㅜ instruction 과 opcode 에 대해 좀 더 알아봤다.

먼저, 간단한 instuction 에 대한 그림을 하나 보자.
<출처 : wiki, http://en.wikipedia.org/wiki/File:Mips32_addi.svg>
위에서 숫자(001000 0001 00010 0000000101011110) 가 우리가 알고 있는 기계어(Machine code) 이다. 이 기계어가 뜻하는 바가 그 밑에 적혀있는 것들이다. (OP Code, Addr 1, Addr 2, Immediate value)

위에 예제에서 보듯이 instruction 을 구성하는 binary 값들중에 operation(명령)을 나타내는 부분이 opcode 이다. 그래서 operation code 라고 하는 것 아닐까 생각된다.

그리고 우리가 흔히 assembler 라고 하는 것이 밑에 mnemonic 이라고 하는 부분에 있다.

결론은 이렇다. instruction 은 명령어에 대한 가장 추상적인 개념이고, 그 instruction 의 실체(기계어) 중 operation 부분이 opcode 이다. 그리고 이 opcode 와 parameter code 등에 대한 mnemonic 이 바로 우리가 아는 assembler 라고 할 수 있겠다.



INT3


soft breakpoint 를 만들기 위해서는 opcode 를 0xCC 로 바꿔야 한다.
예를 들어, 0x8BC3 (2-byte) 가 있다면 이중에 0x8B 1 byte 를 0xCC, interrupt 3(INT 3) instruction,  로 바꿔야 한다. 이 INT 3 instruction 이 halt CPU 를 하는 명령어 이다.
0x89E5 --> 0xCCE5

0x89E5           MOV EBP,ESP
라는 명령어가 있다면,
0xCCE5 로 만드는 것이다.
processor 가 0xCC 를 만나게 되면 execution 을 멈추고 INT3 event 를 발생시키게 된다.


debug-mode run


우리가 debugger에서 breakpoint 를 설정하고 debug mode 로 실행할 때 이런 일이 일어난다.
  1. breakpoint 를 설정하면, debugger 가 첫byte 를 0xCC 로 바꾸고, 기존에 byte(opcode)는 어딘가에 저장해 놓는다.
  2. debug mode 로 run 을 해서 CPU 가 0xCC 를 만나서 INT3 event 가 발생되면, debugger 가 그 interrupt 를 받게 된다.
  3. 그러면, debugger 가 instruction pointer(EIP register) 가 breakpoint list 에 있는 녀석을 가리키고 있는지를 검사한다.
  4. 그래서 breakpoint list 에 있는 녀석이라면, 아까 저장해 놓은 byte(opcode)를 그 주소(0xCC 가 써져 있는)에 다시 쓰게 된다.(write)
  5. 그래서 유저가 resume을 했을 때 opcode 는 다시 온전하게 수행이 된다.


soft breakpoint 와 CRC checksum


이런 soft breakpoint 에서 주의할 점 하나는 memory 에서 opcode 를 0xCC로 바꾸게 되면, cyclic redundancy check(CRC) checksum 도 같이 바뀌게 된다. 는 것이다.
CRC checksum 을 이용해 자신의 packet, file, memory 등의 data 가 변경되는가를 감시할 수 있다.
그리고 어떤 악성코드는 이 CRC checksum 이 변동되면 자기 자신을 kill 하는 것을 이용해 soft breakpoint 를 set 하지 못하게 만든다.


Hardware breakpoint


적은 수의 breakpoint 로 충분하다면 hardware breakpoint 가 알맞을 수 있다. 그리고 hardware breakpoint 는 soft breakpoint 와 다르게 software 의 변경을 가하지 않는다.
soft breakpoint 와 다르게 INT1 event 를 사용한다. 이 INT1 event 는 sigle step event(debugger 에서 next step 을 누를 때) 와 hardware breakpoint 를 위해 쓰인다.
hardware breakpoint 는 debug register 라는 것을 이용해 set 하게 된다.
일반적인 CPU 는 debug register(DR) 를 8개 가지고 있다. (DR0~DR7)
  • DR0~DR3 : breakpoint 들의 address 를 저장하기 위해 쓰인다.
  • DR4, DR5 는 예약되어 있고,
  • DR6는 status register 로 사용된다. 이 status register 는 breakpoint 의 type 을 알려준다.
  • DR7 은 on/off switch 역할을 한다. 그리고 다른 breakpoint 의 조건을 저장한다.
8개의 DR중에 DR0~DR3 만 breakpoint 의 address 를 저장하기 때문에, 실제로는 4개의 hardware breakpoint 를 만들 수 있다는 뜻이 된다.


DR7


DR7 에 특정 flag 를 설정해서 다음과 같은 조건에 호출되는 breakpoint 를 만들 수 있다.
  • 특정주소의 instruction 이 수행됐을 때(0x00)
  • data 가 특정 address 에 write 될 때(0x01)
  • 실행되지는 않지만, 특정 주소를 read 나 write 를 할 때(0x11)


  • Bits 0-7 은 DR0-DR3 의 on/off switch 역할을 하고, L, R field 는 scope를 나타낸다. local 인지, global 인지.
    bit1 은 DR0-local,
    bit2 는 DR0-global 이런 식으로 switch 역할을 한다.
    switch 가 on 이 되면 enable 이 되는 것이다.
  • Bit 8-15 는 일반적인 debugging 목적으로 쓰이지 않는다.
  • Bit (16, 17) - (30, 31) 은 DR0-DR3 에 설정된 breakpoint의 Type과 length 를 나타낸다.
    bit16,17 은 DR0의 type
    bit18,19는 DR0의 length 이런 식으로 쓰인다.


자세한 사항은 아래 그림을 참고하자.
출처 : Gray Hat Python, Chpater 2. Figure 2-4

작동방식


CPU 가 instruction 을 execution 하기 전에 이 instruction address에 hardware breakpoint 가 걸려 있는지 확인한다.
그리고 operator 가 접근하려는 memory 가 hardware breakpoint 에 걸려있는지 확인한다.


제한


hardware breakpoint 의 제한은 4개밖에 없다는 것 이외에, read/write 에 대한 검사가 최대 4-byte 에서만 가능하다는 것이다.
이것을 극복하기 위해 memory breakpoint 가 존재한다.


Memory breakpoint


memory breakpoint 는 실제로 breakpoint 는 아니다. 개인적인 생각에는 우리가 흔히 생각하는 Page Fault 같은 것 같다.
memory 의 가장 작은 단위가 memory page 이다. 이 memory page 가 할당될 때, 이 page 는 특정 permission set 을 갖게 된다. 이런 permission 의 종류에는 아래 4가지가 있다.
  1. Page execution
  2. Page read
  3. Page write
  4. Guard page
OS 에서 이 permission 들을 page 에 할당하게 된다. page 는 이 permission 을 여러 개 가질 수 있다.
그리고, OS 에서 제공하는 함수를 통해서 이 page 가 어떤 permission 을 가졌는지 query 할 수 있고, permission 을 변경할 수도 있다.


Guard Page


여기서 볼 것은 Guard page 이다.
  • heap 을 stack 에서 분리하는 데에 유용하고, 어떤 한계선이상으로 memory 를 쓰지 않게 할 수 있다.
  • 특정 메모리 영역을 접근했을 때 process 를 멈추는데 유용하다.
예를 들어, network 에 연결된 application 을 우리가 reverse engineering 할 때, packet 을 수신한 메모리 부분에 memory breakpoint(Guard Page) 를 걸어 놓을 수 있다.

이 breakpoint 가 application 이 수신한 packet 내용을 언제, 어떻게 이용되는지 알아낼 수 있게 해 준다.

그 memory page 에 대한 접근은 CPU 를 멈추고, guard page debugging exception 을 발생시킨다.

buffer memory 에 접근한 instruction 이 무엇인지를 조사할 수 있고, 무엇을 하는지 알아낼 수 있다.


Soft breakpoint 와 Guard Page


이런 방법을 이용하면, software 를 변경해야 하는 soft breakpoint 의 대신에 사용해서 software 를 변경하지 않고 execution 을 멈출 수 있다.


References

  1. Gray Hat Python, Chapter 2, 2009년

댓글 2개:

  1. [instruction 과 opcode 의 차이] 에서
    -> inst는 opcode 및 operand를 포함한 CPU의 최소한의(atomic한) 실행 단위를 뜻하고, opcode는 inst의 일부로서 command 시맨틱을 갖는 부분이라는 표현이 정확한 것 같습니다.

    일반적으로 MOV "명령어"라고 하면 물론 뒤에 따라오는 파라미터들도 포함하는 말이기는 하지만,

    어셈블리어가 inst.고 기계어가 opcode다..라는 표현은 오해의 소지가 있어보입니다.^^;

    답글삭제
    답글
    1. 감사합니다. ㅜ.ㅜ 글 수정했습니다.

      삭제