Information Security

Debug 방식의 API 후킹 본문

Reverse Engineering

Debug 방식의 API 후킹

leeeeye321 2019. 7. 4. 17:56

[ Debug 방식의 API 후킹 ]


디버거(Debugger): 디버깅/후킹 프로그램(hookdbg.exe)

디버기(Debuggee): 디버깅 당하는 프로그램(notepad.exe)


이번에는 디버그 방식으로 API 후킹을 할 것이다. 위의 두 프로세스로 실습을 진행한다.

디버거는 디버깅을 당하는 디버기에 대한 모든 권한을 가지므로 후킹 함수를 쉽게 설치할 수 있다.


notepad는 파일에 내용을 입력하고 저장할 때 WriteFile() API를 사용한다. 이 API를 후킹하여 저장되는 문자열 중 소문자를 대문자로 덮어쓸 것이다. 


먼저 notepad의 WriteFile() 시작 주소에 Break Point를 설치하고 메모장에서 문자열을 저장한다.  

그리고 스택에서 파라미터를 확인한다. 두 번째 파라미터(ESP+8 : AEE18)가 버퍼의 주소이고, 그 버퍼에는 메모장에서 저장한 문자열이 있다. 

이 버퍼를 덮어쓰면 원하는 대로 내용을 변경할 수 있다.

* 버퍼의 주소, PID는 반복된 실습으로 인해 변경됩니다.


디버거(hookdbg.exe)의 코드이다. 메인 함수에서 DebugLoop()를 호출하고 있다. 

디버깅을 하면서 후킹의 과정을 확인할 것이다.


[ DebugLoop()의 작업 단계 ]

1) WriteFile() API의 시작에 BP(INT3 명령어, 0xCC)를 설치 == Hook 설치

2) 디버깅 중 INT3 명령어를 만나면 CPU는 디버기 실행을 멈추고 EXCEPTION_BREAKPOINT 예외를 디버거에게 전달

3) 디버거에게 제어를 넘김

4) 작업 수행(여기서는 소문자를 대문자로 바꾸기)

5) WriteFile() API의 시작 첫 번째 바이트 원상 복구 == Unhook

6) 수행한 작업 저장을 위해 한 번 더 WriteFile() 실행

7) 다음의 후킹을 위해 다시 Hook 설치

8) 디버기에게 제어 돌려줌 


Ollydbg에서 [F3]을 누르면 파라미터를 주면서 실행할 수 있다.  

디버거의 실행 파라미터는 후킹할 프로세스의 PID이므로, notepad를 실행하고 [Process Explorer]에서 PID를 확인한다.


실행 후 메인 함수부터 찾고, 그로부터 DebugLoop()의 시작(4011D0)을 알아냈다.


WaitForDebugEvent(): 디버기에서 디버그 이벤트가 발생할 때까지 대기하는 API


[4011EE]까지 진행하면 스택에서 WaitForDebugEvent()의 파라미터를 확인할 수 있다. 

[코드 84] 이벤트가 발생하면 첫번째 파라미터 DEBUG_EVENT 구조체 변수 de(pDebugEvent)에 이벤트 정보를 설정한 후 1을 리턴한다. 이로 인해 while 문에 참이 되어 안으로 들어간다.


DEBUG_EVENT 구조체는 이렇게 생겼고, 발생한 이벤트의 종류에 맞춰서 유니온 멤버(u)가 설정된다.
유니온 멤버는 9개의 구조체로 구성된다.
 

 

[88-91] WaitForDebugEvent()를 호출한 후 EVENT_DEBUG 구조체 de 변수(12FF00)에 가보면 

dwDebugEventCode의 값이 3으로 설정되어 있다. 

결과적으로 첫 번째 if 문은 참이 되어 

CREATE_PROCESS_DEBUG_EVENT 이벤트 핸들러 OnCreateProcessDebugEvent()를 호출한다.


OnCreateProcessDebugEvent() 함수이다. 

디버기의 프로세스가 시작될 때 호출되는 함수이다. 파라미터로 DEBUG_EVENT 구조체 변수 de를 pde로 전달한다.


[10] GetModuleHandleA(), GetProcAddress()를 호출하여 결과적으로 WriteFile()의 주소를 리턴한다. 


[11] memcpy() 함수로 ESI를 EDI에 복사한다.


ESI(Source)는 12FF0C를 가리킨다. u.CreateProcessInfo 구조체 변수이다.


EDI(Destination)은 5라인에서 정의한 CREATE_PROCESS_DEBUG_INFO 구조체 변수 g_cpdi이다.

이 구조체에는 디버기 프로세스 핸들 hProcess 멤버가 존재한다. 이를 통해 디버기의 메모리에 읽기/쓰기 작업을 수행할 수 있다.


[13-14] 일단 ReadProcessMemory() API로 WriteFile()의 첫 번째 바이트(0x6A)를 g_ch0rgByte에 저장한다. BP 설치 후 원상 복구할 때를 위한 것이다.


g_ch0rgByte(40DA64)에 0x6A가 저장되었다.


[16-17] WriteProcessMemory() API를 이용하여 notepad의 WriteFile() 첫 번째 바이트를 0xCC(g_chINT3)로 덮어쓴다.

[19] 여기까지 하면 이제 TRUE를 리턴하고 

[108] ContiueDebugEvent() 함수를 만난다. 이 함수는 디버기의 실행을 재개한다. 


WriteFile()의 호출을 위해 메모장에 ReverseCore이라고 쓰고 저장했다.

while 문에 의해 다시 WaitForDebugEvent()를 만나게 되고, BP가 설치된 WriteFile()이 호출되었으니 dwDebugEventCode의 값이 1(EXCEPTION_DEBUG_INFO)로 설정된다. 

else if 문에 참이 되어 EXCEPTION_DEBUG_EVENT 이벤트 핸들러OnExceptionDebugEvent() 함수가 호출된다.


OnExceptionDebugEvent() 함수이다.


★ WriteFile()의 INT 3 명령어를 만나면 EIP는 WriteFile() 주소 + 1(0xCC, 1바이트)이 되고 제어권이 디버거에게 넘어간다.

원하는 대로 내용을 덮어쓰고 저장하기 위해 EIP를 정상적인 WriteFile() 주소로 변경해야 한다.

그리고 정상적인 WriteFile() 시작 주소로 가서 다시 BP를 만나 버리면 안되므로 BP도 제거해 준다.


[27-30] 발생한 이벤트가 BP인지, WriteFile()에서 발생한 것이 맞는지 if 문 두 개로 확인한다.

[31-32] WriteProcessMemory()를 이용하여 미리 저장해 놓은 0x6A로 첫 번째 바이트를 원상 복구(Unhook)한다.


[36-37] GetThreadContext() API는 ctx에 스레드(g_cpdi.hThread: 디버기의 메인 스레드 핸들)의 CONTEXT를 저장한다.


Windows OS는 멀티 스레드 기반이다. CPU가 모든 스레드를 일정시간 실행하고 넘어가고를 반복하기 때문에 작업의 내용을 백업하는 것이 중요하다. CPU 레지스터 값이 유지되어야 다음 실행이 제대로 이루어지는데, 스레드의 CPU 레지스터 정보를 저장하는 구조체가 CONTEXT 구조체이다. 스레드 하나 당 CONTEXT 구조체 하나를 가진다.


[39-42] 덮어 쓸 버퍼의 주소와 크기가 필요하므로 Context.Esp 멤버를 이용해서 디버기 스택의 파라미터 값을 각각 dwAddrOfBuffer[12FC10], dwNumOfBytesToWrite[12FC14]에 저장한다.



 

[44] malloc() 함수로 임시 버퍼를 할당한다. 버퍼의 주소를 리턴하여 EAX에 저장(393F18)한다.


[45] 임시 버퍼(393F18~393F23)를 memset() 함수로 0으로 초기화한다.


[47-48] ReadProcessMemory() API로 WriteFile() 버퍼를 임시 버퍼에 읽어온다. 이전에 저장했던 ReverseCore 문자열이 임시 버퍼에 복사되었다.


[52-56] 버퍼의 문자열이 소문자이면 대문자로 변환(-0x20)하여 저장한다. 임시 버퍼를 보면 for 문을 통하여 한 글자씩 변환해가는 과정을 확인할 수 있다. 


hookdbg.exe에서 다음과 같이 출력된다.


[60-61] 대문자로 변경된 문자열을 WriteFile() 버퍼에 복사

[63] 임시 버퍼 해제



[65-66] CONTEXT.Eip 멤버를 WriteFile() 시작 주소(7C810E17)로 변경하고 SetThreadContext()를 호출하여 이를 셋팅한다.

[68] ContinueDebugEvent() API를 호출하여 멈췄던 디버기의 실행을 재개한다. WriteFile()이 정상적으로 호출되어 변환한 내용을 저장한다.  


[72] 마지막으로 다음의 후킹을 위해 다시 BP(g_chINT3) WriteFile()의 첫 바이트에 써준다(훅 설치).


대문자로 변환되어 저장된 것을 확인한다. 이렇게 디버그 기법으로 API 후킹이 성공했다.

'Reverse Engineering' 카테고리의 다른 글

Code Injection  (0) 2019.06.26
PE 패치를 이용한 DLL 로딩  (0) 2019.06.21
DLL Ejection  (0) 2019.06.21
DLL Injection - Remote Thread 생성 & 레지스트리 이용  (0) 2019.06.20
Windows Message Hooking  (0) 2019.06.17