rop illmatic: exploring universal rop on glibc x86-64 (ja)
DESCRIPTION
2014/11/15 AVTOKYO2014 (Japanese Version) English Version: http://www.slideshare.net/inaz2/rop-illmatic-exploring-universal-rop-on-glibc-x8664-en-41595384TRANSCRIPT
ROP ILLMATIC: EXPLORING UNIVERSAL ROP
ON GLIBC X86-64
@inaz2
AVTOKYO2014
2014/11/15
ABOUT ME
• @inaz2
• Security Engineer & Python Programmer
• Girls Idol Freak
• ブログ「ももいろテクノロジー」
• http://inaz2.hatenablog.com/
2
“ILLMATIC” ?
• http://en.wikipedia.org/wiki/Illmatic
• アメリカのラッパーNasによる造語
• "supreme ill. It's as ill as ill gets. That s*** is a science of
everything ill."
3
BACKGROUND
• バッファオーバーフロー脆弱性などからの任意コード実行
• ドキュメント「各種セキュリティ機構が実装されている」
• どこまで突破できるのか?
• 論文「固定アドレスに十分な実行可能メモリが存在するならば…」
噂話「x86-64では固定アドレスに実行可能メモリが足りないので難し
い」
• 実行可能メモリが少ないときでも使える普遍的(universal)な方法はな
いのか?
4
ENVIRONMENT
• x86-64環境におけるUbuntu Linux最新版
$ uname -aLinux vm-ubuntu64 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -aDescription: Ubuntu 14.04.1 LTS
$ gcc --versiongcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
$ clang --versionUbuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
5
VULNERABLE CODE
• スタックバッファオーバーフロー脆弱性を含む最小限のCコード
6
#include <unistd.h>
int main(){
char buf[100];int size;read(0, &size, 8);read(0, buf, size);write(1, buf, size);return 0;
}
バッファサイズ以上の書き込みが可能
HOW THE CODE IS EXECUTED
• RIPレジスタにその時点で実行されようとしている命令のアドレスが入っ
ている
• x86-64における関数呼び出し
• call命令は次の命令のアドレスをスタックにpushしてripを変える
• ret命令はスタックからアドレスをpopしてripを戻す
• ELFにおけるライブラリ関数呼び出し
• オブジェクトはGOT (Global Offset Table) および PLT (Procedure
Linkage Table) セクションを持つ
• GOTはライブラリ関数のアドレステーブル
• PLTはGOTに置かれた各アドレスにジャンプするためのエントリポイント
• PLTを経由して間接ジャンプを行う
7
SMASHING THE STACK [PHRACK 49]
• Published in 1996, written by Aleph One
• 関数からのリターンアドレスを書き換え、シェルコードに向ける
8
AAAA
shellcode &buf
buf[100]
saved
ebp
return
address
higher
address
SECURITY MITIGATIONS
• Enabled by default
• NX/DEP
• ASLR
• Stack canary
• Additional options
• RELRO + BIND_NOW (FullRELRO)
• FORTIFY_SOURCE
• PIE
9
NX/DEP (DATA EXECUTION PREVENTION)
• 書き換え可能メモリ(データ)の実行を禁止する
• スタック・ヒープ領域に置いたシェルコードを実行しようとすると落ちる
$ cat /proc/$$/maps00400000-004ef000 r-xp 00000000 fc:00 265344 /bin/bash006ef000-006f0000 r--p 000ef000 fc:00 265344 /bin/bash006f0000-006f9000 rw-p 000f0000 fc:00 265344 /bin/bash006f9000-006ff000 rw-p 00000000 00:00 001cb0000-01ed1000 rw-p 00000000 00:00 0 [heap]...7fffe374e000-7fffe376f000 rw-p 00000000 00:00 0 [stack]
10
ROP (RETURN-ORIENTED PROGRAMMING)
[BH USA 2008]
• リターンアドレスを書き換え、実行可能コードに向ける
• ret命令で終わるコード断片(ROP gadget)へのリターンを繰り返す
• jmp/call命令を使うものも含めてcode-reuse attackとも呼ばれる
pop rdi;ret;
system(“/bin/sh”)
runs
11
&gadget/bin/sh¥x00 &buf
buf[100]
return
address
higher
address
&system
X86-64 CALLING CONVENTIONS
• x86-64では、関数の引数はレジスタにセットして渡される
• rdi, rsi, rdx, rcx, r8, r9の順
• 関数にリターンする前に各レジスタへの値のセットが必要
• “pop rdi; ret” / “pop rsi; ret” / “pop rdx; ret” / …
• これらのgadgetが固定アドレスに揃っていないことも多い
12
LIBC_CSU_INIT GADGETS
• ほぼすべての実行ファイルに埋め込まれている__libc_csu_init関数
にあるコード断片を使う
• 3引数までの任意の関数呼び出しが可能
• 第4引数(rcx)はmemset/memcpyを呼ぶことで操作可能
(1) loc_400626
スタックからレジスタに値をセット
(2) loc_400610
引数にセットして [r12+rbx*8] をcall
13
ASLR (ADDRESS SPACE LAYOUT RANDOMIZATION)
• スタック・ヒープ領域や共有ライブラリのアドレスをランダム化
• 実行ファイルが置かれるアドレスは固定のまま
14
heapa.out libc.so.6
1st execution:
higher
address
stack
heapa.out libc.so.6
2nd execution:
stack
ROP STAGER USING IO PRIMITIVES
• PLTにある入出力関数を使い、固定アドレスにROPシーケンスを送り
込む
• read/write, send/recv, fgets/fputs…, だいたい何かしらある
• スタックポインタを送り込んだROPシーケンスに向ける(stack pivot)
• rbpをセットしてleave命令を実行することでrspを差し替える
read@plt # call read(0, 0x601048, 0x400)# 0x601048 = writable address around bss section
pop rbp; ret; # set rbp=0x601048leave; ret; # equiv. to “mov rsp, rbp; pop rbp”
15
DETERMINING LIBRARY FUNCTION ADDRESS
• ランダム化されたアドレスにあるsystem関数を呼ぶには?
• GOTにある__libc_start_main関数のアドレスを読み出す
• __libc_start_main関数からsystem関数までのオフセットを足す
• ターゲットホストで使われているlibcバイナリを特定する必要がある
• __libc_start_main関数の下位ビットから推測する方法も考えられる
$ nm -D -n /lib/x86_64-linux-gnu/libc-2.19.so0000000000021dd0 T __libc_start_main0000000000046530 W system
offset = 0x046530 − 0x021dd0
= 0x24760
16
RETURN-TO-DL-RESOLVE [PHRACK 58]
• Published in 2001, written by Nergal
• 偽のシンボル情報一式を作って、ダイナミックリンカ
(_dl_runtime_resolve関数)に読ませる
• libcバイナリの特定なしで、任意のライブラリ関数の呼び出しが可能
17
buf += struct.pack(‘<Q’, addr_plt) # PLT0 jumps to resolverbuf += struct.pack(‘<Q’, offset2rela)buf += ‘NEXT_RIP’buf += ‘A’ * alignment1buf += struct.pack(‘<QQQ’, writable_addr, offset2sym, 0) # Elf64_Relabuf += 'A' * alignment2buf += struct.pack(‘<IIQQ’, offset2symstr, 0x12, 0, 0) # Elf64_Symbuf += ‘system¥x00’ # symstr
SKIPPING SYMBOL VERSION CHECK
• x86-64ではふつうにReturn-to-dl-resolveを行うと落ちる
• 偽シンボルのバージョンのインデックスを取得する箇所でSEGV (*1)
• GOTセクションにあるlink_map構造体のアドレスを読み出し、
[link_map+0x1c8]の値を0に書き換える
• バージョン情報を取得する処理をスキップ
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL){const ElfW(Half) *vernum =(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
ElfW(Half) ndx =vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; // (*1)
version = &l->l_versions[ndx];if (version->hash == 0)version = NULL;
}18
RELRO + BIND_NOW (FULLRELRO)
• Relocation read-only with lazy binding disabled
• 実行直後にすべてのGOTアドレスを解決し、Read-onlyにする
• 実行時、メモリ上に_dl_runtime_resolve関数のアドレスがセットされ
ない
• 直接呼び出すことができなくなる
(gdb) x/4gx 0x6010000x601000: 0x0000000000000000 0x00000000000000000x601010: 0x0000000000000000 0x0000000000000000
(gdb) x/4gx 0x6010000x601000: 0x0000000000600e28 0x00007ffff7ffe1c80x601010: 0x00007ffff7df04e0 0x0000000000400456
19
FullRELRO (0x601000 = address of GOT section)
DT_DEBUG TRAVERSAL
• DynamicセクションにあるDT_DEBUGの値を見る
• 実行時にデバッグ用構造体 r_debugのアドレスが入る
• r_debugから、ロードされている各ライブラリのlink_map構造体が辿
れる
• link_map構造体を通して、ライブラリに関する各種アドレスが得られる
• ロードされたライブラリのGOTセクションから_dl_runtime_resolve関
数のアドレスを特定する
20
LET’S TRY!
• x86-64かつASLR+NX/DEP+FullRELROが有効な条件下におい
て、シェルを起動する
1. 関数呼び出しにlibc_csu_init gadgetsを用いる
2. IO primitivesを使って固定アドレスにROPシーケンスを送り込み、
stack pivotする
3. DT_DEBUG traversalを行い、_dl_runtime_resolve関数のアドレス
を特定する
4. Return-to-dl-resolveでsystem関数を呼び出す
21
DEMO 1
• http://inaz2.hatenablog.com/entry/2014/10/12/191047
• gccを使い、ASLR+NX/DEP+FullRELROを有効にした実行ファイルを
作る
• ROP stager + Return-to-dl-resolveでシェルを起動する
22
Enable ASLR:
$ sudo sysctl -w kernel.randomize_va_space=2
Compile with NX/DEP+FullRELRO enabled (w/o stack canary):
$ gcc -fno-stack-protector -Wl,-z,relro,-z,now bof.c
Execute the program and exploit it:
$ python exploit.py 100
IT WORKS, BUT IS TOO COMPLICATED…
DT_DEBUG Traversal requires
read & write 5 times
23
DYNAMIC ROP
• Or “Just-in-time Code Reuse” [BH USA 2013]
• libcメモリを丸ごと読み出して使う
1. GOTにある__libc_start_main関数のアドレスを読み出す
2. 読み出したアドレスから0x160000バイト程度を読み出す
3. 読み出したメモリ内にあるROP gadgetを使い、システムコールを実行
するROPシーケンスを構築する
pop rax; ret; # set rax=59 (_NR_execve)pop rdi; ret; # set rdi="/bin/sh"pop rsi; ret; # set rsi={"/bin/sh", NULL}pop rdx; ret; # set rdx=NULLsyscall; # call execve("/bin/sh", {"/bin/sh", NULL}, NULL)
24
LEAVE-LESS EXECUTABLES
• clangでコンパイルすると、関数エピローグにleave命令が使われない
• “add rsp, XXh” の形で厳密にrspが調整される
• leave命令がない場合のstack pivotはめんどい
• 固定アドレスに送り込んだROPシーケンスへのpivotが難しくなる
gcc clang
25
RETURN-TO-VULN
• 同一の脆弱な関数に繰り返しリターンする
• stack pivotなしで次のROPシーケンスを実行できる
26
Vulnerable function
Read the address of
__libc_start_main
Read the
libc memoryExecute system call
1. 2.
3.
ROPUTILS
• https://github.com/inaz2/roputils
• ROPに関する各種タスクを行うツールキット
• readelfコマンドによるELFパース
⇒ セクション、シンボル、よく使うgadgetのアドレスを取得
• パラメータからのROPシーケンス構築
• pipe (local) & socket (remote) 両対応のNon-blocking IO
• Linux i386 / x86-64 シェルコード生成
• 適用されているセキュリティ機構の確認
• Metasploitパターンの生成およびオフセット計算
27
ELFパース
各種アドレスの取得
ROPシーケンスの構築
すべてのクラスをimport
Non-blocking IOターゲットがremoteなら次のようにする:
Proc(host=x, port=y)
28
DEMO 2
• https://github.com/inaz2/roputils/blob/master/examples/libc-
dynamic-no-leave-x64.py
• clangを使い、ASLR+NX/DEP+FullRELROを有効にした実行ファイ
ルを作る
• socatを使ってTCPサービスにする
• Dynamic ROP + Return-to-vulnでリモートシェルを起動
Enable ASLR and compile with NX/DEP+FullRELRO enabled (w/o stack canary):
$ sudo sysctl -w kernel.randomize_va_space=2$ clang -fno-stack-protector -Wl,-z,relro,-z,now bof.c
Execute the program as a TCP service:
$ socat tcp-listen:5000,fork,reuseaddr exec:./a.out &
Exploit it:
$ python libc-dynamic-no-leave-x64.py ./a.out 120 29
HOW ABOUT THE REST?
Stack canary, FORTIFY_SOURCE and PIE
30
STACK CANARY
• リターンアドレスの前にランダムな値を差し込むことでスタックバッファ
オーバーフローを検知
• 値が変わっていたら強制終了
• ターゲットが単純なfork serverなら、1バイトずつのbruteforceで突
破可能(最大試行数 256 * 8 = 2048)
• Heap overflow / Use-after-free脆弱性などによるポインタ書き換え
に対しては効果なし
31
値が変わってないかチェック
FORTIFY_SOURCE
• 危険な標準ライブラリ関数を安全なものに置き換える
• gets → gets_chk, strcpy → strcpy_chk, read → read_chk, …
• バッファサイズのチェックを追加
• ほとんどのスタックバッファオーバーフロー脆弱性は塞がれる
• ただし、*_chkを使ったROP stager自体は可能
• Heap overflow / Use-after-free脆弱性などによるポインタ書き換えを
探す
32
read(0, buf, size)
→ __read_chk(0, buf, size, 100)
PIE (POSITION-INDEPENDENT EXECUTABLES)
• 実行ファイルが置かれるアドレスもランダム化
• もちろんASLRが有効な場合に限る
• 実行ファイルやlibc中にある何かしらのアドレスが得られる場合には
効果なし(information leak)
• Buffer over-readなど他の脆弱性を使う
• 下位バイトのみを書き換えることで、リターンアドレスをずらすことはで
きる(partial overwrite)
• しかし、genericな攻撃は難しいと思われる
33
FURTHER MITIGATIONS
• Shadow stack
• StackShield (2000), TRUSS (2008), ROPdefender (2011)
• リターンアドレスを別の領域にコピーしておき検証する
• Related: “SCADS: Separated Control- and Data-Stacks” (2014)
• Coarse-grained Control-Flow Integrity (CFI)
• ROPGuard (2012), kBouncer (2013), ROPecker (2014)
• Indirect Branches and Behavior-Based Heuristics policies
• 取り得るジャンプ先を集めておいて検証する
• 短い命令列が連続する回数を制限する(閾値ベース)
• Call-ret-pair gadget、Long-NOP gadgetが存在すればbypass可能
34
RECAP
• NX/DEP bypass: ROP
• ASLR bypass: ROP stager
• 前提条件: PLT上のIO primitives、十分なROP用バッファ
• x86-64での関数呼び出し: libc_csu_init gadgets
• ライブラリ関数呼び出し: Return-to-dl-resolve
• FullRELRO bypass: DT_DEBUG traversal
• システムコール実行: Dynamic ROP
• leave-less executable対策: Return-to-vuln
• ROP is illmatic
35
REFERENCES (1/2)
• "Exploit" -記事一覧 - ももいろテクノロジー
• http://inaz2.hatenablog.com/archive/category/Exploit
• Smashing The Stack For Fun And Profit (Phrack 49)
• http://phrack.org/issues/49/14.html
• The advanced return-into-lib(c) exploits: PaX case study (Phrack 58)
• http://phrack.org/issues/58/4.html
• Return-Oriented Programming: Exploits Without Code Injection (Black
Hat USA 2008)
• http://cseweb.ucsd.edu/~hovav/talks/blackhat08.html
• Return to Dynamic Linker (Codegate 2014 Junior)
• http://blog.jinmo123.pe.kr/entry/Codegate-2014-Junior-Presentation
36
REFERENCES (2/2)
• Ghost in the Shellcode 2014 - fuzzy - Code Arcana
• http://codearcana.com/posts/2014/01/19/ghost-in-the-shellcode-2014-fuzzy.html
• Just-In-Time Code Reuse: The more things change, the more they stay
the same (Black Hat USA 2013)
• https://media.blackhat.com/us-13/US-13-Snow-Just-In-Time-Code-Reuse-
Slides.pdf
• SCADS: Separated Control- and Data-Stacks (SECURECOMM 2014)
• https://www1.cs.fau.de/filepool/scads/scads-securecomm2014.pdf
• Stitching the Gadgets: On the Ineffectiveness of Coarse-Grained Control-
Flow Integrity Protection (USENIX Security 2014)
• https://www.usenix.org/conference/usenixsecurity14/technical-
sessions/presentation/davi
• And many others
37
THANK YOU!
@inaz2
38