前言

之前分析过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,并添加了一些新特性

新特性

  1. 静态TLS的支持(比如rust的exe/dll),目前只有IoM/c3/MemoryModulePP等支持部分操作系统
  2. 没有重定位表时,会尝试根据ImageBase申请内存
  3. 更彻底的擦除,除了擦除PE头之外,还擦除了导入表
  4. 通过ollvm生成混淆Loader(暂未公开)

主要流程

  1. Loader部分
    1. 根据数据,解密并解压数据
    2. 通过PELoader加载exe/dll
  2. 生成器部分
    1. 读取exe/dll,并判断是否64bit
    2. 生成并写出shellcode
      1. 添加对应自定位汇编指令
      2. 添加exe/dll的压缩加密数据
      3. 添加对应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位的情况

  1. 先定位字符串LdrpHandleTlsData的地址
  2. 定位到指令push LdrpHandleTlsDataAddr的地址
    1. 即可得到LdrpHandleTlsData中的异常处理函数
    2. 这个异常函数地址被引用于EH4_SCOPETABLE中
  3. 定位EH4_SCOPETABLE的地址
    1. 此地址被直接引用于LdrpHandleTlsData函数中
  4. 定位指令push EH4_SCOPETABLEAddr即可定位到LdrpHandleTlsData函数地址

64位的情况

32位下异常相关结构是保存在栈中,而64位下是通过PE结构中的异常表

  1. 先定位字符串LdrpHandleTlsData的地址
  2. 定位到指令lea rdx, LdrpHandleTlsDataAddr的地址
    1. 即可得到LdrpHandleTlsData中的异常处理函数
    2. 这个异常函数地址被引用于C_SCOPE_TABLE中
  3. 定位C_SCOPE_TABLE结构的地址
    1. 也就定位到UNWIND_INFO结构的地址
    2. UNWIND_INFO结构的地址被引用于RUNTIME_FUNCTION结构中
  4. 定位RUNTIME_FUNCTION结构地址,即可找到LdrpHandleTlsData函数地址

详见:https://github.com/howmp/LdrpHandleTlsData
参考:https://chainreactors.github.io/wiki/blog/2025/01/07/IoM_advanced_TLS/

用zig写shellcode

详见: https://guage.cool/zig-2-windows-shellcode.html