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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
AndroidKernelVulnerability — Android カーネルの脆弱性 CVE-2019-2215 のトリガーと分析 | Kitploit
ツール/GitHubGitHub/sharif-dev/androidkernelvulnerability
Androidセキュリティ特権昇格静的分析動的分析 (サンドボックス)エクスプロイト学習と教育バイナリエクスプロイトラボと実践

人気

すべて見る →

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

すべてのツールを探索

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

すべてのツールを見る →
共有
GitHub
sharif-dev/androidkernelvulnerability

AndroidKernelVulnerability

Android カーネルの脆弱性 CVE-2019-2215 のトリガーと分析

リポジトリを見る
72193年前Kitploit レビュー済み

Android Kernel Vulnerability

概要

2017年11月、syzkallerシステムによってLinuxカーネルにおけるuse-after-freeバグが検出されました。2018年2月には一部のLinuxカーネルとAndroidバージョンで修正が施されました。

この修正はAndroid月次セキュリティアップデートに含まれることはなかったため、PixelやPixel2など多くの新発売デバイスではパッチが適用されていませんでした。

2019年9月、Project Zeroによりこのバグのセキュリティ上の影響がAndroidに通知されました。その後、Androidはこの脆弱性にCVE-2019-2215を割り当て、正式かつ周知のものとしました。

CVE-2019-2215はbinder.cにおけるuse-after-freeであり、Androidアプリケーションから特権の昇格(rootアクセス取得)を可能にします。この脆弱性を悪用するためにユーザーの操作は必要ありません。悪意のあるローカルアプリケーションのインストールのみが必要です。

ここでは、このAndroidカーネル脆弱性についてより詳細に紹介し、この脆弱性を利用してAndroidデバイス全体のrootアクセス(特権昇格)を取得します。

以下のProof of Concept(PoC)を使用します。

https://github.com/cloudfuzz/android-kernel-exploitation

脆弱性の発火

まず、Androidエミュレータ上でこの脆弱性を発火させ、カーネルクラッシュを発生させる方法を示します。その後、その危険性を理解するために、PoCを使用してシミュレートされたAndroidデバイスでrootアクセスを取得する手順を進めます。続いて、カーネルコードを解析し、原因を調べます(静的解析と動的解析)。

解析後、どのようにrootアクセスを取得したかを確認します。最後に、パッチによってこの脆弱性がどのように緩和されるかを見ます。

この脆弱性を発火させてカーネルクラッシュを発生させるには、以下の手順を実行します。

  1. まず、'gdb'と'python'がインストールされたLinux OSが必要です。
  2. PoCリポジトリをクローンします(https://github.com/cloudfuzz/android-kernel-exploitation)。
  3. AndroidエミュレータとAndroid NDKをインストールします(Android Studioのインストールにより)。
  4. Androidカーネルソースコードをクローンします('q-goldfish-android-goldfish-4.14-dev'ブランチを使用します)。
  5. このカーネルは既にパッチ済みですが、脆弱性を再導入するように変更します。
  6. 次に、ソースコードからカーネルをビルドします。KASanを有効にしてビルドします。
  7. ビルドしたカーネルを起動し、それを使ってエミュレータを実行します。
  8. 次に、PoCリポジトリ内の'trigger.cpp'を使用して、クラッシュを発火させます('adb'コマンドを使用)。
  9. PoC内の'root-me.py'と'gdb'コマンドを使用して、エミュレートされたデバイスでrootアクセスを取得します。

次のビデオをご覧ください。

[video]

静的解析

このセクション(および次のセクション)では、静的解析と動的解析を使用してクラッシュが発生する理由を理解します。

ここでは、問題を理解するためにカーネルコードの静的解析を行います。crash_report.txtにはKASanからのレポートが含まれており、これがuse-after-freeバグであると示しています。つまり、ヒープ上にオブジェクトが割り当てられ(それへの参照がある)、そのオブジェクトをヒープから解放した後に、誤って参照を使ってそのオブジェクトを呼び出したことを意味します。このレポートには、これら3つの段階のスタックトレースが出力されています。

上記で、PoC内の'trigger.cpp'を使用したことを覚えているでしょうか。

以下がtrigger.cppのメインコードです。``` int main() { //1 int fd, epfd; //2 struct epoll_event event = {.events = EPOLLIN}; //3 fd = open("/dev/binder", O_RDONLY); //4 epfd = epoll_create(1000); //5 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); //6 ioctl(fd, BINDER_THREAD_EXIT, NULL); }

root@kitploit:~
Let's see what 'trigger.cpp' is doing.

Android(他のUnix系オペレーティングシステムと同様)には、いくつかのプロセスが存在します。実行するプログラムはそれぞれ1つ(または複数)のプロセスを作成し、これらのプロセスはOSによって管理されます。OSはプロセス間を切り替え(マルチタスキング)、プロセスを終了させるなどします。セキュリティ上の理由から、プロセスはデフォルトで互いに隔離されています。

場合によっては、あるプロセスが別のプロセスとデータを交換する必要があります。これをプロセス間通信(IPC)と呼びます。Linuxでは、プロセスが通信するためのいくつかの方法があります。Androidは **'Binder'** と呼ばれる特定のIPCメカニズムを導入しました。Binderはプロセス間通信を容易にするカーネルドライバです。

Androidでは、IPCはカーネルメソッド(ほとんどは drivers/binder.c にあります)を直接呼び出すか、高レベルの実装(たとえばJava)を使用して行うことができます。






![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/binder.jpg)


**binder** を使用するには、カーネルのbinderモジュールを開く必要があります。これは trigger.cpp の3行目で行われます。そして、ファイルディスクリプタポインタを取得します。この fd を使用することで、IPCのカーネルイニシエータと受信者を識別できます。

ドライバとのすべてのやり取りは、少数の **'ioctl'** コマンド(BINDER_THREAD_EXIT、BINDER_WRITE_READ、...)を通じて行われます。

binderの詳細: [link1](https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2012/03/binder.pdf), [link2](http://rts.lab.asu.edu/web_438/project_final/CSE_598_Android_Architecture_Binder.pdf)

Linuxには '**event polling**' と呼ばれる概念があります。複数のファイルディスクリプタ(ドライバを開いたり、IOを扱ったりするときに取得するもの)を監視したい場合に、'**epoll**' API を使用します。

**epoll** は、2つの重要なフィールドを持つカーネル構造体です。



*   interest list = 監視したいファイルディスクリプタのリスト
*   ready list = I/Oの準備ができているファイルディスクリプタのリスト

イベントポーリングを使用するには、まず epoll を作成し(4行目)、次にカーネルの **epoll_ctl** メソッドを呼び出して、ファイルディスクリプタ(**fd**)に関連付けられたイベント(&event)を、作成した epoll(**epfd**)に追加または削除します(EPOLL_CTL_ADD)。

=> `epoll_event event` は、関連付けられたファイル(fd)が読み取り操作に利用可能になったときにトリガーされるイベントです。

これで **trigger.cpp** が何をしているか理解できました(深く掘り下げる必要はありません!)。**binder** モジュールを開き、準備ができたときに待ち受ける **epoll** を作成します。そして6行目で、3行目で開始した binder から exit します。


#### Allocate:

open() を呼び出すことで、実際には **open_binder()**(binder.c 内の open() の実装)を呼び出します。open_binder() では、新しい **'binder_proc'** 構造体が作成され、次のように設定されます:

   ` fd->pricate_data = binder_proc`

**epoll_create()** を呼び出すことで、新しい epoll 構造体が作成され、キュー構造に追加されます。






![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/event_poll.jpg)


**epoll_ctl(epdf, ADD, fd, event)** を呼び出すことで、新しい **ep_item** が作成され、**fd**(待ち受けるファイルディスクリプタ)をこの **ep_item** に関連付け、event_poll の**赤黒木**(ep_item を保存する ep 内のデータ構造)に挿入します。また、**ep_item_poll()** を呼び出します。このメソッドはコールバック関数を ep_item に関連付ける処理を行います。

新しい **binder_thread** 構造体を作成し(**ここで割り当てが行われます**)、それを(上で作成した)**binder_proc** にリンクします。次に **epoll_entry** 構造体が作成され、2つのリスト **epoll_entry->wait** と **epoll_entry->whead** を持ちます。これらのリストは両方とも、先に作成した **binder_thread** へのポインタを持ちます。

そして **epoll_entry** は ep_item にリンクされます(**ep_item->pwqlist** はこの epoll_entry を含むリストです)。





![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/even_poll.jpg)



#### Free:

**ioctl(fd, ...)** を呼び出すことで、fd->private_data を通じて **binder_proc** にアクセスし、その後 **binder_thread** 構造体がメモリから**解放**されます。


#### Use:

現在のプロセスが終了すると、**epoll_ctl(epfd, DEL, fd, event)** が呼び出されます。

これにより **ep_remove(event_poll, ep_item)** が呼び出されます。このメソッドは **ep_item->pwqlist** から **epoll_entry** を取得し、次に **ep_item** の待機リスト(**ep_tem->wait**)を取得します。これはリンクリストであり、この待機リストの項目の1つを削除しようとします。

次のコードを使用します(疑似コードを使用):```
entry = wait->entry;
entry.next.prev = entry.prev;
entry.prev.next = entry.next;

ここで wait->entry は、メモリから削除された binder_thread へのポインタです。つまり、解放後使用(Use-After-Free)であり、バグを引き起こします!!

  • 上記では、実際のカーネルコードと類似したコードを使用しています。細部では異なる場合があります。(一部のケースでは、binder_thread->wait の代わりに binder_thread が使用されます)

まとめ:

red_black_tree を持つ event_poll を作成しました。各ノードは ep_item であり、そのフィールドは epoll_entry のリストです。各 epoll_entry は binder_thread 構造体への2つのポインタ(wait, whead)を持ちます。

ioctl() を呼び出すことで、binder_thread をメモリから解放しました。そして、終了時に、この構造体はまだ利用可能なポインタを介してアクセスされます。

動的解析

動的解析とは、プログラムを実行することでリアルタイムにテストおよび評価し、実行中にエラーを見つけることです。

手順:

  1. KASan なしで Android カーネルをビルドする

    KASAN なしでビルドし、書き込み操作と unlink 操作、および unlink 操作後に実際に何が起こるかを監視します。

  2. 新しくビルドしたカーネルでエミュレータを起動する

  3. エミュレータを起動する

  4. GDB を使用して QEMU インスタンスにアタッチする

  5. 脆弱性トリガーをビルドし、仮想デバイスにプッシュする

  6. GDB でブレークする

    カスタム Python スクリプト(リポジトリ内の dynamic-analysis.py)をロードする: 関数呼び出しをトレースし、解放前後の binder_thread 構造体のチャンクをダンプする。また、unlink 操作の前後で同じ binder_thread 構造体をダンプする。

    このファイルでは、まずすべてのブレークポイントを削除し、2つのブレークポイント(BP)を設定します。最初のシンボルは “binder_free_thread” です(binder_free_thread 関数をトレースします)。binder_thread が解放される前に stop 関数が呼び出されます。そのため、パラメータとシンボルが(gb.write(....) で)表示され、次にコールバックメソッド(set_dump_binder_thread に設定)が呼び出されます。この関数で、binder_thread_address がグローバル変数に設定され、gdb.execute がコマンドで生成された出力を GDB の標準出力に送信します。

    2番目のシンボルは “remove_wait_queue” です(remove_wait_queue 関数をトレースします)。観察したいパラメータは "wq_head"、"wq_entry" で、exit wait.c:52 にブレークポイントが設定されます。それらのコールバック関数は dump_binder_thread です。これらのブレークポイントは、unlink 操作の前後に何が起こるかを示します。

  7. adb shell を起動し、トリガー PoC を実行する

結果:

  • 結果の最初の部分:
root@kitploit:~
binder_free_thread(thread=0xffff88800c18f200)(enter)
0xffff88800c18f200:	0xffff88806793c000	 0x0000000000000001
0xffff88800c18f210:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f220:	0xffff88800c18f220	 0xffff88800c18f220
0xffff88800c18f230:	0x0000002000001b35	 0x0000000000000001
0xffff88800c18f240:	0x0000000000000000	 0xffff88800c18f248
0xffff88800c18f250:	0xffff88800c18f248	 0x0000000000000000
0xffff88800c18f260:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f270:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f280:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f290:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f2a0:	0x0000000000000000	 0xffff88805c05cae0
0xffff88800c18f2b0:	0xffff88805c05cae0	 0x0000000000000000
0xffff88800c18f2c0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2d0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2e0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2f0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f300:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f310:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f320:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f330:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f340:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f350:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f360:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f370:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f380:	0x0000000000000000	 0x0000000000000001
0xffff88800c18f390:	0xffff88806d4bb200

Pythonコードには以下のコードがありました:``` gdb.write ( "{function}({param})(enter)\n".format( function=self.function_name, param=params ) )

root@kitploit:~
そして、これはbinder_free_thread関数であり、この関数のパラメータはbinder_threadへのポインタです。```
static void binder_free_thread(struct binder_thread *thread)
{
        [...]
        kfree(thread);
}

結果として、(binder_free_thread(thread=0xffff88800c18f200)(enter)) が表示されました。 その後の行には実行結果が表示され、以下のコマンドで binder_thread.wait のオフセットを取得できます:``` p offsetof(struct binder_thread, wait)

root@kitploit:~
結果は0xa0で、コマンド , waitの代わりにwait.headを入れると、結果は0xa8になり、`0xffff88805c05cae0`が含まれます。```
0xffff88805c05cae0 is pointer to eppoll_entry->wait.entry which is of type struct list_head
  • 結果の2番目の部分
root@kitploit:~
remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter)
0xffff88800c18f200:	0xffff88800c18f600	 0x0000000000000001
0xffff88800c18f210:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f220:	0xffff88800c18f220	 0xffff88800c18f220
0xffff88800c18f230:	0x0000002000001b35	 0x0000000000000001
0xffff88800c18f240:	0x0000000000000000	 0xffff88800c18f248
0xffff88800c18f250:	0xffff88800c18f248	 0x0000000000000000
0xffff88800c18f260:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f270:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f280:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f290:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f2a0:	0x0000000000000000	 0xffff88805c05cae0
0xffff88800c18f2b0:	0xffff88805c05cae0	 0x0000000000000000
0xffff88800c18f2c0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2d0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2e0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2f0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f300:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f310:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f320:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f330:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f340:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f350:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f360:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f370:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f380:	0x0000000000000000	 0x0000000000000001
0xffff88800c18f390:	0xffff88806d4bb200

remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter) において、 wq_head は binder_thread.wait のアドレスで、 wq_entry は wait.head のデータです。

この後、unlink 操作が行われます。

  • 結果の3番目の部分:
root@kitploit:~
Breakpoint 3 at 0xffffffff802aa5be: file /home/ashfaq/workshop/android-4.14-dev/goldfish/kernel/sched/wait.c, line 53.
remove_wait_queue_wait.c:52(exit)
0xffff88800c18f200:	0xffff88800c18f600	 0x0000000000000001
0xffff88800c18f210:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f220:	0xffff88800c18f220	 0xffff88800c18f220
0xffff88800c18f230:	0x0000002000001b35	 0x0000000000000001
0xffff88800c18f240:	0x0000000000000000	 0xffff88800c18f248
0xffff88800c18f250:	0xffff88800c18f248	 0x0000000000000000
0xffff88800c18f260:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f270:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f280:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f290:	0x0000000000000003	 0x0000000000007201
0xffff88800c18f2a0:	0x0000000000000000	 0xffff88800c18f2a8
0xffff88800c18f2b0:	0xffff88800c18f2a8	 0x0000000000000000
0xffff88800c18f2c0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2d0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2e0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f2f0:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f300:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f310:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f320:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f330:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f340:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f350:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f360:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f370:	0x0000000000000000	 0x0000000000000000
0xffff88800c18f380:	0x0000000000000000	 0x0000000000000001
0xffff88800c18f390:	0xffff88806d4bb200

ハイライトされた部分は こちら の影響です。この結果は unlink 操作後のもので、unlink によりアドレス (0xffff88800c18f2a0 + 0x8) が next と previous に書き込まれていることがわかります。

意味としては:

(binder_thread->wait.head へのポインタ) = binder_thread->wait.head.next = binder_thread->wait.head.prev

脆弱性の悪用

この部分では、このバグを利用して root アクセスを取得する方法を示します。

先ほど、binder_thread 構造体があり、それが 解放 (free) され、ポインタを使って 再使用 (use) されたことを思い出してください。以下が binder_thread 構造体のコードです:``` struct binder_thread { struct binder_proc proc; struct rb_node rb_node; struct list_head waiting_thread_node; int pid; int looper; / only modified by this thread*/ bool looper_need_return; /* can be written by other thread*/ struct binder_transaction *transaction_stack; struct list_head todo; bool process_todo; struct binder_error return_error; struct binder_error reply_error; wait_queue_head_t wait; struct binder_stats stats; atomic_t tmp_ref; bool is_dead; struct task_struct *task; };

root@kitploit:~
フィールドの1つはポインタ **task_struct** であり、この構造体には **addr_limit** というフィールドがあります。

プロセス内のアドレスにアクセスしたい場合、そのアドレスが **ユーザースペース** にあるかどうかがチェックされ、もし **カーネルスペース** にある場合はそのアクセスはブロックされるべきです。このチェックは、アドレスを **addr_limit** と比較することで行われ、アドレスが **addr_limit** より小さい場合、有効なアクセスとみなされます。

**addr_limit** は実際にはユーザースペースとカーネルスペースを分離しているため、**task_struct** 内のこのフィールドを変更することで、カーネルスペースへの完全なアクセスが可能になり、何でもできるようになります!

ここでは、エクスプロイトのための2つのステップがあります:

1. カーネルスペース内の task_struct のアドレスを見つける
2. task_struct 内の addr_limit を変更する

#### task_struct のアドレスを見つける

カーネルでは、ベクトルI/Oを実行できます。つまり、ファイルディスクリプタ(ファイル、ソケットなど)に対して、複数のデータチャンクを書き込んだり読み込んだりできるということです。

ベクトルI/Oは、**writev**、**readv**、**recvmsg** メソッドと **iovec** 構造体を使用して行われます。```
struct iovec 
{ 
     void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ \
     __kernel_size_t iov_len; /* Must be size_t (1003.1g) */ \
};

ベクタI/Oを使用することで、バッファの配列(iovec)に対するI/O操作を行います。各iovecはバッファへのポインタ(iov_base)とバッファのサイズ(iov_len)を持ちます。

ベクタI/O

例えば、バッファの配列をファイル(fd)に書き込みたい場合、次のように呼び出します。

writev(fd, iovecStack, count)

このメソッド(readv や recvmsg も同様)は、まずiovec配列(iovecStack)をカーネル空間にコピーし、次にこれらのバッファから読み取ってfdに書き込みます。

  • 最初の部分(iovec配列をカーネル空間にコピーする部分)は、3つのメソッドすべてで同様です。

パイプを使用してバッファの読み書きができます。パイプは、読み取り用と書き込み用の2つのファイルディスクリプタを提供する構造体です。パイプにはバイト単位の長さがあり、あるプロセスがパイプにその長さを超えて書き込むと、そのプロセスでパイプがブロックされ、別のプロセスがそのパイプから読み取る(そのパイプの読み取りファイルディスクリプタを使用)のを待ちます。

Linuxのパイプ

カーネルは、構造体のサイズに応じてメモリ(構造体用)を割り当てようとします。例えば、binder_thread 構造体をメモリから解放した後(静的解析参照)、binder_thread と同程度のサイズを持つ構造体があれば、解放された binder_thread と同じ場所に割り当てられる可能性が高くなります。

まず、binder_thread 構造体と同程度のサイズを持つ iovecStack(iovec構造体の配列)を作成します。

次に、binder_thread をメモリから解放します(静的解析の「解放」部分を参照)。

その後、この iovecStack に対して writev() を呼び出します。このメソッドの最初の部分で、iovecStack をカーネル空間にコピーします。

解放された binder_thread と同じ場所に、私たちの iovecStack が割り当てられる可能性が高くなります。

iovecStack 内に十分な数の iovec があれば、binder_thread の位置のメモリは次のようになります。

代替テキスト

iovecStack[10].iov_base、iovecStack[10].iov_len、iovecStack[11].iov_base が、binder_thread の wait.lock、wait.head.next、wait.head.prev フィールドと同じ位置にあることがわかります。

静的解析の 「使用」 部分では、リンク解除処理(リンクリストからアイテムを削除する)中に、binder_thread の wait 部分へのアクセスが原因でクラッシュが発生することを確認しました。

リンク解除処理中に、wait.head がリンク解除され、その next および prev フィールドは binder_thread の wait フィールドを指すようになります(リンク解除)。

代替テキスト

writev の2番目の部分(iovecからファイルへの書き込み)が行われる前に、リンク解除プロセスを実行すると、iovecStack[10].iov_len と iovecStack[11].iov_base がカーネルアドレスで上書きされます。その後、writev の残りを実行し、iovecStack[11] を処理しようとするとき、iovecStack[11].iov_base(= binder_thread の wait のアドレス)から iovecStack[11].iov_len の長さのデータを読み取ります。

iovecStack[11].iov_len が十分な場合、binder_thread の wait フィールドから task_struct フィールドまでを読み取り、task_struct のポインタを取得します。

  • 静的解析の解放部分では、binder_thread のすべてが解放されるわけではなく、task_struct のような部分は残ります(簡略化のため、これは述べていません)。

したがって、task_struct のポインタを取得するには、次の手順を実行します。

  1. パイプと iovec のスタックを作成します。
  2. binder_thread と event_poll を作成し、リンクします(静的解析と同じように)。
  3. 子プロセスをフォークします(これで親プロセスと子プロセスになります)。
  4. 親プロセスで writev を呼び出し、iovecStack をカーネル空間にインポートします(続行する前に、例えばパイプにジャンクデータを書き込んで writev をブロックします)。
  5. 子プロセスでリンク解除を実行します。その後、親プロセスに通知されるようにジャンクデータを読み取ります。
  6. 親プロセスで writev() の残りを続行し、カーネル空間の iovec から読み取ってファイルに書き込みます。
  7. これで、ファイルにはカーネル空間内の binder_thread の wait フィールドから下方向への読み取り結果が含まれます(task_struct は wait フィールドから 0xe8 バイトの位置にあります)。

リポジトリ内の exploit.cpp を参照してください。

task_struct の addr_limit を変更する

これで、カーネル空間における task_struct のアドレス(task_ptr)がわかりました。

ここでは、パイプの代わりにソケットペアを使用します。そして、ソケットからの読み取りと iovec への書き込みに recvmsg() を使用します。

手順:

  1. まず、上記のように初期化を行います。
  2. 子プロセスをフォークします。
  3. ソケットペアにいくつかのジャンクを書き込みます。
  4. 親プロセスで recvmsg() を呼び出します。
    1. カーネル空間に iovec をインポートします。
    2. 次にジャンクを読み取り、ソケットから他のデータを受信するのを待ちます(ブロックします)。
  5. 子プロセスでリンク解除操作を実行します。
  6. 子プロセスで次のデータをソケットに書き込みます。``` static uint64_t finalSocketData[] = { 0x1, // iovecStack[10].iov_len 0x41414141, // iovecStack[11].iov_base 0x8 + 0x8 + 0x8 + 0x8, // iovecStack[11].iov_len (uint64_t) ((uint8_t *) task_ptr + OFFSET_OF_ADDR_LIMIT_IN_TASK_STRUCT), // iovecStack[12].iov_base 0xFFFFFFFFFFFFFFFE // addr_limit value };
root@kitploit:~
書き込み後、**recvmsg**()はsocketから読み取り、**iovecStack**に書き込みを開始します。ジャンクデータのため、**iovecStack**[10]まで書き込まれています。

そのため、**iovecStack**[12]に**finalSocketData**の書き込みを開始し、**iovecStack[12].iov_base**からアドレスを取得します。これは、unlink操作により**binder_thread**内の**wait**のアドレスです。**iovecStack[12].iov_len**は4バイトに設定されているため、以下のように書き込みます:

*   0x1 を iovecStack[10].iov_len に
*   0x41414141 を iovecStack[11].iov_base に
*   0x8 + 0x8 + 0x8 + 0x8 を iovecStack[11].iov_len に
*   **pointer_to_addr_limit** を iovecStack[12].iov_base に

これで**iovecStack[11]**に4バイトが書き込まれたため、**recvmsg**()は**iovecStack[12]**に移動し、**finalSocketData**の残りを書き込みます:

**iovecStack[12].iov_base**のアドレス(**pointer_to_addr_limit**に設定されている)に**0xFFFFFFFFFFFFFFFE**を書き込みます。これは、recvmsg()がaddr_limitを0xFFFFFFFFFFFFFFFEに変更することを意味します(arm64のいくつかの問題により0xFFFFFFFFFFFFFFFFではありません)。

これでユーザースペースはほぼすべてのカーネルスペースに拡張されます!(そして何でもできるようになります!!)

# Patch

binder_poll()は、作業のためにスリープ可能なthread->wait待機キューを渡します。epollを使用するスレッドがBINDER_THREAD_EXITを使って明示的に終了すると、待機キューは解放されますが、<span style="text-decoration:underline;">対応するepollデータ構造からは決して削除されません</span>。その後プロセスが終了すると、epollのクリーンアップコードが待機リストにアクセスしようとし、use-after-freeが発生します。

スレッド終了時にPOLLFREEを使用することでこれを防ぎます。

以前は次のコードがありました:```
static int binder_thread_release(struct binder_proc *proc, struct binder_thread *thread)
{
        .
        .
        .
        int active_transactions = 0;
        .
        .
        .
        binder_thread_dec_tmpref(thread);
        return active_transactions;
}

これらの行がソースコードに追加されました:

root@kitploit:~
static int binder_thread_release(struct binder_proc *proc,
binder_thread *thread)
{
	.
	.
	.
	/*
	 * If this thread used poll, make sure we remove the waitqueue
	 * from any epoll data structures holding it with POLLFREE.
	 * waitqueue_active() is safe to use here because we're holding
	 * the inner lock.
	*/
	  if ((thread->looper & BINDER_LOOPER_STATE_POLL) && waitqueue_active(&thread->wait)) {
		wake_up_poll(&thread->wait, EPOLLHUP | POLLFREE);
	  }
	  binder_inner_proc_unlock(thread->proc);
	  /*
	   * This is needed to avoid races between wake_up_poll() above and
	   * and ep_remove_waitqueue() called for other reasons (eg the epoll file
	   * descriptor being closed); ep_remove_waitqueue() holds an RCU read
	   * lock, so we can be sure it's done after calling synchronize_rcu().
	  */
	  if (thread->looper & BINDER_LOOPER_STATE_POLL)
		synchronize_rcu();
	  .
	  .
	  .
}

完全なコードはこちら

定義

カーネル

オペレーティングシステムは、すべてのハードウェアをより効率的に動作させ、アプリケーションが動作するための基盤を構築する役割を担うソフトウェア層であり、その中核がカーネルです。

エクスプロイト

エクスプロイトの背後にある考え方は単純です。ソフトウェアにはバグがあり、バグによってソフトウェアが誤動作したり、本来適切に実行すべきタスクを誤って実行したりします。バグをエクスプロイトするとは、この誤動作を攻撃者の有利に変えることを意味します。

エクスプロイト可能なバグは脆弱性と呼ばれます。

特権と非特権

特権ユーザーまたはプロセスとは、デバイスへの完全なアクセス権を持つものです。

ほとんどの命令セットアーキテクチャは、少なくとも2つの実行モードを提供します。

特権: すべてのマシンレベルの命令にアクセス可能。

非特権: 命令のサブセットにのみアクセス可能。

UAF脆弱性

Use-After-Free脆弱性は、ハッカーが任意のコードを実行するために利用できるメモリ 破損の欠陥の一種です。

Use-After-Freeは特に、解放されたメモリにアクセスしようとする試みを指し、これによりプログラムがクラッシュしたり、Use-After-Freeの欠陥の場合には、任意のコードの実行や、さらには完全なリモートコード実行能力を可能にする可能性があります。

CVE-2019-2215

これはAndroidカーネルのBinderにおけるUse-After-Free脆弱性です。このバグはローカル権限昇格の脆弱性であり、脆弱なデバイスの完全な侵害を可能にします。ブラウザのレンダラエクスプロイトと連鎖させると、悪意のあるWebサイトを介してデバイスを完全に侵害する可能性があります。Chromeサンドボックス内から到達可能です。

注:Pixel 1および2では動作しますが、Pixel 3および3aでは動作しません。この攻撃を参照してください。

Androidセキュリティブルテン

Androidセキュリティブルテンは、Googleが(毎月)公開するリストです。このリストには、Androidフレームワーク、Linuxカーネルなどに影響する修正済みのセキュリティ脆弱性が含まれています。

Syzkaller

Syzkallerはカーネルファザーです。ファジングは、自動化されたプログラムがターゲットプログラムに対して半ランダムな入力を生成し、バグがトリガーされるかどうかをチェックするテスト手法です。ファジングは、CまたはC++プログラムのメモリ破損バグを見つけるのに特に有用です。

Project Zero

Project Zeroは、Googleに雇用されたセキュリティアナリストのチームで、ゼロデイ脆弱性の発見を任務としています。ゼロデイ脆弱性とは、修正すべき者に知られていない脆弱性です。

GDB

GDBはGNU Project Debuggerの略で、UNIXシステムでCおよびC++プログラムをデバッグするための最も一般的なデバッガです。GDBを使用すると、プログラムを特定のポイントまで実行し、そこで停止してその時点での特定の変数の値を表示したり、プログラムを一行ずつステップ実行し、各行の実行後に各変数の値を表示したりできます。

Androidエミュレータをgdbserverに接続してデバッグを行うことができます。

Kernel Address Sanitizer

Kernel Address SANitizer (KASAN) は、領域外アクセスやUse-After-Freeのバグを検出するために設計された動的メモリエラー検出器です。KASANはコンパイル時のインストルメンテーションを使用して、すべてのメモリアクセスの前に有効性チェックを挿入するため、それをサポートするコンパイラバージョンが必要です。カーネルのメモリアクセスは、シャドウマップと照合して有効かどうかを確認できます。

QEMU

QEMU (Quick Emulator) は、ハードウェア仮想化を実行するフリーでオープンソースのエミュレータです。AndroidエミュレータはQEMUエミュレータの下流に位置します。Androidデバイスの起動をサポートし、一般的なAndroidハードウェア(OpenGL、GPS、GSM、センサー)とGUIインターフェースをエミュレートします。Androidエミュレータはqemuをさまざまな方法で拡張しています。

静的解析

静的解析では、プログラムのソースコードを使用してバグや問題を見つけます。プログラムは実行しません。

動的解析

動的解析では、プログラムの実行中の動作を分析します。たとえば、特別な入力を与えることで分析します。

Android NDK

Android NDKは、AndroidデバイスでCやC++などのネイティブコードを実行できるようにするツールセットです。

Android Goldfish

Android Goldfishカーネルは、Androidエミュレータでカーネルコードを実行するために使用されます。クローンして変更し、ビルドしてエミュレータで使用できます。

Android Debug Bridge

ADBはコマンドラインツールです。実行中のAndroidデバイスとの通信やシェルの取得に役立ちます。デバッグ目的で使用できます。

参考文献

https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html

https://nvd.nist.gov/vuln/detail/CVE-2019-2215

https://www.tutorialspoint.com/gnu_debugger

https://www.kernel.org/doc/html/latest/dev-tools/kasan.html

https://android.googlesource.com/kernel/goldfish/

https://man7.org/linux/man-pages/man7/epoll.7.html

https://www.scaler.com/topics/c/debugging-c-program/

...

ツールをダウンロード