안녕하세요. 오늘 포스팅은 후킹의 마지막 파트인 인라인 후킹에 대해 포스팅을 해보겠습니다.
여태까지 IAT후킹과 Frida를 활용한 WinAPI후킹 및 동적 후킹을 해봤습니다.

저번 IAT후킹 포스팅할 때 작성했던 표입니다.
이번 포스팅에서는 inline후킹에 대해 살펴보고 스켈레톤 코드를 공부한 뒤 2편에서 메모장(notepad.exe)를 직접 후킹해보는 실습을 해보곘습니다.
inline 후킹의 핵심 원리
- 후킹하고 싶은 대상 함수의 첫번째 명령어들을 확인한 뒤
- 함수의 시작 부분을 JMP로 내가 작성한 함수로 이동 (5바이트)
- 내가 작성한 함수에서 원하는 후킹을 실행한 뒤에
- 다시 원래 함수의 흐름으로 복귀한다.
기존 우리가 후킹하고 싶던 함수의 어셈 코드가 다음과 같다고 가정해보겠습니다.
0x1000: push ebp //함수시작
0x1001: mov ebp,esp
0x1003: ...
해당 코드를 강제로 다음과 같이 바꿉니다.
0x1000: jmp mycode //jmp 삽입 (E9 xx xx xx xx)
0x1005: ... [원래 코드 일부 삭제됨]
jmp는 보통 5바이트이기 때문에 0x1001부터 0x1004까지 삭제가 되는 상황이 발생합니다.
이때 내가 원하는 코드에서 복귀할 때 함수가 원래 동작대로 작동하지 않고 크래시가 발생하므로, 0x1000부터 0x1004까지의 덮어쓴 5바이트를 따로 백업해둬야 하는데 이걸 trampoline(트램폴린) 개념이라고 합니다. 트램폴린은 인라인 후킹의 제일 핵심적인 개념입니다.
원본 코드를 별도의 메모리 공간에 복사해두는데, 이 공간을 바로 트램펄린 영역이라고 합니다.
트램펄린 영역을 설정할 때 몇가지 필요충분조건이 있습니다.
- 실행 가능 권한 : 트램펄린은 CPU가 실행할 코드를 가지고 있으므로, 해당 메모리 영역은 반드시 실행권한(PAGE_EXECUTE_READWRITE 또는 PAGE_EXECUTE_READ)권한을 가지고 있어야합니다.
- 쓰기 가능 권한 : 초기 코드를 복사하고 점프 백 코드를 작성해야 하므로, 영역 설정시에는 쓰기권한(PAGE_READWRITE 또는 PAGE_EXECUTE_READWRITE)이 필요합니다.
- 32~64바이트 이상의 크기 확보 : 덮어쓴 원본 복구 + jmp 코드 삽입(5바이트)
이번 후킹은 IAT 후킹과 조금 비슷한 방식으로 진행되면서도 조금 더 어려운 부분이 있습니다. 먼저 필요한 파일과 코드들을 정리해보겠습니다.
- hook.dll
- injector.exe
- 후킹 대상 프로세스.exe
저번에 IAT 코드에 관해 포스팅을 쓰면서 바로 실습 코드를 이해하면서 작성
하려다 보니까 포스팅에 시간이 너무 많이 걸리기도 하고 코드 이해 - 실습 적용 이렇게 되는게 아니라 그냥 코드 이해만 되는 것 같아서 아쉬움이 있었습니다.
이번 포스팅부터 이런 개념을 학습할 때는 스켈레톤 코드를 먼저 학습하고 실습은 나눠서 포스팅 해보도록 하겠습니다.
스켈레톤 코드를 공부하기 전에 먼저 원하는 동작을 정해보고 가겠습니다.
- 후킹하고자 하는 함수 찾기
- 후킹하고자 하는 함수 백업하기
- 후킹 함수 작성하기
- 후킹 함수 삽입하기
- 원래 함수 시작 부분 복원하기
다음은 인라인 후킹 스켈레톤 코드입니다.
#include <windows.h>
#include <stdint.h>
typedef void (*TargetFuncType)(...);
TargetFuncType g_original = NULL;
void* g_trampoline = NULL;
uintptr_t g_target_addr = 0x00000000;
__declspec(thread) static BOOL g_inside = FALSE;
void MyHookFunction(...) {
if (g_inside) {
g_original(...);
return;
}
g_inside = TRUE;
...
g_original(...);
g_inside = FALSE;
}
void InstallHook() {
DWORD oldProtect;
BYTE jmp_stub[] = { 0xFF, 0x25, 0,0,0,0 }; // jmp [RIP+0]
uintptr_t hook_target = (uintptr_t)&MyHookFunction;
g_trampoline = VirtualAlloc(NULL, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!g_trampoline) return;
memcpy(g_trampoline, (void*)g_target_addr, 14);
uintptr_t resume_addr = g_target_addr + 14;
memcpy((BYTE*)g_trampoline + 14, jmp_stub, sizeof(jmp_stub));
memcpy((BYTE*)g_trampoline + 14 + sizeof(jmp_stub), &resume_addr, sizeof(resume_addr));
g_original = (TargetFuncType)g_trampoline;
VirtualProtect((void*)g_target_addr, 14, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((void*)g_target_addr, jmp_stub, sizeof(jmp_stub));
memcpy((BYTE*)g_target_addr + sizeof(jmp_stub), &hook_target, sizeof(hook_target));
VirtualProtect((void*)g_target_addr, 14, oldProtect, &oldProtect);
}
void UninstallHook() {
DWORD oldProtect;
if (!g_trampoline || !g_target_addr) return;
VirtualProtect((void*)g_target_addr, 14, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((void*)g_target_addr, g_trampoline, 14);
VirtualProtect((void*)g_target_addr, 14, oldProtect, &oldProtect);
VirtualFree(g_trampoline, 0, MEM_RELEASE);
g_trampoline = NULL;
g_original = NULL;
}
엄청 복잡한 코드입니다.. 저희가 맨처음 정해두었던 동작에 맞춰서 문단 단위로 분석해보겠습니다.
typedef void(*TargetFuncType)(...); //함수 시그니처 정의
TargetFuncType g_original = NULL; //trampoline 호출용 함수 포인터
void* g_trampoline = NULL; //trampoline 코드 저장 주소
uintptr_t g_target_addr = 0x00000000; //후킹 대상 함수의 실제 주소
이 네줄의 코드는 후킹 실행 전 필요한 핵심 변수들을 미리 선언하는 것입니다.
typedef 형식으로 함수의 형태를 정의하는 방법은 IAT 후킹때도 사용했었는데, 가짜 함수를 원래 함수의 형태처럼 정의해서 호출하기 위해서 사용합니다.
다음과 같은 시그니처를 가진 MessageBoxA()를 후킹한다고 가정해보겠습니다.
int MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
그럼 typedef는 다음과 같이 작성해주면 됩니다.
typedef int (WINAPI *targetFuncType)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
(*WINAPI는 windows API의 호출 규약을 맞춰주기 위해 앞에 붙여주는 것으로 _stdcall을 붙이는 것과 동일한 역할을 수행합니다)
그 다음줄입니다.
__declspec(thread) static BOOL g_inside = FALSE;
이 코드는 무한 재귀 발생 방지와 스레드 안정성을 확보해주는 코드입니다.
__declspec는 C/C++에서 변수에 특별한 속성을 부여해주는 Microsoft 전용 키워드입니다.
__declspec(thread)는 다음 변수가 각 스레드마다 따로 보관되게 해줘서, 스레드 단위로 복사본을 갖게 해줍니다.
g_inside를 BOOL형식으로 선언해주었기 때문에 후킹 함수가 진입할 때 g_inside가 True상태이면 바로 함수를 종료해줍니다.
static은 접근 제한 + 안정성 확보를 위해 붙여줬습니다.
다음은 후킹 함수 작성하는 코드입니다.
void MyHookFunction(...){
if(g_inside){
g_original(...);
return;
}
g_inside = TRUE;
//todo : 후킹 코드
g_original(...)
g_inside = FALSE;
}
비교적 이해하기 쉬운 코드인데, 아까 설정해둔 g_inside값을 확인하고
g_inside가 TRUE이면 : 이미 후킹 코드 안이므로 g_original(트램펄린코드)로 돌아갑니다.
g_inside가 FALSE이면 : g_inside를 TRUE로 바꾸고, 원하는 코드를 실행한 뒤 마지막에 g_inside값을 다시 FALSE로 변경해둡니다.
//todo 부분에는 원하는 후킹 동작을 넣으면 됩니다.
다음은 inline hooking 설치하는 함수입니다.
타겟 함수의 앞부분에 jump 명령어를 넣고 다시 트램폴린으로 이어지게 합니다.
void installhook(){
DWORD oldProtect;
BYTE jmp_stub[] = {0xFF, 0x25, 0,0,0,0 };
uintptr_t hook_target = (uintptr_t)&MyHookFunction;
installhook()의 초반 부분입니다.
oldProtect는 기존 설정되어있는 보호 속성을 저장해두는 변수입니다.
jmp_stub은 바이트 형태로 점프 명령어의 기계어 바이트를 담은 상수 배열입니다. 6바이트를 어셈블리어로 해석하면 다음과 같습니다. [FF 25 00 00 00 00 -> jmp qword ptr [rip+0]]
hook_target은 uintptr_t (int형)으로 MyHookFunction의 주소를 정수형으로 저장합니다.
(*uintptr_t은 void* 같은 포인터를 숫자로 바꾸거나 비교할 때 사용하고, 주소를 정수처럼 안전하게 다룰수 있게 도와주는 타입입니다.)
g_Trampoline = VirtualAlloc(NULL, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!g_trampoline) return;
g_Trampoline의 영역을 동적 할당하는 코드입니다.

https://learn.microsoft.com/ko-kr/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
VirtualAlloc 함수(memoryapi.h) - Win32 apps
호출 프로세스의 가상 주소 공간에서 페이지 영역의 상태를 예약, 커밋 또는 변경합니다. (VirtualAlloc)
learn.microsoft.com
해당 주소를 참고하시면 더 쉽게 이해가 가능하실 것 같고, 시스템이 정한 주소에 64바이트의 실행공간을 주소공간과 실제 메모리 공간을 같이 할당면서 읽기+쓰기+실행 권한까지 같이 부여합니다.
memcpy(g_trampoline, (void*)g_target_addr, 14);
uintptr_t resume_addr = g_target_addr + 14;
memcpy((BYTE*)g_trampoline + 14, jmp_stub, sizeof(jmp_stub));
memcpy((BYTE*)g_trampoline + 14 + sizeof(jmp_stub), &resume_addr, sizeof(resume_addr));
함수 위치를 저장하는 코드들입니다. 트램펄린을 만드는 핵심 코드들입니다.
①원본 함수 앞부분 명령어
②원래 함수로 이어지는 주소로 점프할 주소값
③JMP 명령어
를 붙여서 완성하는 코드입니다.
g_original = (TargetFuncType)g_trampoline;
g_original에 g_trampoline을 복사합니다. 따라서 나중에 g_original을 호출하면 트램폴린 함수가 실행되서 원래 함수가 이어져서 실행됩니다.
VirtualProtect((void*)g_target_addr, 14, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((void*)g_target_addr, jmp stub, sizeof(jmp_stub));
memcpy((BYTE*)g_target_addr + sizeof(jmp_stub), &hook_target, sizeof(hook_target));
VirtualProtect((void*)g_target_addr, 14, oldProtect, &oldProtect);
}
아까 memcpy 코드 부분과 비슷한 역할을 하는 코드입니다.
① 후킹 대상 코드부분의 권한을 읽기,쓰기,실행 권한으로 바꾼 뒤
②시작주소에 jmp 주소를 삽입(hook_target은 MyHookFunction의 포인터)
③다시 메모리 보호 권한을 복구시킵니다.
여기까지가 후킹을 설치하는 InstallHook()부분입니다.
void UninstallHook() {
DWORD oldProtect;
if (!g_trampoline || !g_target_addr) return;
VirtualProtect((void*)g_target_addr, 14, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((void*)g_target_addr, g_trampoline, 14);
VirtualProtect((void*)g_target_addr, 14, oldProtect, &oldProtect);
VirtualFree(g_trampoline, 0, MEM_RELEASE);
g_trampoline = NULL;
g_original = NULL;
}
설치한 후킹함수를 제거하는 UninstallHook()입니다.
함수의 목적은 g_target_addr에 덮어씌운 JMP 명령어를 제거하고, 원래 함수의 앞 14바이트를 복원 + 트램펄린 메모리 해제입니다.
만약 trampoline이 존재하지 않거나 g_target_addr이 0인경우 실행하지않고 종료합니다.
그 뒤에는 다음과 같습니다.
① 후킹 대상 코드부분의 권한을 읽기,쓰기,실행 권한으로 바꾼 뒤
② g_target_addr에 g_trampoline을 복사해서 붙여넣습니다.
③ 다시 메모리 보호 권한을 복구시킨 뒤 동적 메모리를 해제시킵니다.
④ g_trampoline, g_original을 NULL로 초기화시킵니다.
이렇게하면 후킹을 하는 코드 hook.c까지는 다 살펴보았습니다. 이런류의 코드들이 처음에 읽는게 엄청 힘들지만 그래도 읽다보면 눈에 익어서 조금 속도가 빨라지는 것 같습니다.
그래도 스켈레톤 코드다 보니까 더 쉽게 읽혀지는 것 같습니다.
공부하면서 주석을 달아놓은 코드입니다.
#include <windows.h>
#include <stdint.h>
typedef void(*TargetFuncType)(...); //함수 시그니처 정의
TargetFuncType g_original = NULL; //trampoline 호출용 함수 포인터
void* g_trampoline = NULL; //trampoline 코드 저장 주소
uintptr_t g_target_addr = 0x00000000;//후킹 대상 함수의 실제 주소
__declspec(thread) static BOOL g_inside = FALSE; //무한 재귀 발생을 위한 스레드 분리
void MyHookFunction(...){
if(g_inside){
g_original(...);
return; //이미 후킹코드안이면 실행 종료
}
g_inside = TRUE; //후킹 실행중
//todo : 후킹 코드
g_original(...) // 트램펄린 코드
g_inside = FALSE; //후킹 종료
}
void installhook(){
DWORD oldProtect; //기존 보호속성 저장
BYTE jmp_stub[] = {0xFF, 0x25, 0,0,0,0 }; //점프코드 바이트배열로 저장
uintptr_t hook_target = (uintptr_t)&MyHookFunction; //내 후킹코드 주소를 uintptr_t형식으로 저장
//64바이트 크기의 주소공간+실제 메모리를 읽기쓰기실행권한으로 동적할당
g_Trampoline = VirtualAlloc(NULL, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!g_trampoline) return; //할당실패시 후킹 종료
//시작 주소를 안전하게 g_trampoline에 백업
memcpy(g_trampoline, (void*)g_target_addr, 14);
uintptr_t resume_addr = g_target_addr + 14;
memcpy((BYTE*)g_trampoline + 14, jmp_stub, sizeof(jmp_stub));
memcpy((BYTE*)g_trampoline + 14 + sizeof(jmp_stub), &resume_addr, sizeof(resume_addr));
//g_original을 포인터타입으로 g_trampline을 지정
g_original = (TargetFuncType)g_trampoline;
//타깃함수의 권한을 바꾼 뒤
VirtualProtect((void*)g_target_addr, 14, PAGE_EXECUTE_READWRITE, &oldProtect);
//jmp + myHookFunction의 포인터 주소인 hook_target 삽입
memcpy((void*)g_target_addr, jmp stub, sizeof(jmp_stub));
memcpy((BYTE*)g_target_addr + sizeof(jmp_stub), &hook_target, sizeof(hook_target));
//메모리 권한 복구
VirtualProtect((void*)g_target_addr, 14, oldProtect, &oldProtect);
}
//후킹 코드 제거부분
void UninstallHook() {
DWORD oldProtect; //oldProtect 선언
//trampoline이나 target_addr이 없을경우 함수 종료
if (!g_trampoline || !g_target_addr) return;
//후킹 대상 코드의 보호 권한을 바꾸고
VirtualProtect((void*)g_target_addr, 14, PAGE_EXECUTE_READWRITE, &oldProtect);
//target address에 트램펄린 코들르 넣어 원래 함수의 앞 14바이트를 복원합니다.
memcpy((void*)g_target_addr, g_trampoline, 14);
VirtualProtect((void*)g_target_addr, 14, oldProtect, &oldProtect);
//동적할당 해제
VirtualFree(g_trampoline, 0, MEM_RELEASE);
g_trampoline = NULL;
g_original = NULL;
}
(앞부분 주석은 다른 컴퓨터에 있어서 추후 수정하겠습니다)
위의 코드를 DLL로 빌드해서 injector.exe와 함께 dll인젝션을 통해 삽입하면 됩니다.
cl /LD hook.c /Fe:hook.dll
(*저희가 만든 코드는 스켈레톤 코드이기 때문에 명령어 입력시 오류가 발생합니다!)
인젝터 코드를 만드려고 했는데 따로 인젝터 코드에 대해 따로 포스팅을 한번 하고싶어서 오늘은 여기까지 해서 포스팅을 마치겠습니다.
다음 포스팅에는 dll 인젝터에 관한 내용 1편 그리고 오늘 살펴본 스켈레톤 코드에 살을 붙여서 notepad를 후킹해보겠습니다. 후킹을 성공해야 포스팅을 쓸 수 있는거라 다음 포스팅이 언제 나올지는 모르겠지만 아마 화요일까진..할수 있을 것 같습니다.
읽어주셔서 감사합니다~

'취약점분석 > Reversing' 카테고리의 다른 글
| [Reversing] Hooking - 6 (notepad.exe 후킹 ①) (2) | 2025.07.26 |
|---|---|
| [Reversing]DLL injection ( injector 스켈레톤 코드 분석 ) (5) | 2025.07.21 |
| [Reversing] Hooking - 4 (Frida Hooking ②) (3) | 2025.07.12 |
| [Reversing] Hooking - 3 (Frida Hooking ①) (2) | 2025.07.07 |
| [Reversing] Hooking -2 (IAT Hooking) (0) | 2025.07.05 |