前言
之前分析过donut生成shellcode被杀问题,并通过ollvm混淆其Loader
详见: https://guage.cool/donutbypassav.html
实际上donut的Loader可以加载exe/dll/.NET Assemblies/VBScript/JScript
通常情况我们使用更多的还是exe/dll,为了精简Loader大小,也为了预防donut再次被杀
我打算用zig实现了一个简易版的donut,可以将任意exe/dll转换为shellcode,并添加了一些新特性
新特性
- 静态TLS的支持(比如rust的exe/dll),目前只有IoM/c3/MemoryModulePP等支持部分操作系统
- 没有重定位表时,会尝试根据ImageBase申请内存
- 更彻底的擦除,除了擦除PE头之外,还擦除了导入表
- 通过ollvm生成混淆Loader(暂未公开)
主要流程
- Loader部分
- 根据数据,解密并解压数据
- 通过PELoader加载exe/dll
- 生成器部分
- 读取exe/dll,并判断是否64bit
- 生成并写出shellcode
- 添加对应自定位汇编指令
- 添加exe/dll的压缩加密数据
- 添加对应Loader的shellcode
加密/解密
通过16字节key进行异或加解密
压缩/解压
使用aPlib,这也是donut使用的算法 https://ibsensoftware.com/products_aPLib.html
值得一提的是按照官方说法解密算法只有169字节,很多压缩壳也使用这种算法
其解密算法开源,但压缩算法只提供静态/动态库
好在开源库apultra实现了压缩率更高,并且兼容aPlib解压算法
https://github.com/emmanuel-marty/apultra
静态TLS支持的实现
LdrpHandleTlsData函数主要用于解决sRDI中静态TLS问题
32位的情况
- 先定位字符串LdrpHandleTlsData的地址
- 定位到指令
push LdrpHandleTlsDataAddr
的地址- 即可得到LdrpHandleTlsData中的异常处理函数
- 这个异常函数地址被引用于EH4_SCOPETABLE中
- 定位EH4_SCOPETABLE的地址
- 此地址被直接引用于LdrpHandleTlsData函数中
- 定位指令
push EH4_SCOPETABLEAddr
即可定位到LdrpHandleTlsData函数地址
64位的情况
32位下异常相关结构是保存在栈中,而64位下是通过PE结构中的异常表
- 先定位字符串LdrpHandleTlsData的地址
- 定位到指令
lea rdx, LdrpHandleTlsDataAddr
的地址- 即可得到LdrpHandleTlsData中的异常处理函数
- 这个异常函数地址被引用于C_SCOPE_TABLE中
- 定位C_SCOPE_TABLE结构的地址
- 也就定位到UNWIND_INFO结构的地址
- UNWIND_INFO结构的地址被引用于RUNTIME_FUNCTION结构中
- 定位RUNTIME_FUNCTION结构地址,即可找到LdrpHandleTlsData函数地址
详见:https://github.com/howmp/LdrpHandleTlsData
参考:https://chainreactors.github.io/wiki/blog/2025/01/07/IoM_advanced_TLS/