-
21 Windows 메시지 후킹리버싱 엔지니어링/리버싱 핵심 원리 2022. 4. 1. 01:04
훅
- 중간에서 오고가는 정보를 엿보거나 가로채기 위해 초소를 설치하는 일을 훅(hook)을 건다(설치한다)라고 함
- 실제로 정보를 엿보고 조작하는 행위를 후킹(hooking)한다 라고 함
메시지 훅
- 이벤트 발생 시 OS는 미리 정의된 메시지를 응용프로그램에 통보 → 응용 프로그램은 해당 메시지를 분석해 필요한 작업을 진행
- 메시지 훅은 위와 같은 메시지를 엿보는 것
- 키보드 메시지 예시 ▼
- Windows 메시지 흐름
- 키보드 입력 이벤트가 발생하면 WM_KEYDOWN 메시지가 [OS message queue]에 추가됨
- OS는 어느 응용 프로그램에서 이벤트가 발생했는지 파악해 [OS message queue]에서 메시지를 꺼내 해당 응용프로그램의 [application message queue]에 추가
- 응용프로그램은 자신의 [application message queue]를 모니터링하고 있다가 WM_KEYDOWN 메시지가 추가된 걸 확인하고 해당 event handler를 호출
- OS 메시지 큐와 응용프로그램 메시지 큐 사이의 훅 체인(Hook Chain)에 있는 키보드 메시지 훅들이 응용 프로그램보다 먼제 해당 메시지를 볼 수 있음
- 단순 엿보기 기능뿐만 아니라 메시지 자체를 변경할 수도 있고 메시지를 가로채 아래로 내려보내지 않게 할 수도 있음
- 메시지 훅을 여러개 동시 설치도 가능
- 프로그램 : SPY++
SetWindowsHookEx()
- 메시지 훅을 SetWindowsHookEx() API를 사용해 간단 구현 가능
- SetWindowsHookEx() API 정의
HHOOK SetWindowsHookEx( int idHook, // hook type HOOKPROC lpfn, // hook procedure HINSTANCE hMod // 위 hook procedure이 속해 있는 DLL 핸들 DWORD dwThreadId // hook을 걸고 싶은 thread ID );
- hook procedure : 운영체제가 호출해주는 콜백 함수
- hook porcedure은 DLL 내부에 존재해야하고 그 DLL의 인스턴스 핸들이 hMod
- SetWindowsHookEx()를 이용해 훅을 설치
- 어떤 프로세스에서 해당 메시지가 발생했을 때 운영체제가 해당 DLL파일을 해당 프로세스에 강제로 인젝션(injection)하고 등록된 hook procedure을 호출
키보드 메시지 후킹 실습
키보드 메시지 후킹 - KeyHook.dll 파일 : 훅 프로시저(KeyboardProc)가 존재하는 DLL 파일
- HookMain.exe : KeyHook.dll을 최초로 로딩하여 키보드 훅을 설치하는 프로그램
- KeyHook.dll 파일 로딩 후 SetWindowsHookEx()를 이용해 키보드 훅(KeyboardProc)을 설치
- 다른 프로세스(explore.exe, iexplore.exe, notepad.exe 등)에서 키 입력 이벤트가 발생하면 OS에서 해당 프로세스 메모리 공간에 KeyHook.dll을 강제로 로딩하고 KeyboardProc() 함수가 호출됨
실습 예제 HookMain.exe
HookMain.exe 실행 - 키보드 훅 설치
- HookMain.exe 실행 시 사용자의 키보드 입력을 무시함
- 'q' 키를 입력해 키보드 훅을 제거 후 종료 가능
- 실제 시도해본결과 HookMain.exe 실행 시 키보드 입력이 전체적으로 막혀 입력할 수 없었음
소스코드 분석
HookMain.cpp
#include "stdio.h" #include "conio.h" #include "windows.h" #define DEF_DLL_NAME "KeyHook.dll" #define DEF_HOOKSTART "HookStart" #define DEF_HOOKSTOP "HookStop" typedef void (*PFN_HOOKSTART)(); typedef void (*PFN_HOOKSTOP)(); void main() { HMODULE hDll = NULL; PFN_HOOKSTART HookStart = NULL; PFN_HOOKSTOP HookStop = NULL; char ch = 0; // KeyHook.dll 로딩 hDll = LoadLibraryA(DEF_DLL_NAME); if( hDll == NULL ) { printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError()); return; } // export 함수 주소 얻기 HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART); HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP); // 후킹 시작 HookStart(); // 사용자가 'q' 를 입력할 때까지 대기 printf("press 'q' to quit!\n"); while( _getch() != 'q' ) ; // 후킹 종료 HookStop(); // KeyHook.dll 언로딩 FreeLibrary(hDll); }
- HookStart() 함수를 호출하면 후킹이 시작되고, HookStop() 함수를 호출하면 후킹이 종료됨
KeyHook.cpp
#include "stdio.h" #include "windows.h" #define DEF_PROCESS_NAME "notepad.exe" HINSTANCE g_hInstance = NULL; HHOOK g_hHook = NULL; HWND g_hWnd = NULL; BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved) { switch( dwReason ) { case DLL_PROCESS_ATTACH: g_hInstance = hinstDLL; break; case DLL_PROCESS_DETACH: break; } return TRUE; } LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { char szPath[MAX_PATH] = {0,}; char *p = NULL; if( nCode >= 0 ) { // bit 31 : 0 => press, 1 => release if( !(lParam & 0x80000000) ) { GetModuleFileNameA(NULL, szPath, MAX_PATH); p = strrchr(szPath, '\\'); // 현재 프로세스 이름을 비교해서 만약 notepad.exe 라면 0 아닌 값을 리턴함 // => 0 아닌 값을 리턴하면 메시지는 다음으로 전달되지 않음 if( !_stricmp(p + 1, DEF_PROCESS_NAME) ) return 1; } } // 일반적인 경우에는 CallNextHookEx() 를 호출하여 // 응용프로그램 (혹은 다음 훅) 으로 메시지를 전달함 return CallNextHookEx(g_hHook, nCode, wParam, lParam); } #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) void HookStart() { g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0); } __declspec(dllexport) void HookStop() { if( g_hHook ) { UnhookWindowsHookEx(g_hHook); g_hHook = NULL; } } #ifdef __cplusplus } #endif
- HookStart() 함수가 호출되면 SetWindwsHookEx()에 의해 키보드 훅 체인에 KeyboardProc()이 추가됨
- 어떤 프로세스에서 키 입력 이벤트가 발생하면, OS는 해당 프로세스에게 강제로 KeyHook.dll을 인젝션
- KeyHook.dll을 로딩한 프로세스에서 키보드 이벤트가 발생하면 KeyHook.KeyboardProc()가 먼저 호출됨
- 키보드 입력 발생 시 현재 프로세스 이름이 "notepad.exe"문자열과 같다면 KeyboardProc()를 종료시킴. 즉, 메시지를 가로채 없애버림
디버깅 실습
: OllyDbg를 통해 HookMain.exe 디버깅
EP 코드
EP 코드 - VC++의 Stub 코드
핵심 코드 찾기
방법
- 한 줄씩 트레이싱
- 관련 API 검색
- 관련 문자열 검색 → "press 'q' to quit"
문자열 더블클릭
- HookMain.exe 프로그램의 main() 함수
main() 함수 디버깅
- 401000 주소에 BP 설치 후 실행, 차례대로 트레이싱
- 401006 주소에서 LoadLibrary("KeyHook.dll") 호출 후 40104B 주소의 CALL EBX 명령에 의해 KeyHook.HookStart() 함수가 호출됨
- 40104B 주소의 CALL EBX 명령 따라가기[F7]
- HookStart() 함수
- 100010EF에서 CALL SetWindowsHookExW() 명령어 확인
- 100010E8, 100010ED PUSH 명령어에서 1,2번째 파라미터 확인 가능
- 첫 번째 : 10001020 → 훅 프로시저(Hook Procedure)의 주소)
'리버싱 엔지니어링 > 리버싱 핵심 원리' 카테고리의 다른 글
23 DLL 인젝션 (0) 2022.04.05 22 악의적인 목적으로 사용되는 키로거 (0) 2022.04.01 20 인라인 패치 실습 (0) 2022.03.30 19 UPack 디버깅 - OEP 찾기 (0) 2022.03.30 18. UPack PE 헤더 상세 분석 (0) 2022.03.30