# idekCTF 2025 Writeup
>By: TAS.ElPulga
## Misc
### z - easy
@daq

Challenge này cần phải giải 9 bài OSINT để có được flag
#### taxi

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

Dựa tòa tháp bên kia sông, tìm được đó là ở Macau


Vị trí chính xác là https://maps.app.goo.gl/g6pb1WoGwTgfhvBm9
#### site

Có một dải màu trắng khá đặc biệt, thử reverse search nó

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

Để ý 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.

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

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


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

Dựa vào xe bus và người bên trong, có thể thấy đây là ở Nga


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

Thấy có 1 cái nhà khá đặc trưng, thử reverse search

Tìm được là ở Twin Hearts Stone Weir, vị trí chính xác là https://maps.app.goo.gl/QRVy7fUqvpbmcoU36
#### 2011

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

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

Ở đâ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

```
FLAG: idek{f4k3_m3t45_ce6362bd242f34}
```
### gacha-gate
@hphuc204

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

```
FLAG: idek{n4nds_r_funct10nally_c0mpl3t3!}
```
## Rev
### constructor
@hphuc204

Sau khi tải về ta sẽ được file có tên là ```constructor.tar.gz```
Ta sẽ giải nén nó:

Thư mục ```attachments/``` bên trong chứa file tên là ```chall``` thì ta sẽ kiểm tra chall

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ì

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

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

#### Đề 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

#### Đề 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

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