0%

Windows x64 hooking

Code Patch를 통해서 API 후킹을 할 떄, 32bit 환경에서는 JMP Instruction 중 “E9 XXXXXXXX”를 사용하면 됬었다. 하지만 64bit 환경에서는 32bit 환경보다 가상메모리 공간이 훨씬더 커지면서 “E9 XXXXXXXX”을 이용해서는 안정적으로 후킹을 할 수가 없게 되었다. (32bit 가상메모리공간 -> 4GB, 64bit 가상메모리공간 -> 16TB)

따라서 64bit 환경에서는 32bit 환경에서 했던 것처럼 Code Patch를 하지 못한다. 다른 방법을 사용을 해야 한다. 64bit 환경에서 후킹 할 수 있는 방법을 찾는대로 계속 꾸준히 업데이트를 할 계획이다. 하지만 찾은 방법들이 안정적인지는 잘 모르겠다.

후킹 방법 목록

다음 항목들은 64bit에서 후킹할 수 있는 방법들이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 첫번째 방법 -> 14 바이트
0x00007FFF 00000001 'FF 25 00000000' JMP QWORD PTR ds:[7FFF00000007] # JMP QWORD PTR [RIP+addr0]
0x00007FFF 00000007 'XXXXXXXX XXXXXXXX' # 후킹 함수 주소

# 두번째 방법 -> 12 바이트
mov rax, address # "48 b8 000000 000000"
jmp rax # "ff e0"

# 세번째 방법 -> 14 바이트
push 4byte_low_address
mov dword ptr[rsp+4], 4byte_high_address
ret

# 네번째 방법 -> ?? 추후 업데이트
mov rax, address
call rax

1. JMP Instruction “FF 25”

1
2
0x00007FFF 00000001 'FF 25 00000000' JMP QWORD PTR ds:[7FFF00000007] # JMP QWORD PTR [RIP+addr0]
0x00007FFF 00000007 'XXXXXXXX XXXXXXXX' # 후킹 함수 주소

JMP Instruction 중에서 “FF 25 XXXXXXXX”를 이용하는 방법이다. FF25로 시작하는 JMP 명령어는 레지스터 RIP + offset(XXXXXXXX)을 더한 주소로 이동한다. 이를 통해서 RIP+offset(XXXXXXXX) 주소에다가 후킹 함수 주소를 넣으면, 후킹 함수로 이동할 수 있는 기법이다.


테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "Windows.h"
#include "stdio.h"

BYTE g_pOrgBytes[14] = { 0, };

BOOL hook(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes)
{
FARPROC pfnOrg;
DWORD dwOldProtect;
BYTE pBuf1[6] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
BYTE pBuf2[8] = { 0, };
PBYTE pByte;

// 후킹 대상 API 주소를 구한다
pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pfnOrg;

// 만약 후킹되어 있다면 return FALSE
if (pByte[0] == 0xFF && pByte[1] == 0x25)
return FALSE;

// 기존코드 14바이트 백업 및 후킹 함수 시작 주소 구하기
memcpy(pOrgBytes, pfnOrg, 14);
memcpy(pBuf2, &pfnNew, 8);

// 14바이트 패치를 위하여 메모리에 Write 속성 추가
VirtualProtect((LPVOID)pfnOrg, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// Hook: 14바이트 패치
memcpy(pfnOrg, pBuf1, 6);
memcpy((LPVOID)((DWORD_PTR)pfnOrg + 6), pBuf2, 8);

// 메모리에 속성 복원
VirtualProtect((LPVOID)pfnOrg, 14, dwOldProtect, &dwOldProtect);
}

BOOL unhook(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes)
{
FARPROC pfnOrg;
DWORD dwOldProtect;
PBYTE pByte;

// API 주소를 구한다.
pfnOrg = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pfnOrg;

// 만약 이미 언후킹되어 있다면 FALSE
if (pByte[0] != 0xFF && pByte[1] != 0x25)
return FALSE;

// 기존 코드 14바이트를 덮어쓰기 위헤 메모리에 WRITE 속성 추가
VirtualProtect((LPVOID)pfnOrg, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// unHook: 14바이트 패치
memcpy(pfnOrg, pOrgBytes, 14);

// 메모리 속성 복원
VirtualProtect((LPVOID)pfnOrg, 14, dwOldProtect, &dwOldProtect);

return TRUE;
}

void MyMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
unhook("user32.dll", "MessageBoxA", g_pOrgBytes);
MessageBoxA(NULL, "Code Patch API Hooking", "Hook", MB_OK);
hook("user32.dll", "MessageBoxA", (PROC)MyMessageBox, g_pOrgBytes);
}

int main(int argc, char* argv[])
{
HMODULE module = LoadLibraryA("user32.dll");

hook("user32.dll", "MessageBoxA", (PROC)MyMessageBox, g_pOrgBytes);
MessageBEoxA(NULL, "Hello, World!", "Earth", MB_OK);
unhook("user32.dll", "MessageBoxA", g_pOrgBytes);
MessageBoxA(NULL, "Hello, World!", "Earth", MB_OK);


FreeLibrary(module);
return 0;
}

2. mov & jmp instruction

1
2
mov rax, address # "48 b8 000000 000000"
jmp rax # "ff e0"

64bit 값을 가질 수 있는 레지스터를 이용해서 레지스터에 후킹 함수 주소값을 집어넣고, JMP 명령어를 통해서 후킹 함수쪽으로 실행흐름을 바꾸는 방법이다.

어떤 레지스터를 사용할 것인지에 따라서 machine code는 달라질 수 있다. mov 명령어를 통해 immu64 값을 rax의 집어 넣는 machine code 는 “48 b8 XXXXXXXX XXXXXXXX” 이고, rax 레지스터 값으로 이동하는 JMP의 machine code는 “ff e0” 이다.


테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "Windows.h"
#include "stdio.h"

BYTE g_pOrgBytes[12] = { 0, };

BOOL hook(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes)
{
FARPROC pfnOrg;
DWORD dwOldProtect;
BYTE pBuf1[10] = { 0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
BYTE pBuf2[2] = { 0xff, 0xe0};
PBYTE pByte;

// 후킹 대상 API 주소를 구한다
GetModuleHandleA(szDllName);
pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pfnOrg;

// 만약 후킹되어 있다면 return FALSE
if (pByte[0] == 0x48 && pByte[1] == 0xb8)
return FALSE;

// 기존코드 12바이트 백업 및 후킹 함수 시작 주소 구하기
memcpy(pOrgBytes, pfnOrg, 12);
memcpy(pBuf1+2, &pfnNew, 8);

// 12바이트 패치를 위하여 메모리에 Write 속성 추가
VirtualProtect((LPVOID)pfnOrg, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// Hook: 12바이트 패치
memcpy(pfnOrg, pBuf1, 10);
memcpy((LPVOID)((DWORD_PTR)pfnOrg + 10), pBuf2, 2);

// 메모리에 속성 복원
VirtualProtect((LPVOID)pfnOrg, 12, dwOldProtect, &dwOldProtect);
}

BOOL unhook(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes)
{
FARPROC pfnOrg;
DWORD dwOldProtect;
PBYTE pByte;

// API 주소를 구한다.
pfnOrg = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pfnOrg;

// 만약 이미 언후킹되어 있다면 FALSE
if (pByte[0] != 0x48 && pByte[1] != 0xb8)
return FALSE;

// 기존 코드 12바이트를 덮어쓰기 위헤 메모리에 WRITE 속성 추가
VirtualProtect((LPVOID)pfnOrg, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// unHook: 12바이트 패치
memcpy(pfnOrg, pOrgBytes, 12);

// 메모리 속성 복원
VirtualProtect((LPVOID)pfnOrg, 12, dwOldProtect, &dwOldProtect);

}

void MyMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
unhook("user32.dll", "MessageBoxA", g_pOrgBytes);
MessageBoxA(NULL, "Code Patch API Hooking", "Hook", MB_OK);
hook("user32.dll", "MessageBoxA", (PROC)MyMessageBox, g_pOrgBytes);
}

int main(int argc, char* argv[])
{
HMODULE module = LoadLibraryA("user32.dll");

hook("user32.dll", "MessageBoxA", (PROC)MyMessageBox, g_pOrgBytes);
MessageBoxA(NULL, "Hello, World!", "Earth", MB_OK);
unhook("user32.dll", "MessageBoxA", g_pOrgBytes);
MessageBoxA(NULL, "Hello, World!", "Earth", MB_OK);


FreeLibrary(module);
return 0;
}

3. push & mov & ret instruction

1
2
3
push 4byte_low_address
mov dword ptr[rsp+4], 4byte_high_address
ret

64bit에서는 64bit 레지스터 값을 push할 수 있는 명령어는 있지만, immu64 값을 push 할수 있는 명령어가 없다. 따라서 스택에 후킹 함수를 주소를 집어 넣으러면 push 4byte_low_address를 넣고, mov dword ptr[rsp+4], 4byte_high_address를 통해서 rsp값에 후킹 함수 주소를 설정해야 한다. 그 이후 ret 명령어를 통해서 후킹 함수쪽으로 실행흐름을 바꾸는 기법이다.


테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include "Windows.h"
#include "stdio.h"

BYTE g_pOrgBytes[14] = { 0, };

void hook(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes)
{
FARPROC pfnOrg;
DWORD dwOldProtect;
BYTE pBuf1[5] = { 0x68, 0x00, 0x00, 0x00, 0x00};
BYTE pBuf2[8] = { 0xc7, 0x44, 0x24 ,0x04, 0x00, 0x00, 0x00, 0x00 };
BYTE pBuf3[1] = { 0xc3 };
//PBYTE pByte;

// 후킹 대상 API 주소를 구한다
GetModuleHandleA(szDllName);
pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
//pByte = (PBYTE)pfnOrg;

// 기존코드 14바이트 백업 및 후킹 함수 시작 주소 구하기
memcpy(pOrgBytes, pfnOrg, 14);
memcpy(pBuf1 + 1, &pfnNew, 4);
for (int i = 0; i < 4; i++)
pBuf2[i + 4] = (BYTE)((DWORD_PTR)pfnNew >> 32 + i * 8);

// 14바이트 패치를 위하여 메모리에 Write 속성 추가
VirtualProtect((LPVOID)pfnOrg, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// Hook: 14바이트 패치
memcpy(pfnOrg, pBuf1, 5);
memcpy((LPVOID)((DWORD_PTR)pfnOrg + 5), pBuf2, 8);
memcpy((LPVOID)((DWORD_PTR)pfnOrg + 13), pBuf3, 1);

// 메모리에 속성 복원
VirtualProtect((LPVOID)pfnOrg, 14, dwOldProtect, &dwOldProtect);
}

void unhook(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes)
{
FARPROC pfnOrg;
DWORD dwOldProtect;
PBYTE pByte;

// API 주소를 구한다.
pfnOrg = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pfnOrg;

// 기존 코드 14바이트를 덮어쓰기 위헤 메모리에 WRITE 속성 추가
VirtualProtect((LPVOID)pfnOrg, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// unHook: 14바이트 패치
memcpy(pfnOrg, pOrgBytes, 14);

// 메모리 속성 복원
VirtualProtect((LPVOID)pfnOrg, 14, dwOldProtect, &dwOldProtect);

}

void MyMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
unhook("user32.dll", "MessageBoxA", g_pOrgBytes);
MessageBoxA(NULL, "Code Patch API Hooking", "Hook", MB_OK);
hook("user32.dll", "MessageBoxA", (PROC)MyMessageBox, g_pOrgBytes);
}

int main(int argc, char* argv[])
{
HMODULE module = LoadLibraryA("user32.dll");

hook("user32.dll", "MessageBoxA", (PROC)MyMessageBox, g_pOrgBytes);
MessageBoxA(NULL, "Hello, World!", "Earth", MB_OK);
unhook("user32.dll", "MessageBoxA", g_pOrgBytes);
MessageBoxA(NULL, "Hello, World!", "Earth", MB_OK);

FreeLibrary(module);
return 0;
}

참고자료

CALL - Call Procedure

JMP - Jump

PUSH instruction

API Hooking x86 x64

x64 Hooking

x86, x64 API HOOKING

Win32 API 후킹 - Trampoline API Hooking

Windows x64 binary 모듈 단위 이동

x64 Hooks

Assembly Challenge : Jump to a non-relative address without using registers