红队工具:编写自定义Shellcode
备注
原文名称:Red Team Tooling: Writing Custom Shellcode
原文地址:https://www.praetorian.com/blog/red-team-tooling-writing-custom-shellcode/
原文信息:by Adam Crosser on April 8, 2021
概要
本文讨论了我们最近开源的工具Matryoshka[1],操作人员可以利用该工具绕过大小限制,解决通常与Visual Basic for Applications (VBA)宏payload相关的性能问题。由于Microsoft Office限制了VBA宏的大小,操作员可能会遇到大小限制,从而限制了他们在文档中包含更大的有效载荷的能力。Matryoshka允许操作者为egghunter生成shellcode,以解码并运行嵌入在Microsoft Office文档其他地方的第二阶段有效载荷。
Matryoshka Loader 设计
matyoshka由三个主要组件组成—— 序言、嵌入式配置文件和核心加载程序。这些组成部分在下文中详细说明:
- 序言:序言用汇编器编写,并用作引导例程,以在核心加载器的入口点使用指向其配置文件的指针来调用核心加载器。首先,通过读取EIP或RIP的值来获取其在内存中的当前地址,然后利用构建器在构建时硬编码的偏移量来检索嵌入式配置文件和核心加载器入口点的地址。
- 嵌入式配置文件:配置文件包含Egghunter在文档流中查找嵌入式Egg所需的信息。它还包括其他相关细节,比如解密egg的密钥。
- 核心加载程序:核心加载程序是用C编程语言编写的,在这种情况下负责处理核心Egghunter逻辑。由于它需要外部Win32 API例程,因此它必须包括在运行时动态解析这些例程的地址的支持。核心加载器入口点将嵌入式配置文件作为输入,它利用该配置文件在内存中搜索文档流中嵌入的鸡蛋。一旦它确定了鸡蛋在内存中的位置,它将解码嵌入的鸡蛋值,分配具有读写执行(RWX)权限的可执行内存,将解密的鸡蛋复制到新分配的区域,然后执行它。
然后,操作者可以利用构建器组件来构建一个功能齐全的shellcode有效载荷。builder把要执行的第二阶段的有效载荷作为输入,生成一个配置文件,然后把配置文件与前言和核心加载器结合起来,生成一个全功能的egghunter shellcode。
Matryoshka Builder 用法
操作者必须在 "builder/matryoshka.py "中执行 builder 脚本,同时传递"-e"、"-o "和"-s "参数。在这种情况下,"-o "参数代表构建者应该将生成的shellcode写到哪里。"-e "参数指定将生成的 "egg "值写入的文件,"-s "参数指定第二阶段的有效载荷,egghunter应该执行。生成后的预期输出如下图所示。
在文档中嵌入Egg
随着 egghunter shellcode 的建立,下一个问题就变成了确定一种方法,通过这种方法,我们可以将 "egg "值嵌入到有效载荷中,让 egghunter 在执行时定位。这个问题的一个潜在的解决方案是将 egg 值附加到文件的末尾。虽然这种技术在用户第一次打开文档时有效,但当用户关闭文档时,无论用户是否保存文件,Microsoft Word和Excel都会删除附加的数据。
解决这个问题的一个办法是将egg文件作为OLE对象嵌入到文档中。然而,在这种情况下,Microsoft Office将默认压缩嵌入文档中的OLE对象。幸运的是,有一个相当直接的解决方案来解决这个问题。用户可以通过选择 "文件->选项->高级",并在 "图像大小和质量 "标题下选择 "不要压缩文件中的图像 "选项,来指示Microsoft Office不要压缩文件中的图像。
接下来,我们需要在egg值前添加一个PNG头,并将其插入到文档中。由于带有PNG标题,Microsoft Word不会压缩egg,从而允许egghunter找到它。
在 "Insert Object "对话框中,如下图所示,选择 "Package "作为 "Object Type",然后选择 "OK "按钮。
插入的egg将出现在文档中,如下图所示。操作者可以采取额外的步骤来隐藏这个嵌入对象。
虽然可以通过编程方式嵌入egg,但我们认为这不在本文的讨论范围之内。
在VBA中开发一个启动程序
然后我们可以利用Trigen工具[2]来生成VBA代码,调用egghunter有效载荷。因为Trigen工具的输入是一个十六进制字符串,所以我们必须首先使用下面给出的 "xxd "命令将shellcode转换为这种格式。
xxd -p -c 999999999 shellcode.bin
然后,我们可以用生成的十六进制字符串调用Trigen工具,并收到一个生成的宏payload,如下图所示:
python2 trigen/trigen.py $HEXSTR
下图显示了Trigen的预期输出
然后,我们可以将生成的VBA代码放置在我们将蛋值作为OLE对象插入的同一个文档中。执行VBA代码后,egghunter会在进程内存中搜索识别蛋值,提取第二阶段,将其复制到RWX内存缓冲区,然后执行。 不幸的是,最新的64位Microsoft Office版本支持控制流保护(CFG),它将阻止使用传递给Windows API函数的用户定义回调间接调用我们的shellcode(例如,当Trigen生成VBA代码时,shellcode使用EnumCalendarInfoA函数pCalInfoEnumProc参数执行)。相反,我们观察到,当CFG检查失败时,应用程序会引发 "STATUS_STACK_BUFFER_OVERRUN "异常。幸运的是,在32位的Microsoft Office版本上,CFG没有被启用。
在这种情况下,我们绕过CFG的策略是用一个蹦床覆盖一个有效的跳转位置,将执行转移到我们的shellcode有效载荷上。因为被覆盖的位置是CFG中允许的目标位置,所以允许执行。我们不提供这个功能的源代码。相反,我们将这种绕过的开发作为练习留给读者。
用C编程语言编写核心加载器
我们选择用C语言开发核心加载器shellcode,因为它相对于汇编语言来说更容易开发。利用C编程语言,我们可以重用和开发可以针对多种架构的代码,同时还可以对生成的代码的格式和结构进行高度控制。这一属性与Golang等其他更高级别的语言形成了鲜明的对比,因为在这些语言中,语言语义并没有那么干净利落地转化为汇编语言结构。
此外,程序语义与底层机器代码解耦的能力,使得开发者可以通过编译器的定制来动态改变底层机器代码的属性,而无需修改更高级别的应用源代码。不幸的是,这在汇编器层面一般是不可能的,因为这种更高层次的抽象不存在。例如,在C语言中,开发者可以为了可读性的目的,开发和调试不经过优化而生成的代码,然后无缝启用代码优化和其他标志,以减少程序的大小,阻碍逆向工程工作。
在汇编器中开发shellcode是可取的主要场景是在适用于大小或字符("坏字节")限制的极端情况下。在这些情况下,对生成的代码的额外控制压倒了利用高级语言的好处。在这种情况下,这种担心是不适用的,因为没有我们需要避免的 "坏字节"。此外,虽然shellcode的大小是一个重要的因素,但与编译后的shellcode相关的稍大的大小并不是我们使用案例的限制因素。
用C语言编写Shellcode的样式指南
在用C语言编程语言编写shellcode时,操作者一定要注意避免某些语言结构或模式,导致编译器生成的代码与位置无关。首先要避免以通常的方式使用char指针赋值静态字符串(例如,char *string = "Hello World")。在这种情况下,字符串 "Hello World "存储在二进制数据部分内,生成的代码无法保证位置独立。为了解决这个问题,我们将Matryoshka利用的所有字符串定义为数组,如下图所示:
生成的,未经优化的,与Kernel32WStr变量相关的代码如下图所示。在这种情况下,我们观察到字符串是直接写在程序栈上的,字符串的字节存储在指令操作码中。
其次,我们必须避免利用外部函数或API,而不首先在运行时动态地解析它们的地址。通常情况下,这不会是一个问题,因为当用户执行程序时,Windows加载器会将这些外部例程的地址写入导入地址表(IAT)。为了实现这一点,我们利用标准技术,即利用fs或gs段寄存器从进程环境块(PEB)中获取加载模块列表的地址。然后我们对加载的模块列表进行解析,以找到合适的DLL文件。在找到相应的DLL文件的地址后(如kernel32.dll),我们就可以解析导出地址表来确定与我们试图解析的函数相关的地址。 第三,在编译时,所有的源代码都应该包含在一个'.c'文件中。这样可以确保所有的函数调用都将以相对调用与绝对调用的形式生成,而绝对调用与位置无关,当我们调用另一个'.c'文件中定义的函数时,可能会出现这种情况。为了避免对源代码的可读性产生不利影响,我们在'.h'文件中定义外部代码,并通过预处理器 "#include "它们。通常,这将被认为是不好的做法,然而,在这种情况下,通过将应用逻辑分散在多个文件中,在保持位置独立性的同时,保持可读性、可维护性和组织性是必要的。
最后,我们应该避免利用全局变量,原因与我们将字符串定义为写到堆栈上的数组一样,因为生成的代码通常不是位置独立的,需要重新定位功能。通常情况下,这会导致一个定义为栈变量的全局状态单子在shellcode调用的所有函数之间传递。根据广为接受的软件设计原则,这很可能会被认为是一种架构上的 "反模式";然而,在shellcode开发的情况下,这是一个不可避免的弊病。
调整Visual Studio编译设置
为了确保编译器生成有效的与位置无关的代码,操作员还必须修改某些编译器标志,以利用堆栈cookie和控制流保护等漏洞缓解技术。项目也有必要处于“发布”模式。下图显示了我们更改的编译器标志,以确保生成的代码与位置无关。
在本例中,我们还更改了“Linker->Advanced”选项卡下的“Entry Point”标志,并将我们的入口点函数指定为“MatryoshkaEntrypoint”,以确保编译后的DLL将该函数设置为其入口点。在解析编译后的DLL时,构建器随后将在构建时利用此入口点值来确定前同步码应该跳到加载器外壳代码中的偏移量
调整编译器和链接器设置后,我们可以使用CFF Explorer检查编译后的PE文件,以确定编译后的二进制文件是否需要重新定位。在下图中,我们可以注意到编译后的PE没有重定位表。缺少重定位表通常是生成的代码与位置无关的好迹象。
下图显示了当生成的代码不是位置独立且需要重新定位时的预期结果。
未来工作
在未来,我们希望扩展Matryoshka,以包括对当前egghunter技术之外的其他暂存机制的支持。我们还希望增加其他围绕反调试能力的功能,以检测沙箱分析或环境内的执行情况。例如,我们可以通过HTTP使用域前沿增加对有效载荷暂存的支持,而现有的Cobalt Strike暂存shellcode目前还不支持。
攻击性安全工具(OST)发布政策
在Praetorian,我们的目标是解决网络安全问题。在发布新的攻击性安全工具(OST)时,我们总是会权衡发布特定工具或技术的潜在利益与相关成本和滥用风险。在这种情况下,我们认为收益大于成本,因此继续发布工具。
通过发布这个工具,对手模拟团队可以更有效地模拟我们在野外观察到的真实世界威胁行为者所利用的攻击技术。虽然恶意威胁行为者也可以利用这个工具,但这需要他们进行额外的开发工作才能操作。
结束语
本文讨论了几种方法,攻击者可以利用直接执行shellcode来解决通常与VBA或Excel 4.0宏有效载荷相关的性能和大小限制。此外,我们还发布了一个开源工具,操作者可以利用这个工具来生成egghunter shell代码,以解决这些现有的限制。
我们还讨论了操作者可以使用C编程语言开发位置独立的shellcode的方法,通过对所使用的编程风格进行微小的调整,并修改编译器和链接器标志,以确保编译器生成位置独立的代码。
参考文献
[1] https://github.com/praetorian-inc/Matryoshka [2] https://github.com/karttoon/trigen