# idekCTF 2025 Writeup >By: TAS.ElPulga ## Misc ### z - easy @daq ![image](https://hackmd.io/_uploads/S1tHTP1_xl.png) Challenge này cần phải giải 9 bài OSINT để có được flag #### taxi ![image](https://hackmd.io/_uploads/rJS5pD1ueg.png) Dựa vào dòng chữ trên xe taxi là `Vulcano Taxi Santi`, tra google thấy được đó là dịch vụ taxi ở Vulcano Porto Mò google streetview một lúc trên hòn đảo đó tìm được vị trí chính xác là https://maps.app.goo.gl/c7LDRr9aH6UicBTV7 #### bridge ![image](https://hackmd.io/_uploads/SyIcRP1_xg.png) Dựa tòa tháp bên kia sông, tìm được đó là ở Macau ![image](https://hackmd.io/_uploads/BkSzkdkdlx.png) ![image](https://hackmd.io/_uploads/SJfEJu1uxe.png) Vị trí chính xác là https://maps.app.goo.gl/g6pb1WoGwTgfhvBm9 #### site ![image](https://hackmd.io/_uploads/HkgaJuJOlg.png) Có một dải màu trắng khá đặc biệt, thử reverse search nó ![image](https://hackmd.io/_uploads/HyGnJukuex.png) Có được địa chỉ là Pamukklae, mò ở gần đó và tìm được vị trí chính xác là https://maps.app.goo.gl/jTTTEEKZvCGaaxjr7 #### sanic ![image](https://hackmd.io/_uploads/BJzAgOJ_el.png) Để ý biển báo, hơi mờ nhưng có vẻ như bên phải là chỉ đường đi Houston còn bên trái là chỉ đường đi San Antonio Sử dụng ChatGPT thì tìm được chỗ này gần Seguin, Texas. ![image](https://hackmd.io/_uploads/ryuXZdkdge.png) Có thể thấy là gần đoạn nhập làn cao tốc, tìm trên google streetview những chỗ đó và tìm được vị trí chính xác là https://maps.app.goo.gl/eVnVsP5WWX51gu2s7 #### no_entry ![image](https://hackmd.io/_uploads/SJFEV_1dgx.png) Dựa vào bảng này thì tìm ra đây là ở Ấn Độ, vùng Mizo Tìm một lúc thì ra vị trí chính xác là https://maps.app.goo.gl/SeAApx8PRg1Arjcc8 #### parking ![image](https://hackmd.io/_uploads/BJEJBdkdxg.png) ![image](https://hackmd.io/_uploads/r1kxH_k_xl.png) Dựa vào tòa tháp và nhà nhỏ bên dưới, cùng với bãi đỗ xe, tìm ra đây là Parkplatz ở Đan Mạch Vị trí chính xác là https://maps.app.goo.gl/L3ZzZ5dd3dGuGbgN6 #### tour_bus ![image](https://hackmd.io/_uploads/BydJUuydll.png) Dựa vào xe bus và người bên trong, có thể thấy đây là ở Nga ![image](https://hackmd.io/_uploads/r1LML_kdgl.png) ![image](https://hackmd.io/_uploads/ByyQUuJ_el.png) Còn có thể thấy đây có vẻ là một đường đi của tour nào đó mà ở giữa 2 dãy núi Thu hẹp phạm vi tìm kiếm ở bán đảo Kamchatka và mò dọc theo con đường duy nhất chạy giữa 2 dãy núi ở đó và tìm được vị trí chính xác là https://maps.app.goo.gl/Z5D4UAGT3yL5G97Y9 #### dragon ![image](https://hackmd.io/_uploads/SJbJwO1Olg.png) Thấy có 1 cái nhà khá đặc trưng, thử reverse search ![image](https://hackmd.io/_uploads/HkwWDd1uge.png) Tìm được là ở Twin Hearts Stone Weir, vị trí chính xác là https://maps.app.goo.gl/QRVy7fUqvpbmcoU36 #### 2011 ![image](https://hackmd.io/_uploads/S1l_Du1Ogx.png) Dựa vào loại nhà như này và khung cảnh xung quanh, ChatGPT gợi ý là ở **Holmon, Thụy Điển** Khi mò streetview ở phía Bắc của hòn đảo này thấy một khu vực rất tương tự, kể cả glitch dưới đây ![image](https://hackmd.io/_uploads/HJ5hD_Jdxg.png) Chưa tìm được vị trí chính xác nhưng submit bừa khu vực xung quanh đấy thì lại ăn may đúng ![image](https://hackmd.io/_uploads/rkRxuO1_xe.png) Ở đâu đó gần chỗ này https://maps.app.goo.gl/kzwSUwvdkXocZ4sC7 Sau khi tìm được đủ 9/10 challenge osint thì flag xuất hiện ở menu ![image](https://hackmd.io/_uploads/rkS_dd1dxx.png) ``` FLAG: idek{f4k3_m3t45_ce6362bd242f34} ``` ### gacha-gate @hphuc204 ![image](https://hackmd.io/_uploads/By6ivU1uee.png) Sau khi giải nén ta được ```python attachments/ attachments/server.py ``` Ta sẽ xem thử trong sever.py có gì ```python #!/usr/bin/env python3 import contextlib import os import random import re import signal import sys from z3 import ArithRef, BitVec, BitVecRef, BitVecVal, Solver, simplify, unsat WIDTH = 32 OPS = ['~', '&', '^', '|'] MAX_DEPTH = 10 FLAG = os.getenv('FLAG', 'idek{fake_flag}') VARS = set('iIl') def rnd_const() -> tuple[str, BitVecRef]: v = random.getrandbits(WIDTH) return str(v), BitVecVal(v, WIDTH) def rnd_var() -> tuple[str, BitVecRef]: name = ''.join(random.choices(tuple(VARS), k=10)) return name, BitVec(name, WIDTH) def combine( op: str, left: tuple[str, BitVecRef], right: tuple[str, BitVecRef] | None = None, ) -> tuple[str, ArithRef]: if op == '~': s_left, z_left = left return f'(~{s_left})', ~z_left s_l, z_l = left s_r, z_r = right return f'({s_l} {op} {s_r})', { '&': z_l & z_r, '^': z_l ^ z_r, '|': z_l | z_r, }[op] def random_expr(depth: int = 0) -> tuple[str, ArithRef]: if depth >= MAX_DEPTH or random.random() < 0.1: return random.choice((rnd_var, rnd_const))() op = random.choice(OPS) if op == '~': return combine(op, random_expr(depth + 1)) return combine(op, random_expr(depth + 1), random_expr(depth + 1)) TOKEN_RE = re.compile(r'[0-9]+|[iIl]+|[~&^|]') def parse_rpn(s: str) -> ArithRef: tokens = TOKEN_RE.findall(s) if not tokens: raise ValueError('empty input') var_cache: dict[str, BitVecRef] = {} stack: list[BitVecRef] = [] for t in tokens: if t.isdigit(): stack.append(BitVecVal(int(t), WIDTH)) elif re.fullmatch(r'[iIl]+', t): if t not in var_cache: var_cache[t] = BitVec(t, WIDTH) stack.append(var_cache[t]) elif t in OPS: if t == '~': if len(stack) < 1: raise ValueError('stack underflow') a = stack.pop() stack.append(~a) else: if len(stack) < 2: raise ValueError('stack underflow') b = stack.pop() a = stack.pop() stack.append({'&': a & b, '^': a ^ b, '|': a | b}[t]) else: raise ValueError(f'bad token {t}') if len(stack) != 1: raise ValueError('malformed expression') return stack[0] def equivalent(e1: ArithRef, e2: ArithRef) -> tuple[bool, Solver]: s = Solver() s.set(timeout=5000) s.add(simplify(e1) != simplify(e2)) return s.check() == unsat, s def _timeout_handler(_: int, __) -> None: raise TimeoutError def main() -> None: signal.signal(signal.SIGALRM, _timeout_handler) print('lets play a game!') for _ in range(50): random.seed() expr_str, expr_z3 = random_expr() print(expr_str, flush=True) signal.alarm(5) try: line = sys.stdin.readline() signal.alarm(0) except TimeoutError: print('too slow!') return try: rpn_z3 = parse_rpn(line.strip()) except Exception as e: print('invalid input:', e) return print('let me see..') is_eq, s = equivalent(expr_z3, rpn_z3) if not is_eq: print('wrong!') with contextlib.suppress(BaseException): print('counter example:', s.model()) return print(FLAG) if __name__ == '__main__': main() ``` Thử truy cập vào server ```nc gacha-gate.chal.idek.team 1337``` xem có gì ```python == proof-of-work: disabled == lets play a game! ((~(((1826740726 ^ (((~((illlllIIIl | llIiilIlII) & (3340018179 | 2505753133))) ^ (~((3277586387 ^ 2171724908) | (3370115072 & llliliIiii)))) ^ (((~(~1069264708)) ^ ((3746618499 & 54360827) ^ (~2269633560))) ^ (~((2877412742 | iIiIIlIlil) & (~IlIillilil)))))) ^ (((~((~(iiilIIIiIl & 2715567444)) ^ ((~llIlliIlli) & (3228664518 | lIliIlIiiI)))) | ((((lIlIiiIlIi & lIIiIllill) | (1366321256 | 1812863215)) & ((illIllIIll | 4063932483) & (iiIllllIil | 3858865606))) ^ (~(~(1287990978 ^ 2919476272))))) ^ 428164373)) & (((((~(~(ilIIllIIli & iIlllIIili))) & (((~IIIiiIiili) & (911988901 | 1934963093)) | ((IlIIiIIIII ^ 1063856059) ^ iliIliiiiI))) & ((((~IIIIllllii) ^ (1109922622 & lIlIIiIllI)) ^ (~(ililllIiIi | iIIIIIIIIi))) | ((~(lIIIiilill ^ 316462482)) & ((~2519592857) | (iIilIlIiiI | 2836117025))))) & ((IliiilIlIi | ((~(IiiIliiiii | 967440967)) | ((~iIiiIiIIlI) | 2513808743))) | (~((~iiliIlIllI) & (~(~IiIliIIill)))))) | ((((((IliIIlIllI & 251369565) | (IlilIiiIiI & 2159635066)) ^ (~(~350424569))) ^ ((~(2358223589 ^ IIlllilIii)) & ((IlliilIlil | lllIIIllII) ^ (IIlliIIIIi ^ 3603415135)))) | ((liiIiilIli ^ ((~2458977787) & (lilillllll ^ ilIiIliilI))) & (((illliiliIl & 687267710) ^ (1560201300 ^ lilIiilili)) ^ ((3855568014 | 1633230165) ^ (lIlIiilIli ^ IIliliIiIi))))) & lllilIlIil)))) ^ ((((~((((~(1092642433 ^ IIIIIliiIi)) & ((1411309612 | lIiiiiliII) ^ (3704857912 ^ lilIilIiII))) & ((~ililIIiIIi) | ((3437961719 | IllliiiIlI) ^ (iIliIIiIil ^ 1317697808)))) | ((((47402168 | 3609982486) | (~illiilllli)) ^ (~(1204039911 ^ 3448837036))) | (837909739 & ((3963554296 ^ liIiliiiii) & (IiIIlliilI & 3979564845)))))) & (iiilIliiIi & ((~(((1171274201 & IIIiililIi) ^ (765661848 ^ 1202856496)) | ((~IilliIilli) & (4151317126 | liIliIIiil)))) | (((~(lllIiIiIII | llIliiIiii)) | ((3020839615 & 2333575048) ^ 1735575677)) ^ (((3254092307 & ililIIIIiI) | (IIiIIliiIl ^ 3509869649)) & ((lliliiiilI | 1306350341) & (lliIIiiliI & 1536735397))))))) | (((((((~357658369) & (liililiIiI & 4105512550)) & ((1824632396 | 15278696) & (1730267306 | 4109074556))) | (((~lIiiiIilii) ^ (445549869 & 3189795287)) ^ ((IIiIliliii | IIIIlillIl) & (1138549645 & illlIlliIl)))) ^ (~(~546646643))) | (~(~(((3387209451 & 618648913) | (2777460002 | 1805896021)) ^ (745384422 ^ (4078666784 | 3965471813)))))) ^ 123391639)) ^ (~(((~234308468) | (~((((2070643626 & 1538239433) ^ (~4071022283)) ^ ((~IiIililllI) | (~IIIilIlllI))) | (((IIIiiiilll ^ 1667683125) & 1627363368) ^ (~(~3923482784)))))) | (~((~(~1953235523)) & (~((~(illiiiiIII | iilIliIIil)) | ((~1813064594) ^ (115440384 | IiiIiliill)))))))))) too slow! ``` ```too slow!``` là output trả ra khi ta không nhập gì và nó sẽ tự out server Phân tích kĩ trong ```server.py``` có ```expr_str, expr_z3 = random_expr()``` có nghĩa là tạo ra một biểu thức và một object z3 tương ứng. Khi ta nhập input thì server đợi nhập RPN tương đương ```123456789 IlIiIiIil ~ | iIiIlIlIi ~ &``` và so sánh giá trị biểu thức với z3 ```equivalent(expr_z3, rpn_z3)``` nếu Z3 xác định hai biểu thức là tương đương logic thì sẽ bước qua vòng tiếp theo. Vì vậy ta cần viết một parser chuyển từ infix sang RPN ví dụ như ```(~x & (y ^ z)) | a → x ~ y z ^ & a |``` Do đó ta sẽ viết một đoạn script tự động chơi game có hàm chuyển infix sang postfix (RPN) và nó sẽ tự động solve 50 round ```python from pwn import * import re # Độ ưu tiên và hướng kết hợp của toán tử precedence = {'~': 3, '&': 2, '^': 1, '|': 1} associativity = {'~': 'right', '&': 'left', '^': 'left', '|': 'left'} def infix_to_rpn(expr: str) -> str: tokens = re.findall(r'[0-9]+|[iIl]+|[~&^|()]', expr) output = [] stack = [] for token in tokens: if token.isdigit() or re.fullmatch(r'[iIl]+', token): output.append(token) elif token in precedence: while stack and stack[-1] in precedence: if (associativity[token] == 'left' and precedence[token] <= precedence[stack[-1]]) or \ (associativity[token] == 'right' and precedence[token] < precedence[stack[-1]]): output.append(stack.pop()) else: break stack.append(token) elif token == '(': stack.append(token) elif token == ')': while stack and stack[-1] != '(': output.append(stack.pop()) stack.pop() while stack: output.append(stack.pop()) return ' '.join(output) def solve(): conn = remote("gacha-gate.chal.idek.team", 1337) conn.recvline_contains(b"lets play a game!") for i in range(50): expr = conn.recvline().decode().strip() rpn = infix_to_rpn(expr) conn.sendline(rpn) out = conn.recvline().decode().strip() if "wrong!" in out or "invalid input" in out or "too slow" in out: print(f"[{i+1}/50] ❌ Failed: {out}") return print(f"[{i+1}/50] ✓ {expr} → {rpn}") flag = conn.recvline().decode().strip() print("\n🎉 FLAG:", flag) if __name__ == '__main__': solve() ``` Cuối cùng thì ouput chính cũng xuất hiện ![image](https://hackmd.io/_uploads/ryRVp81Oxx.png) ``` FLAG: idek{n4nds_r_funct10nally_c0mpl3t3!} ``` ## Rev ### constructor @hphuc204 ![image](https://hackmd.io/_uploads/r1CGdwjPlg.png) Sau khi tải về ta sẽ được file có tên là ```constructor.tar.gz``` Ta sẽ giải nén nó: ![image](https://hackmd.io/_uploads/SJL2dvjvxl.png) Thư mục ```attachments/``` bên trong chứa file tên là ```chall``` thì ta sẽ kiểm tra chall ![image](https://hackmd.io/_uploads/HkZLtwowge.png) Khi thấy file ```chall```là một ```ELF 64-bit executable``` ta sẽ chạy thử file xem bên trong nó có gì ![image](https://hackmd.io/_uploads/HJu3FDiPex.png) Sau khi dùng ```gdb``` để kiểm tra ```strings``` nhưng không tìm thấy flag trong plaintext nên ta sẽ dùng ```radare2``` để xác định phân tích static rồi dùng ```gdb``` sau Ta sẽ dùng ```radare2``` để mở nó ra ```r2 -A attachments/chall``` ```pyth WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time INFO: Analyze all flags starting with sym. and entry0 (aa) INFO: Analyze imports (af@@@i) INFO: Analyze entrypoint (af@ entry0) INFO: Analyze symbols (af@@@s) INFO: Analyze all functions arguments/locals (afva@@@F) INFO: Analyze function calls (aac) INFO: Analyze len bytes of instructions for references (aar) INFO: Finding and parsing C++ vtables (avrr) INFO: Analyzing methods (af @@ method.*) INFO: Recovering local variables (afva@@@F) INFO: Type matching analysis for all functions (aaft) INFO: Propagate noreturn information (aanr) INFO: Use -AA or aaaa to perform additional experimental analysis [0x0040118b]> ``` Ta sẽ thử liệt kê các function xem sao ```python [0x0040118b]> afl 0x0040118b 2 130 entry0 0x004012c0 34 531 fcn.004012c0 0x00401620 1 5 fcn.00401620 0x00401630 5 61 fcn.00401630 0x004023f0 13 158 fcn.004023f0 0x00401dc0 2 31 fcn.00401dc0 0x00401670 9 184 fcn.00401670 0x00401a10 1 47 fcn.00401a10 0x004019a0 2 39 fcn.004019a0 0x004019c0 1 65 fcn.004019c0 0x004019b0 1 7 fcn.004019b0 0x00401730 3 52 fcn.00401730 0x00401830 19 192 fcn.00401830 0x00401910 6 53 fcn.00401910 0x00401770 12 171 fcn.00401770 0x00401010 1 56 fcn.00401010 0x00401610 1 5 fcn.00401610 0x004011e0 4 34 fcn.004011e0 0x00401b60 25 537 fcn.00401b60 0x004015b0 4 74 fcn.004015b0 0x0040194e 6 50 fcn.0040194e 0x00401000 1 3 fcn.00401000 0x004014f0 4 58 fcn.004014f0 0x00401da0 1 18 fcn.00401da0 0x00402110 1 57 fcn.00402110 0x004022d0 12 110 fcn.004022d0 0x00402230 7 147 fcn.00402230 0x00401ec0 4 66 fcn.00401ec0 0x00401f10 12 163 fcn.00401f10 0x00401de0 12 197 fcn.00401de0 0x0040234d 1 16 fcn.0040234d 0x00401ad0 4 137 fcn.00401ad0 0x004020a0 3 82 fcn.004020a0 0x00402440 12 292 fcn.00402440 0x00402150 16 199 fcn.00402150 0x00402570 1 32 fcn.00402570 0x004025a0 19 193 fcn.004025a0 ``` Ta có thể thấy ```0x00401b60 25 537 fcn.00401b60``` là function constructor vì nó khá dài vậy nên kiểm tra nó xem sao ```python ; CALL XREF from fcn.004012c0 @ 0x4013be(x) ┌ 537: fcn.00401b60 (int64_t arg1); │ `- args(rdi) │ 0x00401b60 f30f1efa endbr64 │ 0x00401b64 53 push rbx │ 0x00401b65 4883ec10 sub rsp, 0x10 │ 0x00401b69 488b4f28 mov rcx, qword [rdi + 0x28] ; arg1 │ 0x00401b6d 4c8b5718 mov r10, qword [rdi + 0x18] ; arg1 │ 0x00401b71 4885c9 test rcx, rcx │ ┌─< 0x00401b74 0f84fe010000 je 0x401d78 │ │ 0x00401b7a 488b7f20 mov rdi, qword [rdi + 0x20] ; arg1 │ │ 0x00401b7e 4c89d0 mov rax, r10 │ │ 0x00401b81 31f6 xor esi, esi │ │ 0x00401b83 4531c0 xor r8d, r8d │ ┌──< 0x00401b86 eb2d jmp 0x401bb5 .. │ ││ ; CODE XREF from fcn.00401b60 @ 0x401bba(x) │ ┌───> 0x00401b90 83fa02 cmp edx, 2 ; 2 │ ┌────< 0x00401b93 0f857f010000 jne 0x401d18 │ │╎││ 0x00401b99 48c7c20000.. mov rdx, 0 │ │╎││ 0x00401ba0 4885d2 test rdx, rdx │ ┌─────< 0x00401ba3 7407 je 0x401bac │ ││╎││ 0x00401ba5 4889d6 mov rsi, rdx │ ││╎││ 0x00401ba8 482b7010 sub rsi, qword [rax + 0x10] │ ││╎││ ; CODE XREFS from fcn.00401b60 @ 0x401ba3(x), 0x401d20(x), 0x401d2e(x), 0x401d42(x), 0x401d5b(x) │ ┌┌└─────> 0x00401bac 4801f8 add rax, rdi ; arg1 │ ╎╎ │╎││ 0x00401baf 4883e901 sub rcx, 1 │ ╎╎┌─────< 0x00401bb3 7417 je 0x401bcc │ ╎╎││╎││ ; CODE XREFS from fcn.00401b60 @ 0x401b86(x), 0x401bca(x) │ ─────└──> 0x00401bb5 8b10 mov edx, dword [rax] │ ╎╎││╎ │ 0x00401bb7 83fa06 cmp edx, 6 ; 6 │ ╎╎││└───< 0x00401bba 75d4 jne 0x401b90 │ ╎╎││ │ 0x00401bbc 4c89d6 mov rsi, r10 │ ╎╎││ │ 0x00401bbf 482b7010 sub rsi, qword [rax + 0x10] │ ╎╎││ │ 0x00401bc3 4801f8 add rax, rdi ; arg1 │ ╎╎││ │ 0x00401bc6 4883e901 sub rcx, 1 │ ────────< 0x00401bca 75e9 jne 0x401bb5 │ ╎╎││ │ ; CODE XREF from fcn.00401b60 @ 0x401bb3(x) │ ╎╎└─────> 0x00401bcc 4d85c0 test r8, r8 │ ╎╎ │ ┌──< 0x00401bcf 0f84a3010000 je 0x401d78 │ ╎╎ │ ││ 0x00401bd5 498b4020 mov rax, qword [r8 + 0x20] │ ╎╎ │ ││ 0x00401bd9 49037010 add rsi, qword [r8 + 0x10] │ ╎╎ │ ││ 0x00401bdd 488d1d5c3a.. lea rbx, [0x00405640] │ ╎╎ │ ││ 0x00401be4 498b5028 mov rdx, qword [r8 + 0x28] │ ╎╎ │ ││ 0x00401be8 488935593a.. mov qword [0x00405648], rsi ; [0x405648:8]=0 │ ╎╎ │ ││ 0x00401bef 4889055a3a.. mov qword [0x00405650], rax ; [0x405650:8]=0 │ ╎╎ │ ││ 0x00401bf6 498b4030 mov rax, qword [r8 + 0x30] │ ╎╎ │ ││ 0x00401bfa 48c705c335.. mov qword [0x004051c8], 1 ; [0x4051c8:8]=0 │ ╎╎ │ ││ 0x00401c05 488905543a.. mov qword [0x00405660], rax ; [0x405660:8]=0 │ ╎╎ │ ││ 0x00401c0c 48891d9d35.. mov qword [0x004051b0], rbx ; [0x4051b0:8]=0 │ ╎╎ │ ││ ; CODE XREF from fcn.00401b60 @ 0x401d8d(x) │ ╎╎ │┌───> 0x00401c13 4801d6 add rsi, rdx │ ╎╎ │╎││ 0x00401c16 488d48ff lea rcx, [rax - 1] │ ╎╎ │╎││ 0x00401c1a 48f7de neg rsi │ ╎╎ │╎││ 0x00401c1d 4821ce and rsi, rcx │ ╎╎ │╎││ 0x00401c20 4801d6 add rsi, rdx │ ╎╎ │╎││ 0x00401c23 488d90df00.. lea rdx, [rax + 0xdf] │ ╎╎ │╎││ 0x00401c2a 488935273a.. mov qword [0x00405658], rsi ; [0x405658:8]=0 │ ╎╎ │╎││ 0x00401c31 488935303a.. mov qword [0x00405668], rsi ; [0x405668:8]=0 │ ╎╎ │╎││ 0x00401c38 4883f807 cmp rax, 7 ; 7 │ ╎╎┌─────< 0x00401c3c 7715 ja 0x401c53 │ ╎╎││╎││ 0x00401c3e 48c705173a.. mov qword [0x00405660], 8 ; [0x405660:8]=0 │ ╎╎││╎││ 0x00401c49 bae7000000 mov edx, 0xe7 ; 231 │ ╎╎││╎││ 0x00401c4e b808000000 mov eax, 8 │ ╎╎││╎││ ; CODE XREF from fcn.00401b60 @ 0x401c3c(x) │ ╎╎└─────> 0x00401c53 4801d6 add rsi, rdx │ ╎╎ │╎││ 0x00401c56 4889056335.. mov qword [0x004051c0], rax ; [0x4051c0:8]=0 │ ╎╎ │╎││ 0x00401c5d 488d3d1c3a.. lea rdi, [0x00405680] │ ╎╎ │╎││ 0x00401c64 4883e6f8 and rsi, 0xfffffffffffffff8 │ ╎╎ │╎││ 0x00401c68 4889354935.. mov qword [0x004051b8], rsi ; [0x4051b8:8]=0 │ ╎╎ │╎││ 0x00401c6f 4881fe5001.. cmp rsi, 0x150 ; 336 │ ╎╎┌─────< 0x00401c76 7621 jbe 0x401c99 │ ╎╎││╎││ 0x00401c78 41ba22000000 mov r10d, 0x22 ; '\"' ; 34 │ ╎╎││╎││ 0x00401c7e 4531c9 xor r9d, r9d │ ╎╎││╎││ 0x00401c81 b809000000 mov eax, 9 │ ╎╎││╎││ 0x00401c86 31ff xor edi, edi │ ╎╎││╎││ 0x00401c88 49c7c0ffff.. mov r8, 0xffffffffffffffff │ ╎╎││╎││ 0x00401c8f ba03000000 mov edx, 3 │ ╎╎││╎││ 0x00401c94 0f05 syscall │ ╎╎││╎││ 0x00401c96 4889c7 mov rdi, rax │ ╎╎││╎││ ; CODE XREF from fcn.00401b60 @ 0x401c76(x) │ ╎╎└─────> 0x00401c99 e832feffff call fcn.00401ad0 │ ╎╎ │╎││ 0x00401c9e 488900 mov qword [rax], rax │ ╎╎ │╎││ 0x00401ca1 66480f6ec0 movq xmm0, rax │ ╎╎ │╎││ 0x00401ca6 4889c7 mov rdi, rax ; int64_t arg1 │ ╎╎ │╎││ 0x00401ca9 4889c3 mov rbx, rax │ ╎╎ │╎││ 0x00401cac 660f6cc0 punpcklqdq xmm0, xmm0 │ ╎╎ │╎││ 0x00401cb0 0f290424 movaps xmmword [rsp], xmm0 │ ╎╎ │╎││ 0x00401cb4 e894060000 call fcn.0040234d │ ╎╎ │╎││ 0x00401cb9 85c0 test eax, eax │ ╎╎┌─────< 0x00401cbb 0f88ab000000 js 0x401d6c │ ────────< 0x00401cc1 0f8499000000 je 0x401d60 │ ╎╎││╎││ ; CODE XREF from fcn.00401b60 @ 0x401d67(x) │ ────────> 0x00401cc7 c743380200.. mov dword [rbx + 0x38], 2 │ ╎╎││╎││ 0x00401cce b8da000000 mov eax, 0xda ; 218 │ ╎╎││╎││ 0x00401cd3 488d3df63a.. lea rdi, [0x004057d0] │ ╎╎││╎││ 0x00401cda 0f05 syscall │ ╎╎││╎││ 0x00401cdc 894330 mov dword [rbx + 0x30], eax │ ╎╎││╎││ 0x00401cdf 488d05f234.. lea rax, [0x004051d8] │ ╎╎││╎││ 0x00401ce6 660f6f0c24 movdqa xmm1, xmmword [rsp] │ ╎╎││╎││ 0x00401ceb 488983a800.. mov qword [rbx + 0xa8], rax │ ╎╎││╎││ 0x00401cf2 488d838800.. lea rax, [rbx + 0x88] │ ╎╎││╎││ 0x00401cf9 4889838800.. mov qword [rbx + 0x88], rax │ ╎╎││╎││ 0x00401d00 488b057134.. mov rax, qword [0x00405178] ; [0x405178:8]=0 │ ╎╎││╎││ 0x00401d07 0f114b10 movups xmmword [rbx + 0x10], xmm1 │ ╎╎││╎││ 0x00401d0b 48894320 mov qword [rbx + 0x20], rax │ ╎╎││╎││ 0x00401d0f 4883c410 add rsp, 0x10 │ ╎╎││╎││ 0x00401d13 5b pop rbx │ ╎╎││╎││ 0x00401d14 c3 ret .. │ ╎╎││╎││ ; CODE XREF from fcn.00401b60 @ 0x401b93(x) │ ╎╎│└────> 0x00401d18 83fa07 cmp edx, 7 ; 7 │ ╎╎│┌────< 0x00401d1b 750b jne 0x401d28 │ ╎╎││╎││ 0x00401d1d 4989c0 mov r8, rax │ ────────< 0x00401d20 e987feffff jmp 0x401bac .. │ ╎╎││╎││ ; CODE XREF from fcn.00401b60 @ 0x401d1b(x) │ ╎╎│└────> 0x00401d28 81fa51e57464 cmp edx, 0x6474e551 │ ────────< 0x00401d2e 0f8578feffff jne 0x401bac │ ╎╎│ ╎││ 0x00401d34 488b5028 mov rdx, qword [rax + 0x28] │ ╎╎│ ╎││ 0x00401d38 448b0dcd33.. mov r9d, dword [0x0040510c] ; [0x40510c:4]=0x20000 │ ╎╎│ ╎││ 0x00401d3f 4939d1 cmp r9, rdx │ └───────< 0x00401d42 0f8364feffff jae 0x401bac │ ╎│ ╎││ 0x00401d48 41b900008000 mov r9d, 0x800000 │ ╎│ ╎││ 0x00401d4e 4c39ca cmp rdx, r9 │ ╎│ ╎││ 0x00401d51 490f47d1 cmova rdx, r9 │ ╎│ ╎││ 0x00401d55 8915b1330000 mov dword [0x0040510c], edx ; [0x40510c:4]=0x20000 │ └──────< 0x00401d5b e94cfeffff jmp 0x401bac │ │ ╎││ ; CODE XREF from fcn.00401b60 @ 0x401cc1(x) │ ────────> 0x00401d60 c605393400.. mov byte [0x004051a0], 1 ; [0x4051a0:1]=0 │ ────────< 0x00401d67 e95bffffff jmp 0x401cc7 │ │ ╎││ ; CODE XREF from fcn.00401b60 @ 0x401cbb(x) │ └─────> 0x00401d6c f4 hlt .. │ ╎││ ; CODE XREFS from fcn.00401b60 @ 0x401b74(x), 0x401bcf(x) │ ╎└└─> 0x00401d78 488b15d938.. mov rdx, qword [0x00405658] ; [0x405658:8]=0 │ ╎ 0x00401d7f 488b35c238.. mov rsi, qword [0x00405648] ; [0x405648:8]=0 │ ╎ 0x00401d86 488b05d338.. mov rax, qword [0x00405660] ; [0x405660:8]=0 └ └───< 0x00401d8d e981feffff jmp 0x401c13 ``` Có thể thấy ```rax``` chứa địa chỉ của một vùng nhớ được cấp phát hoặc trỏ tới dữ liệu quan trọng và câu lệnh ```mov rbx, rax``` ghi lại địa chỉ này vào thanh ghi ```rbx``` dựa vào ```0x00401ca9 4889c3 mov rbx, rax``` Sau đó dùng ```GDB``` để set breakpoint và chạy chương trình ![image](https://hackmd.io/_uploads/Bym3Rvjwgg.png) Và xem nội dung của ```rbx``` ```python (gdb) x/s $rbx 0x405140: "idek{he4rd_0f_constructors?_now_you_d1d!!}" ``` ``` FLAG: idek{he4rd_0f_constructors?_now_you_d1d!!} ``` ## Crypto @ansobad ### Catch ![image](https://hackmd.io/_uploads/HJ3JntyOel.png) #### Đề bài cho ```python from Crypto.Random.random import randint, choice import os # In a realm where curiosity roams free, our fearless cat sets out on an epic journey. # Even the cleverest feline must respect the boundaries of its world—this magical limit holds all wonders within. limit = 0xe5db6a6d765b1ba6e727aa7a87a792c49bb9ddeb2bad999f5ea04f047255d5a72e193a7d58aa8ef619b0262de6d25651085842fd9c385fa4f1032c305f44b8a4f92b16c8115d0595cebfccc1c655ca20db597ff1f01e0db70b9073fbaa1ae5e489484c7a45c215ea02db3c77f1865e1e8597cb0b0af3241cd8214bd5b5c1491f # Through cryptic patterns, our cat deciphers its next move. def walking(x, y, part): # Each step is guided by a fragment of the cat's own secret mind. epart = [int.from_bytes(part[i:i+2], "big") for i in range(0, len(part), 2)] xx = epart[0] * x + epart[1] * y yy = epart[2] * x + epart[3] * y return xx, yy # Enter the Cat: curious wanderer and keeper of hidden paths. class Cat: def __init__(self): # The cat's starting position is born of pure randomness. self.x = randint(0, 2**256) self.y = randint(0, 2**256) # Deep within, its mind holds a thousand mysterious fragments. while True: self.mind = os.urandom(1000) self.step = [self.mind[i:i+8] for i in range(0, 1000, 8)] if len(set(self.step)) == len(self.step): break # The epic chase begins: the cat ponders and strides toward the horizon. def moving(self): for _ in range(30): # A moment of reflection: choose a thought from the cat's endless mind. part = choice(self.step) self.step.remove(part) # With each heartbeat, the cat takes a cryptic step. xx, yy = walking(self.x, self.y, part) self.x, self.y = xx, yy # When the wild spirit reaches the edge, it respects the boundary and pauses. if self.x > limit or self.y > limit: self.x %= limit self.y %= limit break # When the cosmos beckons, the cat reveals its secret coordinates. def position(self): return (self.x, self.y) # Adventurer, your quest: find and connect with 20 elusive cats. for round in range(20): try: print(f"👉 Hunt {round+1}/20 begins!") cat = Cat() # At the start, you and the cat share the same starlit square. human_pos = cat.position() print(f"🐱✨ Co-location: {human_pos}") print(f"🔮 Cat's hidden mind: {cat.mind.hex()}") # But the cat, ever playful, dashes into the unknown... cat.moving() print("😸 The chase is on!") print(f"🗺️ Cat now at: {cat.position()}") # Your turn: recall the cat's secret path fragments to catch up. mind = bytes.fromhex(input("🤔 Path to recall (hex): ")) # Step by step, follow the trail the cat has laid. for i in range(0, len(mind), 8): part = mind[i:i+8] if part not in cat.mind: print("❌ Lost in the labyrinth of thoughts.") exit() human_pos = walking(human_pos[0], human_pos[1], part) # At last, if destiny aligns... if human_pos == cat.position(): print("🎉 Reunion! You have found your feline friend! 🐾") else: print("😿 The path eludes you... Your heart aches.") exit() except Exception: print("🙀 A puzzle too tangled for tonight. Rest well.") exit() # Triumph at last: the final cat yields the secret prize. print(f"🏆 Victory! The treasure lies within: {open('flag.txt').read()}") """ ``` `nc catch.chal.idek.team 1337` Server gồm **20 vòng**. Mỗi vòng, server sẽ gửi: 1. **Vị trí ban đầu** của bạn và mèo (trùng nhau): $$ (x_0, y_0) $$ 2. **"Tâm trí" của mèo**: - Chuỗi **1000 byte hex**, tương ứng với **125 ma trận $2 \times 2$** - Mỗi ma trận = **8 byte** 3. Mèo di chuyển tối đa **30 bước** (ngẫu nhiên). Sau khi chạy, server gửi **vị trí cuối**: $$ (x_{30}, y_{30}) $$ 4. **Nhiệm vụ**: Gửi lại **chuỗi 30 ma trận** (240 byte hex) đã dùng để "đuổi kịp mèo". → Hoàn thành **20 vòng** sẽ nhận được **flag**. #### Bài làm **Mỗi bước là nhân ma trận** Mỗi khối 8 byte là một ma trận $2 \times 2$: $$ M = \begin{pmatrix} a & b \\ c & d \end{pmatrix}, \quad a, b, c, d \in [0, 2^{16}) $$ **Không bị modulo trong 30 bước** Các hệ số chỉ 16-bit, `limit` là số 1024-bit, nên giá trị $(x, y)$ tăng rất chậm. Trong 30 bước gần như không bao giờ vượt `limit`, nên có thể coi toàn bộ phép biến đổi là nhân ma trận tuần tự: $$ v_f = M_{30} \cdot M_{29} \cdots M_{1} \cdot v_0 $$ **Hồi ngược bằng ma trận nghịch đảo** Vì không có modulo xen giữa, ta có thể bắt đầu từ $v_f$ và lần ngược: - Với mỗi bước ngược, thử tất cả ma trận còn lại trong `mind`. - Tính nghịch đảo: $$ M^{-1} = \frac{1}{\det} \begin{pmatrix} d & -b \\ -c & a \end{pmatrix}, \quad \det = ad - bc $$ - Áp $v_{i-1} = M^{-1} v_i$. - Điều kiện hợp lệ: - $\det \neq 0$ - $x_{i-1}, y_{i-1}$ nguyên và $0 \leq x_{i-1}, y_{i-1} < limit$ - Bước cuối cùng phải khớp $v_0$. --> Nhờ tính chất **hệ số nhỏ**, mỗi bước chỉ có một ma trận thỏa mãn $\Rightarrow$ tìm được đúng chuỗi 30 bước. #### Code: ```py= from pwn import remote import re LIMIT = int("e5db6a6d765b1ba6e727aa7a87a792c49bb9ddeb2bad999f5ea04f047255d5a72e193a7d58aa8ef619b0262de6d25651085842fd9c385fa4f1032c305f44b8a4f92b16c8115d0595cebfccc1c655ca20db597ff1f01e0db70b9073fbaa1ae5e489484c7a45c215ea02db3c77f1865e1e8597cb0b0af3241cd8214bd5b5c1491f", 16) def solve_round(v0, vf, mind_bytes): parts = [mind_bytes[i:i+8] for i in range(0, len(mind_bytes), 8)] rem = parts[:] x, y = vf chosen_rev = [] for step in range(30, 0, -1): for p in rem: a = int.from_bytes(p[0:2], 'big') b = int.from_bytes(p[2:4], 'big') c = int.from_bytes(p[4:6], 'big') d = int.from_bytes(p[6:8], 'big') det = a*d - b*c if det == 0: continue nx = d*x - b*y ny = -c*x + a*y if nx % det or ny % det: continue xp, yp = nx // det, ny // det if not (0 <= xp < LIMIT and 0 <= yp < LIMIT): continue if step == 1 and (xp, yp) != v0: continue chosen_rev.append(p) x, y = xp, yp rem.remove(p) break return b''.join(reversed(chosen_rev)) io = remote("catch.chal.idek.team", 1337) for rnd in range(20): io.recvuntil("Co-location: ") v0 = tuple(map(int, re.findall(r"\d+", io.recvline().decode()))) io.recvuntil("hidden mind: ") mind_bytes = bytes.fromhex(io.recvline().decode().strip()) io.recvuntil("Cat now at: ") vf = tuple(map(int, re.findall(r"\d+", io.recvline().decode()))) path = solve_round(v0, vf, mind_bytes) io.sendline(path.hex()) print(io.recvline().decode().strip()) flag = io.recvuntil("}").decode() print("Flag:", flag) ``` ``` Flag: idek{Catch_and_cat_sound_really_similar_haha} ``` ### Sadness ECC ![image](https://hackmd.io/_uploads/ry0cP5kOgg.png) #### Đề bài: ```py= from Crypto.Util.number import * from secret import n, xG, yG import ast class DummyPoint: O = object() def __init__(self, x=None, y=None): if (x, y) == (None, None): self._infinity = True else: assert DummyPoint.isOnCurve(x, y), (x, y) self.x, self.y = x, y self._infinity = False @classmethod def infinity(cls): return cls() def is_infinity(self): return getattr(self, "_infinity", False) @staticmethod def isOnCurve(x, y): return "<REDACTED>" def __add__(self, other): if other.is_infinity(): return self if self.is_infinity(): return other # ——— Distinct‑points case ——— if self.x != other.x or self.y != other.y: dy = self.y - other.y dx = self.x - other.x inv_dx = pow(dx, -1, n) prod1 = dy * inv_dx s = prod1 % n inv_s = pow(s, -1, n) s3 = pow(inv_s, 3, n) tmp1 = s * self.x d = self.y - tmp1 d_minus = d - 1337 neg_three = -3 tmp2 = neg_three * d_minus tmp3 = tmp2 * inv_s sum_x = self.x + other.x x_temp = tmp3 + s3 x_pre = x_temp - sum_x x = x_pre % n tmp4 = self.x - x tmp5 = s * tmp4 y_pre = self.y - tmp5 y = y_pre % n return DummyPoint(x, y) dy_term = self.y - 1337 dy2 = dy_term * dy_term three_dy2 = 3 * dy2 inv_3dy2 = pow(three_dy2, -1, n) two_x = 2 * self.x prod2 = two_x * inv_3dy2 s = prod2 % n inv_s = pow(s, -1, n) s3 = pow(inv_s, 3, n) tmp6 = s * self.x d2 = self.y - tmp6 d2_minus = d2 - 1337 tmp7 = -3 * d2_minus tmp8 = tmp7 * inv_s x_temp2 = tmp8 + s3 x_pre2 = x_temp2 - two_x x2 = x_pre2 % n tmp9 = self.x - x2 tmp10 = s * tmp9 y_pre2 = self.y - tmp10 y2 = y_pre2 % n return DummyPoint(x2, y2) def __rmul__(self, k): if not isinstance(k, int) or k < 0: raise ValueError("Choose another k") R = DummyPoint.infinity() addend = self while k: if k & 1: R = R + addend addend = addend + addend k >>= 1 return R def __repr__(self): return f"DummyPoint({self.x}, {self.y})" def __eq__(self, other): return self.x == other.x and self.y == other.y if __name__ == "__main__": G = DummyPoint(xG, yG) print(f"{n = }") stop = False while True: print("1. Get random point (only one time)\n2. Solve the challenge\n3. Exit") try: opt = int(input("> ")) except: print("❓ Try again."); continue if opt == 1: if stop: print("Only one time!") else: stop = True k = getRandomRange(1, n) P = k * G print("Here is your point:") print(P) elif opt == 2: ks = [getRandomRange(1, n) for _ in range(2)] Ps = [k * G for k in ks] Ps.append(Ps[0] + Ps[1]) print("Sums (x+y):", [P.x + P.y for P in Ps]) try: ans = ast.literal_eval(input("Your reveal: ")) except: print("Couldn't parse."); continue if all(P == DummyPoint(*c) for P, c in zip(Ps, ans)): print("Correct! " + open("flag.txt").read()) else: print("Wrong...") break else: print("Farewell.") break ``` - **Mã nguồn** `chall.py` với class `DummyPoint` mô phỏng cộng/nhân điểm ECC giả, tham số bí mật $n$, $x_G$, $y_G$ lấy từ `secret.py` (không cho). - Chạy chương trình sẽ có menu: 1. Lấy 1 điểm ngẫu nhiên $P = kG$ (chỉ 1 lần). 2. Server sinh: $$ P_1 = k_1 G,\quad P_2 = k_2 G,\quad P_3 = P_1 + P_2 $$ In ra mỗi $P.x + P.y$, yêu cầu bạn nhập lại tọa độ 3 điểm. - Nếu tất cả điểm bạn nhập khớp → in ra **flag** từ `flag.txt`. #### Bài làm: Bài này có bug logic ở đoạn check: ```python if all(P == DummyPoint(*c) for P, c in zip(Ps, ans)): print("Correct! " + open("flag.txt").read()) ``` - `zip(Ps, ans)` sẽ chỉ lặp tới khi **1 trong 2 danh sách hết phần tử**. - Nếu bạn gửi `ans = []` thì `zip(...)` rỗng ⇒ `all([])` luôn **True** (*vacuous truth*). - Nghĩa là **không cần tính toán gì**, chỉ cần gửi `[]` ở phần *"Your reveal"* là nhận flag. > **Giải thích**: > - Đây là một lỗi kiểm tra điều kiện rỗng trong Python. > - `all([])` trả về `True` vì không có phần tử nào vi phạm điều kiện (mặc định đúng). > - Kẻ tấn công có thể lợi dụng bằng cách gửi danh sách trống. #### Code ```py= import socket HOST, PORT = "sad-ecc.chal.idek.team", 1337 s = socket.create_connection((HOST, PORT)) print(s.recv(4096).decode(), end="") # menu s.sendall(b"2\n") # chọn solve print(s.recv(4096).decode(), end="") # sums + prompt s.sendall(b"[]\n") # gửi danh sách rỗng print(s.recv(4096).decode()) # flag s.close() ``` ``` Flag: idek{the_idea_came_from_a_Vietnamese_high_school_Mathematical_Olympiad_competition_xD} ``` ### Happy ECC ![image](https://hackmd.io/_uploads/B19uCcyOgg.png) #### Đề bài ```py from sage.all import * from Crypto.Util.number import * # Edited a bit from https://github.com/aszepieniec/hyperelliptic/blob/master/hyperelliptic.sage class HyperellipticCurveElement: def __init__( self, curve, U, V ): self.curve = curve self.U = U self.V = V @staticmethod def Cantor( curve, U1, V1, U2, V2 ): # 1. g, a, b = xgcd(U1, U2) # a*U1 + b*U2 == g d, c, h3 = xgcd(g, V1+V2) # c*g + h3*(V1+V2) = d h2 = c*b h1 = c*a # h1 * U1 + h2 * U2 + h3 * (V1+V2) = d = gcd(U1, U2, V1-V2) # 2. V0 = (U1 * V2 * h1 + U2 * V1 * h2 + (V1*V2 + curve.f) * h3).quo_rem(d)[0] R = U1.parent() V0 = R(V0) # 3. U = (U1 * U2).quo_rem(d**2)[0] U = R(U) V = V0 % U while U.degree() > curve.genus: # 4. U_ = (curve.f - V**2).quo_rem(U)[0] U_ = R(U_) V_ = (-V).quo_rem(U_)[1] # 5. U, V = U_.monic(), V_ # (6.) # 7. return U, V def parent( self ): return self.curve def __add__( self, other ): U, V = HyperellipticCurveElement.Cantor(self.curve, self.U, self.V, other.U, other.V) return HyperellipticCurveElement(self.curve, U, V) def inverse( self ): return HyperellipticCurveElement(self.curve, self.U, -self.V) def __rmul__(self, exp): R = self.U.parent() I = HyperellipticCurveElement(self.curve, R(1), R(0)) if exp == 0: return HyperellipticCurveElement(self.curve, R(1), R(0)) if exp == 1: return self acc = I Q = self while exp: if exp & 1: acc = acc + Q Q = Q + Q exp >>= 1 return acc def __eq__( self, other ): if self.curve == other.curve and self.V == other.V and self.U == other.U: return True else: return False class HyperellipticCurve_: def __init__( self, f ): self.R = f.parent() self.F = self.R.base_ring() self.x = self.R.gen() self.f = f self.genus = floor((f.degree()-1) / 2) def identity( self ): return HyperellipticCurveElement(self, self.R(1), self.R(0)) def random_element( self ): roots = [] while len(roots) != self.genus: xi = self.F.random_element() yi2 = self.f(xi) if not yi2.is_square(): continue roots.append(xi) roots = list(set(roots)) signs = [ZZ(Integers(2).random_element()) for r in roots] U = self.R(1) for r in roots: U = U * (self.x - r) V = self.R(0) for i in range(len(roots)): y = (-1)**(ZZ(Integers(2).random_element())) * sqrt(self.f(roots[i])) lagrange = self.R(1) for j in range(len(roots)): if j == i: continue lagrange = lagrange * (self.x - roots[j])/(roots[i] - roots[j]) V += y * lagrange return HyperellipticCurveElement(self, U, V) p = getPrime(40) R, x = PolynomialRing(GF(p), 'x').objgen() f = R.random_element(5).monic() H = HyperellipticCurve_(f) print(f"{p = }") if __name__ == "__main__": cnt = 0 while True: print("1. Get random point\n2. Solve the challenge\n3. Exit") try: opt = int(input("> ")) except: print("❓ Try again."); continue if opt == 1: if cnt < 3: G = H.random_element() k = getRandomRange(1, p) P = k * G print("Here is your point:") print(f"{P.U = }") print(f"{P.V = }") cnt += 1 else: print("You have enough point!") continue elif opt == 2: G = H.random_element() print(f"{(G.U, G.V) = }") print("Give me the order !") odr = int(input(">")) if (odr * G).U == 1: print("Congratz! " + open("flag.txt", "r").read()) else: print("Wrong...") break else: print("Farewell.") break ``` *Phân tích đề bài* - Khi kết nối vào challenge bằng `nc`, đôi khi ta thấy xuất hiện bài toán **proof-of-work** (*PoW*): - PoW yêu cầu ta tìm số nguyên `n` sao cho đoạn hash **MD5** khớp với chuỗi đã cho. Nếu không có PoW, server hiển thị một menu như sau: >1. Get random point >2. Solve the challenge *Phân tích `chall.py`* - File `chall.py` sử dụng một thư viện toán học tùy chỉnh cho **Hyperelliptic Curve Cryptography (HECC)** – một loại curve tổng quát hơn elliptic curve. - Thành phần chính: - Curve hoạt động trên một trường hữu hạn lớn $(GF(p))$. - Thay vì điểm $(x, y)$ như trong ECC, trong HECC ta làm việc với các *divisor* dạng $(U, V)$ là các đa thức. - Bài toán yêu cầu tìm một số nguyên `order` sao cho: ```python (order * G).U == 1 ``` > **Quan trọng**: Service **chỉ kiểm tra** `U == 1`, > không kiểm tra `V`, và **không chặn** `order = 0`. - Họ chỉ **kiểm tra** `U`, không kiểm tra `V`. - Họ không yêu cầu `order` phải > 0. - Điều này mở ra trường hợp: - Nếu nhập `order = 0` → $0 \cdot G = O \rightarrow U = 1$ → Pass check. - Không cần biết $G$ là gì, không cần biết bậc thật sự. #### Bài làm: - Nếu có PoW: brute-force `n` từ `0` đến `2**28` để tìm **MD5** khớp. $$ \text{md5}(\text{str}(n) + \text{SECRET}) = \text{target\_hash} $$ với $n < 2^{28}$. Điều này chạy nhanh vì giới hạn nhỏ và MD5 tính toán nhanh. - Gửi `2` để chọn *"Solve the challenge"*. - Gửi `0` làm `order`. - Server trả **flag**. #### Code ```py= from pwn import * import re, hashlib, sys HOST, PORT = "happy-ecc.chal.idek.team", 1337 def solve_pow(banner, io): m = re.search(r"md5\(str\(n\)\.encode\(\) \+ (b'.*?')\)\.hexdigest\(\) = ([0-9a-f]{32})", banner.decode()) if not m: return suffix = eval(m.group(1)) target = m.group(2) for n in range(1 << 28): if hashlib.md5(str(n).encode() + suffix).hexdigest() == target: io.sendline(str(n).encode()) return sys.exit(1) def main(): io = remote(HOST, PORT) banner = io.recvuntil(b"== proof-of-work:", drop=False, timeout=1) if b"proof-of-work: disabled" not in banner: banner += io.recvuntil(b"Input the decimal result", drop=False) solve_pow(banner, io) io.recvuntil(b"1. Get random point") io.sendline(b"2") io.recvuntil(b">") io.sendline(b"0") print(io.recvall(timeout=2).decode()) if __name__ == "__main__": main() ``` ``` Flag: idek{find_the_order_of_hyperelliptic_curve_is_soooo_hard:((} ```