Profile

youngsouk

youngsouk

API hooking

debugging을 이용해서 후킹

1. DebugActiveProcess()함수를 통해 PID에 해당하는 프로세스를 디버깅을 셋팅한다.

2. WaitForDebugEvent를 통해 debug event

debug event 예 = CREATE_PROCESS_DEBUG_EVENT : 프로세스 생성 or attach

                            EXCEPTION_DEBUG_EVENT : 디버거한테 전해지는 예외 (ex : int 3(0xcc) )

                           EXIT_PROCESS_DEBUG_EVENT : 디버깅 중인 프로세스 종료

 

3. CREATE_PROCESS_DEBUG_EVENT일 경우, 즉 처음 attach 될 때 

3 - 1. GetProcAddres() 와 GetModuleHandle()을 이용해 후킹할 API 주소 알아내기

3 - 2. 얻어온 예외 정보를 통해 프로세스 정보를 구한다.

3 - 3. ReadProcessMemory()함수를 통해 후킹할 API의 첫번째 1바이트 기계어 명렁어를 백업

3 - 4 WriteProcessMemory()함수를 통해 후킹할 API의 첫번쨰 1바이트를 int 3(0xcc)로 변경

 

4. EXCEPTION_DEBUG_EVENT일 경우

4 - 1. 예외가 int e(0xcc) 즉 bp 인지 확인

4 - 2. bp가 걸려서 예외가 발생한 주소가 후킹할 API의 주소인지 확인

4 - 3. WriteProcessMemory()함수를 통해 3-3번에서 백업한 1바이트 복구

4 - 4. Thread Context(레지스터 등의 정보 저장하는 구조체) 

4 - 5. 4-4에서 얻은 레지스터 정보(ebp, esp 등)을 통해 인자 주소를 알아냄

4 - 6. 4-5에서 알아낸 주소를 바탕으로 ReadProcessMemory()함수로 인자를 알아냄

4 - 7. 알아낸 정보를 바탕으로 원하는 대로 프로그램 내부에서 수정

4 - 8. WriteProcessMemory()함수를 통해 수정한 데이터 디버기에 반영

4- 9. 4-4에서 얻은 구조체의 eip를 후킹할 API의 주소로 수정.

왜냐하면 0xcc에서 예외가 발생하면 eip가 1 증가 즉, bp가 걸리게 되면 eip는 bp 걸린 주소 + 1 이 되기 때문이다.

4 - 10. SetThreadContext()를 통해 수정한레지스터 구조체 반영

4- 11. ContinueDebugEvenet()를 통해 디버기를 계속 진행

4 - 12. sleep(0)

4 - 13. WriteProcessMemory()를 통해 다시 후킹할 API의 첫번쨰 1바이트를 int 3(0xcc)로 변경

 

4-12 에서 sleep(0)를 사용하는 이유

cpu가 시분할 방식으로 작동한다. 즉 프로세스의 스레드를 짧은 시간동안 오가며 작업을 하게 되는데, sleep() 함수는 cpu가 다른 스레드에 작업을 하도록 해준다. 즉 다른 스레드 즉 후킹될 프로세스의 스레드가 작동된다. 

만약 sleep(0)를 안해주어서 cpu가 이 프로세스를 계속하여 작업해주면 4-13에서 int 3(0xcc)로 변경된 후에 디버기가 실행되어서 정상적으로 작동이 안되기 때문이다. 

 


IAT(Import Address Table) 후킹

기본적으로 dll injection 기법을 사용하여 CreateRemoteThread()를 통해 IAT를 수정하는 코드를 실행시킨다.

1. GetModuleHandle(NULL)을 통해 현재 프로세스의 핸들(시작주소)를 얻는다.

2. (DWORD*)(DWORD *로 형변환한 주소)[0x3C]를 통해 IMAGE_NT_HEADERS의 주소 값을 읽어들인다.

3. (2번에서 얻은 주소)[0x80]을 통해 IMAGE_IMPORT_DESCRIPTOR Table 의 RVA를 읽어들인다.

4. 3번에서 얻은 주소 + 1번에서 얻은 주소를 하여 IMAGE_IMPORT_DESCRIPTOR Table의 실제 주소를 읽어들인다.

5. IMAGE_IMPORT_DESCRIPTOR Table의 이름을 통해 dll 이름을 비교하면서 원하는 dll을 찾는다.

6. 원하는 dll 주소를 찾았으면 IMAGE_IMPORT_DESCRIPTOR의 FirstThunk(Import Address Table)의 RVA를 읽어서 VA를 구한다.

7. 6번에서 찾은 Import Address Table을 순회하면서 원하는 함수의 주소를 찾는다.

8. 원하는 함수의 주소를 찾으면, Import Address Table의 메모리 속성을 쓰기 가능하게 바꾼다. (VirtualProtect 함수 이용)

9. 그런뒤에 Import Address Table의 값을 덮어쓴다.

10. 다시 Import Address Table의 메모리 속성을 원래대로 바꾼다.

 

'윈도우' 카테고리의 다른 글

ZwResumeThread() 후킹  (0) 2019.10.28
스텔스 프로세스  (0) 2019.10.28
윈도우 관련 볼 것들  (0) 2019.10.24
window code injection  (0) 2019.10.23
dll injection 방법  (0) 2019.10.18