前置回顾:远程线程原理
CreateRemoteThread 不是在本地开线程,线程实体运行在目标进程
● 发起进程:注入器,负责打开目标进程、分配内存、写入机器码、创建远程线程
● 目标进程:真正执行线程函数代码
● 核心:lpStartAddress 填写目标进程虚拟地址
代码注入整体思路
- 在注入器内写好功能函数
- 提取该函数机器码,写入目标进程预先分配的内存页
- 调用CreateRemoteThread,让目标进程 CPU 直接执行这片机器码
区别远程线程 DLL 注入:DLL 注入是调用LoadLibrary加载模块;代码注入直接拷贝二进制指令,不存在 DLL 模块,PEB 链表看不到,隐蔽性更强
机器码、汇编、源码的关系
- 机器码:CPU 可直接识别的二进制 / 十六进制指令(55 8B EC这类),内存里只保存机器码
- 汇编:机器码的可读文本翻译,不是内存中存在的数据
- C/C++ 源码:编译后就消失,运行阶段不存在
⚠️ 核心坑:绝对地址硬编码 如果机器码里写死固定地址(全局变量、字符串常量、导入函数),复制到目标进程直接崩溃
示例:mov dword ptr 0x00427c50,1
0x00427c50是注入器进程内地址,目标进程同地址是无效内存
可拷贝函数(Shellcode)编写 5 条硬性规则
- ❌ 禁止全局变量:全局变量地址编译固定,跨进程失效
- ❌ 禁止常量字符串:字符串放在只读常量段,地址硬编码
- ❌ 不能直接调用 API(不能依赖导入表):导入表函数地址是本进程的
- ❌ 不能嵌套调用自定义函数:call 指令会硬编码函数地址
- ✅ 所有需要的 API 地址由注入器作为参数传入
原理:同版本 Windows,系统 dll 内函数基地址全进程共享,注入器本地GetProcAddress拿到的 API 地址,可以直接传给目标进程使用
参数传递方案
线程函数必须符合 DWORD WINAPI ThreadProc(LPVOID lpParam) 标准格式
● 单独在目标进程分配一块内存,存放自定义结构体
● 结构体预先存放:API 函数地址、各类参数、字符串指针
● 字符串也要单独在目标进程分配内存,不能使用注入器进程内字符串指针
示例结构体:
c
typedef struct {
HANDLE (*pCreateFile)(LPCSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
char* szFileName;
}PARAM;
三段内存分配(案例:远程创建文件)
都在目标进程空间分配:
- 文件名缓冲区:存放文件名字符串,带\0结束符
- Shellcode 内存:存放拷贝的机器码(线程执行函数)
- 参数结构体内存:存放函数指针、文件名指针等
重点:结构体里字符串指针,必须指向目标进程内的文件名缓冲区,不能是本地注入器地址
Debug 版本函数陷阱(E9 跳转)
Debug 模式下,C 函数开头经常是 E9 xx xx xx xx(JMP 跳转指令)
● E9:近跳转,占 5 字节(1 字节 op + 4 字节偏移)
● 计算公式:目标地址 = 当前指令地址 + 5 + 偏移
● 问题:直接复制函数首地址只会复制跳转指令,不是真正业务代码 ✅ 解决:反汇编解析跳转,拿到跳转后的真实入口作为机器码拷贝起点
代码注入优缺点
✅ 优点
- 没有 DLL 模块,PEB 链表无记录,普通模块枚举检测不到
- 继承目标进程权限,可使用目标进程权限执行操作
❌ 缺点 - 开发复杂,必须手动处理地址、重定位、参数传递
- 调试极难,崩溃直接触发目标进程异常
- 内存扫描(PE 特征、内存页扫描)依然可以检测
整体流程总步骤
- OpenProcess 获取目标进程句柄
- VirtualAllocEx 在目标进程分配 3 块内存(shellcode、参数、字符串)
- WriteProcessMemory 写入字符串、参数结构体、功能函数机器码
- CreateRemoteThread,传入 shellcode 地址 + 参数结构体地址
- WaitForSingleObject 等待线程执行完毕
- CloseHandle / VirtualFreeEx 释放资源
c
// 自定义参数结构体,注入器填充API地址
typedef struct
{
HANDLE (__stdcall *pCreateFileA)(LPCSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
char* lpFileName;
}SHELL_PARAM;
// Shellcode函数(必须满足:无全局变量、无常量字符串,仅读取传入参数)
DWORD __stdcall RemoteFunc(SHELL_PARAM* pParam)
{
HANDLE hFile = pParam->pCreateFileA(pParam->lpFileName,GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile) CloseHandle(hFile);
return 0;
}
速记
- 代码注入拷贝机器码,不是源码 / 汇编;核心难点:硬编码绝对地址
- Shellcode 禁止全局变量、常量字符串、直接 call 其他函数
- API 地址:注入器本地 GetProcAddress 获取,通过结构体传给远程代码
- Debug 函数开头可能存在 E9 JMP,要解析跳转拿到真实入口
- 所有指针(字符串、参数)都必须指向目标进程虚拟地址