Mafia의 진실
Mafia의 진실 2010.02.10

낚시성 제목에 방문하신 분께는 죄송합니다. 이 글은 오래전부터 twitter 사용자들을 대상으로 퍼져나가고 있는 'Mobster world'라는 온라인 MMORPG 게임과 관련된 글입니다. 트위터 사용자 분들 중에는 위와 유..

iPhoto 슬라이드 쇼

트위터에 질문이 하나 올라와 짧게 작성해봤습니다. iPhoto에서 슬라이드 쇼 생성하는 방법입니다. 1. 먼저 사진 메뉴에서 슬라이드 쇼에 추가할 사진을 선택합니다. 사진 선택은 Command 키와 마우스를 이용합니다. 2...

노키아에서 동작하는 Mac OS X 10.3

핀란드에 사는 Toni Nikkanen이라는 분이 자신의 Nokia N900 모델에 Mac OS 10.3 Panther를 설치하고 실행시키는데 성공했다고 합니다. Toni의 블로그에 가보니 Mac OS X 말고도 Windows..

개요

디버깅 절차를 단순화 시켜보면 아래와 같습니다.
 
  1. 디버기를 실행시킨다.
  2. 디버기를 정지시킨다.
  3. 메모리(데이터 및 코드)나 레지스터의 값을 조사한다.
  4. 다시 1부터 반복한다.

뭐.. 디버기를 어떻게 정지시키는가, 어디에서 정지시키는가 하는 문제가 디버깅의 효율성을 좌우하는 중요한 문제라는 것은 누구나 동의하는 이야기죠. 디버기를 정지시키는 방법 중 가장 주된 수단은 브레이크 포인트입니다. 코드나 데이터 영역에 브레이크 포인트를 설정해 놓고 해당 코드를 실행시키거나 메모리 영역에 접근할 때 디버기가 멈추도록 하는 것이죠. 잘 알려진 안티리버싱 기법들 중에는 이러한 브레이크 포인트를 무력화 시키는 것들이 있습니다. 알고나면 우회하는 것이 별것 아니지만 어쨌든 짜증나는 녀석들입니다. 그렇지 않아도 삽질할 일이 많은데... 매번 느끼는 것이지만 어떤 의미에서 리버싱은 제로섬 게임같다는 느낌이 듭니다. 어쨌든 이번에는 브레이크 포인트 무력화 기법에 대해서 알아보도록 하겠습니다.  시리즈 중  첫번째는 하드웨어 브레이크 포인트 무력화 기법에 대한 것이며, 두번째는 소프트웨어 브레이크 포인트 무력화 기법에 대한 내용입니다.


브레이크 포인트의 종류

브레이크 포인트는 분류 기준에 따라 아래와 같이 나눠볼 수 있습니다. (왜 티스토리에는  표를 삽입하는 기능이 없을까요...)

구현 방식에 따라 크게 2가지 유형으로 분류할 수 있습니다.
  - 하드웨어 브레이크 포인트
    IA32에서 제공하는 디버그 레지스터를 이용하는 브레이크 포인트입니다.
    IA32에는 DR0~DR3까지의 레지스터에 브레이크 포인트를 설정할 주소를 지정하고
    DR7를 브레이크 포인트를 제어하는 용도로 사용합니다. 보다 자세한 설명은 아래 부분에
    있는 [하드웨어 브레이크 포인트의 이해]부분을 참고하세요.  
    하드웨어 브레이크 포인트는 아무래도 아키텍쳐에서 직접 지원하는 기능이므로, 보다 빠르고
    정확하다는 장점이 있지만 레지스터의 개수가 제한되는 이유로 동시에 설정할 수 있는
    하드웨어 브레이크 포인트의 개수 역시 제한된다는 단점이 있습니다.
 
 - 소프트웨어 브레이크 포인트
    디버그 레지스터를 사용하지 않고 디버거가 직접 관리하는 브레이크 포인트입니다.  
    예를들어 소트웨어 방식으로 구현된 인스트럭션 브레이트 포인트(아래 참고)의 경우
    디버그 레지스터를 사용하는 대신에 명령어의 첫번째 바이트를 0xcc(INT 3)으로 변경하는
    방식을 사용합니다. INT 3는 디버그 인터럽트로 정의되어 있으며 INT 3을 전달받은 프로세스
    는 실행이 일시 정지됩니다.
 
기능에 따라 크게 2가지 유형으로 분류할 수 있습니다.
  - 인스트럭션 브레이크 포인트
    특정 번지의 명령어에 설정되는 브레이크 포인트입니다. 디버기는 실행되다가 인스트럭션
    브레이크 포인트를 만나면 명령어는 실행되지 않은 채로 일시 정지됩니다.  하드웨어/소프트
    웨어 방식으로 구현됩니다.

  - 메모리 브레이크 포인트
    흔히 개발자들 사이에서 워치 포인트라고 불리는 것이 바로 메모리 브레이크 포인트입니다.
    메모리 브레이크 포인트는 특정 메모리 영역에 위치한 데이터를 읽거나 변경할 때 디버기를
    멈추고자 할 때 사용합니다. 역시 소프트웨어와 하드웨어 브레이크 포인트 형태로 구현됩니다.
    코드 영역도 어차피 프로세스의 메모리 영역의 일부이므로 메모리 브레이크 포인트를 활용하여
    인스트럭션 브레이크 포인트와 동일한 효과를 얻을 수 있습니다.
 
그 밖에도 응용 방법에 따라서 조건부 브레이크 포인트, 로그 브레이크 포인트 등 다양한 형태의 브레이크 포인트가 존재합니다.

하드웨어 브레이크 포인트 탐지 및 무력화
널리 사용되는 브레이크 포인트 탐지/무력화 기법은 크게 소프트웨어 브레이크 포인트 탐지/무력화 방법과 하드웨어 브레이크 포인트 탐지/무력화 방법, 메모리 브레이크 포인트 무력화 방법 등으로 구분됩니다. 이번 글에서는 하드웨어 브레이크 포인트 탐지 및 무력화에 대해서 알아보도록 하겠습니다.

하드웨어 브레이크 포인트의 이해
하드웨어 브레이크 포인트는 IA32에서 제공하는 디버그 레지스터를 이용하여 구현된 브레이크 포인트를 지칭하는 용어입니다. 메모리 브레이크 포인트나 인스트럭션 브레이크 포인트 모두 하드웨어 브레이크 포인트로 구현이 가능합니다.  IA32에서는 DR0~DR7 까지 모두 8개의 디버그 레지스터를 제공합니다.

이 중 DR0~DR3까지 4개의 레지스터는 브레이크 포인트를 설정할 곳의 주소를 지정하는데 사용됩니다. 이 때문에 하드웨어 브레이크 포인트는 동시에 4개만 설정 가능합니다.

DR4와 DR5는 현재는 사용하지 않습니다.

DR7은 Debug Control Register입니다. 그림은 생략합니다만, 이 레지스터의 역할은 하드웨어 브레이크 포인트의 활성화 여부와 유형(인스트럭션,메모리)에 대한 정보를 담고 있습니다.하위 8bytes는 활성화 여부를 나타내는데, 이 중 0,2,4,6 번째 bit는 local enable 비트이며 1,3,5,7 번째 bit는 global enable 비트로 사용됩니다. 예를들어 0번 비트의 값이 1로 설정되어 있다면 DR0에 기록된 주소에 관련하여 하드웨어 브레이크 포인트가 활성화 되어 있다는 이야기입니다. 이 때 브레이크 포인트의 유형은 16~23번째 비트에서 결정됩니다. 이 값이 이진수 00이면 인스트럭션 브레이크 포인트임을 의미하며 01이면 메모리 브레이크 포인트(write), 11이면 메모리 브레이크 포인트(read/write)를 의미합니다. 메모리 브레이크 포인트인 경우 24~31번째 bit내에 위치한 2개의 bit가 모니터링할 사이즈를 지정하게 됩니다. (헉헉 그림은 나중에.. 궁금하시면 IA32 메뉴얼 참고하세요.)

DR6은 debug staus 레지스터로 브레이크 포인트가 히트되었는지에 대한 정보를 가지고 있다고 보면 됩니다.
 
하드웨어 브레이크 포인트 탐지 및 무력화
IA32 메뉴얼에는 디버그 레지스터에 대해서 다음과 같이 기술되어 있습니다.

The debug registers are privileged resources; a MOV instruction that access these registers can only be executed in real-address mode, in SIMM, or in protected mode at a CPL of 0. An attemp to read or write the debug regiters from any other privilege level generates a general protection exception (#GP).

IA32 메뉴얼에 의하면 디버그 레지스터는 일반적으로 Ring0 모드에서만 접근할 수 있겠군요. 다시 말해  일반 애플리케이션에서는 디버그 레지스터의 값을 읽거나 변경할 수 없다는 이야기죠. 그런데 MS 윈도우의 경우 SEH를 이용하면 Ring3에서도 디버그 레지스터의 값을 읽거나 변경하는 것이 가능합니다. 좀 더 자세하게 알아볼까요. 익셉션이 발생했을 때 실행되는 익셉션 핸들러의 프로토타입은 아래와 같습니다.

__cdecl  _except_handler (

    struct _EXCEPTION_RECORD *ExceptionRecord,

    void * EstablisherFrame,

    struct _CONTEXT * ContextRecord,

    void * DispatcherContext );

익셉션 핸들러의 프로토타입에 나타난 세번째 아규먼트 ContextRecord를 이용하면 익셉션이 발생했을 당시의 컨텍스트 즉 레지스터들의 값에 접근하거나 변경하는 것이 가능합니다. 익셉션이 발생했을 때 익셉션 핸들러가 익셉션 발생 당시의 레지스터의 값을 조사하고 수정이 필요한 경우 수정할 수 있도록 해주기 위해서 이렇게 디자인된거죠. 재미있는 사실은 _CONTEXT 구조체 안에 디버그 레지스터가 포함되어 있다는 사실입니다.

정리해보겠습니다. 하드웨어 브레이크 포인트 탐지는 디버그 레지스터의 값을 읽어야만 가능하고 하드웨어 브레이크 포인트를 무력화시키는 것은 디버그 레지스터의 값을 변경할 수 있을 때만 가능합니다. IA32 아키텍쳐에서는 Ring0 모드에서만 디버그 레지스터에 접근할 수 있도록 제한을 하고 있지만 윈도우즈의 경우 SEH를 이용하면 Ring3 모드에서도 디버그 레지스터의 값을 읽거나 변경하는 것이 가능하다는 거죠. 그 방법은 아래와 같습니다.

  1. 디버그 레지스터의 값을 읽고, 변경하는 즉 하드웨어 브레이크 포인트를 탐지하고 무력화 시키
     는 코드를 SEH 핸들러로 등록한다.
  2. 익셉션을 유발시킨다.
  3. 미리 등록해 놓은 SEH 핸들러가 실행된다. 핸들러는 세번째 아규먼트인 ContextRecord를
     이용하여 디버그 레지스터의 값을 조사하거나, 디버그 레지스터의 값을 변경하는 방법으로
     하드웨어 브레이크 포인트를 무력화 시킬 수 있다.  
 
비교적 단순하지 않습니까? 그럼 위의 내용을 바탕으로 간단한 코드를 구현해 보도록 하겠습니다. 코드 구현에는 MASM을 사용하였습니다.  주석을 달아놓았으니 참고하세요.^^;

[예제 코드 1] 하드웨어 브레이크 포인트 탐지
;
; Hardware Breakpoint Detection Example by zer0one (
zer0one@xstone.org)
;
;

   .586
   .model flat, stdcall
   option casemap :none

      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc
      include \masm32\include\comdlg32.inc
     
      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
      includelib \masm32\lib\comdlg32.lib
     
     
  .data
     HardwareBPXTitle db  "Hardware Detection Example by zer0one",0h
     DetectedMsg db "브레이크포인트가 탐지되었습니다",0h,0h
     NotDetectedMsg db "브레이크 포인트가 탐지되지 않았습니다",0h,0h
     SetBPXHere db "여기에 브레이크 포인트를 설정해주세요",0h,0h
          
    .code

start:

   ASSUME FS:NOTHING
 
   ;익셉션 핸들러 설치
   PUSH  offset @DetectHardwareBPX
   PUSH FS:[0]                   
   MOV  FS:[0], ESP
 
   ;하드웨어 브레이크포인트가 설정되었는지를 검사하기 위해 익셉션 유발
   ;보통 익셉션 유발은 Access violation을 유발시키거나
   ;INT 3을 사용하거나, 잘못된 코드를 삽입하거나..암튼 방법이 많습니다.
   ;아래의 코드는 Access violation을 유발시키는 코드의 한 예에 불과합니다.
   XOR  EAX, EAX
   XCHG [EAX], EAX

   ;보호되는 코드
   PUSH 40h
   PUSH offset HardwareBPXTitle
   PUSH offset SetBPXHere
   PUSH 0
   CALL MessageBox
 
   POP FS:[0]   ;Exception List 복구
   ADD ESP, 4h  ;스택 청소 쓱싹~쓱싹~
   
   PUSH 0
   CALL ExitProcess
   
 @DetectHardwareBPX :
    PUSH EBP  
    MOV  EBP, ESP  ;익셉션 핸들러를 위한 스택 프레임 오픈
                   ;사실 예제에서는 필요없음. ㅠ.ㅠ;
    
    PUSH ESI ;callee saved register
             ;ContextRecord를 가르키기 위해 사용할 것입니다.
    
    MOV  ESI, [EBP + 10h]  ;3번째 아규먼트(ContextRecord)의 값을 EAX에 저장
    CMP  DWORD PTR DS:[ESI+04h], 0h ;DR0의 값이 0인지 비교
    JNE  @Detected
    CMP  DWORD PTR DS:[ESI+08h], 0h ;DR1의 값이 0인지 비교
    JNE  @Detected
    CMP  DWORD PTR DS:[ESI+0Ch], 0h ;DR2의 값이 0인지 비교
    JNE  @Detected
    CMP  DWORD PTR DS:[ESI+10h], 0h ;DR3의 값이 0인지 비교
    JNE  @Detected
 
    PUSH 30h
    PUSH offset HardwareBPXTitle
    PUSH offset NotDetectedMsg
    PUSH 0
    CALL MessageBox
   
    JMP @EndOfDetectHardwareBPX
    
  @Detected :
    PUSH 30h
    PUSH offset HardwareBPXTitle
    PUSH offset DetectedMsg
    PUSH 0
    CALL MessageBox
   
  @EndOfDetectHardwareBPX :
   
    ; ContextRecord 상의 EIP값을 변경합니다.
    ; 변경 전 EIP는 XCHG [EAX], EAX 부분을 가르키지만
    ; 변경 후에는  PUSH 40h 를 가르키게 됩니다.
    ; 이 작업을 해 주지 않으면 계속 Exception이 발생하겠죠.
    ; 다른 방법도 있지만 SEH 안에서 EIP값을 변경할 수 있다는
    ; 것도 이야기 할 겸 겸사겸사 직접 EIP를 변경하였습니다.   
    MOV  ECX, DWORD PTR DS:[ESI+0B8h]
    ADD  ECX, 2 ;XCHG [EAX], EAX는 2bytes
    MOV  DWORD PTR DS:[ESI+0B8h], ECX
   
    XOR  EAX, EAX ;EAX=0 익셉션 핸들러의 리턴값이 0이면
                  ;컨텍스트를 다시 로드한 다음 실행을 계속합니다.
                 
    POP  ESI      ;ESI값 복구
    MOV  ESP, EBP ;지역변수 해제(쓴것도 없는데..ㅠ.ㅠ;)
    POP  EBP      ;프레임포인터 복구
    RET  

end start

다음은 예제 코드를 테스트하는 화면입니다. 먼저 [그림 1]에서와 같이 보호되는 코드중 메시지박스 호출 부분에 하드웨어 브레이크 포인트를 설정합니다.

사용자 삽입 이미지
[그림 1] 하드웨어 브레이크 포인트 설정

이제 [F9]를 눌러 코드를 실행시켜 보겠습니다. 아래 [그림 2]에서 처럼 00401015번지에서 익셉션이 발생하였습니다.

사용자 삽입 이미지
      [그림 2] 익셉션 발생

이 때 Ollydbg의 하단 메시지바를 살펴보면 [그림 3]과 같습니다.
사용자 삽입 이미지
      [그림 3] 익셉션 발생 시 하단 메시지바의 내용

시키는 대로 Shift+F9를 눌러 보겠습니다. 익셉션이 발생한 경우 위와 같이 Shift 버튼을 누른채 F7/F8/F9를 누르면 디버기로 익셉션이 전달되어 익셉션 핸들러가 실행됩니다. 잠시 후에 아래와 같이 메시지 박스가 출력될 것입니다.

사용자 삽입 이미지
      [그림 4] 브레이크 포인트 탐지

탐지가 잘 되는 군요.. ^^; 예제에서는 탐지된 후에 메시지박스를 띄웠지만 실제로는 리버싱을 방해할 만한 행동을 할 것입니다. 예를 들어 예제코드를 살짝 비틀어 EIP값을 엉뚱한 곳으로 변경한다든지 아니면 곧이어 설명할 것처럼 하드웨어 브레이크 포인트를 제거한다든지, 실행에 영향을 끼치는 레지스터의 값을 변경한다든지 뭐 여러가지 방법을 사용할 수 있겠습니다.

하드웨어 브레이크 포인트 무력화
하드웨어 브레이크 포인트를 제거하는 방법 역시 매우 간단합니다. SEH 핸들러 안에서 DR0~DR3에 저장되어 있는 값을 모두 0으로 조작하면 됩니다. 아래 예제 코드가 있습니다.

 @DetectHardwareBPX :
    PUSH EBP  
    MOV  EBP, ESP    
    PUSH ESI ;calle saved register
              ;ContextRecord를 가르키기 위해 사용할 것입니다.
    
    MOV  ESI, [EBP + 10h]  ;3번째 아규먼트(ContextRecord)의 값을 EAX에 저장
    MOV  DWORD PTR DS:[ESI+04h], 0h ;DR0의 값을 0으로 초기화
    MOV  DWORD PTR DS:[ESI+08h], 0h ;DR1의 값을 0으로 초기화
    MOV  DWORD PTR DS:[ESI+0Ch], 0h ;DR2의 값을 0으로 초기화
    MOV  DWORD PTR DS:[ESI+10h], 0h ;DR3의 값을 0으로 초기화
             
    PUSH 30h
    PUSH offset HardwareBPXTitle
    PUSH offset DetectedMsg ;메시지 내용 : 지웠지롱~
    PUSH 0
    CALL MessageBox
   
    MOV  ECX, DWORD PTR DS:[ESI+0B8h]
    ADD  ECX, 2 ;XCHG [EAX], EAX는 2bytes
    MOV  DWORD PTR DS:[ESI+0B8h], ECX
   
    XOR  EAX, EAX ;EAX=0 익셉션 핸들러의 리턴값이 0이면
                  ;컨텍스트를 다시 로드한 다음 실행을 계속합니다.
                 
    POP  ESI      ;ESI값 복구
    MOV  ESP, EBP ;지역변수 해제(쓴것도 없는데..ㅠ.ㅠ;)
    POP  EBP      ;프레임포인터 복구
    RET
   


하드웨어 브레이크 포인트 제거 기법 우회
하드웨어 브레이크 포인트 탐지나 제거 기법이 구현되어 있는 경우 우회는 어떻게 해야 할까요? 사실 정답은 없습니다. 우회 방법은 상황에 따라 다를 것이며 지금까지의 내용을 잘 이해하고 있다면 잠깐의 생각만으로도 충분히 우회 가능한 방법들을 찾아낼 수 있을 것입니다. 그 중 간단한 방법 몇 가지만 살펴보도록 하겠습니다.
 
* 소프트웨어 브레이크 포인트 사용
하... 다소 어처구니 없는 이야기처럼 들릴수도 있겠습니다만, 하드웨어 브레이크 포인트 탐지와 소프트웨어 브레이크 포인트 탐지 기법이 동시에 사용되지 않은 경우라면 제일 효율적인 방법이 될 수 있겠죠. ^^; 물론 소프트웨어 브레이크 포인트를 탐지/제거하는 루틴이 포함되어 있다면 당연히 무의미 하겠습니다.

* 브레이크 포인트 설정 타이밍 조정
이러한 방식의 브레이크 포인트 탐지 및 무력화 방법의 단점은 어느 한 시점에서 브레이크 포인트 탐지 및 제거를 수행한다는 것입니다. 따라서 이러한 코드를 식별해 낼 수 있다면 탐지/제거 코드 이후에 브레이크 포인트를 설정하는 방법을 통해 간단히 우회할 수 있겠습니다. 유저모드 애플리케이션에 구현되어 있는 하드웨어 브레이크 포인트를 탐지하는 하는 코드를 식별해 내는 방법 중 도움이 될 만한 사실은 아래와 같습니다. 유저모드 애플리케이션에서 하드웨어 브레이크 포인트를 탐지하거나 제거하려면 반드시 SEH를 호출해야 한다는 사실을 이용한 것들입니다.

- FS:[0]을 보거든 SEH를 떠올려라. ^^;
- 익셉션 핸들러에서 스택 프레임을 사용하는 경우 EBP+10h가 의미하는 것은 아규먼트로 전달받은 ContextRecord 값이다. 이러한 코드를 발견하면 ContextRecord를 전달받아 어떠한 일을 하는지 살펴볼 필요가 있다.
 - 익셉션 핸들러에서 스택 프레임을 사용하지 않는 경우에는 ESP+0ch가 ContextRecord를 의미한다.
-  리버싱 중 익셉션 리스트를 종종 살펴보라. (Ollydbg의 View->SEH chain, Windbg의 경우 아래 명령어 참고)

0:000> !list -t _EXCEPTION_REGISTRATION_RECORD.Next -x "dt   _EXCEPTION_REGISTRATION_RECORD @$extret" poi(fs:[0])
ntdll!_EXCEPTION_REGISTRATION_RECORD
 +0x000 Next    : 0x0012fcf0 _EXCEPTION_REGISTRATION_RECORD
 +0x004 Handler : 0x77368bf2 _EXCEPTION_DISPOSITION ntdll!_except_handler4+0

ntdll!_EXCEPTION_REGISTRATION_RECORD
 +0x000 Next    : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
 +0x004 Handler : 0x77368bf2 _EXCEPTION_DISPOSITION ntdll!_except_handler4+0

ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : ????
   +0x004 Handler          : ????
Memory read error 00000003

cf.
악성코드 등이 커널모드 드라이버 형태로 구성되어 있는 경우라면 이야기가 좀 달라질 수 있습니다. 이 부분에 대해서는 나중에 다루도록 하겠습니다.(언제? -.-;; )

* Exception을 유발시키는 코드를 NOP로 변경
익셉션 핸들러가 실행되지 않아도 코드 실행에 문제가 없는 경우라면 익셉션을 유발시키는 코드를 NOP로 지워버리는 방법도 효과적일 수 있습니다. 아래 예제 그림은 익셉션을 유발시키는 코드 부분을 NOP로 처리하고 하드웨어 브레이크 포인트를 설정하여 실행한 화면입니다. 그림만으로는 실제 브레이크 포인트가 제대로 동작하였는지 파악하기 힘들겠지만 실제로 해보면 쉽게 확인할 수 있습니다. ^^

사용자 삽입 이미지


맺음말
지금까지 하드웨어 브레이크 포인트에 대해서 간단히 알아보았습니다. 내용상 오류가 개선사항/질문은 댓글 또는 zer0one@xstone.org로 메일 주세요. 즐핵~





 



Posted by zesrever

BLOG main image
Slow but Steady, Broad and Deep ... by zesrever

공지사항

카테고리

분류 전체보기 (44)
Digital Forensics (4)
Reverse Engineering (21)
Vulnerability (2)
Secure Coding (0)
Book Story (1)
Digital Life (7)
My Life (7)
세미나자료 (1)
개인용 (0)
Musics (0)
Total : 263,910
Today : 5 Yesterday : 18