Inline-Execute-PE 是一套 Beacon 对象文件(BOF)及对应的 CobaltStrike Aggressor 脚本,使操作员能够将非托管的 Windows 可执行文件加载到 Beacon 内存中并执行,捕获其输出并在 Beacon 控制台中显示。
这使操作员能够使用许多第三方工具(Mimikatz、Dsquery、Sysinternals 工具等),而无需将其写入磁盘、使用 Donut 等工具将其重新格式化为位置无关代码,或创建新进程来运行它们。
这些可执行文件被映射到 Beacon 内存中,因此可以重复运行,无需每次通过网络发送、分配新内存并创建新的 conhost.exe 进程。
加载到 Beacon 中的可执行文件可供所有连接到 CobaltStrike 团队服务器的 CobaltStrike 客户端访问和运行。
Inline-Execute-PE 针对 x64 Beacon 以及使用 Mingw 或 Visual Studio 编译的 x64 Windows C 或 C++ 可执行文件设计。该项目不支持 x86 可执行文件或使用其他语言或编译器编译的 x64 可执行文件。

克隆仓库,可选运行 make 以重新编译 BOF。
将 Inline-Execute-PE.cna 加载到 CobaltStrike 客户端中。确保 CobaltStrike 运行目录可由您的用户写入;Inline-Execute-PE 会在该目录下创建一个文本文件(petable.txt)以确保 Inline-Execute-PE 运行所需数据的可用性。
Inline-Execute-PE 包含 3 个面向目标的命令(运行 BOF)和 3 个操作项目数据结构的内部命令:
面向目标:
内部数据结构:
peload 是 Inline-Execute-PE 的起始步骤。此命令用于将 PE 加载到 Beacon 内存中。它执行以下主要操作:
perun 是 Inline-Execute-PE 的第二步。它执行以下主要操作:
当操作员完成 PE 使用或希望加载其他 PE 时,调用 peunload 从 Beacon 内存中移除 PE。它执行以下主要操作:
petable 用于显示当前加载到所有 Beacon 中的 PE 信息。
每个 CobaltStrike 客户端都有自己的 petable;Inline-Execute-PE 尽力确保所有连接的 CobaltStrike 客户端之间数据同步,以便所有操作员都能使用 PE。更多信息请参见“设计考量与评述”。

peconfig 用于配置 Inline-Execute-PE 运行的相关选项。当前可修改的两个选项是:
pebroadcast 可用于手动将客户端的 petable 内容广播到所有其他连接的 CobaltStrike 客户端。
其他 CobaltStrike 客户端将用广播的数据更新其 petable。通常无需使用此功能,但以防万一而保留。
使用 peload 将 PE 加载到 Beacon 内存中

或者,如果目标机器上已有 PE 且不想创建新进程,提供路径和 --local 开关

调用 perun,向加载的 PE 传递参数

参数中的双引号必须使用反斜杠转义

如果发现某个 PE 在卸载时释放 DLL 会导致问题,使用 peconfig 将 unloadlibraries 设置为 false

使用完 PE 后,调用 peunload 从 Beacon 中清理

现在可以将另一个 PE 加载到 Beacon 中

必须小心传递到 PE 的命令行参数;某些 PE 在收到错误参数时会直接崩溃,而另一些则会无限运行,导致 Beacon 即使进程仍在运行也无法回连。
当 Mimikatz.exe 的参数列表末尾未指定 'exit' 时会出现此情况

...

Inline-Execute-PE 会在指定的超时时间后终止正在运行的 PE 线程。这使 Beacon 能够恢复正常的通信(在 perun BOF 完成执行之前,Beacon 不会回连)。虽然此 Beacon 中仍可使用正常的 CobaltStrike 命令和其他 BOF,但 Inline-Execute-PE 已被禁用;以这种方式终止正在运行的 PE 似乎会破坏 Beacon 进程中的 stdout 和 stderr,并且后续加载的 PE 无法正常工作。
PE 仍然可以(并且应该)从 Beacon 内存中卸载,但查看 petable 会显示此 Beacon 可能无法再加载其他 PE。

请务必测试您希望使用 Inline-Execute-PE 运行的 PE,并在向 perun 传递命令行参数时格外小心。某些 PE 比其他 PE 更宽容。
以下是一些在测试和开发过程中关于某些用户可能希望加载到 Beacon 的 PE 的观察结果(无特定顺序)。
与 Inline-Execute-PE 相关的 IOC 包括但不限于:
在开发过程中,我并未对 EDR 进行全面测试,部分由于懒惰,部分由于缺乏测试环境。不过,它已针对最新补丁的 Windows Defender(根据我的经验,这是一款相当好的防病毒产品)进行了测试。
Mimikatz.exe 可能是最适合用 Inline-Execute-PE 运行的已知 PE。我发现 Windows Defender 检测使用 Inline-Execute-PE 运行的 Mimikatz 的能力取决于 Beacon 运行所在的进程。
在独立可执行文件(例如使用 artifact kit 的 beacon.exe,使其能够在 Defender 下正常运行)中运行的 Beacon,在使用 Mimikatz.exe 与 Inline-Execute-PE 时会被检测到。
在 Windows 进程(注入到 Explorer.exe、notepad.exe 等,或通过 DLL 侧加载到合法进程中)中运行的 Beacon,在使用 Mimikatz.exe 与 Inline-Execute-PE 时不会被检测到。
关于执行用户层钩子的 EDR,我尚未测试,但我有以下一般性想法:
由于 PE 运行在 Beacon 进程内部,而您可能已经取消了 NT DLL 的钩子/刷新,因此我认为 PE 调用的 API 被标记的问题应该不大。但 PE 实际执行的操作(访问进程、修改注册表键等)仍然会引发问题。
几个月前,我偶然发现了 RunPE-In-Memory,产生了将其转换为 CobaltStrike BOF 的想法。后续的旅程比预期的要复杂得多,耗时也更长。这个项目特别具有挑战性,因为它本身不是一个独立的工具,而是一个用于运行其他工具的工具。这需要极大的灵活性和努力,以实现与各种 PE 及其完成相同任务(获取参数、终止等)的不同方式的兼容性。
起初,Inline-Execute-PE 被设想为一个一体化的 BOF,负责在 Beacon 中加载、执行和释放 PE。项目进行约 3 周时,我完成了约 75% 的概念验证,发现 PEzor 已在约 1.5 年前发布,并且几乎完成了我想做的所有事情;主要区别在于 PEzor 在底层调用 Donut 将 PE 转换为 shellcode,而不是手动将原始 PE 映射到内存。
这一发现在一方面令人欣喜,另一方面也令人失望;有一个成熟的项目可以借鉴灵感,帮助我解决代码中的一些难题,这非常棒,但令人沮丧的是,我实际上是在不知情的情况下重新发明了轮子。在阅读了 PEzor 的相关资料并思考其设计、一些战术相关事宜以及我所在组织的运营需求后,我调整了 Inline-Execute-PE 的方向,成为今天的样子。这一决定由多个因素驱动,下面将讨论这些因素,以及一些可能让读到这里的读者感到好奇的设计选择。
根据我的运营经验,我遇到了多个需要重复运行某个工具的场景和工具;使用 PEzor 时,操作员必须反复通过网络发送 PE、创建 conhost.exe、在 Beacon 中分配新内存等,这在考虑 AV/EDR 时可能不太理想。这种思考导致了将 PE“加载”到 Beacon 中的想法,类似于将 .PS1 加载到 Beacon 中重复使用。conhost.exe 在首次加载 PE 时创建,并在 PE 加载到内存期间持续存在;同样,新内存只在首次加载时分配一次,当然也避免了每次使用时都需要通过网络发送 PE。Inline-Execute-PE 采用的模式并非没有缺陷,我尝试以不同程度的成功来解决这些问题。
一个显而易见的设计选择是 Inline-Execute-PE 将 PE 在 Beacon 中映射两次。这当然不是理想的选择,也不是我自愿做出的,而是出于必要。如前所述,Inline-Execute-PE 必须挂接与命令行参数相关的几个函数。由于映射的 PE 在 Beacon 进程内部运行,PE 会尝试使用 PEB 中 PROCESS_PARAMETERS 部分指定的命令行参数;为了解决这个问题,当 PE 调用各种获取命令行参数的函数时,我们必须将 PE 引导到我们自己定义的自定义函数中,以便提供通过 perun 从 CobaltStrike 传递的预期参数。