CVE-2024-41592 vigor 栈溢出漏洞分析
…TL;DR这个漏洞其实是分析于今年11月份,鉴于今年只更新了四篇博客,所以就把这篇也拿出来了。这也 2025-1-3 08:25:38 Author: govuln.com(查看原文) 阅读量:24 收藏

TL;DR

这个漏洞其实是分析于今年11月份,鉴于今年只更新了四篇博客,所以就把这篇也拿出来了。这也是大概率今年最后一篇博客了。

CVE-2024-41592 是 forescout 一篇为 《Breaking Into DrayTekRouters Before Threat Actors Do It Again》[1]的漏洞报告其中的一个漏洞。

image-20241230143455676

漏洞产生于 GetCGI() 函数中, 在该函数中处理字符串参数会造成越界导致栈溢出。

漏洞分析

固件解压和调试准备

这里以Draytek 3910的 4.3.1 的版本作为调试 测试版本,进行展开分析。固件的解密和解压不展开赘述,可以参考之前 《HEXACON2022 - Emulate it until you make it! Pwning a DrayTek Router by Philippe Laulheret》 [2]slide 或者其他研究员的文章。

解压后能在 rootfs/firmware/vqemu/sohod64.bin 目录下找到主程序, Draytek 3910 采用了奇葩的 Linux + Qemu + RTOS 的奇葩架构,即在 arm linux操作系统上使用qemu 运行 drayos 的RTOS 操作系统。这里的调试方式采用的是使用编译 Draytek 开源的qemu代码进行编译,然后就可以正常调试。

调试之前需要对 firmware/setup_qemu_linux.shrun_linux.sh 进行部分修改, 例如对run_linux.shqemu-system-aarch64 添加 -s 参数方便用于调试

image-20241230145133293

漏洞成因

我们通过一个有符号的 draytek 2830 的固件来快速定位到Draytek 3910 4.3.1的 GetCGI() 函数, 或者直接对 QUERY_STRING 字符串进行交叉引用。

image-20241230145702255

在各个 cgi 处理函数的时候都会进行一次 GetCGI 函数的调用来处理参数。

image.png

在这个函数(GetCGI)里面,当有 & 出现, 就会通过 makeword 函数生成一个内存空间,然后将地址赋值到栈上, 这个函数的部分逻辑伪代码如下:

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
v19 = sub_400BFA18("REQUEST_METHOD", a3);
if ( v19 )
{
if ( !strcmp(v19, "GET") )
{
v18 = sub_400BFA18("QUERY_STRING", a3);
if ( !v18 )
return 0;
idx = 0;
while ( *v18 )
{
*(a2 + 8 * idx) = makeword(v18, '&');
plustospace(*(a2 + 8 * idx));
unescape_url(*(a2 + 8 * idx));
v16 = safe_strcrh(*(a2 + 8 * idx), '=');
if ( v16 )
{
*v16 = 0;
*(a2 + 8 * idx + 4LL) = v16 + 1;
}
else
{
*(a2 + 8 * idx + 4LL) = 0;
}
++idx;
}
}

这里的 (a2 + 8 * idx) 在栈上, 当输入过多的 & 就有如下的效果:

image-20241230150417353

会有一堆指针覆盖栈上的变量, 甚至能覆盖到返回地址。

Exploit

虽然我们在GetCGI() 函数中覆盖到了返回地址, 但是在各个 CGI 函数结尾的时候会有一个 FreeCtrlName 函数的调用, 该函数会将将覆盖掉得返回地址的指针置零。

image-20241230152340712

也正如原文章所说的, 我们需要绕过这个函数

Although this seems straightforward, challenges exist. Consider the “FreeCtrlName()” function called when a
CGI handler returns (Figure 13). This function “frees” all the POST/GET request data structures, including the
query string buffer. It simply iterates over the 32-bit pointers located in the lower 4 bytes of the stack
21
DRAY:BREAK - BREAKING INTO DRAYTEK ROUTERS BEFORE THREAT ACTORS DO IT AGAIN
addresses and frees them, zeroing out the pointer values as well. Oddly, the higher 4-byte addresses (e.g.,
pointers to query string parameters values) are never freed

FreeCtrlName 函数伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall FreeCtrlName(__int64 result)
{
int v1;
int i;

v1 = result;
for ( i = 0; *(v1 + 8 * i); ++i )
{
result = sub_4061D7CC(*(v1 + 8 * i), 0x154u);
*(v1 + 8 * i) = 0;
}
return result;
}

这个函数的 free 逻辑是, 遍历栈上的指针, 一直free 直到为 0 为止, 因此我们需要找到一个函数可以在栈上写一个 0 , 这样就能避免这个问题。在原文[1] 甚至后来 12月在 Blackhat EU 《When (Remote) Shells Fall Into The Same Hole: Rooting DrayTekRouters Before Attackers Can Do It Again》[3]的slide 上都没有提及这个所谓的 [vulnerable-cgi-page].cgi 是什么。

image-20241230151257971

但是通过一些途径我们还是能找到这个能设置 0 的 cgi , 思路也是比较简单

  1. 首先先将所有的 CGI 调用函数定义出来,

  2. 过滤出不需要授权的 CGI 函数

    粗浅的记得是只要函数里没有 CGIbyFieldName = GetCGIbyFieldName(v6 + 32, "sFormAuthStr");的调用就不需要授权

  3. 猜想哪些函数可以写 0 , 例如 atoi(query_string), query_string 是 HTTP 请求传入的参数

通过以上操作,我们其实很快就能找到一个不用授权、且参数可控可写 0 的CGI。最后的效果就是我们可以控制返回地址跳转到一个内容完全可控的地址里(内容为具体参数的内容)且由于程序运行在 qemu 环境上, 因此我们可以在目标地址上写入任意的shellcode。 但是我们需要逃逸到 qemu 外面, 本身程序提供了一个, virtcons_out 这个函数, 可以执行一些特殊的命令, 我们可以在第一个参数中拼接命令注入来在host上执行任意命令。

image-20241230154445770


文章来源: https://govuln.com/news/url/g88X
如有侵权请联系:admin#unsafe.sh