다루는 내용
메시지 후킹은 O/S에서 특정 메시지를 응용 큐에 전송하는 것을 가로채는 것을 말합니다.
여기에서는 메시지 후킹을 설정 및 해제하는 함수를 살펴봅니다.
여러 가지 후킹 중에 키보드 후킹을 살펴볼 것입니다.
그리고 전역 후킹하는 DLL을 만드는 것과 이를 사용하는 예를 살펴봅니다.
1. SetWindowsHookEx
2. UnhookWindowsHookEx
3. 전역 후킹 DLL 만들기
4. 전역 후킹 DLL 사용 예
1. SetWindowsHookEx
메시지 후킹 프로시저를 후킹 체인에 설정하는 메서드입니다.
가장 최근에 후킹 체인에 설정한 후킹 프로시저에게 먼저 전달합니다.
HHOOK
WINAPI
SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hmod,
DWORD dwThreadId); msdn 바로가기
반환 값
성공하면 후크 프로시저에 대한 핸들을 반환합니다.
실패하면 NULL을 반환합니다.
int idHook
설치할 후크 프로시저의 유형입니다.
#define WH_MIN (-1)
#define WH_MSGFILTER (-1)
#define WH_JOURNALRECORD 0
#define WH_JOURNALPLAYBACK 1
#define WH_KEYBOARD 2
#define WH_GETMESSAGE 3
#define WH_CALLWNDPROC 4
#define WH_CBT 5
#define WH_SYSMSGFILTER 6
#define WH_MOUSE 7
#if defined(_WIN32_WINDOWS)
#define WH_HARDWARE 8
#endif
#define WH_DEBUG 9
#define WH_SHELL 10
#define WH_FOREGROUNDIDLE 11
#if(WINVER >= 0x0400)
#define WH_CALLWNDPROCRET 12
#endif /* WINVER >= 0x0400 */
#if (_WIN32_WINNT >= 0x0400)
#define WH_KEYBOARD_LL 13
#define WH_MOUSE_LL 14
#endif // (_WIN32_WINNT >= 0x0400)
#if(WINVER >= 0x0400)
#if (_WIN32_WINNT >= 0x0400)
#define WH_MAX 14
#else
#define WH_MAX 12
#endif // (_WIN32_WINNT >= 0x0400)
#else
#define WH_MAX 11
#endif
#define WH_MINHOOK WH_MIN
#define WH_MAXHOOK WH_MAX
여기에서는 Low Level의 키보드 후킹을 할 것입니다. 따라서 WH_KEYBOARD_LL을 전달할 거예요.
HOOKPROC lpfn
후킹한 메시지를 처리하는 콜백 프로시저입니다.
typedef LRESULT (CALLBACK* HOOKPROC)(int code, WPARAM wParam, LPARAM lParam);
WH_KEYBOARD_LL일 때 code값은 언제나 0(HC_ACTION)입니다.
wParam은 WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP 중에 하나입니다.
#define WM_KEYDOWN 0x0100
#define WM_KEYUP 0x0101
#define WM_SYSKEYDOWN 0x0104
#define WM_SYSKEYUP 0x0105
lParam은 KBDLLHOOKSTRUCT 구조체에 대한 포인터입니다.
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT; msdn 바로가기
HINSTANCE hmod
Hook 프로시저를 포함하는 모듈의 핸들입니다.
현재 프로세스에 Hook 프로시저가 있을 때 NULL로 지정합니다.
DWORD dwThreadId
Hook 프로시저를 연결할 스레드의 식별자입니다.
만약 0이면 모든 스레드와 연결합니다.
2. UnhookWindowsHookEx
Hook 프로시저를 제거하는 함수입니다.
BOOL UnhookWindowsHookEx(
HHOOK hhk
);
반환 값
성공하면 0이 아닌 값을 반환합니다.
실패하면 0을 반환합니다.
HHOOK hhk
제거할 Hook 핸들입니다.
3. 전역 후킹 DLL 만들기
Windows 데스크톱 마법사에서 동적 연결 라이브러리 유형의 빈 프로젝트(KeyHookLib)를 생성하세요.
프로젝트에 KeyHook.h 파일과 KeyHook.cpp 파일을 추가합니다.
KeyHook.h
Hook을 설치하는 함수와 해제하는 함수를 제공합니다.
#pragma once
#include <Windows.h>
#ifdef CIEMCVUEHNCEHC83764CYEHCTEH
#define KEY_DLL __declspec(dllexport)
#else
#define KEY_DLL __declspec(dllimport)
#endif
#define MWM_KEY (WM_USER+1)
extern "C" KEY_DLL void InstallHook(HWND hWnd);
extern "C" KEY_DLL void UninstacllHook();
KeyHook.cpp
다음은 KeyHook.cpp 소스 코드 전체 내용입니다.
#define CIEMCVUEHNCEHC83764CYEHCTEH
#include "KeyHook.h"
HMODULE hInstance;
HHOOK hkey_hook;
HWND gWnd;
LRESULT EHHookProc(int code, WPARAM wParam, LPARAM lParam)
{
SendMessage(gWnd, MWM_KEY, wParam, lParam);
return CallNextHookEx(hkey_hook, code, wParam, lParam);
}
extern "C" KEY_DLL void InstallHook(HWND hWnd)
{
hkey_hook = SetWindowsHookEx(WH_KEYBOARD_LL, EHHookProc, hInstance, 0);
gWnd = hWnd;
}
extern "C" KEY_DLL void UninstacllHook()
{
UnhookWindowsHookEx(hkey_hook);
}
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpRes)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:hInstance = hInst; break;
}
return TRUE;
}
전역 변수 선언
DLL 모듈의 HINSTANCE와 Hook 핸들과 윈도우 핸들을 전역 변수로 선언합니다.
HMODULE hInstance;
HHOOK hkey_hook;
HWND gWnd;
EHHookProc
Hook 프로시저입니다.
후킹한 메시지를 윈도우에게 사용자 메시지로 전달합니다.
원래 수신해야 할 곳에서도 키보드 메시지를 정상적으로 수신하여 처리할 수 있게 CallNextHookEx를 호출합니다.
LRESULT EHHookProc(int code, WPARAM wParam, LPARAM lParam)
{
SendMessage(gWnd, MWM_KEY, wParam, lParam);
return CallNextHookEx(hkey_hook, code, wParam, lParam);
}
InstallHook 메서드
Hook 프로시저를 설치하는 메서드입니다.
여기에서는 Low Level Keyboard 메시지를 후킹할 것입니다.
그리고 전역 후킹을 할 것이므로 SetWindowsHookEx메서드의 마지막 인자는 0으로 설정합니다.
입력인자로 전달받은 윈도우 핸들을 전역 변수에 설정합니다.
extern "C" KEY_DLL void InstallHook(HWND hWnd)
{
hkey_hook = SetWindowsHookEx(WH_KEYBOARD_LL, EHHookProc, hInstance, 0);
gWnd = hWnd;
}
UninstacllHook 메서드
Hook 프로시저를 해제하는 메서드입니다.
extern "C" KEY_DLL void UninstacllHook()
{
UnhookWindowsHookEx(hkey_hook);
}
DllMain 진입점
DLL 진입점으로 선택적으로 정의할 수 있습니다.
여기에서는 DLL 핸들을 전역 변수에 설정하기 위해 정의합니다.
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpRes)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:hInstance = hInst; break;
}
return TRUE;
}
4. 전역 후킹 DLL 사용 예
데스크톱 애플리케이션 유형의 빈 프로젝트로 생성합니다.
리소스에 대화 상자를 추가하고 ListBox를 배치하세요.
대화 상자의 ID는 컨트롤 IDD_DIALOG_MAIN으로 정의합니다.
List Box의 ID는 IDC_LIST_KEY로 정의합니다.
Program.cpp 소스 파일을 추가합니다.
Program.cpp
다음은 Program.cpp 소스 코드의 전체 내용입니다.
#include <Windows.h>
#include "resource.h"
#include "../KeyHookLib/KeyHook.h"
#pragma comment(lib,"..\\x64\\Debug\\KeyHookLib.lib")
void OnInit(HWND hDlg)
{
InstallHook(hDlg);
}
void CancelProc(HWND hDlg)
{
UnInstacllHook();
EndDialog(hDlg, 0);
}
void OnCommand(HWND hDlg,WORD cid, WORD cmsg,HWND cWnd)
{
switch (cid)
{
case IDCANCEL: CancelProc(hDlg); return;
}
}
void OnKey(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
HWND lWnd = GetDlgItem(hDlg, IDC_LIST_KEY);
KBDLLHOOKSTRUCT* pks = (KBDLLHOOKSTRUCT*)lParam;
DWORD key = pks->vkCode;
if (wParam == WM_KEYDOWN)
{
wchar_t buf[256];
if ((isalnum(key)| isblank(key) | (key==VK_RETURN)) == FALSE)
{
return;
}
if (GetKeyState(VK_CAPITAL)==FALSE)
{
key = tolower(key);
}
switch (key)
{
case VK_TAB:wsprintf(buf, TEXT("TAP")); break;
case VK_SPACE:wsprintf(buf, TEXT("SPACE")); break;
case VK_RETURN:wsprintf(buf, TEXT("ENTER")); break;
default: wsprintf(buf, TEXT("%c"), key); break;
}
SendMessage(lWnd, LB_ADDSTRING, 0, (LPARAM)buf);
DWORD cnt =(DWORD) SendMessage(lWnd, LB_GETCOUNT, 0, 0);
SendMessage(lWnd, LB_SETCURSEL, cnt-1, 0);
}
}
LRESULT CALLBACK DlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
switch (iMessage)
{
case WM_INITDIALOG: OnInit(hDlg); return TRUE;
case MWM_KEY:OnKey(hDlg, wParam, lParam); return TRUE;
case WM_COMMAND: OnCommand(hDlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
}
return FALSE;
}
INT APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPrev, LPSTR cmd, INT nShow)
{
DialogBox(hIns, MAKEINTRESOURCE(IDD_DIALOG_MAIN), 0, DlgProc);
return 0;
}
키보드 후킹 DLL 포함 및 참조
앞에서 만든 KeyHoolLib를 사용하기 위해 헤더 포함문과 참조문을 추가합니다.
#include "../KeyHookLib/KeyHook.h"
#pragma comment(lib,"..\\x64\\Debug\\KeyHookLib.lib")
OnInit 메시지 처리기
대화 상자 초기에 수행할 메서드입니다.
Hook 프로시저를 설치합니다.
void OnInit(HWND hDlg)
{
InstallHook(hDlg);
}
CancelProc 메서드
대화 상자의 닫기를 눌렀을 때 수행하는 메서드입니다.
Hook 프로시저를 해제하고 대화 상자를 끝냅니다.
void CancelProc(HWND hDlg)
{
UnInstacllHook();
EndDialog(hDlg, 0);
}
OnCommand 메시지 처리기
WM_COMMAND 메시지 처리기입니다.
여기에서는 IDCANCEL일 때 CancelProc을 호출하는 부분을 구현합니다.
void OnCommand(HWND hDlg,WORD cid, WORD cmsg,HWND cWnd)
{
switch (cid)
{
case IDCANCEL: CancelProc(hDlg); return;
}
}
OnKey 메시지 처리기
사용자 정의 메시지 MWM_KEY를 수신하였을 때 처리하는 메서드입니다.
설치한 키보드 Hook 프로시저에 위해 키보드 메시지를 후킹하면 전달받는 메시지입니다.
여기에서는 wParam이 키 누룸일 때 가상 키 코드를 List Box에 추가합시다.
여기에서는 알파벳이나 숫자, 공백(탭 포함), 엔터가 아닐 때는 ListBox에 추가하지 않고 있습니다.
그리고 CAPS Lock (VK_CAPITAL)이 눌러진 상태인지 확인하여 대소문자를 구분합니다.
void OnKey(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
HWND lWnd = GetDlgItem(hDlg, IDC_LIST_KEY);
KBDLLHOOKSTRUCT* pks = (KBDLLHOOKSTRUCT*)lParam;
DWORD key = pks->vkCode;
if (wParam == WM_KEYDOWN)
{
wchar_t buf[256];
if ((isalnum(key)| isblank(key) | (key==VK_RETURN)) == FALSE)
{
return;
}
if (GetKeyState(VK_CAPITAL)==FALSE)
{
key = tolower(key);
}
switch (key)
{
case VK_TAB:wsprintf(buf, TEXT("TAP")); break;
case VK_SPACE:wsprintf(buf, TEXT("SPACE")); break;
case VK_RETURN:wsprintf(buf, TEXT("ENTER")); break;
default: wsprintf(buf, TEXT("%c"), key); break;
}
SendMessage(lWnd, LB_ADDSTRING, 0, (LPARAM)buf);
DWORD cnt =(DWORD) SendMessage(lWnd, LB_GETCOUNT, 0, 0);
SendMessage(lWnd, LB_SETCURSEL, cnt-1, 0);
}
}
진입점과 대화상자 Proc
진입점에서는 대화상자를 띄웁니다.
대화상자 프로시저는 초기화, WM_KEY, WM_COMMAND 메시지를 처리하는 처리기를 호출합니다.
LRESULT CALLBACK DlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
switch (iMessage)
{
case WM_INITDIALOG: OnInit(hDlg); return TRUE;
case MWM_KEY:OnKey(hDlg, wParam, lParam); return TRUE;
case WM_COMMAND: OnCommand(hDlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
}
return FALSE;
}
INT APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPrev, LPSTR cmd, INT nShow)
{
DialogBox(hIns, MAKEINTRESOURCE(IDD_DIALOG_MAIN), 0, DlgProc);
return 0;
}