---
# System prepended metadata

title: idekCTF 2025 Writeup

---

# 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:((}
```


