리버싱에서 후킹이 중요한 이유
리버싱에서 후킹의 가치가 큰 이유는, 정적 분석만으로는 잘 안 잡히는 것들이 있기 때문이다.
후킹은 이런 질문에 답하기 위한 관찰 지점이자, 필요하면 통제 지점이 된다.
용어 정리
- Hook Point: 가로챌 목표 지점 (예: 특정 API 함수, 메시지 큐 등)
- Hook Handler: 흐름을 가로챈 뒤 실행될 '나의 코드' (여기서 로깅, 차단 등을 수행)
- Original Path: 원래 가야 했을 정상적인 실행 경로
- Forward / Block: 내 코드(Handler) 실행 후, 원래 경로로 돌려보낼지(Forward) 여기서 아예 끊어버릴지(Block) 결정

모든 후킹은 구현 방식이 달라도, 리버싱 관점에서는 다음 4단계의 공통된 프로세스를 거친다. 단, 무작정 코드를 덮어쓰는 것이 아니라 철저한 분석과 복귀 과정이 필수적이다.
→ “내가 무엇을 관찰/통제할 것인가?” 목표를 먼저 정하고, 그 목표 데이터가 지나가는 경계(레이어) 를 찾는다. 이때 어떤 함수 이름 하나를 고르는 게 아니라, 어느 레벨에서 잡을지를 함께 결정해야 한다. (상위 API는 의미가 명확하지만 우회될 수 있고, 하위 레벨은 포착률이 높지만 의미가 거칠 수 있다.)
kernel32/user32 같은 상위 API, ntdll 같은 더 하위 레벨(또는 프로그램 내부 함수) 중 어디가 “의미/포착률/우회 가능성” 관점에서 적절한지 결정한다.실제로 실행 흐름이 지나가는 “경계”를 내 훅 함수로 우회시키는 단계다. 후킹 방식은 크게 이벤트 경로를 가로채는 방식과 함수 호출 경로를 가로채는 방식(API 후킹) 으로 나뉜다.
“이벤트 후킹”은 이벤트 흐름을 잡고, “API 후킹(IAT/인라인)”은 함수 호출 경계를 잡는다.
IAT는 모듈 단위로 범위를 좁혀 잡기 좋고, 인라인은 범용적으로 강제 우회가 가능하다.
가로채는 대상과 위치에 따라 후킹 방식은 다양하지만, 리버싱에서 자주 만나는 대표 방식 3개를 먼저 잡고 간다.

→ 메시지/이벤트 후킹은 API 후킹(IAT/인라인)처럼 함수 호출 경계를 낚는 게 아니라, 입력/메시지 같은 이벤트 흐름 자체를 가로채는 방식이다.

[일반적인 경우의 Windows 메시지 흐름에 대한 설명]
윈도우 GUI 프로그램은 기본적으로 메시지 중심으로 동작한다.
GetMessage/PeekMessage 같은 루프에서 메시지를 꺼내고, DispatchMessage를 통해 윈도우 프로시저(WndProc) 로 전달한다.여기서 포인트
메시지/이벤트 후킹은 “WndProc 안으로 들어간 뒤”가 아니라, 그 전에 흐름을 먼저 보는 쪽에 가깝다.
윈도우는 입력/메시지 처리 과정에서 훅 체인이라는 중간 경로를 둘 수 있다.
훅 체인에 내 훅 프로시저를 등록하면, 메시지가 응용 프로그램에 도달하기 전에 내 코드가 먼저 호출될 수 있다.
CallNextHookEx로 다음 훅에 넘겨 체인을 유지한다.메시지/이벤트 후킹은 범위가 중요하다. 범위를 넓히면 강력해 보이지만, 동시에 리스크도 커진다.
메시지/이벤트 후킹은 다음 3가지 목적에 쓰인다.
이러한 메시지 훅 기능은 Windows 운영체제에서 제공하는 기본 기능이며, 대표적으로 MS Visual Studio에서 제공되는 SPY++ 가 있다.
예시: Keylogger 후킹 코드 작성해보기
#include <iostream>
#include <windows.h>
#include <string>
// 훅 핸들 보관용 전역 변수
HHOOK _hook;
// Hook Handler (흐름을 가로챈 뒤 실행될 코드)
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
// 키보드가 눌렸을 때 (WM_KEYDOWN)
if (wParam == WM_KEYDOWN) {
KBDLLHOOKSTRUCT* kbdStruct = (KBDLLHOOKSTRUCT*)lParam;
DWORD vkCode = kbdStruct->vkCode;
std::string log_message;
// 조합키(Ctrl, Shift) 상태 확인
if (GetAsyncKeyState(VK_LCONTROL) & 0x8000 || GetAsyncKeyState(VK_RCONTROL) & 0x8000) {
log_message += "[Ctrl] + ";
}
if (GetAsyncKeyState(VK_LSHIFT) & 0x8000 || GetAsyncKeyState(VK_RSHIFT) & 0x8000) {
log_message += "[Shift] + ";
}
// 일반 키보드 문자 판별
if (vkCode >= 0x30 && vkCode <= 0x5A) { // 숫자 0-9, 알파벳 A-Z
log_message += (char)vkCode;
}
else if (vkCode == VK_BACK) {
log_message += "[Backspace]";
}
else if (vkCode == VK_RETURN) {
log_message += "[Enter]";
}
else if (vkCode == VK_SPACE) {
log_message += "[Space]";
}
// 가로챈 키보드 입력을 콘솔에 출력
if (!log_message.empty()) {
std::cout << "Intercepted Key: " << log_message << std::endl;
}
// 만약 특정 키(예: 'A')를 완전히 먹통으로 만들고 싶다면?
// 여기서 return 1; 을 호출하여 흐름을 끊어버리면(Block)
// 원래 응용프로그램에는 'A' 입력이 절대 전달되지 않는다.
}
}
// 처리가 끝났으므로 원래 가야 했을 다음 훅 체인으로 전달 (Forward)
return CallNextHookEx(_hook, nCode, wParam, lParam);
}
int main() {
// 타깃 선정 및 실행 컨텍스트 확보
// OS의 가장 앞단(WH_KEYBOARD_LL)에 나의 훅 프로시저(KeyboardHookProc)를 설치한다.
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, NULL, 0);
if (_hook == NULL) {
std::cerr << "후킹에 실패했습니다!" << std::endl;
return 1;
}
std::cout << "키보드 훅이 시작되었습니다. (아무 곳에나 타이핑해 보세요!)" << std::endl;
// 메시지 루프 (훅이 해제되지 않고 계속 대기하도록 유지)
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 프로그램 종료 시 훅 해제 (안전한 복귀)
UnhookWindowsHookEx(_hook);
return 0;
}
간단하게 이와 같이 코드를 작성해 키보드 입력을 가져오는 테스트를 할 수도 있다.


프로그램이 외부 DLL 함수를 호출할 때 참고하는 ‘수입 주소록(IAT)’의 함수 포인터를 바꿔치기해서, 호출 흐름이 내 훅 함수로 들어오게 만드는 방식이다.
MessageBoxW)를 import로 선언해두고, 로더가 실행 시점에 그 함수들의 실제 주소를 IAT 엔트리(함수 포인터 칸) 에 채워 넣는다. 프로그램은 해당 API를 호출할 때 (많은 경우) 이 IAT 엔트리를 통해 간접 호출을 수행한다. IAT 후킹은 이 엔트리 값을 원본 함수 주소 → 내 훅 함수 주소로 교체하여, 이후 호출이 내 훅으로 먼저 들어오게 만든다.GetProcAddress 등으로 런타임에 함수 주소를 얻어 함수 포인터로 직접 호출하거나, 한 번 얻은 주소를 다른 곳에 캐싱해서 호출하는 경우에는 IAT 후킹만으로는 빠질 수 있다(범위가 제한될 수 있다).참고
jmp CreateFile 표기는 IAT를 경유한 간접 호출을 단순화한 표현이다.주소록을 건드리는 대신 메모리에 올라간 타깃 함수의 실제 실행 코드 첫 부분을 강제로 점프(JMP) 명령어로 수정해 버린다.
JMP) 명령으로 덮어써서 호출이 무조건 훅으로 들어오게 만든다. 원본 실행을 유지하려면 덮어쓴 원본 앞부분을 트램폴린이라는 공간에 보존해 두었다가, 훅 함수 실행이 끝난 뒤 트램폴린을 거쳐 원본 경로로 합류시킨다.이러한 여러 후킹 기법 중에서도, 시스템의 동작 흐름을 완벽하게 꿰뚫어 볼 수 있는 API 후킹(API Hooking)은 윈도우 환경 리버싱의 핵심으로 불린다.
어떤 대상을 어떻게 후킹할 것인지 한눈에 파악할 수 있는 구조다.
메모리상의 '어느 위치'를 공략하느냐, 그리고 '어떻게 침투'하느냐에 따라 나뉜다.

윈도우 OS에서 어플리케이션은 보안을 위해 메모리나 파일 같은 시스템 자원에 직접 접근할 수 없다. 반드시 OS가 제공하는 Win32 API (kernel32.dll, ntdll.dll 등)를 거쳐 시스템 커널에게 부탁해야만 한다.
API 후킹은 이 필수적인 통로 길목에 갈고리를 걸어 제어권을 완전히 빼앗는 기술이다.
정상적인 API 호출 (https://reversecore.com/54 참고)

CreateFileA/ CreateFileW 로 나뉜다.)kernel32 CreateFile()이 후킹된 경우


윈도우 기본 프로그램인 메모장(notepad.exe)이 텍스트 파일을 여는 과정을 API 후킹으로 어떻게 통제하는지 확인한다.
1) 정상적인 실행 흐름 (Original Path)
c:\abc.txt 파일을 연다.kernel32.dll의 CreateFileW() 함수를 호출ntdll.dll을 거쳐 시스템 커널로 진입하여 정상적으로 파일을 읽어온다.2) API 후킹이 적용된 실행 흐름 (Hooked Path)
hook.dll을 강제로 침투시킨다.hook.dll은 실행되자마자, 원본 kernel32.dll 내부의 CreateFileW() 함수의 시작 코드를 조작하여(코드 후킹), 내가 작성한 hook!MyCreateFile() 함수로 향하도록 방향을 꺾어버린다.MyCreateFile()이 먼저 실행된다.MyCreateFile() 함수 내부에서는 인자로 넘어온 파일 경로(c:\abc.txt)를 엿볼 수 있습니다. 만약 열려는 파일 이름이 "기밀문서.txt"라면, 원래 함수로 보내지 않고 강제로 에러를 반환하게 조작하여 메모장이 해당 파일을 절대 열지 못하도록 원천 차단할 수 있다.출처 및 참고