-榮-

디버깅 실습2 - Self-Creation (JIT 디버깅) 본문

리버싱 핵심원리

디버깅 실습2 - Self-Creation (JIT 디버깅)

xii.xxv 2024. 1. 16. 02:29

1. Self-Creation

실행 중에 자기 자신을 자식(Child) 프로세스로 생성시키는 것을 Self-Creation이라고 한다.

Self-Creation 기법

이 파일들은 같은 실행 파일이면서 부모(Parent)로 실행될 때와 자식으로 실행될 때 서로 다르게 동작한다. , 하나의 실행 파일에 두 가지 실행 경로가 존재하는 것이다.

Self-Creation 동작 화면

 

 - 동작 원리

Self-Creation 기법의 동작 원리는 아래 그림과 같다.

Self-Creation 기법 동작 원리

 

● Create Child Process (SUSPEND mode)

부모 프로세스가 실행되면 main() 함수가 호출되고, main()에서 SUSPEND 모드로 자식 프로세스를 생성한다.

SUSPEND 모드로 생성되면, Import DLL들은 로딩되지만 메인 스레드(Main Thread)가 멈춘 상태가 된다.(‘스레드를 재운다’라고 표현)
메인 스레드는 프로세스가 생성될 때 기본적으로 생성되는 스레드로, EP 코드를 실행시키는 가장 중요한 역할을 수행한다.

따라서 SUSPEND 모드로 실행된 프로세스는 메인 스레드가 멈추면서 EP 코드가 실행되지 못하고 아무런 동작을 하지 못하는 상태가 된다.

 

Change EIP

외부의 프로세스의 스레드가 실행할 코드 주소를 임의로 변경하는 것은 디버거-디버기 관계에서 가능하며, 부모 프로세스와 자식 프로세스는 디버거-디버기 관계이다. 디버거는 Debugging API를 이용하여 디버기 프로세스의 코드 실행 위치를 맘대로 변경할 수 있다. 부모 프로세스는 현재 멈춰있는 자식 프로세스의 메인 스레드의 컨텍스트(Context)를 얻어서 EIP 멤버를 원하는 주소 값으로 변경한 후 적용하면 된다.

자식 프로세스의 메인 스레드는 PROCESS_INFORMATION 구조체의 hThread 멤버이다. GetThreadContext() APIhThread 핸들을 입력하면 스레드 컨텍스트(CONTEXT) 구조체를 얻어낼 수 있다.

PROCESS_INFORMATION 구조체 정의
typedef struct _PROCESS_INFORMATION {
    HANDLE   hProcess;
    HANDLE   hThread;
    DWORD   dwProcessId;
    DWORD   dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;

출처 : MSDN

EIPCONTEXT 구조체의 Eip 멤버이다(EIP 레지스터 의미). 이 값을 ChildProc()함수 주소로 변경하면 된다.

[ctx.Eip = (DWORD)ChildProc;]

WOW64_CONTEXT 구조체 정의
typedef struct _WOW64_CONTEXT {
    DWORD ContextFlags;
    DWORD Dr0;       //04h
    DWORD Dr1;       //08h
     DWORD Dr2;      //0Ch
     DWORD Dr3;      //10h
     DWORD Dr6;      //14h
     DWORD Dr7;      //18h


     WOW64_FLOATING_SAVE_AREA FloatSave;


     DWORD SegGs;      //88h
     DWORD SegFs;        //90h
     DWORD SegEs;       //94h
     DWORD SegDs;       //98h

     DWORD Edi;       //9Ch
     DWORD Esi;       //A0h
     DWORD Ebx;      //A4h
     DWORD Edx;      //A8h
     DWORD Ecx;      //ACh
     DWORD Eax;      //B0h


     DWORD Ebp;          //B4h
     DWORD Eip;           //B8h
     DWORD SegCs;     //BCh
     DWORD EFlags;     //C0h
     DWORD Esp;          //C4h
     DWORD SegSs;      //C8h


     BYTE ExtendedRegisters[WOW64_MAXIMUM_SUPPORTED_EXTENSION];
} WOW64_CONTEXT;

출처 : MSDN

그리고 SetThreadContext() API를 호출하여 새로 변경된 CONTEXT 구조체를 자식 프로세스의 메인 스레드에 세팅하고 ResumeThread() API를 호출하면 자식 프로세스의 메인 스레드가 깨어나면서 변경된 EIP(=실행할 코드 주소=ChildProc())를 실행한다. 스레드 컨텍스트를 변경하는 것이 핵심이다.

 

● Resum Main Thread

자식 프로세스의 멈춰져 있는 메인 스레드를 실행(‘스레드를 깨운다라고 표현)

이제 메인 스레드는 변경시킨 코드 주소를 실행한다.

 


 

2. 디버깅 실습

 - 고려 사항

Self-Creation 기법의 디버깅에서 고려할 사항은 디버깅 중에 개로 생성되는 자식 프로세스를 어떻게 시작 시점부터 디버깅할 수 있는지이다.

  1. 부모 프로세스를 디버깅하여 자식 프로세스의 EP가 어느 주소로 변경되는지를 확인
  2. ‘무한 루프 설치 방법’ 또는 JIT(Jump-In-Time) 디버깅 방법 사용
SUSPEND 프로세스
Q. 실습 예제에서는 SUSPEND 모드로 자식 프로세스를 생성시키고, 메인 스레드의 EIP를 변경시키는 방식이 사용되었다. 부모 프로세스를 디버깅 중 자식 프로세스가 생성된 순간 다른 디버거로 Attach하면 안 되나?
 
A. 디버거는 Suspended 프로세스를 Attach할 수 없다. 디버거의 Attach 대상 프로세스 목록에 아예 나타나지 않을 것이며, 메인 스레드가 깨어난 상태(Resume)가 되어야 목록에서 확인될 것이다.

 

 - JIT 디버깅

JIT(Jump-In-Time) 디버깅은 실행 중인 프로세스에 예외(Exception)가 발생했을 때 OS에서 자동으로 지정된 디버거를 실행하여 해당 프로세스에 Attach해주는 것이다.

JIT 디버깅은 주로 애플리케이션 개발 과정에서 사용되며, Visual C++을 JIT 디버거로 설정하여 예외가 발생한 소스코드를 직접 확인하는 데 사용된다(소스코드를 가지고 있는 경우).

 

● JIT 디버거 설정

OllyDbg에는 ‘Options - Just-in-debugging’ 메뉴에 자신을 JIT 디버거로 등록시키는 기능이 있다.

Just-in-time debugging 메뉴

이후 다이얼로그에서 ‘Make OllyDbg just-in-time debugger’ 선택

Just-in-time 디버깅 다이얼로드 설정 전/후

OllyDbgJIT 디버거로 등록되었으며, 현재 시스템에서 설정된 JIT 디버거는 아래 레지스트리의 키에서 확인 가능하다.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug


64bit 환경에서는 JIT 설정이 안 된다. 32bit 환경에서 실습 진행하는 것을 추천

OllyDbg JIT 설정 시 regedit (64bit / 32bit)


 

 - 실습 예제 파일(DebugMe2.exe) 디버깅

실습 예제 파일의 디버깅 목표는 자식 프로세스의 (새로운)EP 코드로부터 정확히 디버깅을 하는 것이다.

Self-Creation 기법은 자기 자신을 자식 프로세스로 실행하면서 시작되는 코드(EP code) 위치를 변경하기에 먼저 부모 프로세스를 디버깅하여 자식 프로세스의 시작 코드 주소를 얻어야 한다. 그리고 JIT 디버깅 방법으로 자식 프로세스를 디버깅하면 된다.

 

● 부모 프로세스 디버깅

OllyDbgDebugMe2.exe를 열어 main() 함수까지 진행한다.

JIT 디버거로 등록된 OllyDbg

트레이싱을 하다 보면 CreateProcess() API 호출 코드(401102)가 보이는데 이 코드가 실행되면 SUSPEND 모드의 자식 프로세스가 생성된다.

CreateProcess() 호출 코드 및 파라미터 값

디버깅을 진행하면 GetThreadContext()/SetThreadContext() API 호출 코드가 나타난다. 이 코드에서 자식 프로세스의 메인 스레드 컨텍스트(CONTEXT) 구조체를 구하여 CONTEXT.Eip 값을 ChildProc() 주소로 변경한다.

CONTEXT 구조체의 시작 주소는 GetThreadContext()/SetThreadContex() API의 두 번째 파라미터를 확인하면 알 수 있다.

CONTEXT 구조체의 시작 주소(18FA68)에서 B8h만큼 떨어진 곳에 EIP(18FA68+B8h=18FB20)가 존재한다. EIP 변경 주소는 401186주소에서 401000인 것을 확인할 수 있다.

CONTEXT.Eip 변경 코드

이후는 ResumeThread(pi.hThread) API를 호출하여 자식 프로세스의 메인 스레드를 시작시키고, WaitForSingleObject(pi.hProcess, INFINITE) API를 호출하여 자식 프로세스가 종료할 때까지 기다리는 상태가 된다.

ResumeThread(pi.hThread) API 호출
WaitForSingleObjevt(pi.hProcess, INFINITE) API 호출

 

● 자식 프로세스 디버깅

Stud_PE 유틸리티를 이용하여 401000 주소(VA : Virtual Address)를 파일 옵셋(Offset)으로 변환하면 400이 나온다.

Stud_PE 유틸리티의 RVA <=> RAW 기능

HxD 유틸리티로 파일 옵셋 400 위치의 한 바이트를 0xCC로 변경(원본 값 : 0x6A)한 후 저장한다.

HxD 유틸리티로 400 파일 옵셋 값을 0xCC로 변경

0xCC는 INT3 명령어(Break Point)를 의미한다. , 파일 옵셋 0x400(VA401000)의 0xCC 코드가 실행되면, EXCEPTION_BREAKPOINT 예외가 발생하게 된다.

예외 발생 다이얼로그

현재 시스템에 JIT 디버거가 등록되어 있으므로 ‘Debug’를 선택하면 등록된 디버거에 Attach되어 해당 부분부터 디버깅이 가능하다.

0xCC가 설치된 ChildProc() 함수

401000 주소의 0xCC를 원래 코드(0x6A)로 되돌린다.

원본 코드(0x6A)로 변경

이제 전상 코드 상태에서 디버깅을 진행하면 된다.

원본 ChildProc() 함수

 


 

3. 마무리

JIT 디버깅의 기본 개념은 서비스 디버깅의 무한 루프 설치방법과 매우 유사하다.

유용한 디버깅 기법들이므로 까다로운 프로세스의 디버깅에 사용하면 좋다.

 


리버싱 핵심원리 - 디버깅 실습2_업로드용.pdf
0.61MB