远程线程注入
问题引入
如果有一个程序,我们想在他里面执行我们自己的东西有什么办法呢?

我们可以让程序执行到这个虚线的地方,然后让他跳到这个function(),执行完function后再跳回原来的下一条指令。这种方式执行的就叫做HOOK
方法二:
我们可以直接开一个线程,这个线程就用来跑外面自己的代码。

但是不管是哪种方法,都需要将外面自己的代码贴到进程A中才行
分为模块注入和代码注入
代码注入:
没有全局变量和没有使用IAT表的硬编码,放在任何位置都能执行
- 优点:很难被发现
- 缺点:写起来很麻烦(因为有全局变量地址和IAT函数地址)
模块注入:
将模块一整个注入进去,这个模块可以是dll、exe等等
自己的进程加载DLL
1 2 3 4
| HINSTANCE hModule = LoadLibrary("InjectDll.dll");
FreeLibrary(hModule);
|
远程线程注入
原理:
B 往A注入需要提供线程函数的地址(需要在A中),和线程函数参数(也要在A中)

线程函数是有格式要求的,类型是DWORD,调用约定是WINAPI,参数是void* 的指针:
1 2 3
| DWORD WINAPI Function(LPVOID lParameter){ return ExitCode; }
|
有个偶然的东西就是,在程序中加载模块时用的函数是:LoadLoadLibrary
而这个函数是这样的
1
| HMODULE LoadLibraryA( [in] LPCSTR lpLibFileName );
|
根据以往的学到的知识,我们可以知道这个HMODULE其实就是一个DWORD,然后参数是个LPCSTR,可以转成void* 用。
由于LoadLibrary所属模块是每个程序一定要加载的,所以他这个函数的地址都一样(关闭地址随机化),所以我们创建线程时的线程函数直接将这个LoadLibrary的函数指针扔过去就行了
此外这个函数还需要一个函数参数,即模块名称,这时候就要从进程B,将这个模块名写道进程A中了(WriteProcessMemory)先申请空间
最后通过GetExitCodeThread获取线程的退出码,而LoadLibrary的返回值就是模块首地址,所以我们获取的就是模块句柄
所需函数
平常我们在一个进程中创建线程时用的是CreateThread:
1 2 3 4 5 6 7 8
| HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, PDWORD lpThreadId );
|
而我们要在进程B中让进程A创建一个线程时用的是CreateRemoteThread:
1 2 3 4 5 6 7 8 9
| HANDLE WINAPI CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
|
这两个唯一的不同点就是CreateRemoteThread多了一个hProcess
注入流程
- 打开要被注入的进程
- 远程进程中申请空间VirtualAllocEx
- 向进程中写入数据(dll名)WriteProcessMemory
- 在远程进程中创建线程CreateRemoteThread
- 等待线程结束返回 WaitForSingleObject
- 释放空间VirtualFreeEx
代码实现
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
| #include <windows.h>
void Init() { MessageBox(NULL, TEXT("注入成功,Dll初始化"), TEXT("Init"), MB_OK); }
void Destory() { MessageBox(NULL, TEXT("程序结束,销毁"), TEXT("Destory"), MB_OK); }
BOOL APIENTRY DllMain (HANDLE hModule, DWORD u1_reason_for_call, LPVOID lpReserved) { switch (u1_reason_for_call) { case DLL_PROCESS_ATTACH: Init(); break; case DLL_PROCESS_DETACH: Destory(); break; } return TRUE; }
|
这里的dll模块就不写那么花里胡哨了,只要能体现出来注入成功就行了:在dll初始化时弹出一个对话框,还有被销毁时弹出一个对话框。
进程B代码:
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
| #include <windows.h> #include <TlHelp32.h> #include <assert.h> #include <wchar.h> #include <stdio.h>
DWORD GetPid(LPWSTR name) { HANDLE hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); assert(hProcSnap != INVALID_HANDLE_VALUE); PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32);
BOOL flag = Process32First(hProcSnap, &pe32); while (flag) { if (wcscmp(pe32.szExeFile, name) == 0) { CloseHandle(hProcSnap); return pe32.th32ProcessID; } flag = Process32Next(hProcSnap, &pe32); }
CloseHandle(hProcSnap); MessageBox(NULL, TEXT("没有获取到相关进程"), TEXT("错误"), MB_OK); return 0;
}
int main() { printf("请输入要注入的进程名称(.exe): "); TCHAR Filename[MAX_PATH] = {0}; wscanf_s(L"%ls", Filename, MAX_PATH); printf("请输入模块名称(.exe): "); CHAR DllName[MAX_PATH] = { 0 }; scanf_s("%s", DllName, MAX_PATH);
DWORD Pid = GetPid(Filename); HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
LPVOID Address = VirtualAllocEx(hProcess, NULL, 256, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!Address) { printf("进程内存空间申请失败\n"); return 0; }
DWORD DllNameAddr = WriteProcessMemory(hProcess, Address, DllName, 256, NULL);
HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, Address, NULL, NULL); if (!hRemoteThread) { printf("远程线程创建失败\n"); return 0; }
WaitForSingleObject(hRemoteThread, INFINITE); DWORD ExitCode = 0; GetExitCodeThread(hRemoteThread, &ExitCode);
printf("注入的dll地址是: 0x%X\n", ExitCode);
}
|
我们先打开要注入的进程A(32位),这里我就用之前写的Pe查看器来测试,先把他重命名一下test1.exe。不知道为什么用之前的进程名搜不出pid。打开后直接运行注入程序。

这里的模块(.exe)写多了,不用管。然后查看”pe查看器”

已经注入成功了,dll贴到4GB空间了。当我们关掉”test1.exe”时还会弹出销毁窗口,并且进程B中会输出dll注入到的地址:


问题提出
- 如何启动我们想在A进程中执行的代码?
- 如何卸载我们远程注入的DLL?
1. 卸载我们远程注入的DLL
用FreeLibrary为线程函数,远程卸载就行了:
1 2 3 4 5 6 7 8
| hRemoteThread = ::CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)FreeLibrary, (LPVOID)ExitCode, NULL, NULL); if (!hRemoteThread) { printf("卸载dll远程线程创建失败\n"); return 0; } WaitForSingleObject(hRemoteThread, INFINITE); GetExitCodeThread(hRemoteThread, &ExitCode);
|
2. 如何启动我们想在A进程中执行的代码?
答: 需要用到进程通信