API Hooking 을 진행하는데 있어 여러가지 methods 가 있지만, 오늘은 Custom Structure를 이용해 API hooking을 해보겠습니다.
Custom Structure
typedef struct _HookSt {
PVOID pFunctionToHook;
PVOID pFunctionToRun;
BYTE pOriginalBytes[TRAMPOLINE_SIZE];
DWORD dwOldProtection;
} HookSt, *PHookSt;
우선, 보다 쉽게 구현하기 위해 structure를 만들어 줍니다.
- PVOID pFunctionToHook - hooking할 function의 주소입니다.
- PVOID pFunctionToRun - 실행할 function의 주소입니다.
- BYTE OriginalBytes[TRAMPOLINE_SIZE] - 원본 bytes를 보존하기 위한 변수입니다.
- DWORD dwOldProtection - 원본 Protection을 저장하기 위한 변수입니다.
Initializing Hook
hooking 을 하기 전 작업입니다.
BOOL InitializeHookStruct(IN PVOID pFunctionToHook, IN PVOID pFunctionToRun, OUT PHookSt Hook) {
Hook->pFunctionToHook = pFunctionToHook;
Hook->pFunctionToRun = pFunctionToRun;
memcpy(Hook->pOriginalBytes, pFunctionToHook, TRAMPOLINE_SIZE);
if (!VirtualProtect(pFunctionToHook, TRAMPOLINE_SIZE, PAGE_EXECUTE_READWRITE, &Hook->dwOldProtection)) {
printf("[!] VirtualProtect Failed with Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
- memcpy(Hook->pOriginalBytes, ...); - 원본 bytes를 보존합니다.
- if (!VirtualProtect(...)) { - 메모리의 권한을 바꿔주어 Trampoline 코드가 실행 가능하도록 만들어줍니다.
이 함수로 HookSt structure를 초기화 해줍니다.
Install Hook
hook 을 설치하는 작업입니다.
BOOL InstallHook(IN PHookSt Hook) {
uint8_t uTrampoline[] = {
0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x41, 0xFF, 0xE2
};
uint64_t uPatch = (uint64_t)(Hook->pFunctionToRun);
memcpy(&uTrampoline[2], &uPatch, sizeof(uPatch));
memcpy(Hook->pFunctionToHook, uTrampoline, sizeof(uTrampoline));
return TRUE;
}
먼저, 쉘코드에 대해 알아봐야 합니다.
쉘코드란, 기계어 명령어로 16진수로 표현되고 실행 가능한 영역에 있다면 별도의 컴파일 없이 실행됩니다.
여기서 어셈블리 언어가 나오는데, 어셈블리어는 기계어를 사람이 보기 쉽게 만들어놓은 언어입니다.
0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
이 쉘코드는 x64bit machine에서
mov r10, [Address]
를 의미합니다.
이 어셈블리어는 r10 레지스터에 [Address]를 넣어라. 라는 의미로, [Address] 는 hook을 설치할 함수의 주소로 덮어쓰게 됩니다.
0x41, 0xFF, 0xE2
이 쉘코드는
jmp r10
로, r10 레지스터의 주소로 rip 를 옮기겠다는 의미입니다.
이 쉘코드들로 인하여 13bit 크기의 간단한 쉘코드가 완성됩니다.
memcpy(&uTrampoline[2], &uPatch, sizeof(uPatch));
memcpy로 uPatch의 주소를 uTrampoline 의 3번째, 즉 mov r10 뒤 [Address] 부분 부터 8byte를 덮어쓰게 됩니다.

만약 주소가 0x123456789ABCDEF0 라면, little endian 규칙에 맞춰 메모리에 입력됩니다.
memcpy(Hook->pFunctionToHook, uTrampoline, sizeof(uTrampoline));
이제 훅을 설치할 차례입니다.
uTrampoline을 hooking할 함수의 주소에 uTrampoline 크기만큼 덮어씌웁니다.
Remove Hook
BOOL RemoveHook(IN PHookSt Hook) {
DWORD dwOldProtection = NULL;
memcpy(Hook->pFunctionToHook, Hook->pOriginalBytes, TRAMPOLINE_SIZE);
memset(Hook->pOriginalBytes, '\0', TRAMPOLINE_SIZE);
if (!VirtualProtect(Hook->pFunctionToHook, TRAMPOLINE_SIZE, Hook->dwOldProtection, &dwOldProtection)) {
printf("[!] VirtualProtect Failed with Error %d \n", GetLastError());
return FALSE;
}
Hook->pFunctionToHook = NULL;
Hook->pFunctionToRun = NULL;
Hook->dwOldProtection = NULL;
return TRUE;
}
hook을 해제할 때는 이 함수를 사용합니다.
- memcpy(Hook->pFunctionToHook, Hook->pOriginalBytes, ... ); - 저장해뒀던 OriginalBytes를 다시 hooking한 함수에 덮어씌웁니다.
- if (!VirtualProtect( ... )) { - 저장해뒀던 OldProtection을 다시 적용합니다.
- Hook->pFunctionToHook = NULL; ... 사용한 주소를 초기화합니다.
Usage
실제 함수를 사용할 때는 이렇게 사용합니다.
int main() {
HookSt st = { 0 };
if (!InitializeHookStruct(&MessageBoxA, &MyMessageBoxA, &st)) {
return -1;
}
MessageBoxA(NULL, "is this function hooked?", "Origianl MsgBox", MB_OK | MB_ICONQUESTION);
if (!InstallHook(&st)) {
return -1;
}
MessageBoxA(NULL, "This function is NOT hooked", "Original MsgBox", MB_OK | MB_ICONWARNING);
if (!RemoveHook(&st)) {
return -1;
}
MessageBoxA(NULL, "Unhooked Function", "Original MsgBox", MB_OK | MB_ICONINFORMATION);
return 0;
}
먼저, InitializeHookStruct로 hook을 초기화해준 뒤,
hooking을 수행하지 않은 상태에서 MessageBoxA를 호출해봅니다.
그리고, hooking을 수행한 뒤 MessageBoxA를 호출하면, MyMessageBoxA 가 호출됩니다.
마지막으로 RemoveHook으로 hook을 제거한 뒤 MessageBoxA를 호출하면, hook이 제거된 걸 확인할 수 있습니다.
마치며
이것으로 별도의 라이브러리를 사용하지 않고 API Hook 을 사용해보았습니다.
이해가 안되는 부분이나 부족한 부분 있으면 댓글 남겨주세요!
'IT' 카테고리의 다른 글
| Syscall - 커널의 소통 창구 (0) | 2026.06.03 |
|---|---|
| IAT Hiding (0) | 2026.05.30 |
| Classic APC Injection (0) | 2026.05.25 |
| Process, Thread Enumeration (0) | 2026.05.25 |
| API Hooking feat.DF (0) | 2026.05.21 |