ROP初探之ret2shellcode
ret2shellcode
之前提到的ret2text中,利用了程序中已有的sys函数中的恶意代码来进行getshell,而实际情况中很可能没有这种代码,我们就需要自己拼接或构造恶意代码。这篇文章就讲另一种方式,使用填充shellcode的方法构造恶意代码。
ret2shellcode和ret2text的区别
- ret2shellcode是通过栈溢出,将shellcode写入到栈中,然后通过ret指令跳转到栈中的shellcode
- ret2text是将返回地址覆盖为程序.text段中已有的代码的地址,从而执行程序本身的代码。
一般情况下,当程序开启了NX保护(禁止栈和堆执行代码)时,ret2shellcode就无法成功,因为shellcode所在的内存区域不可执行。这时候可以使用ret2text来绕过NX保护,利用程序已有的可执行代码来实现攻击目的。
当然,ret2text也有一些限制,比如需要知道gadget的地址,需要找到合适的gadget来控制寄存器或变量的值,需要考虑程序是否开启了其他保护机制(如ASLR、PIE、RELRO等)。
ret2shellcode的原理
ret2shellcode的原理很简单,就是通过栈溢出将shellcode写入到栈中,然后通过ret指令跳转到栈中的shellcode。这里需要注意的是,shellcode的地址需要是可执行的,所以需要找到一个可执行的内存区域来存放shellcode。
ret2shellcode的步骤
- 找到shellcode的地址(可执行的内存区域)
- 找到ret指令的地址(跳转到shellcode)
- 构造payload
ret2shellcode的例子
这里以一个简单的程序为例,演示ret2shellcode的过程。
1 |
|
这个程序很简单,就是一个栈溢出漏洞,通过gets函数读取用户输入,但是没有限制输入的长度,所以可以通过输入超过100个字符来覆盖buf数组后面的内容,从而覆盖返回地址。
编译
要在内存中执行代码,需要关闭NX保护,所以编译时需要加上 -z execstack 参数,并打开调试信息 -g,关闭其他保护机制 -fno-stack-protector,关闭ASLR -no-pie。
1 | gcc -g -z execstack -fno-stack-protector -no-pie -o ret2shellcode64 ret2shellcode.c |
gdb调试
查看程序的保护机制
我们使用gdb来调试程序,查看程序的保护机制:
1 | cheksec ret2shellcode64 |
分析漏洞
代码中gets(msg)部分存在栈溢出漏洞,所以我们需要找到一个目标地址来执行攻击代码。这里我们选择返回地址,因为返回地址是可控的,而且是可执行的。本题目没有另一个子函数,却有一个buf数组,msg中的内容会被拷贝到buf中,所以要想办法构造溢出填充返回地址指向全局变量buf的地址,且strcpy将buf填充为shellcode。
需要这几个信息:
- buf的地址
- 局部变量msg的地址
- func函数的返回地址
找到buf、msg以及返回地址的地址
在char msg[100]处下断点,运行到func函数内部,并找到msg的地址、buf的地址以及此时rbp的值:
- msg的地址:0x7fffffffe240
- buf的地址:0x404080
- rbp的值:0x7fffffffe2b0
所以func函数的返回地址为:0x7fffffffe2b8
vmmap查看全局变量buf所在地址是否是可执行的内存区域
构造payload
payload的构成
我们要填充局部变量msg,这部分填充会被拷贝到buf中,所以要先填充shellcode,然后再填充一部分字符,使其使其占用空间直到返回地址,然后再将后面八个字节的位置填充为全局变量buf的地址0x404080,就可以使程序跳转到我们构造好的恶意代码处执行。
payload为:
1 | payload = shellcode + 'a'*(120-len(shellcode)) + p64(0x404080) |
构造shellcode
加上shellcode的总偏移长度是返回地址减去局部变量的地址也就是0x7fffffffe2b8-0x7fffffffe240=0x78=120,所以我们需要填充120个字符,然后再填充buf的地址0x404080。
利用execve函数执行/bin/sh,shellcode为:
1 | shellcode = asm(shellcraft.execve('/bin/sh')) |
写exp
1 | from pwn import * |