DLL注入技术

旧文迁移,DLL注入就是强制插入dll到其他进程并使其在目标进程权限下运行,本文持续补充新方法。

消息钩取


Windows的GUI是事件驱动的,当系统接收到一个事件,将会把它发送到发生该事件的程序的消息队列,程序会循环读取消息队列取出消息并执行相应的事件函数,而用户可以在事件从系统到应用程序传递过程中安装钩子,截获消息,对消息进行操作,钩子可以安装多个,形成钩链,最后安装的钩子最先获取消息!安装钩子后,当事件发生时,将自动将dll注入到进程。基础知识可以参考:http://blog.csdn.net/jiangxinyu/article/details/5284079
接下来,是写一个针对键盘事件的系统钩子,但是它只对名为”NOTEPAD.EXE”的进程起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
                        /*KeyHook.cpp*/
#include<stdio.h>
#include<Windows.h>

#define DEF_PROCESS_NAME "NOTEPAD.EXE"
/*
这里定义了三个全局变量
*/
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstance, DWORD dwReason, LPVOID lpvReversed) {
//当DLL被载入时,会自动执行此函数,第一个参数作为dll的内核对象句柄
//第二个参数是被调用的理由,用于下面switch语句,对其进行适当的初始化或者是清除
//最后一个为保留值
switch (dwReason) {
case DLL_PROCESS_ATTACH://当第一次附加在进程时dwReason为DLL_PROCESS_ATTACH
g_hInstance = hinstance;
break;
case DLL_PROCESS_DETACH://当卸载此dll时dwReason为DLL_PROCESS_DETACH,可以写清理操作
break;
}
return TRUE;
}
//这是一个回调函数,用于勾取操作
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
//定义一个字符串存放路径
char szPath[MAX_PATH] = { 0 };
char *p = NULL;
if (nCode >= 0) {
if (!(lParam & 0x80000000)) {
GetModuleFileNameA(NULL, szPath, MAX_PATH);//此函数返回调用程序的路径
p = strrchr(szPath, '\\');//返回从右到左第一个\的指针
//stricmp是忽略大小写的字符串比较,当相等时返回0
if (!_stricmp(p + 1, DEF_PROCESS_NAME))
return 1;
}
}
//要是不是NOTEPAD.EXE线程,就将这个消息往后传
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HookStart()
{
//对WH_KEYBOARD安装钩子,发生事件时执行KeyboardProc回调函数,第三个参数为回调函数所在的实例句柄
//最后一个参数为要钩取的线程号,0为所有线程
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop() {
if (g_hHook)
{
UnhookWindowsHookEx(g_hHook);//卸载钩子
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif
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
                    /*HookMain.cpp*/
#include "stdio.h"
#include "conio.h"
#include "windows.h"

#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

void main()
{
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;

// 载入KeyHook.dll
hDll = LoadLibraryA(DEF_DLL_NAME);
if( hDll == NULL )
{
printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError());
return;
}

// 获取hook函数的地址
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

// 运行hook函数
HookStart();

printf("press 'q' to quit!\n");
while( _getch() != 'q' ) ;

HookStop();

FreeLibrary(hDll);
}

此DLL会hook所有进程,即使是启动钩取再启动其他程序,其他程序也会被钩取,钩取发生在发生相应的事件时,如下,这里只有发生键盘事件dll才会被注入:

这里验证了,只要发生键盘事件的进程都被注入了dll:

调试:
调试HookMain:

很简单就可以发现主函数,进入:

由于是自己写的代码,还是C很好分析,当将od的分析选项设置显示本地变量,将会使用[local.n]的形式显示函数本地,n一般为相对EBP的双字距离,这里看出调用返回值保存在[local.1]这个变量。

这里先是调用了GetProcAddress函数,返回值保存在[local.2],[local.3]这两个变量里,接着调用了[local.2](其实它里面存的就是HookStart函数的实际地址),跟入:

跟着跳转:

安装钩子,返回上一级,停止钩取,释放DLL:

试试NOTEPAD:

先开启此选项,等下记事本中被注入dll时会暂停,现在调试notepad,并运行HookMain,接着在记事本里面触发键盘事件:

若是第一次接触Win SDK,应该会很懵逼,什么HINSTANCE, LPVOID,PCSRT类型,根本没遇见过,其实使用VS可以直接转到定义,会发现他们其实就是C的基本数据类型,只是使用了typedef重新拥有了更加准确的名字,然后遇到的很多宏定义也可以转到定义,甚至可以看到注释,而我们写的函数那些似乎没有被调用,或者没有使用实参,其实那些都是系统或者编译器隐式的写入了。。。

使用调试器注入


调试器几乎拥有和被调试者同等的权限,可以对被调试者进行各种操作,YY一下od对被调试者进行的操作即可,在API钩取处与反调试中有,此处不写。

使用CreateProcess注入


进程在创建子进程时可以得到子进程的句柄,并将它挂起执行一些操作后再使之运行。

使用木马注入


替换原程序使用的dll,使用函数转移实现原函数的功能。

使用注册表


User32.dll是每一个有图形化界面软件都载入的,它在被映射到新进程时会执行DllMain函数,其中有第一次附加在进程将会读取AppInit_DLLs项的值(值为以空格或逗号分隔的dll文件名),并加载他们(LoadAppInit_DLLs值为1时)

于是,这种方式只对所有GUI程序起作用,而且对所有的这类程序都起作用,外力不能对其进行控制。

创建远程线程


据称是最好用的,因为它能很好的控制dll。dll注入本质就是让目标进程执行LoadLibrary API加载指定dll,而一般情况下,每个进程都有各自的内存空间,一个进程不能操作另一个进程,幸运的是Windows系统提供了一些特殊的API,可以对其他进程进行一些操作(一般用于调试等特殊操作),CreateRemoteThread()就是这样的API,可以在目标进程中创建新线程,并令其执行指定命令。

1
2
3
4
5
6
7
8
9
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

那么我们可以写一个注入程序A,在这个程序里面执行CreateRemoteThread()向目标程序B注入代码,这里需要注意,A虽然可以向B发出指令,使其新创建线程并运行指定函数,但他们并不在同一个进程空间,所以下面很多东西不能直接用呢,观察此API:

第一个参数为目标进程句柄,可以轻易获取
第二个参数为线程安全属性,随意设置
第三个参数,堆栈大小,开心就好
第四个参数,回调函数的地址(是目标进程空间中的)
第五个参数,回调函数的参数地址(是目标进程空间中的)
。。。

问题就在这里,为使目标加载我们指定的dll就是使用LoadLibrary(“name.dll”),即第四,五个参数为这两个,但是第四个参数如果直接填LoadLibrary它将是注入程序A中指向导入地址表里面的跳转的地址,和目标进程B不一定一样,在B中执行很可能会出错,不过根据《Windows核心编程》作者这位大佬的经验,所有的Windows系统库将会在所有程序中被加载到同样的地址,于是我们可以直接在A中获取LoadLibraryA这个API的VA,它在B中VA也是那么多。第二个问题是回调函数的参数是A进程中指向”name.dll”这个字符串的地址,那么在B中就不知道指向的东西是什么了,我们必须将这个字符串放在B的内存空间中才行!依然幸运,Windows提供操作其他进程内存空间的API,可以对目标进程空间进行分配释放和内存读写操作:

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
LPVOID VirtualAllocEx( 
HANDLE hProcess, // 申请内存所在的进程句柄
LPVOID lpAddress, // 保留页面的内存地址;一般用NULL自动分配
SIZE_T dwSize, // 欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍
DWORD flAllocationType,
DWORD flProtect );
BOOL WINAPI VirtualFreeEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD dwFreeType );
BOOL WINAPI ReadProcessMemory(
__in HANDLE hProcess,
__in LPCVOID lpBaseAddress,
__out LPVOID lpBuffer,
__in SIZE_T nSize,
__out SIZE_T *lpNumberOfBytesRead
);
BOOL WINAPI WriteProcessMemory(
__in HANDLE hProcess,
__in LPVOID lpBaseAddress,
__in LPCVOID lpBuffer,
__in SIZE_T nSize,
__out SIZE_T *lpNumberOfBytesWritten
);

解决了上面两个问题,就可以来贴代码了!

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
                /*InjectDll.exe*/
#include "windows.h"
#include "tchar.h"

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

//获取当前进程token句柄,并制定要对此句柄进行的操作为TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY
if( !OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken) )
{
//输出错误信息
_tprintf(L"OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}
//获取指定权限的LUID
if( !LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid) ) // receives LUID of privilege
{
_tprintf(L"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;

// 调整token权限
if( !AdjustTokenPrivileges(hToken,
FALSE, //修改权限还是除能所有权限
&tp, //新权限
sizeof(TOKEN_PRIVILEGES), //上一个结构体的大小
(PTOKEN_PRIVILEGES) NULL, //旧权限
(PDWORD) NULL) )//上一个结构体大小
{
_tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}

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

return TRUE;
}

//参数为目标进程ID和要注入的Dll路径
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess = NULL, hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;

// #1. dwPID 以PROCESS_ALL_ACCESS权限打开指定进程,获取进程对象
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}

// #2. 在目标进程里分配dwBufSize大小的可读写空间,立即分配
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

// #3. 向目标进程指定位置写入dll路径
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

// #4.获取本进程的 LoadLibraryA()函数的地址,这个地址在目标进程中也是这么多
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

// #5. 执行 CreateRemoteThread,在目标进程创建线程
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

int _tmain(int argc, TCHAR *argv[])
{
//检查命令参数是否正确
if( argc != 3)
{
_tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
return 1;
}

// 提升软件权限
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
return 1;

// 主要部分,注入dll
if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) )
_tprintf(L"InjectDll(\"%s\") success!!!\n", argv[2]);
else
_tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);

return 0;
}
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
                /*Myhack.dll*/
#include "windows.h"
#include "tchar.h"

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

#define DEF_URL (L"http://www.baidu.com/index.html")
#define DEF_FILE_NAME (L"lala.html")

HMODULE g_hMod = NULL;//Dll句柄
//此回调函数用来下载一个指定文件到当前进程 文件所在目录
DWORD WINAPI ThreadProc(LPVOID lParam)
{
TCHAR szPath[_MAX_PATH] = {0,};
//获取当前进程文件路径
if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
return FALSE;
//获取文件名指针
TCHAR *p = _tcsrchr( szPath, '\\' );
if( !p )
return FALSE;
//将指定的文件名赋给szPath
_tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME);
//下载指定URL文件到指定路径
URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);

return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HANDLE hThread = NULL;

g_hMod = (HMODULE)hinstDLL;//HMODULE和HINSTANCE其实是一样的

switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
//输出信息
OutputDebugString(L"<myhack.dll> Injection!!!");
//创建一个线程,执行指定回调函数
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
}

return TRUE;
}

效果:
没有效果。。。在win10上先是权限不够注入失败

有权限后显示成功并且能被杀软拦截,但是放行后任然没有下载文件到本地,使用debugview也捕获不到调试输出,使用process explorer查看没有进程被载入

然后突然想到自己太懒了,这里只输入了相对路径,在cmd下没毛病,但是注入到指定进程绝壁会出错。。。

哈哈哈,成功把目标进程弄崩溃啦!不过还是成功执行并下载了文件,但是,我似乎说错了一个地方:GetModuleFileName 获取的是dll的路径!

卸载


之前是使用LoadLibrary()加载dll,现在就使用FreeLibrary()卸载它,需要注意的是,前者使dll引用计数加一,后者使之减一,只有引用计数为0才会从进程空间卸载掉,另外FreeLibrary()只能卸载动态加载的dll,A进程要使B进程卸载dll需要A进程有权限这样做,一般即使进程有权限也是出于禁用状态,需要先启用该权限,使用此函数:

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
BOOL setPrivilege(LPCSTR lpszPrivilege,BOOL bEnablePrivilege) {
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tokenPrivilege;
//首先获取当前进程的令牌
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
_tprintf("打开进程令牌出错:%u\n",GetLastError());
return FALSE;
}
//获取luid值
if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) {
_tprintf("查找LUID出错:%u\n", GetLastError());
return FALSE;
}
//设置tokenPrivilege
tokenPrivilege.PrivilegeCount = 1;
tokenPrivilege.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tokenPrivilege.Privileges[0].Attributes = 0;
//调整权限
if (AdjustTokenPrivileges(hToken, FALSE, &tokenPrivilege, sizeof(tokenPrivilege), NULL, NULL)) {
_tprintf("调整权限出错:%u\n", GetLastError());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
_tprintf("未获取到全部权限:%u\n", GetLastError());
return FALSE;
}
return TRUE;
}

有权限后,需要先获取B进程的pid,之前我们是使用任务管理器直接获取的pid,现在写一个用进程名获取pid的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DWORD getProcessId(LPCSTR processName) {
HANDLE hSnapshot;
DWORD processID = 0xffffffff;
PROCESSENTRY32 tmpProcessSnap;
//获取进程列表
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapshot == (HANDLE)-1) {
_tprintf("获取进程列表出错!\n");
return processID;
}
//查找进程ID
Process32First(hSnapshot, &tmpProcessSnap);
do {
if (_tcsicmp(processName, tmpProcessSnap.szExeFile)) {
processID = tmpProcessSnap.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &tmpProcessSnap));
CloseHandle(hSnapshot);
return processID;
}

接下来就是对B进程的DLL操作了,由于已经加载了,可以使用CreateToolhelp32Snapshot函数获取模块的基址,即FreeLibrary的参数:

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
BOOL unload(DWORD processId,LPCSTR dllName) {
HMODULE hModule;
LPVOID dllBaseAddress;
LPTHREAD_START_ROUTINE freeLibrary;
BOOL finded = FALSE;
HANDLE hSnapshot;
MODULEENTRY32 tmpModuleSnapshot;
HANDLE hProcess;
HANDLE hThread;
//获取基址
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId);
Module32First(hSnapshot, &tmpModuleSnapshot);
do {
if (!_tcsicmp(dllName, tmpModuleSnapshot.szModule)||!_tcsicmp(dllName,tmpModuleSnapshot.szExePath)) {
finded = TRUE;
break;
}
} while (Module32Next(hSnapshot, &tmpModuleSnapshot));
if (!finded) {
CloseHandle(hSnapshot);
return FALSE;
}
dllBaseAddress = tmpModuleSnapshot.modBaseAddr;
//获取FreeLibrary地址(为A进程内的地址)
hModule = GetModuleHandle("kernel32.dll");
freeLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
//远程创建线程
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId))) {
_tprintf("打开目标进程出错:%u\n", GetLastError());
return FALSE;
}
hThread = CreateRemoteThread(hProcess, NULL, 0, freeLibrary, dllBaseAddress, 0, NULL);
WaitForSingleObject(hThread, INFINITE); //等待线程执行完成
CloseHandle(hProcess);
CloseHandle(hThread);
CloseHandle(hSnapshot);
return TRUE;
}

一切OK,开始执行试试:



(真心忘得差不多了,在win10下总是失败,最后才想起来CreateRemoteThread在64位下不能注入成功,还有注入时dll必须写全路径!)

代码注入[误]


实在不知道怎么安放它,就添在这里吧,因为代码注入和DLL注入技术原理相似,在DLL注入中,是在目标进程中开辟一个空间用来存放LoadLibrary函数的参数,即要注入的进程的路径,而想要注入的代码存在于DLL中,利用DLL被加载后自动运行DLLMain函数中的代码来实现目的,于是我们需要将想注入的代码存放在dll中,它适合代码量比较大的情况,而要是只有少量代码则可使用代码注入技术,它是在目标进程中开辟一片空间用来存放代码和代码运行所需要的数据,这样就不再需要独立的DLL了,隐蔽性较好。方便理解的是直接在目标空间注入机器语言,它的首地址为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
//CreateRemoteThread函数的第五个参数,即线程函数的参数
typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; //存放两个函数的地址,LoadLirary,GetProcAddress,这两个函数是为了保证MessageBoxA函数已经存在于进程空间
char szBUF[4][128]; //这里存放的另外四个参数,分别是上面前两个函数需要用到的两个和后一个函数需要用到的两个

}THREAD_PARAM,*PTHREAD_PARAM;
//定义上面提到的三个函数
typedef HMODULE (WINAPI *PFLOADLIBRARYA)(LPCSTR lpLibName);
typedef FARPROC(WINAPI *PFGERPROCADDRESS)(HMODULE hModule, LPCSTR lpProcName);
typedef int (WINAPI *PFMESSAGEBOXA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
DWORD ThreadProc(LPVOID lParam) {
PTHREAD_PARAM nParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
//载入user32.dll,GUI可以不要这一步
hMod = ((PFLOADLIBRARYA)nParam->pFunc[0])(nParam->szBUF[0]);
//获取MessageBoxA的地址,GUI可以不要这一步
pFunc = ((PFGERPROCADDRESS)nParam->pFunc[1])(hMod,nParam->szBUF[1]);
//执行弹窗MessageBoxA
((PFMESSAGEBOXA)pFunc)(NULL, nParam->szBUF[2], nParam->szBUF[3], MB_OK);

return 0;
}

接下来是注入部分:

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
BOOL InjectCode(DWORD dwPid) {
THREAD_PARAM param = { 0 };
HMODULE hMod = NULL;
HANDLE hProcess = NULL;
DWORD dwSize = 0;
LPVOID paramAddress = 0;
LPVOID threadProcAddress = 0;
HANDLE hThread = NULL;
//设置参数
hMod = GetModuleHandleA("kernel32.dll");
param.pFunc[0] = GetProcAddress(hMod,"LoadLiraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBUF[0], "user32.dll");
strcpy_s(param.szBUF[1], "MessageBoxA");
strcpy_s(param.szBUF[2], "betamao.me");
strcpy_s(param.szBUF[3], "BetaMao's Notes");
//打开目标进程
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
//先将线程函数需要的参数传进去【数据部分】
dwSize = sizeof(THREAD_PARAM);
paramAddress = VirtualAllocEx(hProcess, 0, dwSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, paramAddress, &param, dwSize, NULL);
//接着将线程函数写入目标进程【代码部分】
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;//这里比较精妙顺序编译,这是编译后的地址,这样可以计算出ThreadProc的长度
threadProcAddress = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//这里要有执行权限
WriteProcessMemory(hProcess, threadProcAddress, ThreadProc, dwSize, NULL);
//创建线程
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)threadProcAddress, paramAddress, 0, 0);
WaitForSingleObject(hThread,INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

接着,再把之前写的提升权限和查找pid的函数加进来就可以实现功能了

来源


逆向工程核心原理