Windows 운영체제는 GUI를 제공하고, Event Driven 방식으로 동작한다. 이런 이벤트가 발생할 때, OS는 메세지를 응용 프로그램으로 보낸다. 이런 메세지를 중간에서 엿보는 것을 Message Hooking이라고 한다.


여기서 hook procedure란 운영체제가 호출하는 콜백 함수이며, DLL 내부에 존재해야 한다. 이 API를 이용하여 훅을 설치하면 메세지가 발생했을 때, 운영체제가 DLL 파일을 프로세스에 강제로 인젝션하고 등록된 hook procedure를 호출한다. 이것이 훗날 DLL Injection인 것 같다.
#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);
}
main 함수가 중요한데, 이는 간단해 보인다.
KeyHook.dll 파일을 로딩한 후 Hookstart와 HookStop 함수의 주소를 얻어 후킹 진행
#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
전처리 한 부분이 main으로 봐야 할 코드들이다. HookStart 함수가 호출되면 SetWindowHookEx()가 호출되고 이로 인해 키보드 훅 체인에 KeyboardProc()이 추가된다. KeyboardProc()은 키보드 입력이 발생했을 때 현재 프로세스 이름과 "notepad.exe" 문자열과 비교하여 만약 같다면 1을 리턴해서 KeyboardProc()를 종료시킨다. 즉, 프로그램의 메세지 큐에 전달되지 않는다.
1번은 최후의 보루로 사용하는 거고 보통 API 또는 문자열을 검색한다. 일단 "press 'q' to quit!" 문자열을 우리가 알고 있으므로 검색해 본다.

그 전에 있는 HookStart 함수가 호출되는 코드로 따라가보면 다음 KeyHook.dll의 HookStart() 함수 영역으로 갈 수 있다.

이때 push 명령어가 4번 일어나는데 모두 SetWindowsHookExW() 함수의 파라미터 4개라고 볼 수 있다. 참고로 스택에 push하기 때문에 역순으로 입력하고 있다. 우리가 주의깊게 봐야 할 부분은 Hook procedure의 주소이다. 이는 SetWindowsHookExW() 함수의 두번재 파라미터이므로 1001020임을 알 수 있다. 확인해보면 아래와 같다.
