ret2shellcode

ret2shellcode,即控制程序执行shellcode代码——修改函数返回地址,让其指向溢出数据中的一段指令

shellcode指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。一般来说,shellcode 需要我们自己填充。另外,要想执行 shellcode,需要shellcode所在的区域具有可执行权限。

ret2shellcode的实现在于:在溢出数据内包含一段攻击指令,用攻击指令的起始地址覆盖掉返回地址。

payload : padding1 + address of shellcode + padding2 + shellcode

padding1 处的数据可以随意填充(注意如果利用字符串程序输入溢出数据不要包含 “\x00” ,否则向程序传入溢出数据时会造成截断),长度应该刚好覆盖函数的基地址。address of shellcode 是后面 shellcode 起始处的地址,用来覆盖返回地址。padding2 处的数据也可以随意填充,长度可以任意。shellcode 应该为十六进制的机器码格式,但有个前提是shellcode所在的代码段需要具有可执行权限。

一个问题——shellcode地址如何确定?

我们可以在调试工具里查看返回地址的位置(可以查看 ebp 的内容然后再加4(32位机)),可是在调试工具里的这个地址和正常运行时并不一致,这是运行时环境变量等因素有所不同造成的。所以这种情况下我们只能得到大致但不确切的 shellcode 起始地址,解决办法是在 padding2 里填充若干长度的 “\x90”。这个机器码对应的指令是 NOP (No Operation),也就是告诉 CPU 什么也不做,然后跳到下一条指令。有了这一段 NOP 的填充,只要返回地址能够命中这一段中的任意位置,都可以无副作用地跳转到 shellcode 的起始处,所以这种方法被称为 NOP Sled。这样我们就可以通过增加 NOP 填充来配合试验 shellcode 起始地址。

操作系统可以将函数调用栈的起始地址设为随机化(这种技术被称为内存布局随机化,即ASLR),这样程序每次运行时函数返回地址会随机变化。反之如果操作系统关闭了上述的随机化(这是技术可以生效的前提),那么程序每次运行时函数返回地址会是相同的,这样我们可以通过输入无效的溢出数据来生成core文件,再通过调试工具在core文件中找到返回地址的位置,从而确定 shellcode 的起始地址。

最后可以拼接出最终的溢出数据,输入至程序来执行 shellcode 了:

这种方法生效的一个前提是在函数调用栈上的数据(shellcode)要有可执行的权限(另一个前提是上面提到的关闭内存布局随机化)。很多时候操作系统会关闭函数调用栈的可执行权限,这样 shellcode 的方法就失效了,不过我们还可以尝试使用内存里已有的指令或函数,毕竟这些部分本来就是可执行的,所以不会受上述执行权限的限制。这就包括ret2libc等其他的ROP方法。

前提条件

由前面分析可知,ret2shell这项技术的前提是需要操作系统关闭内存布局随机化(ASLR)以及需要程序调用栈有可执行权限。

解题过程

运行程序,提示这次没有system()给你直接调用,且文件是动态链接的:

checksec可以看出源程序没有开启任何保护:

IDA打开查看,程序仍然是基本的栈溢出漏洞,不过这次还同时将对应的字符串复制到buf2处,简单地说,就是程序允许用户输入一段内容,然后程序将该内容复制到buf2中:

点击查看可知buf2在bss段:

string窗口确实如题目所示,没有直接提供system(“/bin/sh”)给我们调用了:

为了明确是否可以将shellcode写入buf2中执行,通过vmmap可以看到bss段对应的地址具有可执行权限(先main下断点、r再vmmap):

可以看到,buf2变量所在的bss段是可读可写可执行的,因此这里可以输入shellcode、然后程序将shellcode复制至此处,最后在函数返回地址处踩掉该地址跳转至buf2执行shellcode。整个过程即为ret2shellcode原理。

使用GDB pattern字符串溢出计算变量v4到函数返回地址的偏移量为112,即0x70:

编写payload,使用pwntools的asm(shellcraft.sh())可以直接简便地生成汇编代码形式的反弹shell的shellcode:

1
2
3
4
5
6
7
8
from pwn import *

sh = process("./ret2shellcode")
shellcode = asm(shellcraft.sh())
buf2_addr = 0x0804a080
payload = flat([shellcode.ljust(0x70, "A"), buf2_addr])
sh.sendline(payload)
sh.interactive()

运行直接getshell:

参考

手把手教你栈溢出从入门到放弃(上)

ret2shellcode