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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
Tenda-Router-VR-and-Exploit — Tenda AC15 ルーターのファームウェアリホスティングとリモートコマンド実行(CVE-2020-10987)のエクスプロイト再現に関する解説記事。 | Kitploit
ツール/GitHubGitHub/jaden-bowers/tenda-router-vr-and-exploit
組み込みシステムセキュリティIoTセキュリティ脆弱性分析エクスプロイトリバースエンジニアリングペネトレーションテストハードウェアセキュリティ学習と教育ファームウェア解析

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
バイナリエクスプロイト
ラボと実践
GitHubjaden-bowers/tenda-router-vr-and-exploit

Tenda-Router-VR-and-Exploit

Tenda AC15 ルーターのファームウェアリホスティングとリモートコマンド実行(CVE-2020-10987)のエクスプロイト再現に関する解説記事。

リポジトリを見る
12110ヶ月前未レビュー

Tenda-Router-VR-and-Exploit

この記事では、QEMUを使ってAC15 (V15.03.05.19) ファームウェアのWebサーバーをエミュレートし、ホストブラウザからアクセスできるようにし、脆弱な /goform/setUsbUnload ハンドラ (CVE-2020-10987) を実行して、エミュレートされたrootfs内でコマンド実行を取得する方法を正確に示します。


Overview

  • ファームウェアイメージからsquashfsファイルシステムを抽出し(または今回の場合は入手し)、リバースエンジニアリングを開始します。
  • qemu-system-arm 上で動作する Debian armhf ゲストを構築します。
  • ゲストのネットワークと、エミュレートされたルーターへのポートフォワーディング経路を設定します。
  • ブートスクリプトを作成します。
  • エミュレートされたルーターをエクスプロイトします。

Extracting the filesystem

まず、ファームウェアイメージを入手する必要があります。AC15 V15.03.05.19 のイメージダウンロード先は見つけられませんでしたが、既に抽出済みの squashfs ファイルシステムを含む github リポジトリを見つけることができました。 正しいイメージファイルがあれば、次のように binwalk を使って抽出できたでしょう。``` user@computer $ binwalk -e AC15_V15.03.05.19.bin

DECIMAL HEXADECIMAL DESCRIPTION

64 0x40 TRX firmware header, little endian, image size: 6778880 bytes, CRC32: 0x80AD82D6, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x1A488C, rootfs offset: 0x0 92 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4177792 bytes 1722572 0x1A48CC Squashfs filesystem, little endian, version 4.0, compression:xz, size: 5052332 bytes, 848 inodes, blocksize: 131072 bytes, created: 2017-04-19 16:18:08

user@computer $ cd _AC15_V15.03.05.19.bin.extracted

正しいイメージファイルはありませんが、既に抽出されたファイルシステムを持つリポジトリがあるので、そのリポジトリをクローンするだけで済みます。```
git clone https://github.com/lapinpt/Tenda-AC15-Firmware-V15.03.05.19-9061

VR というディレクトリを作成し、その中にこのリポジトリをクローンしました。私の rootfs は $HOME/VR/Tenda-AC15-Firmware-V15.03.05.19-9061/rootfs にあります。

これで AC15 V15.03.05.19 ファームウェアが手に入りました。次に何が起きているのか確認するため、リバースエンジニアリングに進みましょう。

リバースエンジニアリング

リバースエンジニアリングには Ghidra 11.4.2 を使用します。 対象のバイナリ rootfs/bin/httpd に移動し、Ghidra でロードしましょう。 formsetUsbUnload 関数に移動すると、次のように確認できます。```C uVar1 = FUN_0002bd4c(param_1,"deviceName",&DAT_000f4bdc); doSystemCmd("cfm post netctrl %d?op=%d,string_info=%s",0x33,3,uVar1); FUN_0002c6cc(param_1,"HTTP/1.0 200 OK\r\n\r\n"); FUN_0002c6cc(param_1,"{"errCode":0}"); FUN_0002cc14(param_1,200); return;

これは脆弱性です。`deviceName` パラメータが `doSystemCmd` に直接渡されるため、任意のコマンドを送信できます。

さて、このファームウェアを元のルーターハードウェアではなく qemu を使ってリホストするため、一部のプログラムが存在しないデバイスにアクセスしようとして、起動処理をクラッシュさせることになります。
私たちの目標はウェブサーバー (`httpd`) を悪用することなので、私はそのバイナリのみをリホストすることに集中し、起動処理全体 (`/rootfs/etc_ro/init.d/rcS`) は対象にしませんでした。_振り返ってみると、これが正しい判断だったかはわかりません。_

そこで `rcS` を確認する際、`rcS` が行うことで `httpd` が必要とする可能性のあるものを探しました。ファイルの終わり近くで、以下が確認できます:```bash
cfmd &
echo '' > /proc/sys/kernel/hotplug
udevd &
logserver &

rcS は、他のスタックが起動する直前に cfmd をバックグラウンドで起動します。 さらに調査を進め、脆弱な関数がコマンドを送信する方法を確認した結果、次の結論に至りました。

  • httpd は cfm post を構築します
  • そして cfm クライアントは UNIX ドメインソケット (例: /var/cfm_socket) を介して cfmd と通信します

cfmd の InitServer ルーチンでは、次のことが確認できます。```C unlink("/var/cfm_socket"); strncpy(sa_unix.sun_path, "/var/cfm_socket", ...); bind(fd, (sockaddr*)&sa_unix, 0x6e); listen(fd, 5);

UIXソケットを作成してリッスンします。全体的な仕組みとしては、ハンドラは各クライアントから固定長0x7e0バイトのフレームを読み取ります(`RecvMsg`/`SendMsg`)。最初の4バイトはコマンドコードで、その後に512バイトのキーバッファと1500バイトの値バッファがあります(ハンドラ内のスタックオブジェクトのサイズを確認できます)。オペコードに応じて分岐し、ACKコードを返信します:
- `2` -> Get: `GetCfmValue(key, value)` を呼び出し、応答コード `3` を返します
- `0` -> Set: `SetCfmValue(key, value)` を呼び出し、応答コード `1` を返します
- `0x11` -> Unset: `UnSetCfmValue(key)` を呼び出し、応答コード `0x12` を返します
- `10` -> Commit: `SaveCfm2Flash()` を実行し、`0x10`(OK)または `0xB`(エラー)を返します

そこで、`cfmd` をエミュレートするために、短いスクリプト `cfm_stub` を作成しました。```C
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define SOCK_PATH "/var/cfm_socket"

// minimal UNIX-domain server that httpd expects.
// Replies with an IP string when it sees the key it asks for.
int main(void) {
  int s = socket(AF_UNIX, SOCK_STREAM, 0);
  struct sockaddr_un addr = {0};
  if (s < 0) { perror("socket"); return 1; }
  unlink(SOCK_PATH);
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path)-1);
  if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
  if (listen(s, 5) < 0) { perror("listen"); return 1; }

  for (;;) {
    int c = accept(s, NULL, NULL);
    if (c < 0) { if (errno==EINTR) continue; perror("accept"); break; }
    char buf[1024]; ssize_t n = read(c, buf, sizeof(buf));
    if (n > 0) {
      // In some builds httpd asks for "lan.webiplansslen" etc.
      // Any non-empty reply that looks like an IP keeps init happy.
      const char *reply = "192.168.0.1";
      write(c, reply, strlen(reply));
    }
    close(c);
  }
  close(s);
  return 0;
}

I will show how to compile this after the next part.

次のヘルパーファイルは hooks.so です。httpd と cfm で使用される関数の中には、存在しないハードウェアとやり取りしようとするものがいくつかあります。

以下は、プログラム起動時に前提としている内容です。

  • Flash + MTD パーティションが存在し、マウント可能であること。
  • NVRAM デバイスが存在し (/dev/nvram)、妥当なデフォルト値を返すこと。
  • その他のプラットフォームルーチンが成功すること (Layer-7 設定ローダー、RF 電力復元など)。

以下の関数が問題になるため、LD_PRELOAD=/hooks.so でパッチを当てる必要があります。

  • get_flash_type() -> これが 4 を返す場合、コードはファイルベースのパス (cfm_file_init) を採用します。それ以外の場合は MTD (存在しません) と通信しようとします。
  • get_cfm_blk_size_from_cache() (またはその亜種 j_get_cfm_blk_size_from_cache) が構成ブロックのサイズ決定に参照されます。
  • Broadcom NVRAM シム (bcm_nvram_get) への呼び出しが失敗してはいけません。失敗するとスタックは “NVRAM destroyed” とみなし、復元/再起動ロジックに入ります。
  • load_l7setting_file() や restore_power() などの追加ルーチンは成功することが期待されていますが、存在しないハードウェア/ファイルに触れます。

以下が私たちの hooks.c です。オリジナルは azeria-labs に帰属します。```C #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <dlfcn.h> #include <string.h>

int j_get_cfm_blk_size_from_cache(const int i) { puts("j_get_cfm_blk_size_from_cache called....\n"); return 0x20000; // 128 KiB block — what the file path expects }

int get_flash_type() { puts("get_flash_type called....\n"); return 4; // force file-backed CFM init, not MTD }

int load_l7setting_file() { puts("load_l7setting_file called....\n"); return 1; // pretend Layer-7 settings loaded OK }

int restore_power(int a, int b) { puts("restore_power called....\n"); return 0; // success (don’t touch RF/power hardware) }

char *bcm_nvram_get(char *key) { char *value = NULL;

if (strcmp(key, "et0macaddr") == 0) { value = strdup("DE:AD:BE:EF:CA:FE"); // any valid MAC works } if (strcmp(key, "sb/1/macaddr") == 0) { value = strdup("DE:AD:BE:EF:CA:FD"); } if (strcmp(key, "default_nvram") == 0) { value = strdup("default_nvram"); // signals “nvram is OK” }

ツールをダウンロード