Project Moon

Use After Free in mDNSOffloadUserClient.kext

2018-10-07

Both Qixun Zhao of Qihoo 360 Vulcan Team and Liang Zhuo of Qihoo 360 Nirvan Team found this issue independently.

Background

IOKit UserClient classes usually override the method IOUserClient:: clientClose which can be triggered by IOServiceClose from user space. It is just the way of closing handle of IOUserClient used by IOKit and is not responsible for resources management. The resources acquired before should be released in the asynchronous ::free method not rather ::clientClose. Ian Beer made a clear explanation about this pattern and the root cause was described as follow:

IOUserClient::clientClose is not a destructor and plays no role in the lifetime management of an IOKit object.

Vulnerability

It appears that mDNSOffloadUserClient in mDNSOffloadUserClient.kext does not obey this programming rule on macOS High Sierra. The code of method mDNSOffloadUserClient::clientClose is shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 mDNSOffloadUserClient::clientClose(mDNSOffloadUserClient *this) {
mDNSOffloadUserClient *v1; // rbx __int64 v2; // rdi
__int64 v3; // rax
v2 = *((_QWORD *)this + 27);
if ( v2 ){
...
if ( this−>CommandGate ) {
v3 = (*(__int64 (__cdecl **)(_QWORD))(**((_QWORD **)v1 + 27) + 1672LL))
(*((_QWORD *)v1 + 27)); if ( v3 )
(*(void (__fastcall **)(__int64, _QWORD))(*(_QWORD *)v3 + 328LL))(v3, *((_QWORD *)v1 + 28));
this−>CommandGate−>release();
this−>CommandGate = NULL;
}
}
*((_QWORD *)v1 + 27) = 0LL;
return 0LL;
}

In this method, the CommandGate object is released and it will also be freed at once. Notice that we can also trigger another method mDNSOffloadUserClient::doRequest(Shown below) in which CommandGate object is used for synchronization through IOConnectCallMethod in another thread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 mDNSOffloadUserClient::doRequest(mDNSOffloadUserClient * this, void *a2, 
void *a3, __int64 a4, unsigned __int64 *a5) {
__int64 result; // rax
__int64 v6; // rdi
__int64 v7; // [rsp+8h] [rbp−8h]
v7 = a4;
result = 0xE0000001LL;
if ( *((_QWORD *)this + 27) ) {
if ( this−>CommandGate )
result = this−>CommandGate−>runAction(mDNSOffloadUserClient::doRequestGated,
a2, a3, &v7, a5);
}
return result;
}

The proof of concept can be found here and the reproduction steps are as follow:

1. clang mDNSOffloadUserClientUaF.c -o mdns_uaf -framework IOKit 
2. while true; do ./mdns_uaf; done

About Exploitation

There’s a post written by Bazad talking about the steps for a privilege escalation exploit for macOS including vulnerabilities, mitigations and how to build the ROP stack. In this post we will talk about another different exploiting bits we used for surviving from small time window of this case.

First we need a lot of threads, one for race, one for triggering the issue and the others for spray.

1
2
3
4
5
6
7
8
9
#define PADDING_THREAD_NUM 0x40

for(int z = 0; z < PADDING_THREAD_NUM; z++){
if(z == PADDING_THREAD_NUM/2){
pthread_t race_thread;
pthread_create(&race_thread, NULL, race, NULL);
}
pthread_create(&padding_thread[z], NULL, padding_after_free, (void*)err);
}

In padding_after_free, io_service_get_matching_services_ool is used for allocating many objects with fixed size 0x50, same as sizeof(IOCommandGate), in kernel and race thread is just for racing through IOServiceClose and the main thread is served for triggering the issue by IOConnectCallMethod.

The second point is that we used pthread_setschedparam to increase the success rate for the race. We use this API in all threads, including main thread, to make sure all the threads have the same schedule priority. Through that the threads may run as the sequence as we expect.

1
2
3
struct sched_param sp;
sp.sched_priority = 3;
pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp);

Fixing

This issue has been fixed on macOS Mojave 10.14 as below without any acknowledgemnt from Apple.

1
2
3
4
5
6
7
8
9
mDNSOffloadUserClient::clientClose(): 
0000000000001d78 pushq %rbp
0000000000001d79 movq %rsp, %rbp
0000000000001d7c movq (%rdi), %rax
0000000000001d7f xorl %esi, %esi
0000000000001d81 callq *0x600(%rax)
0000000000001d87 xorl %eax, %eax
0000000000001d89 popq %rbp
0000000000001d8a retq