防衛省サイバーコンテスト2025冬 Writeup
はじめに
本記事では、2025-02-02 09:00 ~ 21:00に開催されていた防衛省サイバーコンテスト2025冬について、私が解けた問題についてのWriteupを記述します。
今回の防衛省サイバーコンテストでは、328位でした。
もっと能力を上げていきたいですね。
Writeup
Writeupについて、解いた順に記載していきます。
TR/排他的倫理和 (300 points)
問題
比較対象ファイルの値と各候補ファイルに記載の値のXORを計算し、有意な値を見つけてください。
【回答書式】 flag{IPアドレス}
pattern1, pattern2, pattern3, compareというファイルが添付されており、それぞれのファイルには以下のような値が書かれている。
pattern1
0000 0000 666c 6167 7b7d
pattern2
0500 2c00 7d7f 00cd 1c03
pattern3
0005 0f03 2cc5 67b7 b22f
compare
findWizXOR
メモ
findWizXORは、16進表記のアスキーコードでは、以下のような値になる。
66 69 6e 64 57 69 7a 58 4f 52
pattern01~pattern03について、この値とのXORを取っていく。
Pythonコードは以下。
import argparse
def xor_hex_strings(hex_string1, hex_string2):
# 引数で渡された16進数文字列について、スペース除去した16進数文字列に変換
hex1 = hex_string1.replace(' ', '')
hex2 = hex_string2.replace(' ', '')
result_bytes = []
for i in range(0, len(hex1), 2):
# 16進数文字列をバイトに変換
byte1 = int(hex1[i:i + 2], 16)
byte2 = int(hex2[i:i + 2], 16)
# XOR演算
result_bytes.append(f"{byte1 ^ byte2:02x}")
return ' '.join(result_bytes)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('hex_string1', help='16進数文字列1')
parser.add_argument('hex_string2', help='16進数文字列2')
args = parser.parse_args()
result = xor_hex_strings(args.hex_string1, args.hex_string2)
print("hex: " + result)
pattern01
$ python xor_hex_strings.py "00 00 00 00 66 6c 61 67 7b 7d" "66 69 6e 64 57 69 7a 58 4f 52"
hex: 66 69 6e 64 31 05 1b 3f 34 2f
hexの内容は、find1?4/
となる。
pattern02
$ python xor_hex_strings.py "05 00 2c 00 7d 7f 00 cd 1c 03" "66 69 6e 64 57 69 7a 58 4f 52"
hex: 63 69 42 64 2a 16 7a 95 53 51
hexの内容は、ciBd*zSQ
となる。
pattern03
$ python xor_hex_strings.py "00 05 0f 03 2c c5 67 b7 b2 2f" "66 69 6e 64 57 69 7a 58 4f 52"
hex: 66 6c 61 67 7b ac 1d ef fd 7d
hexの内容は、flag{¬ïý}
となる。
pattern03がそれっぽい。
が、解答形式はIPアドレスなので、IPアドレスを見つける必要がある。
flag{}以外のところは、ac 1d ef fd
となっている。
16進数を10進数に直してみると、
ac -> 172
1d -> 29
ef -> 239
fd -> 253
となる。
これをIPv4アドレスの形にした、172.29.239.253
が答え。
フラグ: flag{172.29.239.253}
TR/Windowsで解きましょう (200 points)
問題
下記のファイルを実行すると「flags」というフォルダが作成され、複数のファイルが生成されます。 すべてのファイルに違うフラグが書かれています。 その中のファイルの一つには印がつけてあります。正解のフラグを探してください
【回答書式】 flag{22桁の半角数字}
flags.batというファイルが添付されており、中身は以下。
@echo off
setlocal
set FDATA1=23
set FDATA2=61
set FDATA3=34
set FDATA4=25
set FDATA5=75
set FDATA6=64
set FDATA7=93
set FDATA8=44
set FDATA9=72
md flags
chdir flags
for /l %%n in (10,1,99) do (
type null > flags_%%n.txt
echo flag{%FDATA5%%FDATA4%%%n%FDATA1%%FDATA6%%FDATA2%%%n%FDATA3%%FDATA7%%FDATA9%%FDATA8%} > flags_%%n.txt
if %%n==%FDATA4% echo > flags_%%n.txt:TrueFlag
)
endlocal
メモ
flags.batファイルを読むと、
if %%n==%FDATA4% echo > flags_%%n.txt:TrueFlag
と書かれており、FDATA4=25
なので、flags_25.txtにTrueFlagという印がついており、その中に書かれている内容が答え。
flags_25.txtには以下の値が書かれていた。
flag{7525252364612534937244}
フラグ: flag{7525252364612534937244}
余談
NTFSのADSを使った問題。dir /R
コマンドを使っても解ける。
>dir /R
ドライブ [REDACTED] のボリューム ラベルは [REDACTED] です
ボリューム シリアル番号は [REDACTED] です
[REDACTED]\flags のディレクトリ
2025/02/02 15:46 <DIR> .
2025/02/02 16:41 <DIR> ..
2025/02/02 15:46 31 flags_10.txt
2025/02/02 15:46 31 flags_11.txt
2025/02/02 15:46 31 flags_12.txt
2025/02/02 15:46 31 flags_13.txt
2025/02/02 15:46 31 flags_14.txt
2025/02/02 15:46 31 flags_15.txt
2025/02/02 15:46 31 flags_16.txt
2025/02/02 15:46 31 flags_17.txt
2025/02/02 15:46 31 flags_18.txt
2025/02/02 15:46 31 flags_19.txt
2025/02/02 15:46 31 flags_20.txt
2025/02/02 15:46 31 flags_21.txt
2025/02/02 15:46 31 flags_22.txt
2025/02/02 15:46 31 flags_23.txt
2025/02/02 15:46 31 flags_24.txt
2025/02/02 15:46 31 flags_25.txt
22 flags_25.txt:TrueFlag:$DATA
2025/02/02 15:46 31 flags_26.txt
2025/02/02 15:46 31 flags_27.txt
2025/02/02 15:46 31 flags_28.txt
2025/02/02 15:46 31 flags_29.txt
2025/02/02 15:46 31 flags_30.txt
2025/02/02 15:46 31 flags_31.txt
2025/02/02 15:46 31 flags_32.txt
2025/02/02 15:46 31 flags_33.txt
2025/02/02 15:46 31 flags_34.txt
2025/02/02 15:46 31 flags_35.txt
2025/02/02 15:46 31 flags_36.txt
2025/02/02 15:46 31 flags_37.txt
2025/02/02 15:46 31 flags_38.txt
2025/02/02 15:46 31 flags_39.txt
2025/02/02 15:46 31 flags_40.txt
2025/02/02 15:46 31 flags_41.txt
2025/02/02 15:46 31 flags_42.txt
2025/02/02 15:46 31 flags_43.txt
2025/02/02 15:46 31 flags_44.txt
2025/02/02 15:46 31 flags_45.txt
2025/02/02 15:46 31 flags_46.txt
2025/02/02 15:46 31 flags_47.txt
2025/02/02 15:46 31 flags_48.txt
2025/02/02 15:46 31 flags_49.txt
2025/02/02 15:46 31 flags_50.txt
2025/02/02 15:46 31 flags_51.txt
2025/02/02 15:46 31 flags_52.txt
2025/02/02 15:46 31 flags_53.txt
2025/02/02 15:46 31 flags_54.txt
2025/02/02 15:46 31 flags_55.txt
2025/02/02 15:46 31 flags_56.txt
2025/02/02 15:46 31 flags_57.txt
2025/02/02 15:46 31 flags_58.txt
2025/02/02 15:46 31 flags_59.txt
2025/02/02 15:46 31 flags_60.txt
2025/02/02 15:46 31 flags_61.txt
2025/02/02 15:46 31 flags_62.txt
2025/02/02 15:46 31 flags_63.txt
2025/02/02 15:46 31 flags_64.txt
2025/02/02 15:46 31 flags_65.txt
2025/02/02 15:46 31 flags_66.txt
2025/02/02 15:46 31 flags_67.txt
2025/02/02 15:46 31 flags_68.txt
2025/02/02 15:46 31 flags_69.txt
2025/02/02 15:46 31 flags_70.txt
2025/02/02 15:46 31 flags_71.txt
2025/02/02 15:46 31 flags_72.txt
2025/02/02 15:46 31 flags_73.txt
2025/02/02 15:46 31 flags_74.txt
2025/02/02 15:46 31 flags_75.txt
2025/02/02 15:46 31 flags_76.txt
2025/02/02 15:46 31 flags_77.txt
2025/02/02 15:46 31 flags_78.txt
2025/02/02 15:46 31 flags_79.txt
2025/02/02 15:46 31 flags_80.txt
2025/02/02 15:46 31 flags_81.txt
2025/02/02 15:46 31 flags_82.txt
2025/02/02 15:46 31 flags_83.txt
2025/02/02 15:46 31 flags_84.txt
2025/02/02 15:46 31 flags_85.txt
2025/02/02 15:46 31 flags_86.txt
2025/02/02 15:46 31 flags_87.txt
2025/02/02 15:46 31 flags_88.txt
2025/02/02 15:46 31 flags_89.txt
2025/02/02 15:46 31 flags_90.txt
2025/02/02 15:46 31 flags_91.txt
2025/02/02 15:46 31 flags_92.txt
2025/02/02 15:46 31 flags_93.txt
2025/02/02 15:46 31 flags_94.txt
2025/02/02 15:46 31 flags_95.txt
2025/02/02 15:46 31 flags_96.txt
2025/02/02 15:46 31 flags_97.txt
2025/02/02 15:46 31 flags_98.txt
2025/02/02 15:46 31 flags_99.txt
[REDACTED] 個のファイル [REDACTED] バイト
[REDACTED] 個のディレクトリ [REDACTED] バイトの空き領域
FR/InSecureApk (300 points)
問題
管理者だけが使えるAndroidアプリを作成しました。 このアプリはパスワードを入れないと使うことができません。 そのパスワードがフラグとなっています。
【回答書式】 flag{n文字のアルファベット}
FR-5.apkというAPKファイルが添付されている。
メモ
FR-5.apkをJADXでデコンパイル。
jp.go.cybercontest.insecureapk.MainActivityは、以下のようなコードになっていた。
package jp.go.cybercontest.insecureapk;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btClick = (Button) findViewById(R.id.button);
AppListener listener = new AppListener(this, null);
btClick.setOnClickListener(listener);
}
private class AppListener implements View.OnClickListener {
private AppListener() {
}
/* synthetic */ AppListener(MainActivity x0, AnonymousClass1 x1) {
this();
}
@Override // android.view.View.OnClickListener
public void onClick(View view) {
EditText input = (EditText) MainActivity.this.findViewById(R.id.inputText);
TextView output = (TextView) MainActivity.this.findViewById(R.id.flush);
int id = view.getId();
if (id == R.id.button) {
String inputStr = input.getText().toString();
if (inputStr.length() != 16) {
output.setText("Incorrect.");
return;
}
String compare = SecretGenerater.decode(inputStr);
if (compare.equals("VUSTIq@H~]wGSBVH")) {
output.setText("Congratulations! you got flag.");
} else {
output.setText("Incorrect.");
}
}
}
}
}
怪しいのはここ。
String compare = SecretGenerater.decode(inputStr);
if (compare.equals("VUSTIq@H~]wGSBVH")) {
output.setText("Congratulations! you got flag.");
} else {
output.setText("Incorrect.");
}
Java側でユーザの入力した文字列をSecretGenerater.decode(inputStr)
に渡し、その戻り値と"VUSTIq@H~]wGSBVH"
との比較を行っている。
jp.go.cybercontest.insecureapk.SecretGeneraterは、以下のようなコードになっていた。
package jp.go.cybercontest.insecureapk;
/* loaded from: classes3.dex */
public class SecretGenerater {
public static native String checkNative(String str);
static {
System.loadLibrary("insecureapp");
}
public static String decode(String str) {
String checkLength = checkNative(str);
if (checkLength.length() == 16) {
return checkLength;
}
return "";
}
}
SecretGenerater.decode()
では、内部でinsecureapp.soで実装されているネイティブ関数checkNative()
を呼び出していることがわかる。
insecureapp.soをGhidraで逆アセンブル。
「Search Program Text」で、「checkNative」で検索すると、以下の関数がヒットする。
undefined8
Java_jp_go_cybercontest_insecureapk_SecretGenerater_checkNative
(_JNIEnv *param_1,undefined8 param_2,_jstring *param_3)
{
byte bVar1;
byte bVar2;
long lVar3;
char *pcVar4;
byte *pbVar5;
undefined8 uVar6;
ulong local_80;
basic_string<> abStack_48 [24];
basic_string<> abStack_30 [24];
long local_18;
lVar3 = tpidr_el0;
local_18 = *(long *)(lVar3 + 0x28);
pcVar4 = (char *)_JNIEnv::GetStringUTFChars(param_1,param_3,(uchar *)0x0);
std::__ndk1::basic_string<>::basic_string<>(abStack_30,pcVar4);
/* try { // try from 0011edb4 to 0011edc3 has its CatchHandler @ 0011ee44 */
std::__ndk1::basic_string<>::basic_string<>(abStack_48,"0923200802022025");
for (local_80 = 0; local_80 < 0xf || local_80 - 0xf == 0; local_80 = local_80 + 1) {
pbVar5 = (byte *)FUN_0011ef98(local_80 - 0xf,abStack_30,local_80);
bVar1 = *pbVar5;
pbVar5 = (byte *)FUN_0011efc8(abStack_48,local_80);
bVar2 = *pbVar5;
pbVar5 = (byte *)FUN_0011ef98(abStack_30,local_80);
*pbVar5 = bVar1 ^ bVar2;
}
FUN_0011f02c(abStack_30);
/* try { // try from 0011ee6c to 0011ee6f has its CatchHandler @ 0011eeb0 */
uVar6 = _JNIEnv::NewStringUTF((char *)param_1);
std::__ndk1::basic_string<>::~basic_string(abStack_48);
std::__ndk1::basic_string<>::~basic_string(abStack_30);
lVar3 = tpidr_el0;
lVar3 = *(long *)(lVar3 + 0x28) - local_18;
if (lVar3 == 0) {
return uVar6;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail(lVar3);
}
abStack_30
がJava側でユーザの入力した文字列、abStack_48
が"0923200802022025"
という文字列を格納している。
forループ内の処理では、abStack_30
から1文字(1バイト)取得したものと、abStack_48
から1文字(1バイト)取得したもののXOR演算を行っており、上記のXOR演算を取得する文字を変更しながら、16回繰り返していることがわかる。
上記の処理から、abstack_30
を特定するPythonコードは以下のようになる。
abstack_48 = "0923200802022025"
checknative_result = "VUSTIq@H~]wGSBVH"
abstack_30 = ""
for adstack_char, checknative_result_char in zip(abstack_48, checknative_result):
print("adstack_char: " + adstack_char + ", " + "checknative_result_char:", checknative_result_char)
abStack_30_char = ord(checknative_result_char) ^ ord(adstack_char)
abstack_30 += chr(abStack_30_char)
print("abstack_30:", abstack_30)
上記を実行させた結果は以下のようになる。
$ python analyze_insecureapk.py
adstack_char: 0, checknative_result_char: V
adstack_char: 9, checknative_result_char: U
adstack_char: 2, checknative_result_char: S
adstack_char: 3, checknative_result_char: T
adstack_char: 2, checknative_result_char: I
adstack_char: 0, checknative_result_char: q
adstack_char: 0, checknative_result_char: @
adstack_char: 8, checknative_result_char: H
adstack_char: 0, checknative_result_char: ~
adstack_char: 2, checknative_result_char: ]
adstack_char: 0, checknative_result_char: w
adstack_char: 2, checknative_result_char: G
adstack_char: 2, checknative_result_char: S
adstack_char: 0, checknative_result_char: B
adstack_char: 2, checknative_result_char: V
adstack_char: 5, checknative_result_char: H
abstack_30: flag{AppNoGuard}
フラグ: flag{AppNoGuard}
FR/chemistry (300 points)
問題
添付のプログラムは実行時に引数として数字を与えることができます。 このプラグラムで「FLAG I AM LUCKY」と表示させるための引数を答えてください。
複数の引数を送る場合は、「,(カンマ)」で区切ってください。 スペースは「0」を送ってください。
【回答書式】 flag{数値,数値,.....}
FR-4というプログラムが添付されている。
メモ
FR-4をGhidraで逆アセンブル。
「Search Program Text」で、「main」で検索すると、main関数がヒットする。
undefined8 main(undefined8 param_1,long param_2)
{
char *local_10;
if (*(long *)(param_2 + 8) == 0) {
printf("[INPUT CODE]");
}
else {
local_10 = strtok(*(char **)(param_2 + 8),",");
asciiChange(local_10);
while (local_10 != (char *)0x0) {
local_10 = strtok((char *)0x0,",");
if (local_10 != (char *)0x0) {
asciiChange(local_10);
}
}
}
putchar(10);
return 0;
}
main関数では、実行時引数のカンマ区切り数字それぞれに対して、asciiChange()
関数を呼び出していることがわかる。
asciiChange()
関数は以下のようになっている。
void asciiChange(char *param_1)
{
int iVar1;
undefined8 local_68;
undefined8 local_60;
undefined8 local_58;
undefined8 local_50;
undefined8 local_48;
undefined8 local_40;
undefined8 local_38;
undefined8 local_30;
undefined8 local_28;
undefined8 local_20;
int local_c;
local_68 = 0x747468206c727563;
local_60 = 0x3472662f2f3a7370;
local_58 = 0x6e6977353230322e;
local_50 = 0x656279632d726574;
local_48 = 0x747365746e6f6372;
local_40 = 0x6568632f74656e2e;
local_38 = 0x663f79727473696d;
local_30 = 0x3d6465655367616c;
local_28 = 0;
local_20 = 0;
strcat((char *)&local_68,param_1);
local_c = atoi(param_1);
if ((local_c < 0) || (0x76 < local_c)) {
printf("[CODE ERROR]");
}
else {
iVar1 = system((char *)&local_68);
if (iVar1 == -1) {
printf("[Command Error[");
}
}
return;
}
着目すべきは以下。
local_68 = 0x747468206c727563;
local_60 = 0x3472662f2f3a7370;
local_58 = 0x6e6977353230322e;
local_50 = 0x656279632d726574;
local_48 = 0x747365746e6f6372;
local_40 = 0x6568632f74656e2e;
local_38 = 0x663f79727473696d;
local_30 = 0x3d6465655367616c;
リトルエンディアンで格納された文字列になっており、ASCIIに変換すると、curl https://fr4.2025winter-cybercontest.net/chemistry?flagSeed=
となる。
その後の
strcat((char *)&local_68,param_1);
で、ユーザが入力した各数字を結合し、
iVar1 = system((char *)&local_68);
でシェルコマンドとして実行するという形になっている。
https://fr4.2025winter-cybercontest.net/chemistry?flagSeed=の振る舞いを調べるために、以下のPythonコードを実装した。
import requests
url_base = "https://fr4.2025winter-cybercontest.net/chemistry?flagSeed={}"
results = []
for i in range(1, 121):
url = url_base.format(i)
try:
response = requests.get(url)
text = response.text.strip()
except Exception as e:
text = f"Error: {e}"
results.append((i, text))
# Markdown形式で出力
print("| flagSeed | Response |")
print("|----------|----------|")
for seed, resp in results:
resp_clean = resp.replace("\n", " ").replace("|", "\\|")
print(f"| {seed} | {resp_clean} |")
結果は以下のようになる。
flagSeed | Response |
---|---|
1 | H |
2 | HE |
3 | LI |
4 | BE |
5 | B |
6 | C |
7 | N |
8 | O |
9 | F |
10 | NE |
11 | NA |
12 | MA |
13 | AL |
14 | SI |
15 | P |
16 | S |
17 | CL |
18 | AR |
19 | K |
20 | CA |
21 | SC |
22 | TI |
23 | V |
24 | CR |
25 | MN |
26 | FE |
27 | CO |
28 | NI |
29 | CU |
30 | ZN |
31 | GA |
32 | GE |
33 | AS |
34 | SE |
35 | BR |
36 | KR |
37 | RB |
38 | SR |
39 | Y |
40 | ZR |
41 | NB |
42 | MO |
43 | TC |
44 | RU |
45 | RH |
46 | PD |
47 | AG |
48 | CD |
49 | IN |
50 | SN |
51 | SB |
52 | TE |
53 | I |
54 | XE |
55 | CS |
56 | BA |
57 | LA |
58 | CE |
59 | PR |
60 | ND |
61 | PM |
62 | SM |
63 | EU |
64 | GD |
65 | TB |
66 | DY |
67 | HO |
68 | ER |
69 | TM |
70 | YB |
71 | LU |
72 | HF |
73 | TA |
74 | W |
75 | RE |
76 | OS |
77 | IR |
78 | PT |
79 | AU |
80 | HG |
81 | TI |
82 | PB |
83 | BI |
84 | PO |
85 | AT |
86 | RN |
87 | FR |
88 | RA |
89 | AC |
90 | TH |
91 | PA |
92 | U |
93 | NP |
94 | PU |
95 | AM |
96 | CM |
97 | BK |
98 | CF |
99 | ES |
100 | FM |
101 | MD |
102 | NO |
103 | LR |
104 | RF |
105 | DB |
106 | SG |
107 | BH |
108 | HS |
109 | MT |
110 | DS |
111 | RG |
112 | CN |
113 | NH |
114 | FL |
115 | MC |
116 | LV |
117 | TS |
118 | OG |
119 | -- |
120 | -- |
上記で、「FLAG I AM LUCKY」となる数字の組み合わせは、
114,47,0,53,0,95,0,71,6,19,39
である。
$ ./FR-4 114,47,0,53,0,95,0,71,6,19,39
FLAG I AM LUCKY
フラグ: flag{114,47,0,53,0,95,0,71,6,19,39}
さいごに
もっと勉強して、CTF力をあげていきたいと思います。
最後に、開催していただいた防衛省様、運営の株式会社AGESTに感謝申し上げます。
Discussion