ABOUT ME

Today
Yesterday
Total
  • 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)의 주소)

     

     

    댓글

Designed by Tistory.