2024 FFRI Security x NFLabs. Cybersecurity Challenge For Students Writeup
t6o_o6t.iconです。
今回は2問だけ解けたので、このページではそのWriteupをします。
フルタイムでインターンが入っていたので時間が取れず..
Web
注
問題文を読まずに解き始めてしまったので、解き方がいびつです..
やったこと(問題文を読む前)
与えられたURLを開くと、次のような画面が表示されます。
https://scrapbox.io/files/66ee49cd53dc94001c9f2309.png
これだけだとどのようなアプリケーションか分かりません。
まずはRegisterからアカウントを登録してLoginしてみます。
次のような画面に遷移します。
https://scrapbox.io/files/66ee4b6fa511ff001c1764fa.png
Downloadボタンを押すと各ファイルがダウンロードされます。
たとえばaaa.txtのDownloadボタンは、次のようなURLへのリンクになっているようです。
http://(問題サーバのIPアドレス):8092/download?file=aaa.txt
/download?file=aaa.txt
クエリパラメータのうち、fileパラメータに任意のパスを指定できないかが気になります。
そこで、fileパラメータに色々な値を入れてがちゃがちゃ試してみます。
..で上の階層を指定できるか?
/download?file=../hoge
No such file or directory: 'contents/../hoge'
この方法は難しそうです。
/download?file=/
Is a directory: '/'
指定できていそうです。
ルートディレクトリを指定できることが分かりました。
次は、実行環境の情報を調べたいと思います。
/etc/passwd
code:/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
特に有益な情報はなさそうです。
/proc/self/cmdline
code:/proc/self/cmdline
/usr/local/bin/python3 server.py
実行中のファイルがserver.pyであることが分かりました。
注:問題文にはヒントとして明記されていました。
サーバプログラムと同一のディレクトリに存在するようです。
/proc/self/cwd/server.py
コマンドを実行した際のディレクトリは/proc/self/cwdで表せるので、その中のserver.pyはこのように表せます。
アクセスすると、次のようなファイルがダウンロードされます。
code:/proc/self/cwd/server.py
import os
from flask import Flask, Response, redirect, render_template, request, session, url_for
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# ...
ここまでで1時間程度使い、その後の方針が立たず翌日に持ち越しました。
/icons/hr.icon
やったこと(問題文を読んだ後)
翌日、初めて問題文を読みました。
SECRET_KEYの値が分かれば良いようです。
前日に取得したserver.pyを読むと、SECRET_KEYは環境変数のようです。
環境変数を取得するときは、/proc/self/environが使えますね。
code:/proc/self/environ
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=126fa7d94e06
DATABASE_URI=mysql://root:password@mysql-server/data
SECRET_KEY=flag{992daabd454669829130c2ca679748c8}
...
問題文のとおり、SECRET_KEYにフラグが書かれていました。
/icons/hr.icon
Misc
[Medium] legend bird
Unityで作成されたWindows用のゲームが与えられます。
https://scrapbox.io/files/66ee5704b3fbce001d61e757.png
住人から得られる情報は次の通りです。
伝説の鳥は「はた」(フラグ)を持っている
リンゴを99999個得ると、伝説の鳥が現れる
転生者は原点に召喚され、魔法でアイテムを増やせる。
初動
最初に考えたのは単純にリンゴを集めることですが、40個くらいしかなさそうです。
プレイヤーが「転生」することが重要なのではないか?と考えました。
ゲームを何度か立ち上げなおしましたが、リンゴの個数は引き継がれるなどの問題はありません。
プレイを開始したときの初期位置は固定です。このことから、プレイヤーこそ転生者であり、原点とはプレイ開始時のプレイヤー位置なのではないかと考えました。
しかし、ゲームのシステム自体はシンプルに作られていそうで、ゲーム内だけで問題が解決するようには見えませんでした。
ゲームチートの手法を適用できないか考える
チートを使ってリンゴを99999個に増やせば良いのではないか?と考えました。
Legend Birdを起動したあと、Cheat Engineからそのプロセスを選択します。
https://scrapbox.io/files/66ee5ae201caba001c51f656.png
りんごの個数を書き換える方法
現在のりんごの個数は画面上に表示されています。
Scan Typeに現在のりんごの個数を入力しFirst Scanをかけ、
りんごを1個拾うたびに、入力値を1増やしてNext Scanを行います。
これを繰り返すと、次の4つのアドレスに収束しました。
https://scrapbox.io/files/66ee5bf7a511ff001c17be88.png
各アドレスを選択し、右クリックしてchange record > Valueからメモリを書き換えます。
https://scrapbox.io/files/66ee5d255b0a44001d6b7fe5.png
書き換えた瞬間は画面上に変化はありません。
しかし、その後りんごを1つ拾うと、書き換えた結果が画面に反映されます。
https://scrapbox.io/files/66ee5d515b0a44001d6b80e7.png
これでマップの上側のキャラクターに話しかけると、鳥が出現します。
https://scrapbox.io/files/66ee5dcd697029001c175ed9.png
しかし、鳥がいる位置までは道がなく、直接歩いて渡ることはできません。
これを解消するには、プレイヤーの位置を書き換える必要がありそうです。
X座標を書き換える
注:Y座標を書き替えようとしたのですが、書き換えたあと一歩移動した瞬間に、書き換え前の位置に戻ってしまう現象が起きました。原因が分からないため、X座標を書き換える方法を取っています。
現在いる位置を表す座標の内部表現は分かりません。
しかし、ゲーム開発の経験のある方なら、次のルールが存在すると推測できるでしょう。
右に移動するとX座標は増加し、左に移動すると減少する。
このルールを元に、次の方法を考えます。
Scan TypeにUnknown initial valueをセットし、First Scanする。
プレイヤーを適当に右に動かし、Increased valueでNext Scanする。
これを繰り返していくと少しアドレス候補が減りますが、単調増加する全てのアドレスがヒットします。
プレイヤーを適当に左に動かし、Decreased valueでNext Scanする。
右と左の移動を繰り返すと、アドレス候補がかなり絞られます。
動いていないにも関わらず変化するアドレスを除くと、最終的には24個のアドレスに絞られました。
下図のような位置に移動します。
https://scrapbox.io/files/66ee60fe082d11001d988ff4.png
伝説の鳥は原点の真上の方向に位置しているので、X座標は0に近いと推測できます。
Cheat Engineを使って、怪しいアドレスの値をすべて0に書き換えてみます。
https://scrapbox.io/files/66ee751f26e7dc001d405771.png
すると島に移動することができました。
https://scrapbox.io/files/66ee7545fbdf91001c19d97d.png
伝説の鳥に近づくと、フラグを得ることができます。
手を付けたが解けなかった問題
[Easy] Pack
表層解析ということで、stringsにかけるとUPXという文字列が含まれています。
これはUPXで圧縮されたファイルなのではないかと思い解凍を試すと、たしかに解凍することができました。 このファイルはユーザにFlag入力を求めてきます。
以降はGhidraをしばらく読んでいたのですが、時間がかかりそうなので撤退しました。angrを使っても良かったかな? Packをリベンジする
Ghidraを読んでみて、文字列を入力させて判定する処理の直前に、関数(FUN_004016d)を呼び出していることが分かった
この関数の呼び出し直後にブレークポイントを置けば、判定処理の関数にどんな引数が渡されているか分かるだろう
WinDbgで解析してみる