Shellcode:The Payload
SHELLCODE
为了直接在堆栈或内存的其他部分执行我们处理二进制的原始漏洞利用代码,我们需要表示目标机器的一组原始机器指令的汇编代码。
shellcode 是一种汇编语言程序,它执行一个 shell,例如 Unix/Linuxshell 的“/bin/sh”,或 DOS 和 MicrosoftWindows 上的 command.com shell。 在 learnshell.org 上以交互方式尝试 shell 命令。 请记住,在exploit中,不仅仅是一个普通的shell,我们想要的是root shell或管理员权限(注意:在某些情况下,在Windows中存在具有高于管理员权限的帐户,例如LocalSystem)。 Shellcode 用于生成(root)shell,因为它会给我们最高权限。 Shellcode 可用作漏洞利用有效载荷,为黑客或攻击者提供对计算机系统的命令行访问。 Shellcode 通常通过利用基于堆栈或堆的缓冲区溢出漏洞或格式字符串攻击来注入计算机内存。 在经典和正常的漏洞利用中,可以通过用注入的 shellcode 的地址覆盖堆栈返回地址来触发 shellcode 执行。 结果,子程序返回给调用者,而是返回到 shellcode,生成一个 shell。 shellcode 的例子可能有以下几种形式:
作为汇编语言 - shellcode.s(shellcode.asm - 适用于 Windows):
1 | #a very simple assembly (AT&T/Linux) program for spawning a shell |
As a C program - shellcode.c:
1 |
|
请注意,可以使用 asm 关键字将汇编代码嵌入到 C 代码中,反之则使用 asm(GCC、Microsoft)。 作为 C 程序中的空终止 C 字符串字符数组:
1 | char shellcode[ ] = "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"; |
声明为char类型的C字符串的shellcode可能是漏洞利用代码中使用最广泛的,典型格式如下所示
1 | char shcode[ ] = "\x90\x31\x89..."; |
在更广泛的定义中,shell 代码不仅可以用于生成 shell,还可以用于创建通用负载。 通常,漏洞利用通常由两个主要部分组成:
- The exploitation technique.
- The payload.
漏洞利用部分的目的是转移易受攻击程序的执行路径。 我们可以通过以下技术之一来实现:
- Stack-based Buffer Overflow.
- Heap-based Buffer Overflow.
- Integer Overflow.
- Format String.
- Race condition.
- Memory corruption, etc.
一旦我们控制了执行路径,我们可能希望它执行我们的代码。 在这种情况下,我们需要在我们的漏洞利用中包含这些代码或指令集。 然后,允许我们执行任意代码的代码部分称为有效载荷(payload)。 有效载荷实际上可以执行计算机程序在易受攻击的程序或服务的适当许可和权利下可以执行的所有操作。
Shellcode as a payload
当 shell 生成时,它可能是允许攻击者以交互方式探索目标系统的最简单方法。 例如,它可能使攻击者能够发现内部网络,进一步渗透到其他计算机中。 shell 还可以允许上传/下载文件/数据库,这通常需要作为成功渗透测试 (pen-test) 的证明。 您还可以轻松安装特洛伊木马、键盘记录器、嗅探器、企业蠕虫、WinVNC 等。shell也可用于重新启动易受攻击的服务以保持服务运行。 但更重要的是,重启易受攻击的服务通常会让我们再次攻击该服务。 我们还可以使用 shell 清理日志文件和事件等跟踪信息。 对于 Windows,我们可能会更改注册表以使其在每个系统启动和停止任何防病毒程序时运行。
您还可以创建一个循环并等待攻击者命令的有效负载。 攻击者可以向负载发出命令以创建新连接、上传/下载文件或生成另一个 shell。 还有一些其他有效载荷策略,其中有效载荷将循环并等待来自攻击者的额外有效载荷,例如在多阶段攻击和(分布式)拒绝服务 (DDOS/DOS) 中。 无论有效载荷是生成 shell 还是循环以等待指令; 它仍然需要与攻击者进行本地或远程通信。 有很多事情可以做。
Shellcode elements
本节将限制对用于利用二进制机器可读程序中基于堆栈的缓冲区溢出的有效负载的讨论。 在这个程序中,shellcode 也必须是机器可读的。 **shellcode 不能包含任何空字节 (0x00)**。 Null (‘\0’) 是一个字符串分隔符,它指示所有 C 字符串函数(和其他类似的实现),一旦找到,将停止处理字符串(以空字符结尾的字符串)。 根据所使用的平台,不仅是 NULL 字节,还有其他定界符,例如换行 (LF-0x0A)、回车 (CR-0x0D)、反斜杠 (\) 和 NOP(无操作)指令,在执行时也必须考虑 创建一个可行的shellcode。 在最好的情况下,shellcode 可能只包含字母数字字符。 幸运的是,有几个称为 Encoder 的程序可用于消除 NULL 和其他分隔符。
为了能够生成真正有效的机器代码,您必须以不同的方式编写汇编代码,但仍使其发挥作用。 您需要在这里和那里做一些技巧以产生与最佳机器代码相同的结果。
由于 shellcode 应该尽可能小很重要,shellcode 编写者通常用汇编语言编写代码,然后以十六进制格式提取操作码,最后在程序中将代码用作字符串变量。 可靠的标准库不适用于 shellcode; 我们通常要直接使用操作系统的内核系统调用(system call)。 Shellcode 也依赖于操作系统和架构。 可行的shellcode还必须考虑绕过防火墙和入侵检测系统(IDS)等网络系统保护。
Creating a shellcode: Making the code portable
编写 shellcode 与编写普通的汇编代码略有不同,主要是可移植性问题。 由于我们不知道我们在哪个地址,因此无法访问我们的数据,更不可能直接在我们的程序中硬编码内存地址。 我们必须应用一个技巧来制作shellcode,而不必以传统方式引用内存中的参数,通过在内存页面上给出它们的确切地址,这只能在编译时完成。 尽管这是一个显着的缺点,但始终有解决此问题的方法。 最简单的方法是在 shellcode 中使用字符串或数据,如下面的简单示例所示
1 | .section .data |
这段代码中发生的事情是我们跳转到标签 dummy 然后从那里调用 _start 标签。 一旦我们到达 _start 标签,我们就可以弹出一个寄存器,这将使该寄存器包含我们字符串的位置。 使用 CALL 是因为它会自动将返回地址存储在堆栈上。 如前所述,返回地址是 CALL 指令后接下来 4 个字节的地址。 通过在调用后面放置一个变量,我们可以间接地将其地址压入堆栈,而无需知道它。 当我们不知道我们的代码将从哪里执行时,这是一个非常有用的技巧。 使用 C 的代码排列示例可以如下所示
1 | void main(int argc, char **argv) |
寄存器示例:
- EAX:0xb – syscall number.
- EBX: Address of program name (address of name[0]).
- ECX: Address of null-terminated(空终止) argument-vector, argv (address of name).
- EDX: Address of null-terminated environment-vector, env/enp (NULL).
在这个程序中,我们需要:
String/bin/sh somewhere in memory.
An Address of the string.
String /bin/sh followed by a NULL somewhere in memory.
An Address of address of string.
NULL somewhere in memory.
内存中某处的字符串/bin/sh。
字符串的地址。
字符串 /bin/sh 后跟内存中某处的 NULL。
字符串地址的地址。
内存中某处为 NULL。
为了确定字符串的地址,我们可以使用使用相对寻址的指令。 我们知道 CALL 指令将 EIP 保存在堆栈上并跳转到函数所以
在shell代码开头使用jmp指令来调用CALL指令。
/bin/sh 字符串之前的 CALL 指令。
CALL 跳转回跳转后的第一条指令。
现在 /bin/sh 的地址应该在堆栈上。
如果您要编写比生成简单 shell 更复杂的代码,您可以在 CALL 后面放置多个 .string。 在这里,您知道这些字符串的大小,因此一旦您知道第一个字符串的位置,就可以计算它们的相对位置。 有了这些知识,让我们尝试创建一个简单的 shellcode 来生成一个 shell。 这里的要点是创建 shellcode 可以遵循的类似过程和步骤。 以下是一个简单的程序示例,用于在程序集 (AT&T/Linux) 中生成 shell
1 | #assembly (AT&T/Linux) for spawning a shell |