逆向工程核心原理笔记-3

0x00 API钩取:逆向分析之花

 代码逆向分析中,钩取(Hooking)是一种截取信息、更改程序执行流向、添加新功能的技术。钩取的流程如下:(1)使用反汇编器/调试器熟悉程序的结构与工作原理;(2)开发钩子代码;(3)操作可执行文件与进程内存,部署钩子代码。

 其中,钩取Win32 API的技术被称为API钩取。分析程序时若有程序源码,大部分情况都不需要使用钩取技术。

API概念

 API(Application Programming Interface,应用程序编程接口)。Windows中,程序要使用系统资源(内存、文件、网络、视频、音频等)时无法直接访问,这些资源都是由Windows管理的,Windows禁止用户程序直接访问它们。程序需要使用这些资源时,必须向系统内核(Kernel)申请,申请的方法就是使用微软提供的API。下图为32位windows进程内存的情况:

image-20230405142732789

 可以看到,由ntdll.dll向内核提出申请。

 所有进程都会默认加载kernel32.dll库(但是有一些例外),kernel32.dll又会加载ntdll.dll库。在GUI程序中,必须加载user32.dllgdi32.dll库。

API钩取

 正常调用API过程举例:(1)在应用程序代码区域中调用CreateFile API。(2)由于CreateFile APIkerel32.dll的导出函数,所以,kernel32.dll区域中的CreateFile API会被调用执行并正常返回。

image-20230405144646893

 API钩取调用过程举例:用户先使用DLL注入技术将hook.dll注入目标进程的内存空间。(2)用hook!MyCreateFile钩取对kernel32!CreateFile的调用。(3)每当目标进程要调用kernel32!CreateFile时都会先调用hook!MyCreateFile

image-20230405144907630

API钩取技术图表(Tech Map)

 常用的技术在下图中已用下划线标出。

image-20230405145131293

 动态与静态钩取的不同如下:

image-20230405145603209

 钩取位置:

  • IAT。将程序内部的API地址更改为钩取函数地址。该方法的优点是实现起来非常简单,缺点是无法钩取不在IAT而在程序中使用的API,如动态加载并使用DLL时。
  • 代码。系统库(*.dll)映射到进程内存时,从中查找API的实际地址,并直接修改代码。

 向目标进程内存设置钩取函数的具体技术:

  • 调试。这里所说的调试器并不是OllyDbg等,而是用户直接编写的、用来钩取的程序(在用户编写的程序中使用调试API附加到目标进程)。当然也可以在ollyDbg上使用自动化脚本,自动钩取API。使用这种方法,即便是在钩取API的过程中,用户也可以暂停程序运行,进行添加、修改、删除API钩取等操作。
  • 注入。DLL注入与代码注入。

0x01 记事本WriteFile的API钩取(调试器)

 此章主要讲解以下API钩取技术(红框)

image-20230405151244207

 本章的钩取将会向用户提供简单的接口,使用户能够控制目标进程的运行,并且可以使用进程内存。

调试器相关知识

1
2
调试器(Debugger)  :进行调试的程序
被调试者(Debuggee):被调试的程序

 调试器可以逐一执行被调试者的命令,并拥有对寄存器和内存的所有访问权限。

调试器的工作原理:调试进程经过注册后,每当被调试者发生调试事件(Debug Event)时,OS就会暂停其运行并向调试器报告相应事件。调试器对相应事件做适当处理后,使被调试者继续运行。其中,一般的异常(Exception)也属于调试事件。若相应进程处于非调试,调试事件会在其被调试程序自身的异常处理或OS的异常处理机制中被处理掉。调试器无法处理或不关心的调试事件最终由OS处理。

 调试器工作原理如下图所示:

image-20230405151803730

 各种调试事件(Debug Event)如下所示:

1
2
3
4
5
6
7
8
9
EXCEPTION_DEBUG_EVENT
CREATE_THREAD_DEBUG_EVENT
CREATE_PROCESS_DEBUG_EVENT
EXIT_THREAD_DEBUG_EVENT
EXIT_PROCESS_DEBUG_EVENT
LOAD_DLL_DEBUG_EVENT
UNLOAD_DLL_DEBUG_EVENT
OUTPUT_DEBUG_STRING_EVENT
RIP_EVENT

 上述调试事件中,真正与调试相关的事件是:EXCEPTION_DEBUG_EVENT,与其相关的异常有很多,例如EXCEPTION_BREAKPOINT,表示断点。

 断点对应的汇编指令为INT3,IA-32指令为0xCC。代码调试遇到INT3指令即中断运行。调试器实现断点的方法:(1)找到要设置断点的代码在内存中的起始地址,把1个字节修改为0xCC。(2)若想继续调试,将它恢复原值即可。

注:IA-32代表32位版本的x86指令集架构,由Intel设计。

调试技术流程

 下面说明借助调试技术钩取API的方法。基本思路是:将被调试者的API起始部分修改为0xCC,控制权转移到调试器后执行指定操作,最后使被调试者重新进入运行状态。
 具体流程如下:

1
2
3
4
5
6
7
1. 对想钩取的进程进行附加操作,使之成为被调试者
2. "钩子":将API起始地址的第一个字节修改为0xCC
3. 调用相应API时,控制权转移到调试器执行所需操作(操作参数、返回值等)
4. 脱钩:将0xCC恢复原值(为了正常运行API)
5. 运行相应API(无0xCC的正常状态)
6. "钩子":再次将API起始地址的第一个字节修改为0xCC(为了继续钩取)
7. 控制权返还被调试者

练习:记事本WriteFile的API钩取

 此示例的功能:钩取Notepad.exeWriteFile(),保存文件时操作输入参数,将小写字母全部转换为大写字母。即,在Notepad中保存文件内容时,其中输入的所有小写字母都会先被转换为大写字母,然后再保存。感觉挺有意思,效果如下所示:

image-20230405153923656

工作原理

 假设notepad要保存文件中的某些内容时会调kernel32!WriteFile,那么我们首先要确定一下假设是否正确。

kernel32!WriteFile的定义如下:

image-20230405154156622

1
2
lpBuffer: 数据缓冲区指针
nNumberOfBytesToWrite: 要写入的字节数

 打开Notepad.exe,在kernel32!WriteFile打断点,然后输入字符后,保存,程序停在了kernel32!WriteFile处。此时堆栈结构如下:

image-20230405161206187

 上述红框地址保存了输入到Notepad中的的字符。

 那么我们确定,notepad要保存文件内容时会调kernel32!WriteFile。且我们验证了我们的思路:钩取WriteFile后,用指定字符串覆盖上述红框中的字符串即可完成上述功能。

 下面我们使用调试方法来钩取API。利用给出的hookdbg.exe,在WriteFile起始地址处设置断点(INT3)后,被调试进程(notepad.exe)保存文件时,EXCEPTION_BREAKPOINT事件就会传给调试器(hookdbg.exe)。

 此时被调试者(notepad.exe)的EIP值不是WriteFile的起始地址,而是WriteFile的起始地址+1。为什么呢?原因在于,我们在WriteFile API的起始地址处设置了断点,被调试者(notepad.exe)内部调用WriteFile时,会在起始地址遇到INT3(0xCC)指令。执行该指令后EIP的值会增加1个字节,然后控制权会转移给调试器(hookdbg.exe)。因为在调试器被调试者关系中,被调试者中发生的异常需要由调试器处理。覆写了数据缓冲区的内容(将0xCC改为原来的值)后,EIP值被重新更改为WriteFile的起始地址继续运行。Ollydbg也是这样做的,只不过页面不展示。

 下面分析hookdbg.exe的代码,注释写的很清楚了,不再赘述。

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
// hookdbg.cpp -> hookdbg.exe
#include "windows.h"
#include "stdio.h"

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

// 被调试进程启动时
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// 获取WriteFile() API地址
// 获取的是调试进程的WriteFile() API地址,由于对于windows os的系统DLL而言,DLL在所有进程中都加载到相同地址,所以这样可以
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");

// API Hook - WriteFile()
// 更改WriteFile()第一个字节为0xCC(INT3)
// 将事件信息保存在g_cpdi中
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
// 读取目标进程中WriteFile() API的第一个字节到g_chOrgByte中
// 后续正常运行WriteFile()时要用到
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
// 将目标进程中WriteFile() API的第一个字节改为0xCC
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);

return TRUE;
}

// 被调试进程遇到INT3时
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;

// 判断是否是断点
if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
{
// 判断断点是否在WriteFile() API处
if( g_pfWriteFile == per->ExceptionAddress )
{
// #1. Unhook-脱钩
// 将WriteFile() API首字节改为g_chOrgByte,改成原值
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);

// #2. Thread Context-获取被调试线程上下文
// 所有程序在内存中都以进程为单位运行,而进程的实际指令代码以线程为单位运行。
// Windows 是一个多线程(multi-thread)操作系统,同一进程中可以同时运行多个线程。
// 多任务(multi-tasking)是将CPU资源划分为多个时间片(time-slice),然后平等地逐一运行所有线程(考虑线程优先级)。
// CPU运行完一个线程的时间片而切换到其他线程时间片时,它必须将先前线程处理的内容准确保存下来,这样再次运行它时才能正常无误。
// 保存到ctx中,ctx是一个结构体,里面保存了各个寄存器的值
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);

// #3. 获得WriteFile() 第 2, 3 个参数,也就是LpBuffer,nNumber0fBytesTowrite
// param 2 : ESP + 0x8
// param 3 : ESP + 0xC
// 分别将参数保存到 dwAddrOfBuffer 与 dwNumOfBytesToWrite 中
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
&dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
&dwNumOfBytesToWrite, sizeof(DWORD), NULL);

// 接下来就是把小写字母转为大写字母,并放到原来的位置
// #4. 分配临时缓冲区lpBuffer
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(lpBuffer, 0, dwNumOfBytesToWrite+1);

// #5. 复制 WriteFile() 的dwAddrOfBuffer所指的字符串到临时缓冲区
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string ###\n%s\n", lpBuffer);

// #6. 将小写字母转为大写字母
for( i = 0; i < dwNumOfBytesToWrite; i++ )
{
if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
lpBuffer[i] -= 0x20;
}

printf("\n### converted string ###\n%s\n", lpBuffer);

// #7. 将lpBuffer放回原位
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);

// #8. 删除分配的临时缓冲区
free(lpBuffer);

// #9. 把线程上下文的EIP修改为WriteFile()起始地址,并恢复上下文
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);

// #10. 继续运行被调试进程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);

// #11. 重新设置API Hook,把首字母设成0xCC,方便下次钩取
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);

return TRUE;
}
}

return FALSE;
}

void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;

// 等待被调试者发生调试事件
while( WaitForDebugEvent(&de, INFINITE))
{
dwContinueStatus = DBG_CONTINUE;

// 被调试进程创建事件
// 被调试进程启动或者attached时调用该函数
if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
OnCreateProcessDebugEvent(&de);
}
// 异常事件
// 说明遇到了INT3指令
else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
{
if(OnExceptionDebugEvent(&de))
continue;
}
// 被调试进程终止事件
// 不调试了,结束了
else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
// 被调试者终止 -> 调试者终止
break;
}

// 使得被调试者继续运行,相当于F9
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}

int main(int argc, char* argv[])
{
DWORD dwPID;

if( argc != 2 )
{
printf("\nUSAGE : hookdbg.exe <pid>\n");
return 1;
}

// Attach Process
dwPID = atoi(argv[1]);
// 使用DebugActiveProcess,将调试器hookdbg附加到目标进程notepad.exe中
// dwPID为目标进程的pid
if(!DebugActiveProcess(dwPID))
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}

// 调试器循环,处理来自被调试者的调试事件
DebugLoop();

return 0;
}

 注:还有一个函数叫做:DebugSetProcessKillOnExit,它可以在不销毁被调试进程的情况下退出调试器,也就是detach。需要注意的是,必须在调试器终止前脱钩(也就是把断点恢复),否则,调用API时就会因为其起始部分仍为0xCC而导致异常,从而终止被调试进程。

 上述代码中Sleep(0)的作用(重点):调用Sleep(0)函数可以释放当前线程的剩余时间片,即放弃当前线程执行的CPU时间片。也就是说,调用Sleep(0)函数后,CPU会立即执行其他线程

具体是啥意思呢?就是,如果不加入Sleep(0),CPU会运行完这个时间片,这个时间片是调试进程hookdbg.exe占用的,然后,notepad.exe还没开始运行正常的writefile过程,这个时间片就运行到hookdbg.exe的下一句了,也就是更改首字节为0xCC

0x02 关于调试器

 略。

0x03 计算器显示中文数字

 本节中,通过注入DLL文件来钩取某个API,DLL文件注入目标进程后,修改IAT来更改进程中调用的特定API以计算器(calc.exe)为示例,向计算器进程插入用户的DLL文件,钩取IAT的user32.SetWindowTextW地址。此函数被钩取之后,计算器中显示出的将是中文数字,而不是阿拉伯数字。本节的技术图表如下:

image-20230413141834499

优点:实现简单,只需先将要钩取的API在用户的DLL中重定义,然后再注入目标进程即可。缺点:如果想钩取的API不在目标进程的IAT中,那么就无法使用该技术进行钩取操作,即如果要钩取的API是由程序代码动态加载DLL文件来使用的,那么将无法使用这项技术。

 使用PEview看calc.exe所用的API,如下所示:

image-20230413143304760

 注意两个API:SetWindowTextWSetDlgItemTextW。其中SetDlgItemTextW调用了SetWindowTextW,所以我们只需要钩取SetWindowTextW即可。SetWindowTextW定义如下:

image-20230413143527735

 所以,钩取时只需要将lpString中的阿拉伯数字转为中文数字即可。注:(1)SetWindowTextW中的W代表宽字符(Unicode),与其对应的SetWindowTextA代表ASCII字符。(2)Unicode码中每个汉字占2个字节。具体细节见P311。

IAT钩取工作原理

 下图表示正常调用SetWindowTextW的程序执行流:

image-20230413145110863

 下图是IAT被钩取后SetWindowTextW的调用流程:

image-20230413145853762

 可以保证,在保持运行代码不变的前提下,将IAT中保存的API地址变为用户函数的地址。函数操作见P315。

源代码分析

补充:

  • FARPROC关键字。FARPROC是一个函数指针类型,通常用于Windows平台的动态链接库(DLL)中。在Windows平台上,DLL中的函数通常被导出为外部符号(external symbols),并且在运行时动态链接到程序中。FARPROC是一个函数指针类型,可以用来指向这些导出的函数。它的定义如下:
1
typedef int (FAR WINAPI *FARPROC)();

 其中,FARWINAPI是Windows API中的宏定义,用于表示函数指针的调用约定和存储方式。FARPROC指向一个无返回值、无参数的函数,可以根据实际情况进行类型转换。在使用DLL中的函数时,可以使用Windows API中的GetProcAddress函数获取导出函数的地址,并将其转换为FARPROC类型的函数指针,然后通过调用该指针来调用DLL中的函数。

钩取过程如下:使用InjectDll.exe,向计算器进程calc.exe中注入hookiat.dll

hookiat.dll代码如下:

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
// hookiat.cpp -> hookiat.dll

// include
#include "stdio.h"
#include "wchar.h"
#include "windows.h"


// typedef
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);


// globals
FARPROC g_pOrgFunc = NULL;


// 具体更换字符串的函数
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;

nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}

// user32!SetWindowTextW() 地址
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}


// hook_iat
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;

// hMod, pAddr = ImageBase of calc.exe
// = VA to MZ signature (IMAGE_DOS_HEADER)
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;

// pAddr = VA to PE signature (IMAGE_NT_HEADERS)
pAddr += *((DWORD*)&pAddr[0x3C]);

// dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
dwRVA = *((DWORD*)&pAddr[0x80]);

// pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

for( ; pImportDesc->Name; pImportDesc++ )
{
// szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
if( !_stricmp(szLibName, szDllName) )
{
// pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
// = VA to IAT(Import Address Table)
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);

// pThunk->u1.Function = VA to API
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD)pfnOrg )
{
// 更改内存属性为 E/R/W
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);

// 修改 IAT 值(钩取)
pThunk->u1.Function = (DWORD)pfnNew;

// 恢复内存属性
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);

return TRUE;
}
}
}
}

return FALSE;
}



BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
// 保持原始 API 地址
g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW");

// # hook
// 用 user32!SetWindowTextW() 钩取 hookiat!MySetWindowText()
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
break;

case DLL_PROCESS_DETACH :
// # unhook
// 将 calc.exe 的 IAT 恢复原值
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
break;
}

return TRUE;
}

InjectDll.exe代码如下:

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
// InjectDll.cpp -> InjectDll.exe

#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#include "winbase.h"
#include "tchar.h"


void usage()
{
printf("\nInjectDll.exe by ReverseCore\n"
"- blog : http://www.reversecore.com\n"
"- email : reversecore@gmail.com\n\n"
"- USAGE : InjectDll.exe <i|e> <PID> <dll_path>\n\n");
}


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

if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
DWORD dwErr = GetLastError();
return FALSE;
}

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

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

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

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}


BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
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, szDllName) || !_tcsicmp(me.szExePath, szDllName) )
{
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;
}


DWORD _EnableNTPrivilege(LPCTSTR szPrivilege, DWORD dwState)
{
DWORD dwRtn = 0;
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
LUID luid;
if (LookupPrivilegeValue(NULL, szPrivilege, &luid))
{
BYTE t1[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
BYTE t2[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
DWORD cbTP = sizeof(TOKEN_PRIVILEGES) + sizeof (LUID_AND_ATTRIBUTES);

PTOKEN_PRIVILEGES pTP = (PTOKEN_PRIVILEGES)t1;
PTOKEN_PRIVILEGES pPrevTP = (PTOKEN_PRIVILEGES)t2;

pTP->PrivilegeCount = 1;
pTP->Privileges[0].Luid = luid;
pTP->Privileges[0].Attributes = dwState;

if (AdjustTokenPrivileges(hToken, FALSE, pTP, cbTP, pPrevTP, &cbTP))
dwRtn = pPrevTP->Privileges[0].Attributes;
}

CloseHandle(hToken);
}

return dwRtn;
}


int _tmain(int argc, TCHAR* argv[])
{
if( argc != 4 )
{
usage();
return 1;
}

// adjust privilege
_EnableNTPrivilege(SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED);

// InjectDll.exe <i|e> <PID> <dll_path>
if( !_tcsicmp(argv[1], L"i") )
InjectDll((DWORD)_tstoi(argv[2]), argv[3]);
else if(!_tcsicmp(argv[1], L"e") )
EjectDll((DWORD)_tstoi(argv[2]), argv[3]);

return 0;
}

0x04 隐藏进程

 本节介绍:(1)通过修改API代码(Code Patch)实现API钩取的技术;(2)全局钩取(Global hooking),它能钩取所有进程;(3)使用全局钩取隐藏(Stealth)特定进程。

 相关技术图表如下:

image-20230414154605820

 0x03中,介绍了 IAT 钩取技术,如果要钩取的 API不在进程的 IAT中,那么就无法使用该技术,而 API code patch 没有这一限制。

API code patch 原理:

IAT钩取通过操作进程的特定IAT值来实现API钩取,而API代码修改技术则将API代码的前5个字节修改为JMP XXXXXXXX指来钩取API。调用执行被钩取的API时,(修改后的)JMP XXXXXXXX指令就会被执行,从而转到hooking函数。

 钩取之前正常调用的API:

image-20230414155107840

 钩取之后调用API的流程如下(procexp.exe注入stealth.dll文件后,钩取ntdll.ZwQuerySystemInformation()的整个过程。ntdll.ZwQuerySystemInformation是为了隐藏进程而需要钩取的API):

image-20230414155229093

 上图过程的详细解释如下:

1
2
3
4
5
6
首先,把stealth.dll注入目标进程,钩取ntdll.ZwQuerySystemInformation。ntdll.ZwQuerySystemInformation起始地址(7C93D92E)的5个字节代码被修改为JMP 10001120(仅修改5个字节代码)。10001120是stealth.MyZwOuerySystemInformation函数的地址。此时,在procexp.exe代码中调用ntd11ZwQuerySystemInformation,程序将按如下顺序执行:
(1)在422CF7地址处调用ntdll.ZwQuerySystemInformation
(2)位于7C93D92E地址处的(修改后的)JMP 10001120指令将执行流转到10001120地址处(hooking函数)。1000116A地址处的CALL unhook指令用来将ntdl.ZwOuerySystemInformation的起始5个字节恢复原值。
(3)位于1000119B地址处的CALL EAX(7C93D92E)指将调用原来的函数(ntdll.ZwOuerySystemInformation)。
(4)ntdll.ZwQuerySystemInformation执行完毕后,由7C93D93A地址处的RETN10指令返回到stealth.dll代码区域(调用自身的位置)。然后10001212地址处的CALL hook指令再次钩取ntdll.ZwQuerySystemInformation。
(5)stealth.MyZwQuerySystemInformation函数执行完毕后,由10001233地址处的RETN10命令返回到procexp.exe进程的代码区域,继续执行。

 API code patch可以钩取任意API,而IAT钩取只能钩取表中有的API。

注:API code patch就是指直接修改映射到目标进程内存空间的系统 DLL的代码。但是,进程的其他线程正在读(read)某个函数时,尝试修改其代码会怎么样呢?这样做会引发非法访问(Access Violation)异常。

进程隐藏原理:

 隐形战机是在自身进行喷涂,使得雷达无法检测到。而进程隐藏则是:要潜入其他所有进程内存,钩取相关API。也就是说,把所有人的雷达都干掉。

 一般来说,进程可以通过一些API检测到其他的进程,这些API有:CreateToolhelp32SnapshotEnumProcess。这两个API在内部都调用了ntdll.ZwQuerySystemInformation。因此,借助此API就可以获得运行中所有进程的信息,形成一个列表,操作该列表(删除某条目)即可隐藏相关进程。注意,我们要钩取的目标进程不是要隐藏的进程,而是其他所有的进程。

 假如我们要隐藏的进程为test.exe,如果钩取运行中的ProExp.exe(进程查看器)(或者任务管理器taskmgr.exe)进程的ZwQuerySystemInformation,那么ProcExp.exe就无法查找到test.exe。这种方法存在两个问题:(1)不是只有ProExp.exe才能查看要运行的进程,我们只钩取了一个。(2)如果新开一个ProExp.exe,新开的ProExp.exe能够查看我们想要隐藏的进程。为了解决上述问题?我们要对运行中的所有进程都要进行钩取,并且对后面要启动的进程也要做钩取操作。

练习

HideProc.exe负责将stealth.dll文件注入所有运行中的进程。stealth.dll负责钩取进程的ntdll.ZwQuerySystemInformation。需要说明的是,上述文件不能解决全局钩取的问题,因此这是一种不完全隐藏技术。具体过程在P334页,跟着做就好。

HideProc.exe 源代码如下:

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
#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)
{
...
}

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;
BOOL ret;
// 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的进程,不进行注入
if( dwPID < 1000 )
continue;

if( nMode == INJECTION_MODE )
ret = InjectDll(dwPID, szDllPath);
if(ret)
printf("OpenProcess(%d) success!!!\n", dwPID);
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
// stealth.dll
hLib = LoadLibrary(argv[3]);

// set process name to hide
// notepad.exe
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;
}

 在说明stealth.dll之前,由于钩取了ntdll.ZwQuerySystemlnformation的API,因此,我们要先了解这个API。

image-20230414225318976

 简单讲解:SystemInformationClass参数设置为SystemProcessInformation后调用ZwQuerySystemInformationSystemInformation [in/out]参数中存储的是SYSTEM_PROCESS_INFORMATION结构体单向链表(sigle linked list)的起始地址。该结构体链表中存储着运行中的所有进程的信息。所以,隐藏某进程前,先要查找与之对应的链表成员,然后断开其与链表的链接。

下面是stealth.dll的代码:

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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// stealth.cpp -> stealth.dll

#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)
// 告诉编译器将代码中的一个特定的段(segment)链接到一个名为
// SHARE的可读写共享内存段(shared memory segment)中。
// 这个共享内存段可以被不同的进程访问,从而实现进程间通信。
#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,};

/*
LPCTSTRszDIIName:[IN]包含要取的API的DLL文件名称。
LPCTSTRszFuncName:[IN]要钩取的API名称。
PROCpfnNew:[IN]用户提供的钩取函数地址。
PBYTEpOrgBytes:[OUT]存储原来5个字节的缓冲区-后面“脱钩”时使用。
*/
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地址:ntdll.ZwQuerySystemInformation
pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pfnOrg;

// 如果已经被钩取,那么 return FALSE(E9是jmp的指令)
if( pByte[0] == 0xE9 )
return FALSE;

// 为了修改5个字节,先向内存添加"写"属性
VirtualProtect((LPVOID)pfnOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 备份原有代码 (5 byte)
memcpy(pOrgBytes, pfnOrg, 5);

// 计算JMP地址,(E9 XXXX)
// => XXXX = pfnNew - pfnOrg - 5
// Jmp相对地址=原来地址的下一条指令的地址-要跳转指令的地址
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地址:ntdll.ZwQuerySystemInformation
pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;

// 如果已经脱钩,则 return FALSE
if( pByte[0] != 0xE9 )
return FALSE;

// 为了修改5个字节,先向内存添加"写"属性
VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

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

// 回复内存属性
VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);

return TRUE;
}

/*
SystemInformationClass:系统信息的类别,指定需要获取哪些系统信息。它是一个枚举类型SYSTEM_INFORMATION_CLASS的值。
SystemInformation:指向一个缓冲区的指针,用于存储获取到的系统信息。
SystemInformationLength:缓冲区的大小,以字节为单位。该参数指定了SystemInformation指向的缓冲区的大小。
ReturnLength:指向一个ULONG类型的指针,用于返回实际返回的系统信息的大小。
*/
// jmp到的函数
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 是单链表的头
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
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

上述代码有几个坑:

1
2
3
4
1. 路径要写全,例如stealth.dll要写成绝对路径。如果不加绝对路径,那只能注入到当前目录下开的进程。
2. Inject之后,Deject只能解除(show)一部分进程,不知道原因。
3. 关于全局钩取的概念,有点混淆。是当前所有进程都被注入stealth.dll叫全局钩取?还是新开一个进程,此进程自动注入stealth.dll叫全局钩取?
4. 注入之后,虽然ProcExp.exe注入了stealth.dll,但是还是能看到notepad.exe。感觉应该是ProcExp.exe版本较新的问题。

 上面的JMP指令每次都要计算相对地址,我们也可以直接用绝对地址跳转,例如:

1
2
3
4
5
6
(1) PUSH+RET
PUSH 00401000
RETN
(2) MOV+JMP
MOV EAX, 00401000
JMP EAX

全局API钩取

 针对的是:(1)当前所有进程;(2)未来所有进程。上述示例不满足条件(2)。

Kernel32.CreateProcess API

Kernel32.CreateProcess 用来创建新进程,其他启动运行进程的API(WinExecShellExecutesystem)在内部也调用的Kernel32.CreateProcess 函数。Kernel32.CreateProcess API定义如下:

image-20230415190834541

 因此,我们不仅要钩取ZwQuerySystemlnformation,还要钩取CreateProcess由于所有进程都是由父进程(使用CreateProcess)创建的,所以,钩取父进程的CreateProcess就可以将stealth.dll文件注入所有子进程(父进程通常都是explorer.exe)。但是还要充分考虑以下几个方面:

1
2
3
1. 钩取CreateProcess时,还要分别钩取kernel32.CreateProcessA、kernel32.CreateProcessW这2个API(ASCI版本与Unicode版本)。
2. CreateProcessA、CreateProcessW函数内部又分别调用了CreateProcessInternalA、CreateProcessInternalW函数。微软的部分软件产品中会直接调用CreateProcessInternalA/w这2个函数。所以具体实现全局API钩取时,为了准确起见,还要同时钩取上面2个函数(CreateProcessInternalA、CreateProcessInternalW)。
3. 钩取函数(NewCreateProcess)还要调用原函数(CreateProcess)而创建的子进程的API。因此,极短时间内,子进程可能在未钩取的状态下运行。

 但是,我们很懒,我们总想一步到位,有这样的好事吗?有,就是钩取Ntdll.ZwResumeThread其定义如下所示:

image-20230415200308638

ZwResumeThread函数在进程创建后、主线程运行前被调用执行(CreateProcess内部调用执行)。所以只要钩取这个函数,即可在不运行子进程代码的状态下钩取API。注:ZwResumeThread是一个尚未公开的API,将来的某个时候可能会被改变。

 练习具体在P346。注:要想全局钩取,就要把stealth2.dll放到%SYSTEM%C:\\Windows\\System32)文件夹下。

HiddenProc2.cpp与之前一样。

stealth2.cpp代码如下所示:(钩取的CreateProcessACreateProcessW以及ZwQuerySystemlnformation。)

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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// stealth2.cpp -> stealth2.dll

#include "windows.h"
#include "stdio.h"
#include "tchar.h"

#define STR_MODULE_NAME (L"stealth2.dll")
#define STR_HIDE_PROCESS_NAME (L"notepad.exe")
#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;
BYTE Reserved1[52];
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);

typedef BOOL (WINAPI *PFCREATEPROCESSA)(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

typedef BOOL (WINAPI *PFCREATEPROCESSW)(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

BYTE g_pOrgCPA[5] = {0,};
BYTE g_pOrgCPW[5] = {0,};
BYTE g_pOrgZwQSI[5] = {0,};

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

pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;
if( pByte[0] == 0xE9 )
return FALSE;

VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

memcpy(pOrgBytes, pFunc, 5);

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

memcpy(pFunc, pBuf, 5);

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

return TRUE;
}

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

pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;
if( pByte[0] != 0xE9 )
return FALSE;

VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

memcpy(pFunc, pOrgBytes, 5);

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

return TRUE;
}

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 InjectDll2(HANDLE hProcess, LPCTSTR szDllName)
{
HANDLE hThread;
LPVOID pRemoteBuf;
DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
FARPROC pThreadProc;

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

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

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

VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);

CloseHandle(hThread);

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_by_code("ntdll.dll", "ZwQuerySystemInformation", g_pOrgZwQSI);

pFunc = GetProcAddress(GetModuleHandleA("ntdll.dll"),
"ZwQuerySystemInformation");
status = ((PFZWQUERYSYSTEMINFORMATION)pFunc)
(SystemInformationClass, SystemInformation,
SystemInformationLength, ReturnLength);

if( status != STATUS_SUCCESS )
goto __NTQUERYSYSTEMINFORMATION_END;

if( SystemInformationClass == SystemProcessInformation )
{
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;

while(TRUE)
{
if(pCur->Reserved2[1] != NULL)
{
if(!_tcsicmp((PWSTR)pCur->Reserved2[1], STR_HIDE_PROCESS_NAME))
{
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:

hook_by_code("ntdll.dll", "ZwQuerySystemInformation",
(PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);

return status;
}

BOOL WINAPI NewCreateProcessA(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
BOOL bRet;
FARPROC pFunc;

// unhook
unhook_by_code("kernel32.dll", "CreateProcessA", g_pOrgCPA);

// 调用原始 API
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessA");
bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);

// 向生成的子进程注入 stealth2.dll
if( bRet )
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);

// hook
hook_by_code("kernel32.dll", "CreateProcessA",
(PROC)NewCreateProcessA, g_pOrgCPA);

return bRet;
}

BOOL WINAPI NewCreateProcessW(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
BOOL bRet;
FARPROC pFunc;

// unhook
unhook_by_code("kernel32.dll", "CreateProcessW", g_pOrgCPW);

// 获得原始 API
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessW");
bRet = ((PFCREATEPROCESSW)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);

// 向生成的子进程注入 stealth2.dll
if( bRet )
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);

// hook
hook_by_code("kernel32.dll", "CreateProcessW",
(PROC)NewCreateProcessW, g_pOrgCPW);

return bRet;
}

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

// 异常处理使注入不会发生在HideProc2.exe进程
GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "HideProc2.exe") )
return TRUE;

// change privilege
SetPrivilege(SE_DEBUG_NAME, TRUE);

switch(fdwReason)
{
case DLL_PROCESS_ATTACH :
// hook
hook_by_code("kernel32.dll", "CreateProcessA", (PROC)NewCreateProcessA, g_pOrgCPA);
hook_by_code("kernel32.dll", "CreateProcessW", (PROC)NewCreateProcessW, g_pOrgCPW);
hook_by_code("ntdll.dll", "ZwQuerySystemInformation", (PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
break;

case DLL_PROCESS_DETACH :
// unhook
unhook_by_code("kernel32.dll", "CreateProcessA", g_pOrgCPA);
unhook_by_code("kernel32.dll", "CreateProcessW", g_pOrgCPW);
unhook_by_code("ntdll.dll", "ZwQuerySystemInformation", g_pOrgZwQSI);
break;
}

return TRUE;
}

“热补丁”技术钩取API

 上述代码中,NewCreateProcessA函数的结构简单梳理如下:

image-20230415211953046

 每当在程序内部调用CreateProcessA 时,NewCreateProcessA就会被调用执行,不断重复脱钩/挂钩。这种反复进行的脱钩/挂钩操作有两个问题:(1)整体性能低下;(2)在多线程环境下还会产生运行时错误。(例如,线程尝试运行某段代码时,若另一进程正在对该段代码进行写操作,这时就会出现冲突,最终引发运行时错误。)热补丁(Hot Patch)技术比修改5个字节代码的方法更稳定。

 热补丁修改7个字节的代码。

 通过观察多个Windows API的代码,代码有2个明显的特点:

1
2
1. API代码以 "MOV EDI, EDI"(0x8bff) 指令开始。
2. API代码上方有5个NOP指令。(实际观察是代码上方有多个INT3:0xCC)

 这些代码没啥意义。微软布置这些的原因是为了方便打”热补丁”。“热补丁”由API钩取组成,在进程处于运行状态时临时更改进程内存中的库文件(重启系统时,修改的目标库文件会被完全取代)

热补丁工作原理

二次跳转

 将API起始代码之前的5个字节修改为FAR JMP指令(E9XXXXXXXX),跳转到用户钩取函数处(用户写的API的封装)(10001000)。然后将API起始代码的2个字节修改为SHORT JMPEB F9)指令。该SHORT JMP指令用来跳转到前面的FAR JMP处。像这样经过二次连续跳转,就可以完成对指定API的钩取操作。

 热补丁钩取与5字节代码修改的比较如下:

image-20230415220758702

image-20230415220827780

 在热补丁技术钩取API时,不需要在钩取函数内部进行脱钩/挂钩工作,因为在5字节代码修改中,脱钩是为了调用原API,但是使用热技术时,一直可以调用原API。除去了脱钩操作,因此热补丁技术更加稳定。

源代码分析

HideProc3.cpp与之前一样。

stealth3.cpp如下所示:

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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#include "windows.h"
#include "stdio.h"
#include "tchar.h"

#define STR_MODULE_NAME (L"stealth3.dll")
#define STR_HIDE_PROCESS_NAME (L"notepad.exe")
#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;
BYTE Reserved1[52];
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);

typedef BOOL (WINAPI *PFCREATEPROCESSA)(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

typedef BOOL (WINAPI *PFCREATEPROCESSW)(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

BYTE g_pOrgZwQSI[5] = {0,};

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

pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;
if( pByte[0] == 0xE9 )
return FALSE;

VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pOrgBytes, pFunc, 5);
dwAddress = (DWORD)pfnNew - (DWORD)pFunc - 5;
memcpy(&pBuf[1], &dwAddress, 4);
memcpy(pFunc, pBuf, 5);
VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);
return TRUE;
}

/*
szDllName:kernel32.dll
szFuncName:CreateProcessA
pfnNew:NewCreateProcessA
*/
BOOL hook_by_hotpatch(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew)
{
FARPROC pFunc;
DWORD dwOldProtect, dwAddress;
BYTE pBuf[5] = { 0xE9, 0, };
BYTE pBuf2[2] = { 0xEB, 0xF9 };
PBYTE pByte;

pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;
if( pByte[0] == 0xEB )
return FALSE;

VirtualProtect((LPVOID)((DWORD)pFunc - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 1. NOP (0x90)
dwAddress = (DWORD)pfnNew - (DWORD)pFunc;
memcpy(&pBuf[1], &dwAddress, 4);
memcpy((LPVOID)((DWORD)pFunc - 5), pBuf, 5);

// 2. MOV EDI, EDI (0x8BFF)
memcpy(pFunc, pBuf2, 2);
VirtualProtect((LPVOID)((DWORD)pFunc - 5), 7, dwOldProtect, &dwOldProtect);
return TRUE;
}

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

pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;
if( pByte[0] != 0xE9 )
return FALSE;

VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pFunc, pOrgBytes, 5);
VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);
return TRUE;
}

BOOL unhook_by_hotpatch(LPCSTR szDllName, LPCSTR szFuncName)
{
FARPROC pFunc;
DWORD dwOldProtect;
PBYTE pByte;
BYTE pBuf[5] = { 0x90, 0x90, 0x90, 0x90, 0x90 };
BYTE pBuf2[2] = { 0x8B, 0xFF };

pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
pByte = (PBYTE)pFunc;
if( pByte[0] != 0xEB )
return FALSE;

VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
// 1. NOP (0x90)
memcpy((LPVOID)((DWORD)pFunc - 5), pBuf, 5);
// 2. MOV EDI, EDI (0x8BFF)
memcpy(pFunc, pBuf2, 2);
VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);
return TRUE;
}

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 InjectDll2(HANDLE hProcess, LPCTSTR szDllName)
{
HANDLE hThread;
LPVOID pRemoteBuf;
DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
FARPROC pThreadProc;

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

WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);
pThreadProc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pThreadProc,
pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
CloseHandle(hThread);
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_by_code("ntdll.dll", "ZwQuerySystemInformation", g_pOrgZwQSI);

pFunc = GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwQuerySystemInformation");
status = ((PFZWQUERYSYSTEMINFORMATION)pFunc)
(SystemInformationClass, SystemInformation,
SystemInformationLength, ReturnLength);

if( status != STATUS_SUCCESS )
goto __NTQUERYSYSTEMINFORMATION_END;

if( SystemInformationClass == SystemProcessInformation )
{
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
while(TRUE)
{
if(pCur->Reserved2[1] != NULL)
{
if(!_tcsicmp((PWSTR)pCur->Reserved2[1], STR_HIDE_PROCESS_NAME))
{
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:

hook_by_code("ntdll.dll", "ZwQuerySystemInformation", (PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
return status;
}

BOOL WINAPI NewCreateProcessA(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
BOOL bRet;
FARPROC pFunc;

// 调用 original API
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessA");
pFunc = (FARPROC)((DWORD)pFunc + 2);
bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);

// 向生成的子进程注入 stealth3.dll
if( bRet )
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);

return bRet;
}

BOOL WINAPI NewCreateProcessW(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
BOOL bRet;
FARPROC pFunc;

// 调用原始API
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessW");
pFunc = (FARPROC)((DWORD)pFunc + 2);
bRet = ((PFCREATEPROCESSW)pFunc)(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);

// 向生成的子进程注入 stealth3.dll
if( bRet )
InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);

return bRet;
}

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

// 异常处理使注入不会发生在HideProc2.exe进程
GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "HideProc2.exe") )
return TRUE;

// change privilege
SetPrivilege(SE_DEBUG_NAME, TRUE);

switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
// hook
hook_by_hotpatch("kernel32.dll", "CreateProcessA", (PROC)NewCreateProcessA);
hook_by_hotpatch("kernel32.dll", "CreateProcessW", (PROC)NewCreateProcessW);
hook_by_code("ntdll.dll", "ZwQuerySystemInformation", (PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
break;

case DLL_PROCESS_DETACH :
// unhook
unhook_by_hotpatch("kernel32.dll", "CreateProcessA");
unhook_by_hotpatch("kernel32.dll", "CreateProcessW");
unhook_by_code("ntdll.dll", "ZwQuerySystemInformation", g_pOrgZwQSI);
break;
}

return TRUE;
}

热补丁要求被钩取的API要有NOP*5 + MOV EDI, EDI,因此,并非所有API都能使用热补丁。例如,ntdll.dll的API代码都很短,且不满足热补丁条件。进行ntdll.dll API代码钩取时,先将原 API备份到用户内存区域,然后使用5字节代码修改技术修改原API的起始部分。在用户钩取函数内部调用原API时,只需调用备份的 API即可,这样实现的 API钩取既简单又稳定。

0x05 高级全局API钩取:IE连接控制

 本节中钩取IE浏览器,当IE连接指定网站时转而连接我的博客。(可以当作拦截恶意网页,当用户访问恶意网页时,转到其他的网页。)常见的与网络连接相关的库有:(1)ws2_32.dll(套接字库);(2)wininet.dllwinhttp.dll(网络访问相关的库);

 通过Process Explorer可以看到,IE加载了ws2_32.dllwininet.dll。其中wininet.dll中有一个IntermetConnect的API,其用来连接某个网站。此API介绍如下:

image-20230416110102050

其中,34.1的实验并未成功,思考应该是盗版win7的原因。但是换了正版win7还是不行。

 IE浏览器的每一个选项卡tab都是一个进程,当开启一个新的网页时,都会创建一个新的进程。因此,要实现全局钩取,否则新开网页的时候就不会达到我们的目的。之前,我们介绍了,要用全局钩取,有两种办法:(1)钩取CreateProcessACreateProcessWZwQuerySystemInformation。(2)钩取ZwResumeThread(不公开的API)。本节中使用方法(2),即,钩取ntdll! ZwResumeThread,创建进程之后,主线程被Resume(恢复运行)时,可以钩取目标API。

 常规API钩取与全局API钩取,图示如下:

image-20230416145007024

image-20230416145033185

Explorer.exeWindows操作系统的基本shell,可以创建子进程。

 当使用CreateProcessW创建进程时(钩取任何一个都能实现全局API钩取),其调用流程如下:

image-20230416152219588

 那么,如果我们钩取ntdll!ZwResumeThread,就可以在子进程的EP代码运行之前,拦截获取控制权。ntdll!ZwResumeThread的定义如下:(ntdll!ZwResumeThread=ntdll!NtResumeThread

image-20230416152529827

练习示例

 向目标进程注入redirect.dll来实现API钩取,redirect.dll钩取下面2个API:

1
2
wininet!InternetConnectW: 可以控制IE进程的连接地址
ntdll!ZwResumeThread: 实现全局API钩取

 由于IE进程以父子进程的形式运行,只要钩取父进程的ntdll!ZwResumeThread API,那么后面生成的所有子IE进程都会自动钩取。实现的效果是,使用InjDll.exe,向IE进程iexplore.exe中注入redirect.dll。此后,打开IE浏览器,访问www.naver.comwww.daum.netwwwnate.comwww.yahoo.com时,会直接访问www.ReverseCore.com(上述34.1的实验不成功,但是这个代码却可以正常运行。)

 相应代码&注释如下:(InjDll.exe与之前的代码类似,不做赘述。)

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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
// redirect.cpp -> redirect.dll

#include "windows.h"
#include "wininet.h"
#include "stdio.h"
#include "tchar.h"

#define STR_MODULE_NAME (L"redirect.dll")
#define STATUS_SUCCESS (0x00000000L)

typedef LONG NTSTATUS;

typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID;

typedef struct _THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PVOID TebBaseAddress;
CLIENT_ID ClientId;
ULONG AffinityMask;
LONG Priority;
LONG BasePriority;
} THREAD_BASIC_INFORMATION;

typedef NTSTATUS (WINAPI *PFZWRESUMETHREAD)
(
HANDLE ThreadHandle,
PULONG SuspendCount
);

typedef NTSTATUS (WINAPI *PFZWQUERYINFORMATIONTHREAD)
(
HANDLE ThreadHandle,
ULONG ThreadInformationClass,
PVOID ThreadInformation,
ULONG ThreadInformationLength,
PULONG ReturnLength
);

typedef HINTERNET (WINAPI *PFINTERNETCONNECTW)
(
HINTERNET hInternet,
LPCWSTR lpszServerName,
INTERNET_PORT nServerPort,
LPCTSTR lpszUsername,
LPCTSTR lpszPassword,
DWORD dwService,
DWORD dwFlags,
DWORD_PTR dwContext
);

BYTE g_pZWRT[5] = {0,};
BYTE g_pICW[5] = {0,};

void DebugLog(const char *format, ...)
{
va_list vl;
FILE *pf = NULL;
char szLog[512] = {0,};

va_start(vl, format);
wsprintfA(szLog, format, vl);
va_end(vl);

OutputDebugStringA(szLog);
}

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

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

if( !LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid) ) // receives LUID of privilege
{
DebugLog("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) )
{
DebugLog("AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}

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

return TRUE;
}

/*
使用5字节修改技术钩取 API。
因为钩取ntdll.ZwResumeThread只能使用5字节修改技术(由于没有足够的空间,所以无法使用7字节修改技术)。
*/
BOOL hook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes)
{
FARPROC pFunc = NULL;
DWORD dwOldProtect = 0, dwAddress = 0;
BYTE pBuf[5] = {0xE9, 0, };
PBYTE pByte = NULL;
HMODULE hMod = NULL;

hMod = GetModuleHandleA(szDllName);
if( hMod == NULL )
{
DebugLog("hook_by_code() : GetModuleHandle(\"%s\") failed!!! [%d]\n", szDllName, GetLastError());
return FALSE;
}

pFunc = (FARPROC)GetProcAddress(hMod, szFuncName);
if( pFunc == NULL )
{
DebugLog("hook_by_code() : GetProcAddress(\"%s\") failed!!! [%d]\n", szFuncName, GetLastError());
return FALSE;
}

pByte = (PBYTE)pFunc;
if( pByte[0] == 0xE9 )
{
DebugLog("hook_by_code() : The API is hooked already!!!\n");
return FALSE;
}

if( !VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect) )
{
DebugLog("hook_by_code() : VirtualProtect(#1) failed!!! [%d]\n", GetLastError());
return FALSE;
}

memcpy(pOrgBytes, pFunc, 5);

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

memcpy(pFunc, pBuf, 5);

if( !VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect) )
{
DebugLog("hook_by_code() : VirtualProtect(#2) failed!!! [%d]\n", GetLastError());
return FALSE;
}

return TRUE;
}

BOOL unhook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes)
{
FARPROC pFunc = NULL;
DWORD dwOldProtect = 0;
PBYTE pByte = NULL;
HMODULE hMod = NULL;

hMod = GetModuleHandleA(szDllName);
if( hMod == NULL )
{
DebugLog("unhook_by_code() : GetModuleHandle(\"%s\") failed!!! [%d]\n",
szDllName, GetLastError());
return FALSE;
}

pFunc = (FARPROC)GetProcAddress(hMod, szFuncName);
if( pFunc == NULL )
{
DebugLog("unhook_by_code() : GetProcAddress(\"%s\") failed!!! [%d]\n",
szFuncName, GetLastError());
return FALSE;
}

pByte = (PBYTE)pFunc;
if( pByte[0] != 0xE9 )
{
DebugLog("unhook_by_code() : The API is unhooked already!!!");
return FALSE;
}

if( !VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect) )
{
DebugLog("unhook_by_code() : VirtualProtect(#1) failed!!! [%d]\n", GetLastError());
return FALSE;
}

memcpy(pFunc, pOrgBytes, 5);

if( !VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect) )
{
DebugLog("unhook_by_code() : VirtualProtect(#2) failed!!! [%d]\n", GetLastError());
return FALSE;
}

return TRUE;
}

// 判断当前系统
BOOL IsVistaLater()
{
OSVERSIONINFO osvi;

ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

GetVersionEx(&osvi);

if( osvi.dwMajorVersion >= 6 )
return TRUE;

return FALSE;
}

typedef DWORD (WINAPI *PFNTCREATETHREADEX)
(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID Unknown
);


/*
在 Windows XP 中,CreateRemoteThread 函数是创建远程线程的主要方法,因为在该操作系统中,内核对于线程的安全监管比较宽松,没有对 CreateRemoteThread 函数的调用进行过多的安全限制,因此该函数可以比较方便地在其他进程中创建新线程。

然而,在 Windows 7 中,由于操作系统内核的安全机制加强,为了防止恶意程序利用远程线程注入等手段进行攻击,操作系统加强了对线程创建的安全限制。在 Windows 7 及之后的版本中,CreateRemoteThread 函数被限制为只能在同一用户会话中的进程间进行创建远程线程,而不能在不同用户会话之间进行创建。这样一来,就大大降低了远程线程注入攻击的可能性。

为了绕过这些安全限制,一些恶意程序使用了 NtCreateThreadEx 函数。该函数是一个系统调用函数,可以绕过一些操作系统内核的安全检查,因此可以在 Windows 7 中成功地创建远程线程。但是,由于 NtCreateThreadEx 函数是一个系统调用函数,需要使用底层的编程技巧来调用,因此使用起来比 CreateRemoteThread 函数更加困难。此外,由于该函数不被 Windows API 提供,因此可能会导致一些兼容性问题。
*/
BOOL MyCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE pThreadProc, LPVOID pRemoteBuf)
{
HANDLE hThread = NULL;
FARPROC pFunc = NULL;

// win7 在 CreateRemoteThread 上还是与传统方法不太一样
// 如果是win7,则使用NtCreateThreadEx
if( IsVistaLater() ) // Vista, 7, Server2008
{
pFunc = GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateThreadEx");
if( pFunc == NULL )
{
DebugLog("MyCreateRemoteThread() : GetProcAddress() failed!!! [%d]\n", GetLastError());
return FALSE;
}

((PFNTCREATETHREADEX)pFunc)(&hThread,
0x1FFFFF,
NULL,
hProcess,
pThreadProc,
pRemoteBuf,
FALSE,
NULL,
NULL,
NULL,
NULL);
if( hThread == NULL )
{
DebugLog("MyCreateRemoteThread() : NtCreateThreadEx() failed!!! [%d]\n", GetLastError());
return FALSE;
}
}
else // 2000, XP, Server2003
{
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
if( hThread == NULL )
{
DebugLog("MyCreateRemoteThread() : CreateRemoteThread() failed!!! [%d]\n", GetLastError());
return FALSE;
}
}

if( WAIT_FAILED == WaitForSingleObject(hThread, INFINITE) )
{
DebugLog("MyCreateRemoteThread() : WaitForSingleObject() failed!!! [%d]\n", GetLastError());
return FALSE;
}

return TRUE;
}

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

if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
DebugLog("InjectDll() : OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
goto INJECTDLL_EXIT;
}

pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
if( pRemoteBuf == NULL )
{
DebugLog("InjectDll() : VirtualAllocEx() failed!!! [%d]\n", GetLastError());
goto INJECTDLL_EXIT;
}

if( !WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL) )
{
DebugLog("InjectDll() : WriteProcessMemory() failed!!! [%d]\n", GetLastError());
goto INJECTDLL_EXIT;
}

hMod = GetModuleHandle(L"kernel32.dll");
if( hMod == NULL )
{
DebugLog("InjectDll() : GetModuleHandle() failed!!! [%d]\n", GetLastError());
goto INJECTDLL_EXIT;
}

pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
if( pThreadProc == NULL )
{
DebugLog("InjectDll() : GetProcAddress() failed!!! [%d]\n", GetLastError());
goto INJECTDLL_EXIT;
}

if( !MyCreateRemoteThread(hProcess, pThreadProc, pRemoteBuf) )
{
DebugLog("InjectDll() : MyCreateRemoteThread() failed!!!\n");
goto INJECTDLL_EXIT;
}

bRet = TRUE;

INJECTDLL_EXIT:

if( pRemoteBuf )
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);

if( hThread )
CloseHandle(hThread);

if( hProcess )
CloseHandle(hProcess);

return bRet;
}

/*
进行全局钩取
*/
NTSTATUS WINAPI NewZwResumeThread(HANDLE ThreadHandle, PULONG SuspendCount)
{
NTSTATUS status, statusThread;
FARPROC pFunc = NULL, pFuncThread = NULL;
DWORD dwPID = 0;
static DWORD dwPrevPID = 0;
THREAD_BASIC_INFORMATION tbi;
HMODULE hMod = NULL;
TCHAR szModPath[MAX_PATH] = {0,};

DebugLog("NewZwResumeThread() : start!!!\n");

hMod = GetModuleHandle(L"ntdll.dll");
if( hMod == NULL )
{
DebugLog("NewZwResumeThread() : GetModuleHandle() failed!!! [%d]\n",
GetLastError());
return NULL;
}

// 查询线程信息
// call ntdll!ZwQueryInformationThread()
pFuncThread = GetProcAddress(hMod, "ZwQueryInformationThread");
if( pFuncThread == NULL )
{
DebugLog("NewZwResumeThread() : GetProcAddress() failed!!! [%d]\n",
GetLastError());
return NULL;
}

statusThread = ((PFZWQUERYINFORMATIONTHREAD)pFuncThread)(ThreadHandle, 0, &tbi, sizeof(tbi), NULL);
if( statusThread != STATUS_SUCCESS )
{
DebugLog("NewZwResumeThread() : pFuncThread() failed!!! [%d]\n", GetLastError());
return NULL;
}

// dwPID为要唤醒的进程ID(子进程),GetCurrentProcessId则是当前创建子进程的进程的ID(父进程)。
dwPID = (DWORD)tbi.ClientId.UniqueProcess;
if ( (dwPID != GetCurrentProcessId()) && (dwPID != dwPrevPID) )
{
DebugLog("NewZwResumeThread() => call InjectDll()\n");

dwPrevPID = dwPID;

// 修改 privilege
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
DebugLog("NewZwResumeThread() : SetPrivilege() failed!!!\n");

// 获取 injection dll 路径
GetModuleFileName(GetModuleHandle(STR_MODULE_NAME),
szModPath,
MAX_PATH);

if( !InjectDll(dwPID, szModPath) )
DebugLog("NewZwResumeThread() : InjectDll(%d) failed!!!\n", dwPID);
}

// call ntdll!ZwResumeThread()
if( !unhook_by_code("ntdll.dll", "ZwResumeThread", g_pZWRT) )
{
DebugLog("NewZwResumeThread() : unhook_by_code() failed!!!\n");
return NULL;
}

pFunc = GetProcAddress(hMod, "ZwResumeThread");
if( pFunc == NULL )
{
DebugLog("NewZwResumeThread() : GetProcAddress() failed!!! [%d]\n",
GetLastError());
goto __NTRESUMETHREAD_END;
}

status = ((PFZWRESUMETHREAD)pFunc)(ThreadHandle, SuspendCount);
if( status != STATUS_SUCCESS )
{
DebugLog("NewZwResumeThread() : pFunc() failed!!! [%d]\n", GetLastError());
goto __NTRESUMETHREAD_END;
}

__NTRESUMETHREAD_END:

if( !hook_by_code("ntdll.dll", "ZwResumeThread", (PROC)NewZwResumeThread, g_pZWRT) )
{
DebugLog("NewZwResumeThread() : hook_by_code() failed!!!\n");
}

DebugLog("NewZwResumeThread() : end!!!\n");

return status;
}

/*
负责监视IE的连接地址,IE尝试连接到特定网站时,将其转到我们指定的网站。
*/
HINTERNET WINAPI NewInternetConnectW
(
HINTERNET hInternet,
LPCWSTR lpszServerName,
INTERNET_PORT nServerPort,
LPCTSTR lpszUsername,
LPCTSTR lpszPassword,
DWORD dwService,
DWORD dwFlags,
DWORD_PTR dwContext
)
{
HINTERNET hInt = NULL;
FARPROC pFunc = NULL;
HMODULE hMod = NULL;

// unhook
if( !unhook_by_code("wininet.dll", "InternetConnectW", g_pICW) )
{
DebugLog("NewInternetConnectW() : unhook_by_code() failed!!!\n");
return NULL;
}

// call original API
hMod = GetModuleHandle(L"wininet.dll");
if( hMod == NULL )
{
DebugLog("NewInternetConnectW() : GetModuleHandle() failed!!! [%d]\n", GetLastError());
goto __INTERNETCONNECT_EXIT;
}

pFunc = GetProcAddress(hMod, "InternetConnectW");
if( pFunc == NULL )
{
DebugLog("NewInternetConnectW() : GetProcAddress() failed!!! [%d]\n", GetLastError());
goto __INTERNETCONNECT_EXIT;
}

if( !_tcsicmp(lpszServerName, L"www.naver.com") ||
!_tcsicmp(lpszServerName, L"www.daum.net") ||
!_tcsicmp(lpszServerName, L"www.nate.com") ||
!_tcsicmp(lpszServerName, L"www.yahoo.com") )
{
DebugLog("[redirect] naver, daum, nate, yahoo => reversecore\n");
hInt = ((PFINTERNETCONNECTW)pFunc)(hInternet,
L"www.reversecore.com",
nServerPort,
lpszUsername,
lpszPassword,
dwService,
dwFlags,
dwContext);
}
else
{
DebugLog("[no redirect]\n");
hInt = ((PFINTERNETCONNECTW)pFunc)(hInternet,
lpszServerName,
nServerPort,
lpszUsername,
lpszPassword,
dwService,
dwFlags,
dwContext);
}

__INTERNETCONNECT_EXIT:

// hook
if( !hook_by_code("wininet.dll", "InternetConnectW", (PROC)NewInternetConnectW, g_pICW) )
{
DebugLog("NewInternetConnectW() : hook_by_code() failed!!!\n");
}

return hInt;
}

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

switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
DebugLog("DllMain() : DLL_PROCESS_ATTACH\n");

GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "iexplore.exe") )
{
DebugLog("DllMain() : current process is [iexplore.exe]\n");

// 钩取 wininet!InternetConnectW() API 前先加载 wininet.dll
// 预先加载 wininet.dll
// 需要在相关进程的主线程开始之前拦截控制权,此时,我们要钩取的wininet.dll模块可能尚未加载。
if( NULL == LoadLibrary(L"wininet.dll") )
{
DebugLog("DllMain() : LoadLibrary() failed!!! [%d]\n", GetLastError());
}
}

// hook
hook_by_code("ntdll.dll", "ZwResumeThread", (PROC)NewZwResumeThread, g_pZWRT);
hook_by_code("wininet.dll", "InternetConnectW", (PROC)NewInternetConnectW, g_pICW);
break;

case DLL_PROCESS_DETACH :
DebugLog("DllMain() : DLL_PROCESS_DETACH\n");

// unhook
unhook_by_code("ntdll.dll", "ZwResumeThread", g_pZWRT);
unhook_by_code("wininet.dll", "InternetConnectW", g_pICW);
break;
}

return TRUE;
}

留言

© 2024 wd-z711

⬆︎TOP