文章总结: 文章介绍了开源的EdenLoader,这是一种利用CrystalPalace构建的用户定义反射加载器。它通过将Draugr调用栈伪装技术转化为位置无关代码并结合页面流式加载,解决了BeaconGate无法覆盖反射加载阶段的局限。文章详细阐述了从BOF到PICO再到PIC的移植与调试过程,展示了如何模块化复用现有能力来快速开发新型加载技术,旨在为红队开发自定义PIC提供思路与参考。 综合评分: 91 文章分类: 红队,免杀,安全工具,安全开发,实战经验
在 Beacon 的技术花园中探索:发现 Eden
William Burgess William Burgess
securitainment
2026年2月15日 13:37 中国香港
| 原文链接 | 作者 | | — | — | | https://www.cobaltstrike.com/blog/playing-in-the-tradecraft-garden-of-beacon | William Burgess |
我们此前曾撰文介绍过使用 BeaconGate 在运行时动态插桩 Beacon。然而,BeaconGate 的局限之一在于它并非贯穿 Beacon 的整个生命周期。具体而言,它不会影响反射加载过程。因此,如果 EDR 正在监控无后备内存的 VirtualAlloc或 LoadLibrary调用,我们无法使用 BeaconGate 来绕过它。要解决这个问题,我们必须深入 UDRL 开发。不过,为了简化这一过程,我们希望从 UDRL 中复用现有的 BeaconGate BOF 示例。
直到最近这还不可能实现,但 Raphael Mudge 于 2025 年 6 月发布的 Crystal Palace 使我们恰好能做到这一点。借助 Crystal Palace,我们可以轻松加载调用栈伪装”能力”(即 BeaconGate BOF)并从 UDRL 中使用它。话虽如此,Crystal Palace 的真正魔力远不止于此——它使我们能够快速组合不同的能力(即自包含的”执行单元”)来创建新颖的加载器/PIC 技术。为了展示这一理念的强大之处,我们已将 Eden Loader开源。
Eden Loader 组合了现有的开源技术,即 Raphael Mudge 的页面流式加载和 Draugr 调用栈伪装,为 Cobalt Strike 创建了一个新颖的 PoC UDRL。Eden Loader 的目标是作为示例资源供他人在此基础上构建,并推动/激发安全对话。在以下章节中,我们将概述将 Draugr BeaconGate BOF 移植到 Crystal Palace,并最终将其与页面流式加载结合以创建新颖 UDRL 的步骤。
本文假设读者熟悉 Crystal Palace、反射加载和 UDRL。如需快速概览,我们推荐阅读 Modular PIC C2 Agents、Harvesting the Tradecraft Garden和 Revisiting the User-Defined Reflective Loader。同样需要注意的是,Eden 的目标是演示使用 Crystal Palace 组合和复用能力的理念,而非作为一个完全”免杀”的加载器。因此,它在设计上缺少许多基本的 OPSEC 特性。例如,它使用 RWX 内存且不会跟踪/掩盖 Beacon 的堆内存。因此,它容易受到诸如此类 YARA 签名的检测。
从 BOF 到 PICO
Crystal Palace 是一个专为位置无关代码设计的链接器,使我们能够在 PIC 中嵌入、链接和运行 COFF。从高层来看,这意味着 Crystal Palace 可以获取一个目标文件并将其嵌入到我们的反射加载器中。加载器随后可以访问嵌入的”能力”,将其加载到内存中并执行其入口点。因此,我们可以利用现有的能力,例如来自 Sleepmask-VS 的 Draugr 调用栈伪装 BOF,轻松地为 UDRL 添加调用栈伪装。
在 Crystal Palace 的术语中,可以从 PIC 运行的 COFF 被称为 PICO,这与传统的 Beacon Object File(BOF)不同。BOF 是一种特殊类型的 COFF,支持 Beacon 特定的约定(即 BOF C API 等),且旨在_由 Beacon 执行_。Crystal Palace 不理解 Beacon 约定(即它无法解析 BeaconOutput),因此必须移除这些约定才能让 Crystal Palace 处理目标文件。
此外,Crystal Palace 构建在 MinGW 之上,因此仅支持使用 MinGW 编译的目标文件。Draugr BOF 最初使用 Sleepmask-VS 编写,后者使用 Clang。因此,Crystal Palace 的 ./link命令在尝试将 Sleepmask-VS 生成的目标文件转换为 PICO 时会抛出错误。
因此,我们需要进行以下更改以使 Draugr BOF 能够被 Crystal Palace 处理:
- 移除所有 BOF/Beacon 特定的约定(即移除所有对 BOF C API 的使用,如
BeaconOutput)。 - 调整 Draugr 代码以使用 MinGW 编译。
- 移除所有静态变量,因为它们在 Crystal Palace 中会生成重定位错误。
至此,我们拥有了一个可以从 PIC 加载器中嵌入和运行的 Draugr 目标文件。以下伪代码展示了如何从使用 Crystal Palace 编写的加载器中访问嵌入的 PICO:
char __DRAUGR__[0] __attribute__((section("draugr")));
char * findAppendedPICO() {
return (char *)&DRAUGR;
}
/* Load appended PICO and run it */
char * src = FindAppendedPico();
/* Allocate memory for our PICO */
dstData = funcs->VirtualAlloc(NULL, PicoDataSize(src), MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE);
dstCode = funcs->VirtualAlloc(NULL, PicoCodeSize(src), MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
/* load our pico into our destination address, thanks! */
PicoLoad((IMPORTFUNCS *)funcs, src, dstCode, dstData);
/* execute our Draugr pico */
((PICOMAIN_FUNC_3)PicoEntryPoint(src, dstCode)) (<draugr args>);
这配合 Crystal Palace 的 spec文件使用,该文件指示 Crystal Palace 加载我们的 Draugr 目标文件,将其转换为 PICO,并链接到上面的代码:
load "bin/draugr.x64.o"
make object # Convert this object file into a PICO
export
link "draugr" # Link it into our loader code above as "draugr"
求调试器若渴
默认情况下,MinGW 不支持 pdb文件,这意味着在 Windows 上调试(例如通过 IDE)可能比较困难。这也是我们的 BOF 开发模板(BOF-VS/Sleepmask-VS)使用 MSVC/Clang 的主要原因之一,因为它们在 Visual Studio 中拥有完整的调试支持。
不过,你可以添加 -g编译器标志来输出带有 DWARF 调试信息的 COFF/PE 文件,如下面的 Makefile片段所示:
$(CC_64) -DWIN_X64 -DDEBUG_EXE=1 -g -shared -masm=intel -Wall -Wno-pointer-arith -fno-jump-tables -c src/draugr/draugr.c -o bin/draugr_dbg.x64.o
$(CC_64) -DWIN_X64 -nostartfiles -g -Wl,-e,dbg_go bin/draugr_dbg.x64.o -o bin/draugr.x64.exe
这使得在 WinDbg中单步调试代码成为可能:
图 1:截图展示 WinDbg调试 draugr.x64.exe。这是 Draugr 目标文件的可执行构建版本(带调试信息),可用于单步执行、设置断点和调试 Draugr 调用栈伪装代码。
在编写复杂加载器时这极其有用,因为它意味着你可以在实时 Beacon 上调试 IAT 钩子,并实时单步调试技术实现。有关 Crystal Palace 调试设置的更全面概述,请参阅 Rastamouse 的这篇博客(上述技巧正是他提供的)。
cv2pdb 也可以与 MinGW 配合使用,将 DWARF调试信息转换为 pdb文件,但这超出了本文的范围。
从 PICO 到 PIC
至此,我们可以从 PIC 加载器中加载 Draugr 目标文件并通过它代理 WinAPI 调用。然而,从 UDRL 中加载和运行 PICO 仍然需要我们进行一次 VirtualAlloc调用(即我们仍然需要为 PICO 分配内存,如上面的伪代码所示)。如果我们的 PICO 包含调用栈伪装例程,那么我们将_始终_存在一个无后备内存的 VirtualAlloc调用。这可能是也可能不是问题(大多数 EDR 可能不会在意单个无后备内存的 VirtualAlloc调用),但让我们假设这是个问题。
为解决这个问题,我们需要更深入一层,修改 Draugr 代码使其能够编译为 PIC。这个 PIC 桩代码随后可以嵌入到我们的加载器中并直接调用,_无需_先加载 PICO(因此也无需分配内存)。
Crystal Palace 提供了 make pic命令来简化此过程。make pic命令将指示 Crystal Palace 加载目标文件,并在高层从给定 COFF 中提取 .text(即代码段)和 .rdata(字符串常量等)节,同时解析重定位将它们合并在一起。如果你的 COFF 存在任何非 PIC 安全的重定位/问题(即你有 DFR 导入如 KERNEL32$Sleep),Crystal Palace 将抛出错误。因此,我们需要进一步修改 Draugr COFF 以使其兼容 Crystal Palace 的 make pic命令。
我们需要做的第一个更改是手动处理所有动态函数解析,这(如上所述)在 PIC 中不受支持。因此,KERNEL32$Sleep等 DFR 语法无法使用,必须手动解析目标 Windows API。为解决此问题,我们向 PIC 桩代码传递了一个函数指针虚表(即 Crystal Palace 的 IMPORTFUNCS结构体),使其能够解析所需的内容。
我们需要做的第二个更改是移除所有全局变量,因为它们会导致重定位错误。这是因为全局变量存在于 make pic不会提取的节中。我们最初在 Sleepmask-VS 中使用全局变量来保存状态(即 Draugr 解析到的 gadget)。这一更改的好处是,我们的 Draugr 调用栈伪装桩代码现在每次都会使用不同的 gadget(代价是略微的性能损失)。
这是 Crystal Palace 在本研究进行期间获得重大更新的领域之一。例如,它现在支持 PIC 中的全局变量和 DFR。我们确实将 Eden 迁移到使用了 Crystal Palace 的较新特性,但结果是失去了调试支持。调试被视为在帮助他人在此基础上构建和理解所涉技术方面的优先事项,因此我们回退到了 Crystal Palace 的原始特性。
完成上述更改后,我们可以如下修改 spec文件:
load "bin/draugr.x64.o"
make pic # This will load the target object file and strip certain sections to create a PIC stub
export
preplen # This prepends the length of the PIC stub directly before it
link "draugr"
经过这些更改,我们现在拥有一个(自包含的)Draugr PIC 桩代码,可以直接嵌入 UDRL 并用于代理所有 API 调用。为此,我们只需为要执行的目标函数传递一个 FUNCTION_CALL结构体(类似于 Sleepmask 的入口点)。如下面的伪代码所示:
functionCall.functionPtr = funcs->VirtualAlloc;
functionCall.numOfArgs = 4;
functionCall.args[0] = (ULONG_PTR)NULL;
functionCall.args[1] = 0x1000;
functionCall.args[2] = MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN;
functionCall.args[3] = PAGE_READWRITE;
/*
* The IMPORTFUNCS struct contains a vtable of ptrs
* so that our PIC stub can resolve what it needs.
*/
funcs->proxy((IMPORTFUNCS*)funcs, &functionCall);
char* pBuffer = (char *)functionCall.retValue;
我们可以使用此方法将反射加载过程中使用的所有 VirtualAlloc和 LoadLibrary调用通过 Draugr PIC 桩代码进行代理。
然而,我们可以更进一步,将 Draugr PIC 桩代码与 IAT 钩子结合。通过这种方式,我们还可以将 Beacon 所有”可疑”的 Windows API 调用通过 Draugr 调用门代理。这近似于(实现略有不同)Rastamouse 的 CrystalKit,后者将 IAT 钩子与 Draugr BeaconGate BOF 结合使用。
我们刻意尝试将调用栈伪装例程与加载器解耦,使其尽可能模块化(即可以用另一个具有相同入口点的目标文件替换它,而无需更改加载器中的任何内容)。
合体变形
虽然这本身已经令人印象深刻,但 Crystal Palace 的真正威力在于组合和复用现有能力以快速开发新颖的加载器和 PIC 技术。例如,我们现在可以轻松地将上述 Draugr 调用栈伪装示例与其他技术结合,例如 Raphael Mudge 的页面流式加载器。
页面流式加载使用保护页和向量异常处理器(VEH)在需要时将 Beacon 页面流式传输到内存中。页面流式加载支持自定义最大可见页数,并确保未使用的页面被移除,最大限度减小 Beacon 的内存驻留足迹。组合这些不同能力的最终成果就是 Eden。通过结合这两种技术,我们可以快速生成一个对 Beacon 所有可疑 WinAPI 调用执行调用栈伪装,并配合自定义运行时混淆技术的 UDRL。
此外,Eden 是模块化的,我们可以将 Draugr 目标文件替换为另一种栈伪装技术。这一理念可以进一步扩展,创建真正模块化的 PIC 加载器,通过 Crystal Palace spec 文件进行配置以选择和应用不同的能力(即一个”静态”PIC 加载器,通过 COFF “模块”完全可定制,如不同的执行条件检查/栈伪装/睡眠混淆技术等)。
本文主要讨论了在加载器上下文中组合和复用能力的强大之处。有关更广泛的 PIC 技术讨论,请参阅以下演讲:‘Linkers and Loaders: Experiments with Crystal Palace’。
结语
本文的目的是分享我们通过复用现有能力使用 Crystal Palace 编写 UDRL 的实验和经验。在此过程中,我们希望展示 Crystal Palace 在使用户能够快速组合能力(即 COFF/DLL)以快速开发自定义加载器/PIC 技术方面的强大之处。通过展示我们创建新颖 UDRL 的步骤,我们希望能够揭开底层 PIC 开发的神秘面纱,为安全从业者提供一条创建自己新颖 PIC 技术并为安全对话做出贡献的路径。
免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:securitainment William Burgess William Burgess《在 Beacon 的技术花园中探索:发现 Eden》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论