House_of_apple
到达世界最高城,glibc2.35!
昨天dsctf的eznote没做出来,2.35的环境是现配的,自己的思路也不是很清晰加上最后时间不太够了没能做出来,对我来说属于船新版本了。赛后ver爷发了一篇house of apple文章,确实太🐂了,通杀目前所有版本。有必要跟上时代,特此学习一下
house of apple
众所周知高版本(>=glibc2.34)移除了诸多hook,已有的攻击方法基本都是针对IO结构体的利用,特别是通过更改vtable地址,利用一个原有的某jumps中的函数指针,进行后续利用。
另外在glibc2.35中似乎exit hook也看不到了。痛,太痛了……
house of apple方法也类似,利用的是_IO_wstrn_jumps中的_IO_wstrn_overflow。达到的效果是任意地址写地址,这里写入的地址通常是堆地址
1 | static wint_t |
首先看一下结构体_IO_wstrnfile构成1
2
3
4
5
6
7typedef struct
{
_IO_strfile f;
/* This is used for the characters which do not fit in the buffer
provided by the user. */
char overflow_buf[64];
} _IO_strnfile;
_IO_wstrnfile由一个_IO_strfile和0x40大小的buf构成
前者由两个结构体_IO_streambuf和_IO_str_fields构成1
2
3
4
5typedef struct _IO_strfile_
{
struct _IO_streambuf _sbf;
struct _IO_str_fields _s;
} _IO_strfile;
_IO_streambuf构成。实际上和_IO_FILE_plus没有区别1
2
3
4
5struct _IO_streambuf
{
FILE _f;
const struct _IO_jump_t *vtable;
};
而_IO_str_fields内含两个指针1
2
3
4
5
6
7
8
9
10
11struct _IO_str_fields
{
/* These members are preserved for ABI compatibility. The glibc
implementation always calls malloc/free for user buffers if
_IO_USER_BUF or _IO_FLAGS2_USER_WBUF are not set. */
_IO_alloc_type _allocate_buffer_unused;
_IO_free_type _free_buffer_unused;
};
typedef void *(*_IO_alloc_type) (size_t);
typedef void (*_IO_free_type) (void*);
所以我们可以知道一个_IO_strfile的大小为0xe0+0x10=0xf0
所以overflow_buf对应的偏移应该为fp+0xf0
再来看一个需要什么条件
(fp->_wide_data->_IO_buf_base != snf->overflow_buf)。这里基本上都是不相等的,因为snf->overflow_buf是一个地址(其实就是fp+0xf0)
接下来的_IO_wsetb需要绕过其中的free。f->_wide_data->_IO_buf_base为空或_flags2&8 !=0即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void
_IO_wsetb (FILE *f, wchar_t *b, wchar_t *eb, int a)
{
if (f->_wide_data->_IO_buf_base && !(f->_flags2 & _IO_FLAGS2_USER_WBUF))
free (f->_wide_data->_IO_buf_base);
f->_wide_data->_IO_buf_base = b;
f->_wide_data->_IO_buf_end = eb;
if (a)
f->_flags2 &= ~_IO_FLAGS2_USER_WBUF;
else
f->_flags2 |= _IO_FLAGS2_USER_WBUF;
}
libc_hidden_def (_IO_wsetb)
//#define _IO_FLAGS2_USER_WBUF 8
然后就进行了一系列赋值操作
所以我们只需要伪造一个IOFILE结构体,并将其中的_wide_data填上想要进行覆写操作的地址,布置好相应的内容来触发调用,即可完成任意地址覆写堆地址的目的。
那么其实到这里house of apple就结束了,并不复杂,但house of apple并不能完成终结,因为其中并没有函数指针的劫持或调用。所以通常其可以作为其他终结方法的前导操作,并通过伪造的IO_FILE的_chain字段链接下一个伪造的结构体,从而多次触发vatable的劫持完成终结
DSCTF eznote
感觉通过两次large bin attack做house of emma应该也可行
这里使用一次largebin attack把堆地址写到_IO_list_all上,通过一次house of apple改写__pointer_chk_guard_local为已知堆地址,第二次使用house of emma,调用_IO_cookie_read并劫持read指针从而getshell
最后会以cookie字段为参数,指向/bin/sh即可
exp
1 | from pwn import* |
后记
发现_IO_strn_overflow和_IO_wstrn_overflow基本一样,也能任意写可控地址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
33static int
_IO_strn_overflow (FILE *fp, int c)
{
/* When we come to here this means the user supplied buffer is
filled. But since we must return the number of characters which
would have been written in total we must provide a buffer for
further use. We can do this by writing on and on in the overflow
buffer in the _IO_strnfile structure. */
_IO_strnfile *snf = (_IO_strnfile *) fp;
if (fp->_IO_buf_base != snf->overflow_buf)
{
/* Terminate the string. We know that there is room for at
least one more character since we initialized the stream with
a size to make this possible. */
*fp->_IO_write_ptr = '\0';
_IO_setb (fp, snf->overflow_buf,
snf->overflow_buf + sizeof (snf->overflow_buf), 0);
fp->_IO_write_base = snf->overflow_buf;
fp->_IO_read_base = snf->overflow_buf;
fp->_IO_read_ptr = snf->overflow_buf;
fp->_IO_read_end = snf->overflow_buf + sizeof (snf->overflow_buf);
}
fp->_IO_write_ptr = snf->overflow_buf;
fp->_IO_write_end = snf->overflow_buf;
/* Since we are not really interested in storing the characters
which do not fit in the buffer we simply ignore it. */
return c;
}
其他的也稍微看了一眼,感觉好像找不到像cookie read这么猛能劫持函数指针的地方了……








