흰싸라기 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)의 주소)