리버싱 엔지니어링/리버싱 핵심 원리
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 코드
- 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)의 주소)