본문 바로가기

리버싱 핵심원리/03. DLL Injection

03-01. Windows 메세지 후킹

1.메세지 훅

Windows는 GUI를 제공하고, 이는 Event Driven 방식으로 동작한다. 우리가 키보드/마우스를 이용하여 메뉴 선택, 버튼 선택 등을 할 때의 작업이 모두 Event이다. Windows는 이런 Event가 발생할 때 응용 프로그램으로 미리 정의된 Message를 보낸다. 이 Message를 중간에서 가로채는 것이 Message Hooking이다.

(자세한 내용은 찰스 펫졸드의 『Programming Windows』를 참고)

 

 

2. SetWindowsHookEx()

 

Message Hooking은 SetWindowsHookEx() API를 통해 간단히 구현할 수 있다.

HHOOK SetWindowsHookExA(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hmod,
  DWORD     dwThreadId
);

 

lpfn(Hook Procedure)은 운영체제가 호출해주는 CallBack 함수이다. 메세지 훅을 걸 때 은 DLL 내부에 존재해야 하며, 그 DLL의 Instance Handle이 바로 hmod 이다. dwThreadId 파라미터에 0을 주고 호출하면 글로벌 훅(Global Hook)이 설치되며, 실행중인(그리고 향후 실행될)모든 프로세스에 영향을 미친다.

 

3. 코드 분석

3.1 HookMain.cpp

/*
	* HookMain.cpp
	* 코드 출처 : reversecore.com
*/

#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);
}

KeyHook.dll 파일을 로딩한 후, HookStart() 함수를 호출하면 후킹이 시작되고 HookStop() 함수를 호출하면 후킹이 종료된다.

 

3.2 KeyHook.cpp

/*
	* KeyHook.cpp
    * 출처 : reversecore.com
*/
#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()가 호출되면 SetWindowsEx()에 의해 키보드 훅 체인에 KeyboardProc()이 추가된다.

참고로 KeyboardProc()의 함수 정의를 보면 다음과 같다.

LRESULT CALLBACK KeyboardProc(
  _In_ int    code,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

위 파라미터 중에서 wParam은 사용자가 누른 키보드의 Virtual Key Code를 의미한다. 여기서는 하드웨어적인 의미로써 'A', 'a', 'ㅁ'은 모두 같은 의미를 지닌다. 반면 lParam 값에서는 각 비트별로 다양한 의미를 가지고 있다.

 

KeyboardProc()의 함수 내용을 보면 키보드 입력이 발생했을 때, 키보드 입력이 발생한 프로세스의 이름과 "notepad.exe"를 비교하여 만약 같다면 1을 리턴하여 KeyboardProc()를 종료시킨다. 이는 메세지를 가로채서 바로 없애버리는 것이다. 결국 키보드 메세지는 notepad.exe의 Message Queue에 전달되지 않는다.

 


모든 코드와 설명의 출처는 https://reversecore.com/ 에 있습니다.

 

 

'리버싱 핵심원리 > 03. DLL Injection' 카테고리의 다른 글

03-04. DLL Ejection  (1) 2020.08.10
03-03. DLL Injection  (0) 2020.08.10