内核前置——注入技术

一共是7种方法

我打算在这里写一下我的最新感悟,有关于前几个方法

关于shellcode

今天分析法四对shellcode有比较好的理解记一下

首先shellcode是一段16进制的汇编代码,简单来讲他是个轴承,一个内存很小的,但是却可以起好作用的,比较独立可以直接运行的,如果要是调用函数啊什么的,又需要复杂的前缀,又不一定十分符合编写者的需求,所以就自己写一个小小的汇编代码直接运行

方法流程

法一、法二流程:

找进程(.exe)->创造新线程->实现注入

原线程——–<(RLT)CreateRemoteThread函数使它进入新线程>|<返回>——-

新线程—————-<返回>

法三流程

找进程->找线程->插入APC->苏醒后实现注入

原线程———<等待状态,QueueUserAPC>|<继续接下来的APC>————

APC: |<苏醒后执行>———–|

法四流程

找进程->找线程->暂停线程->改变eip->完成注入

原线程——– |<返回>———

内存空间 |———-<指向回原本eip>|

1.CreateRemoteThread

实现原理

写一段shellcode,用CreateReoteThread函数注入

Windows C++远程线程(CreateRemoteThread)注入DLL方法、代码示例。-CSDN博客

代码源码

dll代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <windows.h>

// DLL入口点函数
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 弹窗代码
MessageBox(NULL, L"Hello from YourDLL!", L"DLL Injection", MB_OK | MB_ICONINFORMATION);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
  • **入口函数 ****DllMain**:这是 DLL 的入口函数,每当有新的进程或线程加载或卸载此 DLL 时,系统会调用 DllMain 函数。该函数的返回值是一个 BOOL,返回 TRUE 表示加载成功,返回 FALSE 表示加载失败。
  • 参数说明
  • HMODULE hModule:DLL 模块句柄。
  • DWORD ul_reason_for_call:调用原因,可能的值为:
    • DLL_PROCESS_ATTACH:进程加载 DLL 时调用。
    • DLL_THREAD_ATTACH:线程加载 DLL 时调用。
    • DLL_THREAD_DETACH:线程卸载 DLL 时调用。
    • DLL_PROCESS_DETACH:进程卸载 DLL 时调用。
  • LPVOID lpReserved:保留字段,在一般情况下不需要使用。
  • 功能实现
  • case DLL_PROCESS_ATTACH:当 DLL 被加载时执行的代码。在这里,通过 MessageBox 函数显示一个弹窗,内容为 "Hello from YourDLL!"
  • 其他情况(DLL_THREAD_ATTACHDLL_THREAD_DETACHDLL_PROCESS_DETACH)下不执行任何操作。
  • **MessageBox**** 函数**:创建一个带消息的弹窗。传入的参数如下:
  • NULL:弹窗不依赖任何父窗口。
  • L"Hello from YourDLL!":弹窗的消息内容。
  • L"DLL Injection":弹窗的标题。
  • MB_OK | MB_ICONINFORMATION:弹窗显示 “OK” 按钮,并使用信息图标。

被注入dll的进程代码

1
2
3
4
5
6
7
8
9
10

#include<windows.h>
#include<iostream>
int main()
{
while (1)
{
Sleep(1000 * 2);
std::cout << "***\n" << std::endl;
}

执行注入的代码

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
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <stdio.h>

// 获取目标进程ID
DWORD GetTargetProcessID(const TCHAR* targetProcessName)
{
DWORD processID = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &processEntry))
{
do
{
if (_tcsicmp(processEntry.szExeFile, targetProcessName) == 0)
{
processID = processEntry.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &processEntry));
}
CloseHandle(hSnapshot);
}
return processID;
}

// 远程线程注入DLL
BOOL InjectDll(DWORD processID, const TCHAR* dllPath)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
if (hProcess == NULL)
{
return FALSE;
}

LPVOID dllPathAddress = VirtualAllocEx(hProcess, NULL, _tcslen(dllPath) * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE);
if (dllPathAddress == NULL)
{
CloseHandle(hProcess);
return FALSE;
}

SIZE_T bytesWritten;
if (!WriteProcessMemory(hProcess, dllPathAddress, dllPath, _tcslen(dllPath) * sizeof(TCHAR), &bytesWritten))
{
VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}

HMODULE kernel32Module = GetModuleHandle(_T("kernel32.dll"));
LPTHREAD_START_ROUTINE loadLibraryFunction = (LPTHREAD_START_ROUTINE)GetProcAddress(kernel32Module, "LoadLibraryW");
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, loadLibraryFunction, dllPathAddress, 0, NULL);
if (hRemoteThread == NULL)
{
VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}

WaitForSingleObject(hRemoteThread, INFINITE);
VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
return TRUE;
}

int main()
{
const TCHAR* targetProcessName = _T("runTest.exe"); // 目标进程的名称
const TCHAR* dllPath = _T("C:\\Users\\admin\\source\\repos\\DLLinject\\x64\\Debug\\DLLinject.dll"); // 自定义DLL的路径

DWORD targetProcessID = GetTargetProcessID(targetProcessName);
if (targetProcessID != 0)
{
if (InjectDll(targetProcessID, dllPath))
{
printf("DLL injected successfully.\n");
}
else
{
printf("Failed to inject DLL.\n");
}
}
else
{
printf("Target process not found.\n");
}

return 0;
}

代码分析

单段分析

1
2
3
4
5
6
int main()
{
const TCHAR* targetProcessName = _T("Aes!.exe"); // 目标进程的名称
const TCHAR* dllPath = _T("D:\\study\\VSstdio\\dllxxx\\dllmain\\x64\\Debug\\dllin.dll"); // 自定义DLL的路径

DWORD targetProcessID = GetTargetProcessID(targetProcessName);

首先这套程序的使用方法是,被注入进程的先运行,再运行执行注入的代码,代码在刚开始会找你要注入的程序,找到以后,就调用dll代码,说明注入成功

接下来,先细分析执行注入代码的每一段

首先看到,设置了两个变量名称,一个是目标进程名,另一个是dll代码地址,后面设立了目标进程id为得到目标进程id函数里传入目标进程名,就跟着去分析得到目标进程id函数

获取进程的ID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
DWORD GetTargetProcessID(const TCHAR* targetProcessName)
{
DWORD processID = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &processEntry))
{
do
{
if (_tcsicmp(processEntry.szExeFile, targetProcessName) == 0)
{
processID = processEntry.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &processEntry));
}
CloseHandle(hSnapshot);
}
return processID;
}

首先定义,其次定义processID后面用,紧接着函数访问获取一个正在进行的程序返回一个权柄给hSnapshot,if语句判断返回是否成功,紧接着if语句内,用结构体定义快速存储进程这两步是绑定的,作用就是快速遍历所有进程

之后的if语句,Process32First(hSnapshot, &processEntry)这个是初始,把结构体创造的程序空间赋值,赋值的对象是获取的进程,注意此时后面的是指针,给之后是if判定是不是要的程序名,如果是的话,就把此时的id给processid,退出循环,返回id,回到主程序

1
2
3
if (targetProcessID != 0)
{
if (InjectDll(targetProcessID, dllPath))

这里先判断是否成功获取id,获取成功了就进函数

远程线程注入DLL
1
2
3
BOOL InjectDll(DWORD processID, const TCHAR* dllPath)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);

首先是定义一个句柄hProcess,用函数OpenProcess打开进程,获取其句柄,如果失败返回false

1
2
3
4
5

if (hProcess == NULL)
{
return FALSE;
}

后续if语句就是说如果上文的Open失败的话会返回null,如果null了就返回false结束函数

1
2
LPVOID dllPathAddress = VirtualAllocEx(hProcess, NULL, _tcslen(dllPath) * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE);

开辟一处内存空间用于放置需要的dll代码

1
2
3
4
5
6

if (dllPathAddress == NULL)
{
CloseHandle(hProcess);
return FALSE;
}

判断是否成功,失败的话用closehandle函数关闭打开的句柄,并返回失败

1
2
3
4
5
6
7
 SIZE_T bytesWritten;
if (!WriteProcessMemory(hProcess, dllPathAddress, dllPath, _tcslen(dllPath) * sizeof(TCHAR), &bytesWritten))
{
VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}

创建存储返回实际写入字节的变量,if里调用写入dll代码的函数,如果成功返回ture,再根据“!”反义不进条件,反之进if条件,将开辟的空间释放,然后关闭句柄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    HMODULE kernel32Module = GetModuleHandle(_T("kernel32.dll"));
LPTHREAD_START_ROUTINE loadLibraryFunction = (LPTHREAD_START_ROUTINE)GetProcAddress(kernel32Module, "LoadLibraryW");
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, loadLibraryFunction, dllPathAddress, 0, NULL);
if (hRemoteThread == NULL)
{
VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}

WaitForSingleObject(hRemoteThread, INFINITE);
VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
return TRUE;
}

这里没什么就是比较固定的模板了,前三个函数分别是

获取“kernel32.dll”的句柄

获取线程启动的函数指针,这个函数就是负责后续可以正常让dll注入的代码用的,在kernel32.dll里

CreateRemoteThread这个函数把线程启动函数用了让要注入的dll函数,注入到线程中,并且把远程线程句柄传回

如果失败了就释放创建的地方,关闭句柄

1
2
3
4
5
6
7

WaitForSingleObject(hRemoteThread, INFINITE);
VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
return TRUE;
}

等待远程创建的程序接到下一项指令,这里是因为我的dll程序写的有等待响应所以说这里才会要等待函数,后面就是响应完就没事了就结束释放这几个句柄了

用到的新东西

CreateToolhelp32Snapshot(TH32CS_SNAP...,x)函数

是 Windows API 中用于获取系统进程、线程、模块和堆栈信息的函数,函数需要的

传参:

第一位

     表示我们请求创建一个进程快照。该参数指定了快照内容的类型

    * `TH32CS_SNAPPROCESS`:表示获取系统中所有正在运行的进程信息。
    * `TH32CS_SNAPTHREAD`:获取线程信息。
    * `TH32CS_SNAPMODULE`:获取模块(DLL)信息。
    * `TH32CS_SNAPHEAPLIST`:获取堆栈信息。

第二位

表示指定程序的ID,如果是0就代表全部都要

返回值

成功返回句柄

如果失败会返回 INVALID_HANDLE_VALUE

OpenProcess(access,bool,processID) 函数

起作用如名,打开进程,获取句柄

传参

第一位

表示你想要获取的权限

    * `PROCESS_ALL_ACCESS`:表示全部权限

第二位

表示失败返回bool值

第三位

表示要打开的进程id

返回值

成功返回句柄

失败返回FALSE

CloseHandle函数

这个就没啥说的和上面的OpenProcess刚好相反作用,就是注意传参传入你想关闭的句柄就ok

VirtualAllocEx(access,lpaddress,dwsize,flallocationtype,flprotect) 函数

是 Windows API 中用于在指定的进程地址空间内分配内存的函数

传参

第一位

目标进程的句柄

第二位

空间分配的地方,如果是null就表示系统自己安排

第三位

需要分配的大小

第四位

内存分配的类型,指定如何分配

第五位

内存区域的保护属性,指定该区域的访问权限

返回值

成功返回区域指针(LPVOID)

失败返回FALSE

VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE)函数

这个和上一个起相反作用,就是记一下传参,第一个是句柄,第二个是开辟的空间区域指针,第三个第四个如果是为了关闭的话固定传就好,第四个起主关闭作用

WriteProcessMemory(access, dllPathAddress, lpBuffer , lpNumberOfBytesWritten , &bytesWritten)函数
 用于将数据写入指定的进程的内存中  

传参

第一位

目标进程的句柄

第二位

你要写在哪

第三位

你要写入的东西,传入类型为区域指针

第四位

要写入的字节数

第五位

返回实际写入的字节数。

返回值

成功返回TRUE

失败返回FALSE

GetModuleHandle (model) 函数

此函数用于获取已加载的模块的句柄

返回值

句柄

GetProcAddress (access,process_name) 函数

作用是获取指定模块(DLL 或 EXE)中某个函数的地址

传参

第一个

目标模块的权柄

第二个

目标的名称,如果传入的时函数名称,返回的就是对应函数地址,如果传入的是函数序号,返回与序号 对应的函数地址

返回值

成功时返回函数指针

失败时返回null

CreateRemoteThread(access,NULL,0,loadLibraryFunction,dllPathAddress,0,NULL) 函数

传参

第一个

目标进程的权柄

第二个

线程安全属性(默认传null就好)

第三个

线程栈堆大小(默认传0就好)

第四个

你要在这里启动的过度函数

第五个

过渡函数的参数,也就是传给第四个参数的,在本段函数中我们知道是要调用线程启动函数,目的是把dll函数注入进线程中,所以这个地方传入的应该是dll函数的地址

第六个

线程创建标志(默认传0就好)

第七个

返回线程ID,就是说注入之后需不需要接上别的程序,用的话就传id,不用的话就直接null

返回值

成功了就返回创建的远程线程的句柄(这个函数弄出来的句柄)

失败的话就NULL

PROCESSENTRY32 xxx结构体

是用来存储单个进程信息的结构体

Kernel32.dll模块

是 Windows 系统的核心动态链接库,提供了许多重要的系统函数,如内存管理、进程控制、文件操作等

CreateRemoteThread

WaitForSingleObject(hRemoteThread, INFINITE)函数

用于等待指定的对象(如线程、事件、互斥体等)进入“信号状态”。它通常用于同步操作,等待某个操作完成或某个条件满足

传参

第一个

等待线程的句柄

第二个

等待时间

2.RLTCreateReote Thread

实现原理

写一段shellcode,用RLTCreateReoteThread函数注入

主要用到的函数

此法与法一十分相似,不同的只在dll注入的时候函数用的不一样,所以就主要分析这两个函数啥区别

  • CreateRemoteThread 是一个公开的、稳定的用户模式 API,用于创建远程线程,适用于大多数应用程序。
  • RtlCreateRemoteThread 是底层 API,通常用于操作系统内部,或在一些低级别的编程环境(如内核模式或驱动开发)中使用,不推荐普通应用程序直接调用。它可能会直接操作内核级资源,功能上类似于 CreateRemoteThread,但不适用于普通用户应用程序。大概如此

源码

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
/*-----------------------------------------------------------------------
第12章 注入技术
《加密与解密(第四版)》
(c) 看雪学院 www.kanxue.com 2000-2018
-----------------------------------------------------------------------*/

// RtlCreateInject.cpp : Defines the entry point for the console application.
// Tested On:WinXP SP3/Win7 x86,x64

#include "stdafx.h"
#include <windows.h>
#include <TLHELP32.H>

typedef LONG NTSTATUS;

#define STATUS_SUCCESS ((NTSTATUS) 0x00000000L)
#define STATUS_UNSUCCESSFUL ((NTSTATUS) 0xC0000001L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS) 0xC0000004L)
#define STATUS_IO_DEVICE_ERROR ((NTSTATUS) 0xC0000185L)

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

typedef PVOID PUSER_THREAD_START_ROUTINE;

typedef NTSTATUS (__stdcall *PCreateThread)(
IN HANDLE Process,
IN PSECURITY_DESCRIPTOR ThreadSecurityDescriptor OPTIONAL,
IN BOOLEAN CreateSuspended,
IN ULONG ZeroBits OPTIONAL,
IN SIZE_T MaximumStackSize OPTIONAL,
IN SIZE_T CommittedStackSize OPTIONAL,
IN PUSER_THREAD_START_ROUTINE StartAddress,
IN PVOID Parameter OPTIONAL,
OUT PHANDLE Thread OPTIONAL,
OUT PCLIENT_ID ClientId OPTIONAL
);

HANDLE RtlCreateRemoteThread(
IN HANDLE hProcess,
IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
IN DWORD dwStackSize,
IN LPTHREAD_START_ROUTINE lpStartAddress,
IN LPVOID lpParameter,
IN DWORD dwCreationFlags,
OUT LPDWORD lpThreadId
);



DWORD ProcesstoPid(char *Processname);
BOOL WINAPI InjectDllToProcess(DWORD dwTargetPid ,LPCTSTR DllPath );
VOID PrepareShellCode(BYTE *pShellCode);
PCreateThread RtlCreateUserThread;

#ifdef _WIN64
EXTERN_C VOID ShellCodeFun64(VOID);
char g_szDllPath[MAX_PATH] = "D:\\study\\VSstdio\\dllxxx\\dllmain\\x64\\Debug\\dllin.dll" ;
char g_szProcName[MAX_PATH] = "cbs.exe";
#else
VOID ShellCodeFun(VOID);
char g_szDllPath[MAX_PATH] = "F:\\Program2016\\DllInjection\\MsgDll.dll" ;
char g_szProcName[MAX_PATH] = "HostProc.exe";
#endif

int main(int argc, char* argv[])
{
BOOL bResult = FALSE ;
DWORD dwPid = ProcesstoPid(g_szProcName);
printf("[*] Target Process Pid = %d\n",dwPid);
if (dwPid == 0)
{
printf("Process %s not found!\n",g_szProcName);
return 0 ;
}
InjectDllToProcess(dwPid,g_szDllPath);
getchar();
return 0;
}

//注入主函数
BOOL WINAPI InjectDllToProcess(DWORD dwTargetPid ,LPCTSTR DllPath )
{
HANDLE hProc = NULL;
hProc=OpenProcess(PROCESS_ALL_ACCESS,//Win7下要求的权限较高
FALSE,
dwTargetPid
);

if(hProc == NULL)
{
printf("[-] OpenProcess Failed.\n");
return FALSE;
}

LPTSTR psLibFileRemote = NULL;

//使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名缓冲
psLibFileRemote=(LPTSTR)VirtualAllocEx(hProc, NULL, lstrlen(DllPath)+1,
MEM_COMMIT, PAGE_READWRITE);

if(psLibFileRemote == NULL)
{
printf("[-] VirtualAllocEx Failed.\n");
return FALSE;
}

//使用WriteProcessMemory函数将DLL的路径名复制到远程的内存空间
if(WriteProcessMemory(hProc, psLibFileRemote, (void *)DllPath, lstrlen(DllPath)+1, NULL) == 0)
{
printf("[-] WriteProcessMemory Failed.\n");
return FALSE;
}

//计算LoadLibraryA的入口地址
PTHREAD_START_ROUTINE pfnStartAddr=(PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle("Kernel32"),"LoadLibraryA");

if(pfnStartAddr == NULL)
{
printf("[-] GetProcAddress Failed.\n");
return FALSE;
}

//pfnStartAddr地址就是LoadLibraryA的入口地址

//DebugBreak();
HANDLE hThread = RtlCreateRemoteThread(hProc,
NULL,
0,
pfnStartAddr,
psLibFileRemote,
0,
NULL);

if(hThread == NULL)
{
printf("[-] CreateRemoteThread Failed. ErrCode = %d\n",GetLastError());
return FALSE;
}

printf("[*] Inject Succesfull.\n");
return TRUE;
}

typedef struct _INJECT_DATA
{
BYTE ShellCode[0x30];
LPVOID lpThreadStartRoutine;
LPVOID lpParameter;
LPVOID AddrOfZwTerminateThread;
}INJECT_DATA;

#ifndef _WIN64
__declspec (naked)
VOID ShellCodeFun(VOID)
{
__asm
{
call L001
L001:
pop ebx
sub ebx,5
push dword ptr ds:[ebx]INJECT_DATA.lpParameter //lpParameter
call dword ptr ds:[ebx]INJECT_DATA.lpThreadStartRoutine //ThreadProc
xor eax,eax
push eax
push -2 //CurrentThread
call dword ptr ds:[ebx]INJECT_DATA.AddrOfZwTerminateThread //ZwTerminateThread
nop //no return
nop
nop
nop
nop
}
}
#endif

VOID PrepareShellCode(BYTE *pOutShellCode)
{
#ifdef _WIN64
//x64不能直接使用__asm了,只能这样,而且该函数是裸函数,没有人call它,所以开始时esp % 16 =0,在call之前必须保证
//x64下由于指针长度是8,所以注意各个参数的偏移变化
/*
BYTE ShellCode64[]=
"\x50" //push rax ;eq to sub rsp,8
"\x53" //push rbx
"\x9c" //pushfq
"\xe8\x00\x00\x00\x00" //call next
"\x5b" //pop rbx
"\x66\x83\xe3\x00" //and bx,0 ;rbx = Allocated Mem base
"\x48\x8b\x4b\x38" //mov rcx,qword ptr [rbx+38h] ;lpParameter
"\xff\x53\x30" //call qword ptr [rbx+30h] ;lpThreadStartRoutine
"\x48\x33\xC0" //xor rax,rax ;ExitStatus
"\x48\x8d\x48\xfe" //lea rcx,[rax-2] ;-2 = CurrentThread
"\x48\x8b\xd0" //mov rdx,rax
"\xff\x53\x40" //call qword ptr [rbx+40h] ;ZwTerminateThread
"\x9d" //popfq ;no return here
"\x5b" //pop rbx
"\xc3" //ret
"\x90" //nop
;

memcpy(pOutShellCode,ShellCode64,38);
*/

// for x 64
BYTE *pShellcodeStart = (BYTE*)ShellCodeFun64;
#else
// for x86
BYTE *pShellcodeStart = (BYTE*)ShellCodeFun;
#endif

BYTE *pShellcodeEnd = 0 ;
SIZE_T ShellCodeSize = 0 ;
if (pShellcodeStart[0] == 0xE9)
{
//Debug模式下,函数开头是一个跳转指令,这里取它的真正地址
pShellcodeStart = pShellcodeStart + *(ULONG*)(pShellcodeStart +1 ) + 5;
}

//搜索Shellcode结束标志
pShellcodeEnd = pShellcodeStart;
while (memcmp(pShellcodeEnd,"\x90\x90\x90\x90\x90",5) != 0)
{
pShellcodeEnd++;
}

ShellCodeSize = pShellcodeEnd - pShellcodeStart;
printf("[*] Shellcode Len = %d\n",ShellCodeSize);
memcpy(pOutShellCode,pShellcodeStart,ShellCodeSize);

}

HANDLE RtlCreateRemoteThread(
IN HANDLE hProcess,
IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
IN DWORD dwStackSize,
IN LPTHREAD_START_ROUTINE lpStartAddress,
IN LPVOID lpParameter,
IN DWORD dwCreationFlags,
OUT LPDWORD lpThreadId
)
{
NTSTATUS status = STATUS_SUCCESS;
CLIENT_ID Cid;
HANDLE hThread = NULL ;
SIZE_T dwIoCnt = 0 ;

if (hProcess == NULL
|| lpStartAddress == NULL)
{
return NULL;
}

//获取Native API函数地址
RtlCreateUserThread =(PCreateThread)GetProcAddress(GetModuleHandle("ntdll"),"RtlCreateUserThread");

if(RtlCreateUserThread == NULL)
{
return NULL;
}

//在目标进程中申请内存写入ShellCode
PBYTE pMem = (PBYTE)VirtualAllocEx(hProcess,NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
if (pMem == NULL)
{
return NULL;
}

printf("[*] pMem = 0x%p\n",pMem);

//构造并填充INJECT_DATA
INJECT_DATA Data;
ZeroMemory(&Data,sizeof(INJECT_DATA));
PrepareShellCode(Data.ShellCode);
Data.lpParameter = lpParameter;
Data.lpThreadStartRoutine = lpStartAddress;
Data.AddrOfZwTerminateThread = GetProcAddress(GetModuleHandle("ntdll"),"ZwTerminateThread");


//写入ShellCode
if (!WriteProcessMemory(hProcess,pMem,&Data,sizeof(INJECT_DATA),&dwIoCnt))
{
printf("[-] WriteProcessMemory Failed!\n");
VirtualFreeEx(hProcess,pMem,0,MEM_RELEASE);
return NULL;
}

printf("[*] ShellCode Write OK.\n");

status = RtlCreateUserThread(
hProcess,
lpThreadAttributes, //ThreadSecurityDescriptor
TRUE, //CreateSuspend
0, //ZeroBits
dwStackSize, //MaximumStackSize
0, //CommittedStackSize
(PUSER_THREAD_START_ROUTINE)pMem,//pMem开头就是Shellcode
NULL,
&hThread,
&Cid);

if (status >= 0)
{
printf("[*] 线程创建成功!\n");
if (lpThreadId != NULL)
{
*lpThreadId = (DWORD)Cid.UniqueThread;
}

if (!(dwCreationFlags & CREATE_SUSPENDED))
{
ResumeThread(hThread);
}
}


return hThread;
}


DWORD ProcesstoPid(char *Processname) //查找指定进程的PID(Process ID)
{
HANDLE hProcessSnap=NULL;
DWORD ProcessId=0;
PROCESSENTRY32 pe32={0};
hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); //打开进程快照
if(hProcessSnap==(HANDLE)-1)
{
printf("\nCreateToolhelp32Snapshot() Error: %d",GetLastError());
return 0;
}
pe32.dwSize=sizeof(PROCESSENTRY32);
if(Process32First(hProcessSnap,&pe32)) //开始枚举进程
{
do
{
if(!stricmp(Processname,pe32.szExeFile)) //判断是否和提供的进程名相等,是,返回进程的ID
{
ProcessId=pe32.th32ProcessID;
break;
}
}
while(Process32Next(hProcessSnap,&pe32)); //继续枚举进程
}
else
{
printf("\nProcess32First() Error: %d",GetLastError());
return 0;
}
CloseHandle(hProcessSnap); //关闭系统进程快照的句柄
return ProcessId;
}
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
.CODE

ShellCodeFun64 PROC
push rax ;eq to sub rsp,8
push rbx
pushfq
call next
next:
pop rbx
and bx,0 ;rbx = Allocated Mem base
mov rcx,qword ptr [rbx+38h] ;lpParameter
call qword ptr [rbx+30h] ;lpThreadStartRoutine
xor rax,rax ;ExitStatus
lea rcx,[rax-2] ;-2 = CurrentThread
mov rdx,rax
call qword ptr [rbx+40h] ;ZwTerminateThread
popfq ;no return here
pop rbx
ret
nop
nop
nop
nop
nop
ShellCodeFun64 ENDP

END
1
2
3
4
5
6
7
8
9
// stdafx.cpp : source file that includes just the standard includes
// RtlCreateInject.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

3.QueueUserApc/NtQueueAPCThread APC 法

实现原理

当一个线程运行时,需要有函数来指引,也就是从等待状态被线程调用函数来使其苏醒,所以可以在线程等待的过程中,插入一个APC调用自己的shellcode来实现苏醒后运行注入

主要用到不同的函数

QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpData) ;函数

作用上,就是排队插队

大致是,引用第一个参数(kerenel32.dll模块里可以让某空间代码,插入进线程里的LoadLibraryA函数),把第三个参数(dll代码写在系统里的空间区域指针)放到第二个参数(目标进程的句柄)里

注意的地方是这个函数需要进程在等待状态才可以注入,对于这个等待状态我也有点不太理解后面理解了再记

这个方法书中说,条件苛刻,但是一旦满足成功率接近100%

源代码

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
/*-----------------------------------------------------------------------
第12章 注入技术
《加密与解密(第四版)》
(c) 看雪学院 www.kanxue.com 2000-2018
-----------------------------------------------------------------------*/

// UserApcInjectDll.cpp : Defines the entry point for the console application.
// Tested On:WinXP SP3/Win7 x86/Win7 x64

#include "stdafx.h"
#include <windows.h>
#include <TLHELP32.H>

DWORD ProcesstoPid(char *Processname) ;
BOOL InjectModuleToProcessById(DWORD dwPid, char *szDllFullPath);

#ifdef _WIN64
char g_szDllPath[MAX_PATH] = "D:\\study\\VSstdio\\dllxxx\\dllmain\\x64\\Debug\\dllin.dll" ;
char g_szProcName[MAX_PATH] = "cbs.exe";
#else
char g_szDllPath[MAX_PATH] = "F:\\Program2016\\DllInjection\\MsgDll.dll" ;
char g_szProcName[MAX_PATH] = "HostProc.exe";
#endif

int main(int argc, char* argv[])
{
DWORD dwPid = ProcesstoPid(g_szProcName);
if (dwPid == 0)
{
printf("未找到目标进程!\n");
return 0;
}
printf("Target Pid = %d\n",dwPid);
BOOL bResult = InjectModuleToProcessById(dwPid,g_szDllPath);
printf("Result = %d\n",bResult);
return 0;
}

BOOL InjectModuleToProcessById(DWORD dwPid, char *szDllFullPath)
{
SIZE_T stSize = 0 ;
BOOL bStatus = FALSE ;
LPVOID lpData = NULL ;
SIZE_T uLen = lstrlen(szDllFullPath) + 1;

// 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid) ;
if (hProcess)
{
// 分配空间
lpData = VirtualAllocEx(hProcess, NULL, uLen, MEM_COMMIT, PAGE_READWRITE);
DWORD dwErr = GetLastError();
if (lpData)
{
// 写入需要注入的模块路径全名
bStatus = WriteProcessMemory(hProcess, lpData, szDllFullPath, uLen, &stSize) ;
}

CloseHandle(hProcess) ;
}

//以上操作与创建远程线程的准备工作相同

if (bStatus == FALSE)
return FALSE ;

// 创建线程快照
THREADENTRY32 te32 = {sizeof(THREADENTRY32)} ;
HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) ;
if (hThreadSnap == INVALID_HANDLE_VALUE)
return FALSE ;

bStatus = FALSE ;
// 枚举所有线程
if (Thread32First(hThreadSnap, &te32))
{
do{
// 判断是否目标进程中的线程
if (te32.th32OwnerProcessID == dwPid)
{
// 打开线程
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID) ;
if (hThread)
{
// 向指定线程添加APC
DWORD dwRet = QueueUserAPC ((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpData) ;
if (dwRet > 0)
bStatus = TRUE ;
CloseHandle (hThread) ;
}
}

}while (Thread32Next ( hThreadSnap, &te32 )) ;
}

CloseHandle (hThreadSnap) ;
return bStatus;
}

DWORD ProcesstoPid(char *Processname) //查找指定进程的PID(Process ID)
{
HANDLE hProcessSnap=NULL;
DWORD ProcessId=0;
PROCESSENTRY32 pe32={0};
hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); //打开进程快照
if(hProcessSnap==(HANDLE)-1)
{
printf("\nCreateToolhelp32Snapshot() Error: %d",GetLastError());
return 0;
}
pe32.dwSize=sizeof(PROCESSENTRY32);
if(Process32First(hProcessSnap,&pe32)) //开始枚举进程
{
do
{
if(!stricmp(Processname,pe32.szExeFile)) //判断是否和提供的进程名相等,是,返回进程的ID
{
ProcessId=pe32.th32ProcessID;
break;
}
}
while(Process32Next(hProcessSnap,&pe32)); //继续枚举进程
}
else
{
printf("\nProcess32First() Error: %d",GetLastError());
return 0;
}
CloseHandle(hProcessSnap); //关闭系统进程快照的句柄
return ProcessId;
}

4.SetThreadContext 法

实现原理

线程被暂停时,系统会将线程的上下文保存起来,恢复后会从之前保存的eip开始执行

所以注入时可以编写一段shellcode之后,暂停线程,将eip变成shellcode的地址,再在shellcode的结尾跳回

用到的函数

SuspendThread(hThread)函数

用于暂停线程,参数传递的是需要暂停线程的句柄

ZeroMemory(&Context, sizeof(CONTEXT));函数

用于内存空间或结构体清零

Context.ContextFlags = CONTEXT_FULL;

这里设置了 Context.ContextFlagsCONTEXT_FULL,表示你希望获取完整的线程上下文。

GetThreadContext (hThread, &Context) 函数

GetThreadContext 是 Windows API 函数,用于获取一个指定线程的上下文。

hThread:线程的句柄,表示你要获取上下文的线程,

&ContextCONTEXT 结构体的指针,GetThreadContext 会把线程的上下文信息填充到这个结构体中

uEIP = Context.Rip;

此步骤是把rip找出来,给变量uEIP

ResumeThread(hThread)函数

用于继续线程,参数传递的是需要继续线程的句柄

FIELD_OFFSET(INJECT_DATA, szDllPath);函数

用于查看第二个参数(结构体中的某个元素)在第一个参数(结构体)里的偏移

源码

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
/*-----------------------------------------------------------------------
第12章 注入技术
《加密与解密(第四版)》
(c) 看雪学院 www.kanxue.com 2000-2018
-----------------------------------------------------------------------*/

// SetContextInject.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>
#include <TLHELP32.H>
#include <Psapi.h>

#pragma comment(lib,"psapi.lib")

typedef struct _INJECT_DATA
{
BYTE ShellCode[0x30];
ULONG_PTR AddrofLoadLibraryA;
PBYTE lpDllPath;
ULONG_PTR OriginalEIP;
char szDllPath[MAX_PATH];
}INJECT_DATA;

#ifdef _WIN64
EXTERN_C VOID ShellCodeFun64(VOID);
#else
VOID ShellCodeFun(VOID);
#endif

ULONG_PTR GetModuleHandleInProcess(DWORD processID, char* szModuleName);
DWORD ProcesstoPid(char* Processname);
BOOL InjectModuleToProcessBySetContext(DWORD dwPid, char* szDllFullPath);
VOID PrepareShellCode(BYTE* pShellCode);

int main(int argc, char* argv[])
{
#ifdef _WIN64
char szDllPath[MAX_PATH] = "D:\\study\\VSstdio\\dllxxx\\dllmain\\x64\\Debug\\dllin.dll";
DWORD dwPid = ProcesstoPid("cbs.exe");
#else
char szDllPath[MAX_PATH] = "D:\\study\\VSstdio\\dllxxx\\dllmain\\x64\\Debug\\ dllin.dll";
DWORD dwPid = ProcesstoPid("cbs.exe");
#endif
if (dwPid == 0)
{
printf("[-] Target Process Not Found!\n");
return 0;
}
printf("[*] Target Process Pid = %d\n", dwPid);
BOOL bResult = InjectModuleToProcessBySetContext(dwPid, szDllPath);
printf("[*] Result = %d\n", bResult);
return 0;
}




#ifndef _WIN64
__declspec (naked)
VOID ShellCodeFun(VOID)
{
__asm
{
push eax //占位,一会儿用做跳转地址
pushad //大小0x20
pushfd //大小0x04
call L001
L001 :
pop ebx
sub ebx, 8
push dword ptr ds : [ebx + 0x34] //szDllPath
call dword ptr ds : [ebx + 0x30] //LoadLibraryA
mov eax, dword ptr ds : [ebx + 0x38] //OriginalEIP
xchg eax, [esp + 0x24] //将原来的EIP交换到栈上
popfd
popad
retn //jmp to OriginalEIP
nop
nop
nop
nop
nop
}
}
#endif


VOID PrepareShellCode(BYTE* pOutShellCode)
{
#ifdef _WIN64
//x64不能使用__asm了,只能直接写Shellcode或单独写在asm文件中
/*
BYTE ShellCode64[]=
"\x50" //push rax
"\x53" //push rbx
"\x9c" //pushfq
"\xe8\x00\x00\x00\x00" //call next
"\x5b" //pop rbx
"\x66\x83\xe3\x00" //and bx,0
"\x48\x8b\x4b\x38" //mov rcx,qword ptr [rbx+38h] ; lpDllPath
"\xff\x53\x30" //call qword ptr [rbx+30h] ;AddrofLoadLibraryA
"\x48\x8b\x43\x40" //mov rax,qword ptr [rbx+40h] ;OriginalEIP
"\x48\x87\x44\x24\x10" //xchg rax,qword ptr [rsp+10h] ;saved retnaddr
"\x9d" //popfq
"\x5b" //pop rbx
"\xc3" //ret
"\x90" //nop
;

memcpy(pOutShellCode,ShellCode64,33);
*/
BYTE* pShellcodeStart = (BYTE*)ShellCodeFun64;
#else
BYTE* pShellcodeStart = (BYTE*)ShellCodeFun;
#endif

BYTE* pShellcodeEnd = 0;
SIZE_T ShellCodeSize = 0;
if (pShellcodeStart[0] == 0xE9)
{
//Debug模式下,函数开头是一个跳转指令,这里取它的真正地址
pShellcodeStart = pShellcodeStart + *(ULONG*)(pShellcodeStart + 1) + 5;
}

//搜索Shellcode结束标志
pShellcodeEnd = pShellcodeStart;
while (memcmp(pShellcodeEnd, "\x90\x90\x90\x90\x90", 5) != 0)
{
pShellcodeEnd++;
}

ShellCodeSize = pShellcodeEnd - pShellcodeStart;
printf("[*] Shellcode Len = %d\n", ShellCodeSize);
memcpy(pOutShellCode, pShellcodeStart, ShellCodeSize);


}
BOOL InjectModuleToProcessBySetContext(DWORD dwPid, char* szDllFullPath)
{
SIZE_T dwRet = 0;
BOOL bStatus = FALSE;
PBYTE lpData = NULL;
SIZE_T uLen = 0x1000;
INJECT_DATA Data;
HANDLE hProcess, hThread;
DWORD i = 0;


//1.获取目标进程中LoadLibraryA的地址
//之所以这么获取,是考虑了ASLR的影响,此时目标进程中kernel32.dll的加载位置不一定与本进程相同
ULONG_PTR uKernelBaseInTargetProc = GetModuleHandleInProcess(dwPid, "kernel32.dll");
ULONG_PTR uKernelBaseInCurProc = (ULONG_PTR)GetModuleHandle("kernel32.dll");
ULONG_PTR uLoadLibraryAddrInCurProc = (ULONG_PTR)GetProcAddress((HMODULE)uKernelBaseInTargetProc, "LoadLibraryA");
ULONG_PTR uLoadLibraryAddrInTargetProc = uLoadLibraryAddrInCurProc - uKernelBaseInCurProc + uKernelBaseInTargetProc;
printf("[*] 目标进程中 LoadLibraryA Addr = 0x%p\n", uLoadLibraryAddrInTargetProc);

//2.获取目标进程中的线程列表
THREADENTRY32 te32 = { sizeof(THREADENTRY32) };
DWORD dwTidList[1024] = { 0 };
DWORD Index = 0;
HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnap == INVALID_HANDLE_VALUE)
return FALSE;

bStatus = FALSE;
printf("[*] 开始枚举目标进程中的线程.\n");
// 枚举所有线程
if (Thread32First(hThreadSnap, &te32))
{
do {
// 判断是否目标进程中的线程
if (te32.th32OwnerProcessID == dwPid)
{
bStatus = TRUE;
dwTidList[Index++] = te32.th32ThreadID;
}

} while (Thread32Next(hThreadSnap, &te32));
}

CloseHandle(hThreadSnap);

if (!bStatus)
{
printf("[-] 无法得到目标进程的线程列表!\n");
return FALSE;
}
printf("[*] 线程枚举完毕,共有 %d 个线程.\n", Index);

//3. 打开目标进程 ,申请内存,写入Shellcode和参数
ULONG_PTR uDllPathAddr = 0;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL)
{
printf("[-] 无法打开目标进程!\n");
return FALSE;
}

//3.依次打开线程,获取Context
bStatus = FALSE;
CONTEXT Context;
ULONG_PTR uEIP = 0;
for (i = 0;i < Index;i++)
{
hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTidList[i]);
if (hThread == NULL)
{
printf("[-] 打开线程 %d 失败!\n", dwTidList[i]);
continue;
}

printf("[*] 打开线程 %d 成功.\n", dwTidList[i]);

//暂停线程
DWORD dwSuspendCnt = SuspendThread(hThread);
if (dwSuspendCnt == (DWORD)-1)
{
printf("[-] 暂停线程 %d 失败!\n", dwTidList[i]);
CloseHandle(hThread);
continue;
}

printf("[*] 暂停线程 %d 成功 Cnt = %d.\n", dwTidList[i], dwSuspendCnt);
//获取Context
ZeroMemory(&Context, sizeof(CONTEXT));
Context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(hThread, &Context))
{
printf("[-] 无法获取线程 %d 的Context!\n", dwTidList[i]);
CloseHandle(hThread);
continue;
}

#ifdef _WIN64
uEIP = Context.Rip;
#else
uEIP = Context.Eip;
#endif
printf("[*] 获取线程 %d 的Context成功 EIP = 0x%p\n", dwTidList[i], uEIP);

// 分配空间
lpData = (PBYTE)VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (lpData == NULL)
{
printf("[-] 在目标进程申请内存失败!\n");
CloseHandle(hThread);
continue;
}

printf("[*] 在目标进程中申请内存成功, lpData = 0x%p\n", lpData);

//构造ShellCode
ZeroMemory(&Data, sizeof(INJECT_DATA));
PrepareShellCode(Data.ShellCode);
lstrcpy(Data.szDllPath, szDllFullPath); //Dll路径
Data.AddrofLoadLibraryA = uLoadLibraryAddrInTargetProc; //LoadLibraryA的地址
Data.OriginalEIP = uEIP; //原始的EIP地址
Data.lpDllPath = lpData + FIELD_OFFSET(INJECT_DATA, szDllPath); //szDllPath在目标进程中的位置
printf("[*] ShellCode填充完毕.\n");

//向目标进程写入ShellCode
if (!WriteProcessMemory(hProcess, lpData, &Data, sizeof(INJECT_DATA), &dwRet))
{
printf("[-] 向目标进程写入ShellCode失败!\n");
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}

printf("[*] 向目标进程写入ShellCode成功.\n");

//设置线程的Context,使EIP指向ShellCode起始地址
#ifdef _WIN64
Context.Rip = (ULONG_PTR)lpData;
#else
Context.Eip = (ULONG_PTR)lpData;
#endif
//设置Context
if (!SetThreadContext(hThread, &Context))
{
printf("[-] 无法设置线程 %d 的Context!\n", dwTidList[i]);
CloseHandle(hThread);
continue;
}

printf("[*] 设置线程 %d 的Context成功.\n", dwTidList[i]);

//恢复线程执行
dwSuspendCnt = ResumeThread(hThread);
if (dwSuspendCnt == (DWORD)-1)
{
printf("[-] 恢复线程 %d 失败!\n", dwTidList[i]);
CloseHandle(hThread);
continue;
}

printf("[*] 恢复线程 %d 成功. Cnt = %d\n", dwTidList[i], dwSuspendCnt);

bStatus = TRUE;
CloseHandle(hThread);

//Sleep一段时间,继续对下一线程操作,以确保成功率
Sleep(1000);


}

CloseHandle(hProcess);
printf("[*] 操作全部完毕.\n");

return bStatus;
}

ULONG_PTR GetModuleHandleInProcess(DWORD processID, char* szModuleNameToFind)
{
HMODULE hMods[1024];
HANDLE hProcess;
DWORD cbNeeded;
unsigned int i;
char* pCompare = NULL;
ULONG_PTR uResult = 0;

// Print the process identifier.

printf("\nProcess ID: %u\n", processID);

// Get a list of all the modules in this process.

hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
processID
);
if (NULL == hProcess)
return 0;

if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
{
for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
{
char szModName[MAX_PATH];

// Get the full path to the module's file.

if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
sizeof(szModName) / sizeof(char)))
{
// Print the module name and handle value.
pCompare = strrchr(szModName, '\\');
pCompare = (pCompare == NULL) ? szModName : (pCompare + 1);
if (stricmp(pCompare, szModuleNameToFind) == 0)
{
uResult = (ULONG_PTR)hMods[i];
break;
}
}
}
}

CloseHandle(hProcess);

return uResult;
}

DWORD ProcesstoPid(char* Processname) //查找指定进程的PID(Process ID)
{
HANDLE hProcessSnap = NULL;
DWORD ProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //打开进程快照
if (hProcessSnap == (HANDLE)-1)
{
printf("\nCreateToolhelp32Snapshot() Error: %d", GetLastError());
return 0;
}
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hProcessSnap, &pe32)) //开始枚举进程
{
do
{
if (!stricmp(Processname, pe32.szExeFile)) //判断是否和提供的进程名相等,是,返回进程的ID
{
ProcessId = pe32.th32ProcessID;
break;
}
} while (Process32Next(hProcessSnap, &pe32)); //继续枚举进程
}
else
{
printf("\nProcess32First() Error: %d", GetLastError());
return 0;
}
CloseHandle(hProcessSnap); //关闭系统进程快照的句柄
return ProcessId;
}

细析部分代码

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
VOID PrepareShellCode(BYTE* pOutShellCode)
{
#ifdef _WIN64
//x64不能使用__asm了,只能直接写Shellcode或单独写在asm文件中
/*
BYTE ShellCode64[]=
"\x50" //push rax
"\x53" //push rbx
"\x9c" //pushfq
"\xe8\x00\x00\x00\x00" //call next
"\x5b" //pop rbx
"\x66\x83\xe3\x00" //and bx,0
"\x48\x8b\x4b\x38" //mov rcx,qword ptr [rbx+38h] ; lpDllPath
"\xff\x53\x30" //call qword ptr [rbx+30h] ;AddrofLoadLibraryA
"\x48\x8b\x43\x40" //mov rax,qword ptr [rbx+40h] ;OriginalEIP
"\x48\x87\x44\x24\x10" //xchg rax,qword ptr [rsp+10h] ;saved retnaddr
"\x9d" //popfq
"\x5b" //pop rbx
"\xc3" //ret
"\x90" //nop
;

memcpy(pOutShellCode,ShellCode64,33);
*/
BYTE* pShellcodeStart = (BYTE*)ShellCodeFun64;
#else
BYTE* pShellcodeStart = (BYTE*)ShellCodeFun;
#endif

BYTE* pShellcodeEnd = 0;
SIZE_T ShellCodeSize = 0;
if (pShellcodeStart[0] == 0xE9)
{
//Debug模式下,函数开头是一个跳转指令,这里取它的真正地址
pShellcodeStart = pShellcodeStart + *(ULONG*)(pShellcodeStart + 1) + 5;
}

//搜索Shellcode结束标志
pShellcodeEnd = pShellcodeStart;
while (memcmp(pShellcodeEnd, "\x90\x90\x90\x90\x90", 5) != 0)
{
pShellcodeEnd++;
}

ShellCodeSize = pShellcodeEnd - pShellcodeStart;
printf("[*] Shellcode Len = %d\n", ShellCodeSize);
memcpy(pOutShellCode, pShellcodeStart, ShellCodeSize);


}
1
2
3
4
5
6
7
8
9
//结构体声明
typedef struct _INJECT_DATA
{
BYTE ShellCode[0x30];
ULONG_PTR AddrofLoadLibraryA;
PBYTE lpDllPath;
ULONG_PTR OriginalEIP;
char szDllPath[MAX_PATH];
}INJECT_DATA;
1
2
3
4
5
6
7
8
9
10
11

//shellcode构造
ZeroMemory(&Data, sizeof(INJECT_DATA));
PrepareShellCode(Data.ShellCode);
lstrcpy(Data.szDllPath, szDllFullPath); //Dll路径
Data.AddrofLoadLibraryA = uLoadLibraryAddrInTargetProc; //LoadLibraryA的地址
Data.OriginalEIP = uEIP; //原始的EIP地址
Data.lpDllPath = lpData + FIELD_OFFSET(INJECT_DATA, szDllPath); //szDllPath在目标进程中的位置
printf("[*] ShellCode填充完毕.\n");


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
.CODE

ShellCodeFun64 PROC

push rax
push rbx
pushfq
call next
next:
pop rbx
and bx,0
mov rcx,qword ptr [rbx+38h] ; lpDllPath
call qword ptr [rbx+30h] ;AddrofLoadLibraryA
mov rax,qword ptr [rbx+40h] ;OriginalEIP
xchg rax,qword ptr [rsp+10h] ;saved retnaddr
popfq
pop rbx
ret
nop
nop
nop
nop
nop
ShellCodeFun64 ENDP

END

先将结构体清零,后面的PrepareShellcode函数是自己命名的,用于将shellcode存入结构体

后面是把dll路径复制进结构体,然后是LoadLibraryA在线程中的位置传给结构体,原先的eip给结构体,最后是计算偏移,最后就是结构体填充完毕

5.ProcessNotifyInject 法

实现原理

不创造新的线程,而是直接在原线程进行时,挖一个“坑”,坑里是shellcode dll注入,等待系统执行到“坑”里即可完成注入,因为不创造新的线程,所以是汇编写的

方法详解

首先,注入前要选择好注入前的时机

第一种是在线程开始之前

ntdll!LdrInitializeThunk这个是一个程序开始前的初始化函数,用于完成用户层的初始化工作,紧接着会调用ntdll!Continue继续执行,而再次执行的起始位置就在NtContinue的第一个参数CONTINUE.EIP之中。对进程的第一个线程来说,这个值一般是kernel32!BaseProcessStartThunk,因此,我们可以在这里修改CONTEXT,是本该继续执行的目标变成我们事先写入的用于加载dll的shellcode

第二种是注入到运行中的进程

思路和第一种大差不差,只是注入位置变成了调用频率更高的API

dll加载系列–SetWindowsHookEx

实现原理

首先说一下钩子

”钩子”是一种特殊的消息处理机制,它可以监视系统或者进程中的各种事件消息,截获发往目标窗口的消息并进行处理。我们可以在系统中自定义钩子,用来监视系统中特定事件的发生,完成特定功能,如屏幕取词,监视日志,截获键盘、鼠标输入等等。

上面是官方话语,配图

既然你所输入的会被钩子挂住,且有修改的风险,那么也可以通过钩子,来实现dll注入

核心–SetWindowsHookEx函数

SetWindowsHookEx函数,可以创建一个新的系统钩子,先解析一下这个函数传入的参数

SetWindowsHookEx( idHook , lpfn , hmod , dwThreadId );

第一个idHook 传入的是你想要定义监视的钩子类型,比如键盘钩子

第二个lpfn 传入的是包含钩子过程的指针,也就是回调函数,通俗讲就是触发了钩子要干什么的函数,比如按下了某个键,发生什么

第三个hmod 传入的是包含钩子过程的模块的句柄,通俗讲就是,你的回调函数在哪,就传入哪,在一个dll文件里,就传入dll文件的句柄,在同一个文件下,就传入当前进程的句柄,比如hInstance是当前进程的句柄

第四个dwThreadId传入的是安装钩子的线程id,你是想装在哪,0就是全局

这里的SetWindowsHookEx函数可以看到传入的第三个是当前模块的句柄,也就说明传入的第二个参数(回调函数)在当前线程里(在这个项目里就能看到)

方法详解

一.不用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
#include <windows.h>
#include <stdio.h>

HHOOK hHook;

// 钩子函数
// 1. 什么是 LRESULT?
// 定义:LRESULT 是一个 32 位或 64 位的整数类型(取决于系统架构),通常用来表示钩子或窗口消息处理函数的返回值。

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0 && wParam == WM_KEYDOWN) {
KBDLLHOOKSTRUCT* pKey = (KBDLLHOOKSTRUCT*)lParam;
printf("Key Pressed: %c\n", pKey->vkCode);
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
// LRESULT CALLBACK lpfn(int code, WPARAM wParam, LPARAM lParam) {
// return CallNextHookEx(g_hHook, code, wParam, lParam);
// }这里的回调函数就是什么都没有做,直接将当前信息传递到钩子链的下一个钩子


int main() {
// 设置全局键盘钩子
HINSTANCE hInstance = GetModuleHandle(NULL); // 当前模块句柄
hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, hInstance, 0);
if (!hHook) {
printf("Failed to install hook! Error: %d\n", GetLastError());
return 1;
}
printf("Hook installed. Press any key to see the output.\n");

// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

// 卸载钩子
UnhookWindowsHookEx(hHook);
return 0;
}

函数

FindWindow(lpClassName,lpWindowName) 函数

标准传参

  • **lpClassName**** 参数**:窗口的类名,用于更精确地定位窗口。
    • 类名是由窗口创建时注册的,通常由开发者指定。
    • 如果你对类名不清楚,可以传入 NULL,表示不基于类名查找。
  • **lpWindowName**** 参数**:窗口的标题。
    • 标题是窗口当前显示在标题栏的文本内容。
    • 如果标题完全匹配,FindWindow 会返回对应窗口的句柄
func proc = (func)GetProcAddress(hDll, "getDllHookProc");函数

作用

  • 使用 GetProcAddress 从已加载的 DLL 模块 hDll 中获取名为 getDllHookProc 的导出函数地址,并将其转换为 func 类型的函数指针。

标准传参

第一个为dll句柄

第二个为需要定位的函数名

dll加载系列–Applnit_DLLs注册表项注入

加载user32.dll时,会调用一个LoadAppDLLs()函数,他会读取下面这个注册表项

如果发现这个注册表项下面登记了DLL(在windows 7中)只有将LoadAppInit设置为1,AppInit_DLLs才会启用),就会主动加载他。所以,只要把要加载的DLL登记咋爱这里,就可以将其诸如那些加载了user32.dll的进程

dll加载系列–输入法注入