代码注入

代码注入

0x00-前言

感觉《逆向工程核心原理》只是进行了一点介绍,但是我理解能力有限

于是翻阅到了这篇文章

https://www.52pojie.cn/thread-1270499-1-1.html

0x01-原理

在DLL注入中,我们通过远程线程在目标进程中调用LoadLibraryA()函数加载我们的DLL

实际上只要我们定义的函数符合CreateRemoteThread参数中定义的函数模板,就可以通过远程线程的方式去执行它,而不限于LoadLibraryA

1
2
3
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);

所以我们只要定义一个只有一个参数的函数,把它转换成 LPTHREAD_START_ROUTINE(CreateRemoteThread的参数)即可。

但是这里产生了一个问题,如果我们的函数具有多个参数怎么办呢,要成功调用函数的话,我们的参数也需要在目标进程的虚拟内存中

对于将我们的参数写入虚拟内存中,我们可以使用VirtualAllocEx函数向目标申请内存虚拟空间

对于多个参数,我们可以构建一个结构体存放所有的参数,然后在调用的时候通过内存偏移来访问参数

1
2
3
4
5
typedef struct _INJECT_DATA
{
char lpText[8]; //参数1
char lpCaption[8]; //参数2
}INJECT_DATA;

下面以MessageBoxA函数为例

定义函数指针

首先先进行定义我们要使用到的函数指针

因为我们要调用user32.dll中的MessageBoxA,所以要使用LoadLibraryA()加载user32.dll,并且使用GetProcAddress()获取MessageBoxA的函数地址

1
2
3
4
5
6
//定义函数指针,方便调用HOMDULE是返回值,WINAPI也就是__stdcall,是调用约定,类型名为PFLOADLIBRARYA,lpLivFileName是参数
typedef HMODULE(WINAPI *PFLOADLIBRARYA)(LPCSTR lpLibFileName);//前面的是返回值,中间是类型

typedef FARPROC(WINAPI *PFGETPROCADDRESS)(HMODULE hModule,LPCSTR lpProcName);//返回值是一个回调函数

typedef int (WINAPI *PFMESSAGEBOXA)(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType);

定义线程信息块

使用结构体存储我们要调用的函数信息

1
2
3
4
5
6
7
//创建一个存储线程信息的结构体
typedef struct _THREAD_PARAM//定义线程信息
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA", "Inject Success!!", "Hint"
} THREAD_PARAM, * PTHREAD_PARAM;//分别是线程信息块和指向线程信息的指针

线程信息块赋值

1
2
3
4
5
6
7
8
9
hMod = GetModuleHandleA("kernel32.dll");

//先给线程信息块赋值
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");//因为kernel32在内存中优先被装载,所以在不同进程中都一样
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "InjectCode Success!!");
strcpy_s(param.szBuf[3], "Hint");

打开目标进程

1
2
3
4
5
6
//开启进程  
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID)))
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}

开辟内存并写入线程信息

第一次写入的是线程函数要调用的函数信息

1
2
3
4
5
6
7
8
9
10
11
12
13
//为注入的线程信息块中的第一个函数开辟内存 
dwSize = sizeof(THREAD_PARAM);
if (!(pRemoteBuf[0] = VirtualAllocEx(hProcess,NULL,dwSize,MEM_COMMIT,PAGE_READWRITE)))
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
//写入线程信息块中函数的值和参数到进程中
if (!WriteProcessMemory(hProcess,pRemoteBuf[0],(LPVOID)&param,dwSize,NULL))
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

第二次将线程函数写入内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//再次开辟  
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if (!(pRemoteBuf[1] = VirtualAllocEx(hProcess,NULL,dwSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE)))
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}

//函数调用
if (!WriteProcessMemory(hProcess,pRemoteBuf[1],(LPVOID)ThreadProc,dwSize,NULL))
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

开启远程线程

在目标线程中开启远程线程

1
2
3
4
5
if (!(hThread = CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)pRemoteBuf[1],pRemoteBuf[0],0,NULL)))
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}

线程函数实现调用

开启线程后会调用线程函数,把我们写入进程内存中的信息读取到要调用的函数中,最后实现调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//这里的WINAPI是调用约定,相当于__stdcall
//线程函数,线程开始的地方
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;

// LoadLibrary(),将pFunc[0]强制转换为这样类型的函数指针,pParam->szBuf[0]是该函数的参数
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll",第二个括号内的是函数的参数
if (!hMod)
return 1;

// GetProcAddress(),返回值是回调函数,pFunc就是函数指针
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA" ,获取到MessageBoxA的地址
if (!pFunc)
return 1;

// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);//最后的调用

return 0;
}

0x02-代码实现

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
#include<string.h>
#include "windows.h"
#include "stdio.h"

//创建一个存储线程信息的结构体
typedef struct _THREAD_PARAM//定义线程信息
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore"
} THREAD_PARAM, * PTHREAD_PARAM;//分别是线程信息块和指向线程信息的指针

//定义函数指针,方便调用HOMDULE是返回值,WINAPI也就是__stdcall,是调用约定,类型名为PFLOADLIBRARYA,lpLivFileName是参数
typedef HMODULE(WINAPI *PFLOADLIBRARYA)(LPCSTR lpLibFileName);//前面的是返回值,中间是类型

typedef FARPROC(WINAPI *PFGETPROCADDRESS)(HMODULE hModule,LPCSTR lpProcName);//返回值是一个回调函数

typedef int (WINAPI *PFMESSAGEBOXA)(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType);

//这里的WINAPI是调用约定,相当于__stdcall
//线程函数,线程开始的地方
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;

// LoadLibrary(),将pFunc[0]强制转换为这样类型的函数指针,pParam->szBuf[0]是该函数的参数
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll",第二个括号内的是函数的参数
if (!hMod)
return 1;

// GetProcAddress(),返回值是回调函数,pFunc就是函数指针
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA" ,获取到MessageBoxA的地址
if (!pFunc)
return 1;

// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);//最后的调用

return 0;
}

BOOL InjectCode(DWORD dwPID)
{
HMODULE hMod = NULL;
THREAD_PARAM param = { 0, };
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pRemoteBuf[2] = { 0, };
DWORD dwSize = 0;

hMod = GetModuleHandleA("kernel32.dll");

//先给线程信息块赋值
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");//因为kernel32在内存中优先被装载,所以在不同进程中都一样
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "InjectCode Success!!");
strcpy_s(param.szBuf[3], "Hint");

//开启进程
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID)))
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}

//为注入的线程信息块中的第一个函数开辟内存
dwSize = sizeof(THREAD_PARAM);
if (!(pRemoteBuf[0] = VirtualAllocEx(hProcess,NULL,dwSize,MEM_COMMIT,PAGE_READWRITE)))
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
//写入线程信息块中函数的值和参数到进程中
if (!WriteProcessMemory(hProcess,pRemoteBuf[0],(LPVOID)&param,dwSize,NULL))
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

//再次开辟
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if (!(pRemoteBuf[1] = VirtualAllocEx(hProcess,NULL,dwSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE)))
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}

//函数调用
if (!WriteProcessMemory(hProcess,pRemoteBuf[1],(LPVOID)ThreadProc,dwSize,NULL))
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
//开启远程线程,LPTHREAD_START_ROUTINE指向一个回调函数,pRemoteBuf[1]是函数,pRemoteBufe[0]是函数的值和参数
if (!(hThread = CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)pRemoteBuf[1],pRemoteBuf[0],0,NULL)))
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}

WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

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;
}

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

if (argc != 2)
{
printf("\n USAGE : %s <pid>\n", argv[0]);
return 1;
}

//进程提权
if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
return 1;

//code injection
dwPID = (DWORD)atol(argv[1]);
dwPID = 9432;
InjectCode(dwPID);

return 0;
}

0x03-最终效果

0x04-代码注入调试练习

先使用OD打开notepad++,并F9直至notepad++处于运行状态

进行设置

然后查看notepad++ PID并注入

注入之后会断在ThreadProc函数

在x32中