是否可以用 C++ 编写 shellcode?

信息安全 渗透测试 外壳代码 部件 C++
2021-08-11 13:54:07

通常我编写汇编程序,然后转储它们的内容以获取 shellcode。我想知道的是我是否可以编写一个 C++ 程序而不是程序集,然后转储并使用而不是程序集?

3个回答

Linux 内核可以被视为一种终极的 shell 代码,因为它被“注入”在原始机器上(此时只有 BIOS 代码),然后提供了很多功能该内核是用 C 编写的。

如果您使用 C 或 C++ 编写 shell 代码,您将遇到库调用链接问题,这是同一问题的两个方面。

C 编译器生成的代码可能包含对代码其他部分或外部函数或变量的引用。例如,如果您访问一个static变量,即使是在您自己的代码中定义的,那么程序集操作码将包含一个“漏洞”:在程序集级别(gcc -S如果您使用 gcc 作为 C 编译器,您会使用 .查看基本的“mov”操作码和变量名。当翻译成二进制时,在目标文件中(.o.obj,取决于平台),这个洞还没有被填满,因为变量的实际地址还不知道;目标文件包含一个表格(“重定位”),它通知链接器在链接阶段要填充的每个孔的位置。

当然,问题在于您在编写 shell 代码时没有链接器的优势。您必须生成一个已经准备好的字节序列,没有要填充的孔,并且能够在将要加载的任何地址运行,该地址不一定事先知道(部分感谢ASLR)。您必须编写与位置无关的代码编译器通常为此提供一个命令行标志(例如-fPICwith gcc),但这不是“真正的”PIC:那种代码仍然有漏洞需要链接器来填补,但这次是动态链接器;它只是 PIC,因为孔已经在一个特殊的表(GOT,又名“全局偏移表”)中重新组合,使动态链接器的任务更容易。

不幸的是,C 编译器和 C++ 编译器更是如此,会在未经您同意的情况下生成带有漏洞的代码。例如,典型的 C 编译器memcpy()在处理struct值时会生成隐藏调用(标准库函数)的代码。C++ 使情况变得更糟,因为 C++ 功能的所有用具都由隐藏函数和静态变量(例如对象 vtables)支持。

Linux 内核通过使用MMU解决了这些问题。引导加载代码(它是一段手写的程序集)控制 MMU 以确保内核代码所在的 RAM 页面将在地址空间中的固定已知地址上看到:内核,当编译,与该明确假设相关联;只有当内核确实位于地址空间中的那个确切位置时,这些孔才会填充正确的地址。使用 MMU 是一种内核权限,你不能从用户空间代码中做到这一点,更不用说从 shell 代码中了。

您可以设想让您的 shell 代码成为一个程序集,它实际上是一个动态链接器,能够加载由 C 或 C++ 编译器生成的一段二进制代码,动态修补它,即用实际的“漏洞”填充加载地址。这就是metasploit提供的漏洞利用中发生的情况:漏洞利用本身安装了那段漂亮的代码,然后可以安装“有效负载”,这是用高级语言编写的通用 DLL,直到并包括完整的VNC 服务器

但是,无论您怎么说,在某些时候您都必须在手动汇编中执行一些 PIC 代码。编写自己的加载器/链接器是一个很好的教学练习,它可用于将初始漏洞利用到任意控制级别,但其核心仍然需要手工组装。

用任何可以编译为机器代码指令的语言编写 shellcode 都是完全有效的。前提是其操作不需要受害程序未链接的外部库。

然而,直接编译的代码(即使仅来自 C)几乎从来都不是有效的、可注入的 shellcode。最常见的原因是需要NULL在字符串缓冲区注入中删除字节。

编译后的代码也会变得臃肿,执行各种“簿记”类型的任务,例如函数序言、堆栈框架设置和下拉等。所有这些都使它成为 shellcode 的糟糕选择,因为 shellcode 通常必须非常小。

因此,您需要编写程序、编译它,然后在机器代码级别对其进行调整这实际上是许多人采用的一种很常见的方法,他们可能会发现从头开始编写汇编有点令人生畏。

然而,根据我的经验,你最终会弄乱最终的 shellcode,以至于在你完成之前你会非常熟悉它包含的每个字节,而且从一开始就直接使用汇编可能会更快。

当然,如果您正在尝试执行一项大型、复杂的任务,那么用更高级别的语言编写它可能会很有吸引力。但实际上,更好的方法通常是创建一个简单的stager shellcode,由漏洞利用注入。然后 stager 下载真正的有效载荷并执行它。这使得真正的有效载荷成为一个复杂的、独立的可执行文件,可以随意链接自己的库。

当然要记住,这个名字是从哪里来的;shellcode 通常用于执行 stager 的任务,原型 stager 是execve("/bin/sh", null, null).


正如 TildalWave 指出的那样,即使从 C++ 编译,也可以对输出机器代码进行细粒度控制。例如,最直接的方法是在 C++ 源代码中包含内联汇编语句;

__asm__ ("movl %eax, %ebx\n\t"
         "movb %ah, (%ecx)");

您还可以更改编译器指令,并且在编译要编辑的代码时,您应该始终禁用所有编译器优化没有什么比编译器为减少指令数量所做的奇怪事情更令人困惑的了。

是的,可以用 c/c++ 编写 shellcode,但它非常依赖于编译器。你不能使用任何使用绝对地址的特性,一切都必须是相对的,因为你不知道 shellcode 将被加载到哪个绝对地址。这意味着你不能使用字符串、库、全局变量、类继承等。

在这里写了这个例子