ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리버싱 핵심원리 study] 21장 Windows 메세지 후킹
    Reverse Engineering 2021. 1. 4. 18:31

    1. 훅

    컴퓨터 분야에서 OS-App-User 간에 오고 가는 정보를 엿보고 조작할 수 있는데 이와 같은 행위를 후킹(Hooking)이라고 한다. 이번 장에서는 가장 기본적인 형태인 Message Hook에 대해 알아본다.

     

    2. 메시지 훅

    Windows 운영체제는 GUI를 제공하고, 이는 Event Driven 방식으로 작동한다.  이벤트란 키보드 입력, 마우스 클릭 등의 작업을 일컫는다. 이러한 이벤트가 발생할 때 OS는 미리 정의된 메시지를 해당 응용 프로그램으로 통보한다. 메시지 훅이란 이런 통보되는 메시지를 중간에서 엿보는 것이다.

    메세지 후킹 동작 원리

    일반적으로 Windows message는 다음과 같은 흐름으로 진행한다.

     

    - 키보드 입력 이벤트가 발생하고 WM_KEYDOWN 메시지가 OS message queue에 추가된다.

    - OS는 어느 응용 프로그램에서 발생했는지 파악해서 OS message queue에서 메시지를 꺼내 해당 응용 프로그램의 application message queue에 추가한다.

    - 응용 프로그램은 자신의 application message queue를 모니터링하다 WM_KEYDOWN 메시지가 발생한 것을 확인하고 해당 event handler를 호출한다.

     

    위 그림에서는 키보드 메세지 훅이 있는 경우 Hook chain에 의해 응용 프로그램보다 먼저 해당 메시지를 확인할 수 있다. 이러한 훅 기능은 Windows 운영체제에서 기본적으로 제공한다.

     

    3. SetWindowsHookEx()

    메시지 훅은 SetWindowsHookEx() API를 사용해서 구현할 수 있다. SetWindowsHookEx() API는 다음과 같다.

    HHOOK SetWindowsHookExA(
      int       idHook,	//hook 종류(id)
      HOOKPROC  lpfn,	//hooking이 발생할 때 호출되는 함수
      HINSTANCE hmod,	//HOOKPROC이 속해 있는 DLL 핸들
      DWORD     dwThreadId	//hook을 사용할 대상 ID
    );

    SetWindowsHookEx() API를 이용해서 훅을 설치하면 어떤 프로세스에서 메시지가 발생했을 때 운영체제가 해당 DLL 파일을 해당 프로세스에 강제로 injection 하고 hook procedure를 호출한다.

     

    4. 키보드 메시지 후킹 실습

    실습을 진행하기에 앞서, 책에 있는 내용을 그대로 해보았지만 (실습 파일 실행 / 코드 직접 작성하여 프로그램 만들기) 프로그램이 비정상적으로 종료되는 문제가 발생하였다. 구글링을 해보니 다른 분들도 최신 환경에서는 책에 있는 내용을 그대로 진행했을 때 문제가 발생하였다고 한다. (Windows 10 64 bit환경) 어쩔 수 없이 VirtualBox를 이용해서 책과 같은 환경인 Windows 7 32 bit 환경에서 실습을 진행하였다.

     

    1) 실습 예제 HookMain.exe

    실습 예제 파일을 실행해보자.

    실행 화면

    이제 notepad.exe를 실행시키고, 키보드로 아무 내용이나 입력해보자.

    ???

    신기하게도 아무내용도 메모장에 출력되지 않는다. Process Explorer를 통해 살펴보면 아래와 같이 notepad.exe 프로세스에 KeyHook.dll이란 파일이 로딩되어 있는 것을 확인할 수 있다.

    KeyHook.dll

    다른 프로세스에서도 마찬가지로 KeyHook.dll이란 파일이 injection 된 것을 확인할 수 있다.

     

    KeyHook.dll이 인젝션된 프로세스들

    이제 HookMain.exe 창에서 'q' 키를 입력하여 프로그램을 종료해보자.

    말끔히 사라진 KeyHook.dll

    위와 같이 KeyHook.dll 들이 사라진 것을 확인할 수 있다.

     

    2) 소스코드 분석

    이제 HookMain.exe와 KeyHook.dll의 소스코드를 분석해보자.

    /*** 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);
    
    	//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 파일을 LoadLibrary를 통하여 로딩하고, 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) {
    		//31-th bit=> press, 1st bit=> release
    		if(!(lParam & 0x80000000)) {
    			//process의 경로를 알아낸다.
    			GetModuleFileNameA(NULL, szPath, MAX_PATH);
    			//경로로부터 프로세스 name 파싱
    			p = strrchr(szPath, '\\');
    
    			//현재 프로세스가 notepad.exe라면,
    			//응용 프로그램으로 메시지를 전달하지 않는다.
    			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() 함수가 호출되면, SetWindowsHookEx()에 의해 훅 체인에 KeyboardProc()이 추가된다. 키보드 훅이 설치된 상황에서 어떤 프로세스에서 키 입력을 하게 되면, OS는 해당 프로세스에 강제로 KeyHook.dll을 injection 한다. KeyHook.dll을 로딩한 프로세스에서 키보드 이벤트가 발생하면 KeyboardProc()이 먼저 발생하게 되는 것이다. 위 코드에서는 notepad.exe 프로세스인 경우에는 1을 return 하여 KeyboardProc()을 종료시키므로, notepad.exe 프로그램의 메시지 큐까지 이벤트 처리가 도달하지 못하고, 그로 인해 notepad에는 아무런 출력도 나타나지 않는다. 이외에 경우에 대해서는 CallNextHookEx를 호출하여 정상적으로 메시지가 전달되게 된다.

     

    5. 디버깅 실습

    OllyDbg로 HookMain.exe를 디버깅해보자.

    HookMain.exe의 EP

    실행하면 위와 같은 코드를 볼 수 있다. 해당 부분은 VC로 컴파일했을 때 생성되는 Stub Code로, 프로그램에서 호출하는 문자열인 "press 'q' to quit!"을 검색하여 main 함수 쪽으로 이동하자.

    main() 함수

    main 함수를 찾았으니, 401000 주소에 BP를 설정한 후 실행하고, 한 줄씩 내려오다 보면 LoadLibrary를 통해 KeyHook.dll을 로드하고, 이후에 HookStart와 HookStop 함수를 GetProcAddress를 통해 가져오고 40104B에서 HookStart()가 호출되는 것을 확인할 수 있다. 40104B 내부로 들어가 보자.

     

    KeyHook.HookStart()함수

    위 함수는 KeyHook.dll의 HookStart() 함수다. 100010EF에서 SetWindowHookExW()를 호출하는 것을 확인할 수 있다. 해당 명령어 위로는 차례로 argument를 스택에 쌓는 것을 확인할 수 있다. 그중 첫 번째 argument인 2는 WH_KEYBOARD이며, 두 번째 argument는 Hook Procedure의 주소다.

     

    이제 notepad.exe 프로세스 내부에 KeyHook.dll이 들어갔는지 OllyDbg에서 확인해보자. 우선 notepad.exe를 OllyDbg로 연 후, Debugging option에서 다음과 같이 Break on new module(DLL)에 체크 표시를 하자.

    OllyDbg 옵션 변경

    해당 옵션은 디버기 프로세스에 새로운 DLL이 로딩될 때 자동으로 디버깅을 멈추는 옵션이다. 이 상태에서 HookMain.exe를 실행시킨 후, notepad.exe에서 키보드 입력을 해보면 다음과 같이 Executable modules에 KeyHook.dll이 나타난다. (이때, HookMain.exe를 관리자 권한으로 실행시켜야 정상적으로 작동한다.)

    Executable Modules

    KeyHook.dll을 더블클릭하면 KeyHook.dll의 EP로 간다. 이전에 구한 훅 프로시저 주소인 1001020에 BP를 설정한 후 notepad.exe에서 키보드 입력 이벤트가 발생하면 해당 지점에서 멈춘다.

    훅 프로시저

     

    반응형

    댓글

Designed by Tistory.