# Crew CTF 2025 ## Misc - Bytecode Bonanza - Basics > Python bytecode has many different opcodes, but how many opcodes do you really need? This challenge will teach you some of the concepts you need to tackle the RSA challenge. > Author: Aali Source chall: ```python3= import sys import signal assert((sys.version_info.major, sys.version_info.minor) == (3, 9)) signal.alarm(30) FLAG = "crew{test flag please ignore}" def dummy1(a): pass def dummy2(a, b): pass def dummy3(a, b, c): pass dummies = [None, dummy1, dummy2, dummy3] def create_function(parameters, prompt): bytecode = bytes.fromhex(input(prompt)) if len(bytecode) > 512: print("Too long") exit() opcodes = [bytecode[i*2] + bytecode[i*2+1]*256 for i in range((len(bytecode)+1) // 2)] allowlist = [ 0x0001, 0x0004, 0x0006, 0x000f, 0x0017, 0x0190 ] + [0x0073 + i * 512 for i in range(128)] if any([op not in allowlist for op in opcodes]): print("Illegal opcode") exit() preamble = b"".join([bytes([0x7c, i]) for i in range(parameters)]) code = preamble + bytecode + bytes([0x53, 0]) dummy = dummies[parameters] dummy.__code__ = dummy.__code__.replace(co_code=code,co_stacksize=1000000000) return dummy import secrets subtract = create_function(2, "Enter a function which subtracts two numbers: ") for i in range(10000): a = secrets.randbelow(2**32) b = secrets.randbelow(2**32) if subtract(a, b) != a - b: print("Nope") exit() constant1337 = create_function(1, "Enter a function which always returns 1337: ") for i in range(10000): if constant1337(secrets.randbelow(2**32)) != 1337: print("Nope") exit() multiply = create_function(2, "Enter a function which multiplies two positive integers: ") for i in range(10000): a = secrets.randbelow(255) + 1 b = secrets.randbelow(255) + 1 if multiply(a, b) != a * b: print("Nope") exit() print(FLAG) ``` Ở bài này yêu cầu ta nhập vào chuỗi hex chính là dạng bytecode của python nhưng chỉ được dùng các lệnh đơn giản `POP_TOP, DUP_TOP, ROT_FOUR, UNARY_INVERT, BINARY_ADD, EXTENDED_ARG, POP_JUMP_FORWARD_IF_TRUE` để vượt qua 3 phần của challenge. https://unpyc.sourceforge.net/Opcodes.html Link trên là chi tiết các lệnh bytecode của python 3.9 Đầu tiên là bytecode hàm trừ nhận vào 2 tham số a, b trả về a - b: Ta có: a - b = ~( ~(a) + b) Vì ~( ~(a) + b) = ~(-1 - a + b) = ~(-1 - (a - b)) = -1 - (-1 - (a - b)) = -1 + 1 + (a - b) = a - b Sau đó ta chuyển ~( ~(a) + b) sang dạng bytecode của python rồi là pass được part 1. ``` 0f00 UNARY_INVERT # [a, ~b] 1700 BINARY_ADD # [a + ~b] 0400 DUP_TOP # [res, res] 0400 DUP_TOP # [res, res, res] 0f00 UNARY_INVERT # [res, res, ~res] 1700 BINARY_ADD # [res, -1] 0400 DUP_TOP # [res, -1, -1] 1700 BINARY_ADD # [res, -2] 0f00 UNARY_INVERT # [res, 1] 1700 BINARY_ADD # [res + 1] ``` ```040004000600010001000f0017000f00``` Tiếp theo part2 là nhận vào 1 tham số a và luôn trả về 1337 Tạo 0 từ a: a ^ a == 0 z = a ^ a Tạo 1 từ 0: ~0 == -1, -(-1) == 1 one = -~z Bây giờ build 1337 bằng cách nhân 2 và cộng 1 bits của 1337 (từ MSB xuống LSB) = `1 0 1 0 0 1 1 1 0 0 1` Ta "unroll" các bước: cho mỗi bit: result = result*2; nếu bit==1: result += one ``` result = 0 # bit10 result = result + result result = result + one # bit9 result = result + result # bit8 result = result + result result = result + one # bit7 result = result + result # bit6 result = result + result # bit5 result = result + result result = result + one # bit4 result = result + result result = result + one # bit3 result = result + result result = result + one # bit2 result = result + result # bit1 result = result + result # bit0 result = result + result result = result + one ``` Chuyển đoạn trên sang bytecode và pass part2. ```04000f001700040017000f000400170004001700040004000f001700040017000f001700040017000400170004001700040004000f001700040017000f00170004001700040004000f001700040017000f00170004001700040004000f001700040017000f001700040017000400170004001700040004000f001700040017000f001700``` Với part3 nhận 2 tham số a,b trả về a * b ``` result = 0 while b > 0: result += a b -= 1 ``` Ta chuyển đoạn code đơn giản trên qua bytecode là xong. ``` 00: DUP_TOP 01: DUP_TOP 02: UNARY_INVERT 03: BINARY_ADD 04: UNARY_INVERT 05: DUP_TOP 06: ROT_FOUR 07: POP_TOP 08: DUP_TOP 09: POP_JUMP_IF_TRUE 26 ; nếu b != 0 → vào thân (offset 26 = 0x1a) 10: POP_TOP ; (không dùng vì b≥1 theo đề) 11: DUP_TOP ; ---- thân: result += a ---- 12: ROT_FOUR 13: POP_TOP 14: DUP_TOP 15: ROT_FOUR 16: BINARY_ADD 17: DUP_TOP 18: ROT_FOUR 19: POP_TOP 20: DUP_TOP ; ---- b = b - 1 ---- 21: DUP_TOP 22: UNARY_INVERT 23: BINARY_ADD 24: BINARY_ADD ; TOS = b-1 25: DUP_TOP ; copy để test 26: POP_JUMP_IF_TRUE 20 ; *** sửa: quay lại điểm kiểm tra (offset 20 = 0x14) 27: POP_TOP ; rơi ra khi b==0 → bỏ b 28: POP_TOP ; bỏ a, còn lại result ở TOS → RETURN_VALUE (tự chèn) ``` ```040004000f0017000f000400060001000400731a0100040006000100040006001700040006000100040004000f00170017000400731401000100``` ## Hardware ### Gas Gas Gas ![image](https://hackmd.io/_uploads/rJtIONlnel.png) Trong tệp zip, ta có 1 folder `dist` chưa 1 tệp `gas.csv` Tệp csv này có 5001 dòng và những dòng đầu của tệp này là: ```javascript= #,Time(ms),CH1(mV),CH2(mV) 0,1.6800000,3360.00,3360.00 1,1.6760000,3320.00,3360.00 2,1.6720000,3360.00,3320.00 3,1.6680000,3360.00,3360.00 4,1.6640000,3320.00,3360.00 5,1.6600000,3320.00,3360.00 6,1.6560000,3360.00,3400.00 7,1.6520000,3400.00,3400.00 8,1.6480000,3360.00,3400.00 9,1.6440000,3400.00,3400.00 10,1.6400000,3360.00,3400.00 11,1.6360000,3400.00,3400.00 12,1.6320000,3400.00,3400.00 13,1.6280000,3400.00,3440.00 ... ``` Đầu tiên, mình dùng AI để hỏi thêm về thông tin của đoạn này: [LINK](https://chatgpt.com/share/68dddd48-ee20-8006-b9d9-a66f2e604d83) Từ đó mình thấy rằng trên mẫu này, màn hình hiển thị thông tin người lái sử dụng 2 ID CAN mở rộng trên bus tốc độ thấp nên sau đó mình tìm thông tin về Volvo 2 CAN và mình tìm thấy được link github này: https://github.com/martonn98/VolvoP2_CAN 1. Giải mã tệp csv như CAN tốc độ thấp (125kbps), loại bỏ bit, sau đó phân tích các frame mở rộng và bạn sẽ nhận được các frame 0x02A0240E ("activate") theo sau là một loạt các 0x01800008 frame. 2. Mỗi 0x01800008 frame bắt đầu bằng một byte điều khiển/vị trí nhỏ và phần còn lại là các ký tự cho LCD. Nhóm các frames thành hai dòng (2×16 của DIM). Việc ghép lại các ký tự chính xác như DIM sẽ hiển thị sẽ cho kết quả: Dòng 1: Decode Y3Jld3tzdDNwXzBO Dòng 2: Base64 X3RIZV9nNHMhfQ== Flag: crew{st3p_0N_tHe_g4s!} ## PPC ### IOIOIOI ![image](https://hackmd.io/_uploads/r1kkdeb2xg.png) :::spoiler `chall.py` ```python= from solver import Solve import signal import secrets def TLE(signum, frame): print("Time Limit Exceeded. Try again...") exit(0) signal.signal(signal.SIGALRM, TLE) signal.alarm(300) params = [ # (N_min, N_max, K_min, K_max) (1, 15, 0, 15), # Test 1 (1, 1000, 0, 1000), # Test 2 (5 * 10**8, 10**9, 0, 1000), # Test 3 (5 * 10**17, 10**18, 900, 1000), # Test 4 ] T = 300 for i in range(T): test_id = i // 75 N_min, N_max, K_min, K_max = params[test_id] N = N_min + secrets.randbelow(N_max - N_min + 1) K = K_min + secrets.randbelow(K_max - K_min + 1) ans = Solve(N, K) assert 0 <= ans < 998244353 print("Test", i + 1) print(f"{N = }") print(f"{K = }") print("Your answer: ", end="") player_ans = int(input()) if ans == player_ans: print("Accepted!") else: print("Wrong Answer. Try again...") exit(0) print("Congratulations! Here is your flag:") print(open("flag.txt", "r").read()) ``` ::: ::: spoiler `README.md` # IOIOIOI ## Statement Let's think about length N binary string consisting only of the characters I and O. The score of a string is defined as the sum of: - the number of contiguous substrings equal to "IOI" - the number of contiguous substrings equal to "IOIOI" - the number of contiguous substrings equal to "IOIOIOI" and so on. ex. score("IOIOIO") = 2 ("IOI") + 1 ("IOIOI") = 3 Find the number, modulo 998244353, of such strings have a score exactly equal to K. ## Constraints - 1 ≦ N ≦ 10^18 - 0 ≦ K ≦ 1000 ## Samples ### sample 1 #### input ``` N = 6 K = 3 ``` #### output ``` 4 ``` #### explanation There are 4 binary strings with length 6 have a score of 3: `IIOIOI`, `IOIOII`, `IOIOIO`, `OIOIOI`. ### sample 2 #### input ``` N = 9 K = 10 ``` #### output ``` 1 ``` #### explanation There is only 1 binary string with length 9 have a score of 10: `IOIOIOIOI`. ### sample 3 #### input ``` N = 40 K = 4 ``` #### output ``` 167288093 ``` #### explanation There are 144912719278 binary strings with length 40 have a score of 4, so the answer is 144912719278 mod 998244353 = 167288093. ::: Thông qua đọc file `README.md` thì ta biết được yêu cầu của bài toán là đếm số chuỗi N có điểm đúng bằng K rồi mod 998244353, với K được tính là số đoạn con "IOI", "IOIOI", "IOIOIOI",... trong mẫu N. Các mẫu "IOI", "IOIOI", … chính là những đoạn con có dạng lặp lại "IO" nhiều lần rồi kết thúc bằng "I". Vì vậy, mỗi khối luân phiên tối đa có `m` ký tự `I` sẽ góp $\binom{m}{2}$ vào số điểm. $$ score = \displaystyle \sum_{blocks}\binom{m}{2} $$ Ta mã hóa tất cả các xâu bằng hàm sinh hai biến A(x, y) $$ A(x,y) = \displaystyle \sum_{m\ge 1} x^{2m-1}y^{\binom{m}{2}} $$ và đa thức: $$ F(x,y)=\dfrac{x+(1+x^2)A(x,y)}{1-x-(1-x+x^2)A(x,y)} $$ , nên đáp số cần tìm là hệ số: $$ [x^Ny^K]F(x,y) $$ Thay vì viết toàn bộ $F(x,y)$, ta xây dựng một hệ đệ quy trạng thái dựa trên chuỗi Markov (ma trận chuyển tiếp). Từ đó biến nó thành: $$ F(x,y)=\dfrac{P(x,y)}{Q(x,y)} $$ Ở đây vì N rất lớn nên ta sẽ dùng thuật toán **Bostan-Mori** để lấy hệ số $x^N$ trong phân thức P/Q chỉ với $O(\log N)$ bước nhân đa thức. Sau đó, rút gọn x = 0, ta còn 1 biểu thức chỉ phụ thuộc vào y và có thể lấy hệ số $y^K$ cuối cùng. :::spoiler `sol.cpp` ```cpp= #include <bits/stdc++.h> using namespace std; using int64 = long long; const int MOD = 998244353; inline int addmod(int a, int b){ a += b; if(a >= MOD) a -= MOD; return a; } inline int submod(int a, int b){ a -= b; if(a < 0) a += MOD; return a; } inline int mulmod(int a, int b){ return (int)((int64)a * b % MOD); } inline int powmod(int a, long long e){ int r = 1; while(e){ if(e&1) r = mulmod(r,a); a = mulmod(a,a); e >>= 1; } return r; } inline int invmod(int a){ return powmod(a, MOD-2); } using ypoly = vector<int>; // coefficients in y, length K+1 (truncated) using xpoly = vector<ypoly>; // coefficients in x, each coeff is a ypoly // ---------- utilities for y-polynomials (dense) ---------- ypoly y_zero(int K){ return ypoly(K+1, 0); } ypoly y_monomial(int deg, int K){ ypoly r(K+1, 0); if(deg <= K) r[deg] = 1; return r; } ypoly y_add(const ypoly &A, const ypoly &B, int K){ ypoly C(K+1); for(int i=0;i<=K;i++){ int v = A[i] + B[i]; if(v >= MOD) v -= MOD; C[i] = v; } return C; } ypoly y_sub(const ypoly &A, const ypoly &B, int K){ ypoly C(K+1); for(int i=0;i<=K;i++){ int v = A[i] - B[i]; if(v < 0) v += MOD; C[i] = v; } return C; } ypoly y_scalar_mul(const ypoly &A, int s, int K){ if(s % MOD == 0) return y_zero(K); ypoly R(K+1); int ss = s % MOD; for(int i=0;i<=K;i++){ if(A[i]) R[i] = mulmod(A[i], ss); } return R; } ypoly y_mul(const ypoly &A, const ypoly &B, int K){ // naive convolution truncated at degree K ypoly C(K+1); // pick smaller outer if(&A == &B){ // square optimization? we skip to keep code simple } for(int i=0;i<=K;i++){ if(A[i]==0) continue; int ai = A[i]; int lim = K - i; for(int j=0;j<=lim;j++){ if(B[j]==0) continue; C[i+j] = (C[i+j] + (int64)ai * B[j]) % MOD; } } return C; } // ---------- utilities for x-polynomials ---------- void x_trim(xpoly &P){ while(!P.empty()){ // check if last ypoly is all zeros ypoly &last = P.back(); bool allzero = true; for(int v: last){ if(v) { allzero = false; break; } } if(allzero) P.pop_back(); else break; } } xpoly x_zero(int deg = 0, int K=0){ xpoly P(deg+1, y_zero(K)); return P; } xpoly x_add(const xpoly &A, const xpoly &B, int K){ int n = max((int)A.size(), (int)B.size()); xpoly C(n, y_zero(K)); for(int i=0;i<n;i++){ const ypoly &ai = (i < (int)A.size() ? A[i] : y_zero(K)); const ypoly &bi = (i < (int)B.size() ? B[i] : y_zero(K)); for(int d=0; d<=K; d++){ int v = ai[d] + bi[d]; if(v >= MOD) v -= MOD; C[i][d] = v; } } x_trim(C); return C; } xpoly x_sub(const xpoly &A, const xpoly &B, int K){ int n = max((int)A.size(), (int)B.size()); xpoly C(n, y_zero(K)); for(int i=0;i<n;i++){ const ypoly &ai = (i < (int)A.size() ? A[i] : y_zero(K)); const ypoly &bi = (i < (int)B.size() ? B[i] : y_zero(K)); for(int d=0; d<=K; d++){ int v = ai[d] - bi[d]; if(v < 0) v += MOD; C[i][d] = v; } } x_trim(C); return C; } xpoly x_scalar_mul(const xpoly &A, int s, int K){ if(s % MOD == 0) return xpoly(); int ss = s % MOD; xpoly R = A; for(auto &yp : R){ for(int i=0;i<=K;i++){ if(yp[i]) yp[i] = mulmod(yp[i], ss); } } x_trim(R); return R; } xpoly x_mul(const xpoly &A, const xpoly &B, int deg_limit, int K){ if(A.empty() || B.empty()) return xpoly(); int la = A.size(), lb = B.size(); int lim = min(deg_limit, la + lb - 2); xpoly C(lim + 1, y_zero(K)); for(int i=0;i<la;i++){ if(i > lim) break; const ypoly &ai = A[i]; bool ai_non = false; for(int t=0;t<=K;t++){ if(ai[t]) { ai_non=true; break; } } if(!ai_non) continue; int maxj = min(lb - 1, lim - i); for(int j=0;j<=maxj;j++){ const ypoly &bj = B[j]; bool bj_non = false; for(int t=0;t<=K;t++){ if(bj[t]) { bj_non=true; break; } } if(!bj_non) continue; ypoly prod = y_mul(ai, bj, K); if(prod.empty()) continue; ypoly &cij = C[i+j]; for(int d=0; d<=K; d++){ if(prod[d]==0) continue; cij[d] = (cij[d] + prod[d]) % MOD; } } } x_trim(C); return C; } xpoly x_neg_x_substitute(const xpoly &Q, int K){ xpoly R(Q.size(), y_zero(K)); for(size_t i=0;i<Q.size();i++){ if(((int)i)&1){ // negate for(int d=0; d<=K; d++){ if(Q[i][d]) R[i][d] = (MOD - Q[i][d]) % MOD; } }else{ for(int d=0; d<=K; d++) R[i][d] = Q[i][d]; } } x_trim(R); return R; } xpoly x_even_coeffs(const xpoly &P, int K){ int n = P.size(); xpoly R((n+1)/2, y_zero(K)); for(size_t k=0; 2*k < P.size(); ++k){ R[k] = P[2*k]; } x_trim(R); return R; } xpoly x_odd_coeffs(const xpoly &P, int K){ int n = P.size(); if(n <= 1) return xpoly(); xpoly R(n/2, y_zero(K)); for(size_t k=0; 2*k+1 < P.size(); ++k){ R[k] = P[2*k+1]; } x_trim(R); return R; } // ---------- Build T matrix and P/Q ---------- struct PQpair { xpoly P, Q; }; static unordered_map<int, PQpair> PQ_cache; PQpair build_PQ(int K){ auto it = PQ_cache.find(K); if(it != PQ_cache.end()) return it->second; int m_max; if(K > 0){ long long disc = 1 + 8LL*K; long long s = (long long) sqrt((double)disc); while(s*s < disc) ++s; while(s*s > disc) --s; m_max = (int)((1 + s) / 2); while(m_max*(m_max-1)/2 > K) --m_max; } else m_max = 1; int degT = 2*m_max + 1; // create T entries: T00, T01, T10, T11 each xpoly with deg <= degT xpoly T00(degT+1, y_zero(K)), T01(degT+1, y_zero(K)), T10(degT+1, y_zero(K)), T11(degT+1, y_zero(K)); // fill from earlier corrected logic for(int m=1;m<=m_max;m++){ int e = m*(m-1)/2; if(e > K) break; int idx_odd = 2*m - 1; int idx_even = 2*m; // I->I via odd 2m-1 with weight y^{e} if(idx_odd <= degT){ ypoly mono = y_monomial(e, K); for(int d=0; d<=K; d++) if(mono[d]) T00[idx_odd][d] = addmod(T00[idx_odd][d], mono[d]); } // I->O via even 2m if(idx_even <= degT){ ypoly mono = y_monomial(e, K); for(int d=0; d<=K; d++) if(mono[d]) T01[idx_even][d] = addmod(T01[idx_even][d], mono[d]); } } for(int m=0;m<=m_max;m++){ int e = m*(m-1)/2; if(e > K) break; int idx_odd = 2*m + 1; int idx_even = 2*m; if(idx_odd <= degT){ ypoly mono = y_monomial(e, K); for(int d=0; d<=K; d++) if(mono[d]) T11[idx_odd][d] = addmod(T11[idx_odd][d], mono[d]); } if(m>=1 && idx_even <= degT){ ypoly mono = y_monomial(e, K); for(int d=0; d<=K; d++) if(mono[d]) T10[idx_even][d] = addmod(T10[idx_even][d], mono[d]); } } x_trim(T00); x_trim(T01); x_trim(T10); x_trim(T11); // A = I - T, where I = 1 at degree 0 y^0 xpoly ident(1, y_zero(K)); ident[0][0] = 1; // a11 = I - T00 xpoly a11 = x_sub(ident, T00, K); xpoly a12 = x_scalar_mul(T01, MOD-1, K); // -T01 xpoly a21 = x_scalar_mul(T10, MOD-1, K); // -T10 xpoly a22 = x_sub(ident, T11, K); // det Q = a11*a22 - a12*a21 int deg1 = (int)((a11.size()? a11.size()-1:0) + (a22.size()? a22.size()-1:0)); int deg2 = (int)((a12.size()? a12.size()-1:0) + (a21.size()? a21.size()-1:0)); int deg_limit = max(deg1, deg2); xpoly term1 = x_mul(a11, a22, deg_limit, K); xpoly term2 = x_mul(a12, a21, deg_limit, K); xpoly Q = x_sub(term1, term2, K); // adj(A) = [a22, -a12; -a21, a11] xpoly adj11 = a22; xpoly adj12 = x_scalar_mul(a12, MOD-1, K); xpoly adj21 = x_scalar_mul(a21, MOD-1, K); xpoly adj22 = a11; // S = T * adj(A) // Compute S00 = T00*adj11 + T01*adj21 xpoly S00 = x_add(x_mul(T00, adj11, (int)max((int)T00.size()-1,(int)adj11.size()-1) + (int)max((int)T00.size()-1,(int)adj11.size()-1), K), x_mul(T01, adj21, (int)max((int)T01.size()-1,(int)adj21.size()-1) + (int)max((int)T01.size()-1,(int)adj21.size()-1), K), K); xpoly S01 = x_add(x_mul(T00, adj12, (int)max((int)T00.size()-1,(int)adj12.size()-1) + (int)max((int)T00.size()-1,(int)adj12.size()-1), K), x_mul(T01, adj22, (int)max((int)T01.size()-1,(int)adj22.size()-1) + (int)max((int)T01.size()-1,(int)adj22.size()-1), K), K); xpoly S10 = x_add(x_mul(T10, adj11, (int)max((int)T10.size()-1,(int)adj11.size()-1) + (int)max((int)T10.size()-1,(int)adj11.size()-1), K), x_mul(T11, adj21, (int)max((int)T11.size()-1,(int)adj21.size()-1) + (int)max((int)T11.size()-1,(int)adj21.size()-1), K), K); xpoly S11 = x_add(x_mul(T10, adj12, (int)max((int)T10.size()-1,(int)adj12.size()-1) + (int)max((int)T10.size()-1,(int)adj12.size()-1), K), x_mul(T11, adj22, (int)max((int)T11.size()-1,(int)adj22.size()-1) + (int)max((int)T11.size()-1,(int)adj22.size()-1), K), K); xpoly P = x_add(x_add(S00, S01, K), x_add(S10, S11, K), K); PQpair pq; pq.P = P; pq.Q = Q; PQ_cache[K] = pq; return pq; } // ---------- Bostan-Mori for P/Q where coefficients are y-polynomials ---------- xpoly x_trim_copy(xpoly P){ x_trim(P); return P; } xpoly x_even_coeffs_copy(const xpoly &P, int K){ return x_even_coeffs(P, K); } xpoly x_odd_coeffs_copy(const xpoly &P, int K){ return x_odd_coeffs(P, K); } int get_coefficient_bostan(const xpoly &P_in, const xpoly &Q_in, long long N, int K){ xpoly P = P_in; xpoly Q = Q_in; x_trim(P); x_trim(Q); if(N < 0) return 0; while(N){ xpoly Qminus = x_neg_x_substitute(Q, K); xpoly U = x_mul(P, Qminus, (int)max(0, (int)P.size()-1 + (int)Qminus.size()-1), K); xpoly V = x_mul(Q, Qminus, (int)max(0, (int)Q.size()-1 + (int)Qminus.size()-1), K); if(N & 1){ P = x_odd_coeffs(U, K); }else{ P = x_even_coeffs(U, K); } Q = x_even_coeffs(V, K); N >>= 1; x_trim(P); x_trim(Q); if(Q.empty()){ return 0; } } // Now result is P(0)/Q(0) as power series in y; compute coefficient y^K ypoly P0 = (P.size()? P[0] : y_zero(K)); ypoly Q0 = (Q.size()? Q[0] : y_zero(K)); vector<int> Parr(K+1,0), Qarr(K+1,0); for(int d=0; d<=K; d++){ Parr[d] = P0[d] % MOD; Qarr[d] = Q0[d] % MOD; } if(Qarr[0] == 0) return 0; int invq0 = invmod(Qarr[0]); vector<int> R(K+1, 0); for(int n=0; n<=K; ++n){ long long s = Parr[n]; if(n){ long long acc = 0; for(int i=1;i<=n;i++){ if(Qarr[i]) acc = (acc + (long long)R[n-i] * Qarr[i]) % MOD; } s = (s - acc) % MOD; if(s < 0) s += MOD; } R[n] = mulmod((int)s, invq0); } return R[K]; } // ---------- top-level solve ---------- int solve_NK(long long N, int K){ if(N == 0) return (K == 0 ? 1 : 0); PQpair pq = build_PQ(K); int ans = get_coefficient_bostan(pq.P, pq.Q, N, K); return ans; } // ---------- main: sample tests and optional stdin reading ---------- int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); for(int i=0;i<300;i++){ long long N; int K; if(!(cin >> N >> K)) break; int ans = solve_NK(N,K); cout << ans << '\n'; cout.flush(); } return 0; } ``` ::: Sau đó, ta dùng: `g++ -O3 -march=native -std=c++17 -pipe -s sol.cpp -o sol` :::spoiler `sol.py` ```python= from pwn import * context.log_level = "error" solver = process(["/home/r1muru/workspace/CrewCTF2025/PPC/IOIOIOI/sol2"]) # Connect to remote io = remote("ioioioi.chal.crewc.tf", 1337, ssl=True) def take_data(): io.recvuntil(b"N = ") N = int(io.recvline().decode().strip()) io.recvuntil(b"K = ") K = int(io.recvline().decode().strip()) return N, K for _ in range(300): N, K = take_data() # Feed query into C++ solver solver.sendline(f"{N} {K}".encode()) ans = solver.readline().decode().strip() # Send answer back to server io.send(ans.encode()+b"\n") # print(_ + 1) print(io.recvall()) ``` ::: Flag: crew{Th1s_ch4llenge_i5_0ut_of_I0I_sylla6us_th0ugh_8eaf3fed84a94f3c62bc37553b14e270} ## Crypto ### GFLCG ![image](https://hackmd.io/_uploads/SJ3H9QM3xg.png) ::: spoiler `chall.sage` ```python= from Crypto.Cipher import AES from Crypto.Util.Padding import pad class LCG: def __init__(self, nbits): self.nbits = nbits R = GF(2)['x'] mod = 0 while True: mod = R.random_element(degree=nbits) if mod.is_irreducible(): break assert mod.degree() == nbits self.F = GF(2 ** nbits, 'x', modulus=mod) self.a = self.F.random_element() self.b = self.F.random_element() self.m = mod self.state = self.F.random_element() def __next__(self): self.state *= self.a self.state += self.b return self.state.to_integer() >> ((self.nbits * 2) // 3) L = LCG(64 * 3) values = [next(L) for _ in range(200)] print(f'{values = }') v1 = next(L) v2 = next(L) key = v1 + v2 * pow(2, 64) key = int.to_bytes(int(key), 16) flag = 'crew{*** REDACTED ***}' cipher = AES.new(key, AES.MODE_ECB) msg = cipher.encrypt(pad(flag.encode(), 16)) print(f'{msg = }') ``` ::: :::spoiler `out.txt` ```java= values = [14841917648858837769, 9575471537143015742, 15844238770919434615, 5212260032608174747, 9233247444323572291, 10459091850700078324, 6348171461039085016, 11275083358377081726, 15851907641654315174, 5642462723638664555, 12166934227670445515, 16652002833255337213, 1100331227495018541, 17310052757528676070, 10647848709476991180, 7381860977210638817, 14749898712454789023, 9867793747622460486, 1616048454543341186, 8104578690941366513, 4083347094007115737, 11760837183986471162, 16862797396993697119, 1174637638031988756, 1804745826465075031, 16276632568687926630, 5043494920323747028, 11409272895120917919, 16265611200345769799, 14970614558261079002, 2250516726687967919, 1197248850350389243, 14603131310865530726, 16548361948476584779, 13353652992939482174, 13336486704510937571, 10441893770111811970, 4107764688249514273, 11772797255441774916, 12518165417933617541, 7010550077174075010, 14001355721098446832, 437777177382829002, 1722164767628433760, 6559647342847449596, 7938756427230732045, 13281366260429048930, 13092183655381323106, 11806529132310996843, 13947964017447178113, 4621312151518731422, 12501874612357040563, 10826458102042992364, 2159902472703790642, 13882393964322032422, 6529148921315953891, 10134887505878190392, 10419951393751420634, 10135848342454860542, 14313363158598253532, 8934368669847669830, 5124101360181491049, 14913219354552074425, 10908282099950928722, 4253964689299281724, 7407726974875151040, 14219905121435863470, 4181337490774743589, 4184184504223738823, 16936150976212002434, 14433936574129760038, 15520881989251542597, 11194070033146759221, 18174433602168311426, 13020442067015339358, 4382990166888652495, 10254297534043164279, 3817100944212579597, 5331877218604250458, 7185825545642354761, 338711531771068892, 10250264203376128253, 7359215208260510617, 14277514750873457414, 3460304295915392143, 3818816217224032441, 4483483566282319717, 2866544513807354462, 12135646457812113066, 8778669990047144231, 12989768322575258896, 8988116933722638298, 15692389573895602242, 4456647617936315332, 6039845569195475247, 14311304340715617600, 17295981409675358596, 11427453308135142971, 9052862849088317754, 6137397312278782739, 9544766266324391091, 16447228426027058890, 13803636818363488657, 16670480602864962535, 3351397243085772246, 5665702184993835815, 16424003006508152011, 13976194469704404585, 401185194999070420, 5699512823604259627, 17357160984615588720, 3670786096561696620, 4323747368693388297, 10876978042181159325, 7883125366518538304, 2344284687610109741, 12391759147373797536, 12590053927293200960, 8879156692797105548, 8461065756503570819, 17573320578141302871, 15642604376730203427, 12749133263385207476, 4133105186664632492, 18028280465459656846, 3067339009189147778, 15204862189705497345, 18132974384436241649, 8091492506662448517, 18147014166549437993, 5201643316892664569, 3104017212583314140, 3368545846626480386, 14752755621090773653, 9816609813405607817, 10989512726787524104, 17941375633925480992, 5969536734982737360, 3324460409144811670, 14364167734200742327, 10841885411537515505, 12033027733536926218, 13019115357341976440, 13723571084095846113, 14963449044276506843, 9241404467774916626, 1173475476199930336, 10007814422905689012, 880823326108631285, 2864914879726840475, 12348071585001057949, 6441302746542868688, 4806991077471213319, 8933141648678945175, 11649694907667295433, 8708277013041751508, 11719418456350910218, 16635157254108725579, 2134377351096125475, 10289961173017455600, 7055962786096505116, 21728601024181168, 16456998880597164728, 17216116874066795215, 3380132322173996284, 13114502767808007100, 4207348709474901930, 12916699834279235125, 15877440687400786825, 4585779573201771030, 3370966433634988692, 6413163656213101252, 8735986678159831147, 14813660282203931848, 4336269858741206951, 7680697319476289935, 3057350045239125021, 437484923622226917, 12182392489340614340, 7344380605957273905, 1047582581395548343, 9669150555048976950, 3445073427932842783, 5988003049280873305, 9984210927208328016, 8558400548885959238, 14932071850589169092, 10998241484982990572, 4061032217328993314, 16299495071317926943, 14159713152585339233, 7943955737745599039, 2708719529779063308, 5915795415723872866, 15610632814203455383, 9659638034966636775, 10804220067446596648, 3339259544022408735, 16353934319513835005, 7564345426168321381] msg = b'\xf9\xb9p\xf1\xa0\xc2\x15-\xa1\x90b\x06\x85\x1e\xa9bH\x15\x02\xcc\xba\x97\xc9.\xd3\xc8#\x1af6N\x96x)\x89\xddrg\xa2\x8b\xdd\x0c\xc9peNzWU\xfc9fJ1\xef\xdd\n\x9c\xa2\tW\x97F\x9a' ``` ::: #### Phân tích Đây không phải là 1 bài LCG thường mà là biến thể LCG trên trường hữu hạn $\mathbb{G}\mathbb{F}(2^{192})$. Ở hàm sinh số, trạng thái cuối bị cắt bớt (2/3)*nbits và chỉ giữ lại 1/3 số bit cao nhất (ở bài này là 64). ```python def __next__(self): self.state *= self.a self.state += self.b return self.state.to_integer() >> ((self.nbits * 2) // 3) ``` Nói cách khác, giả sử $s_t$ là dãy sinh ra từ LCG thì $y_t = P.s_t$, với $P$ là phép chiếu tuyến tính chọn 64 bit cao $\Rightarrow$ Chuỗi đầu ra là ảnh của 1 hệ tuyến tính hữu hạn chiều, tức là dãy $y_t$ sẽ thỏa 1 quan hệ truy hồi tuyến tính với bậc hữu hạn ($O(n)\le192+1$). #### Ý tưởng Vì hệ này tuyến tính trên $\mathbb{F}_2$, nên bất kỳ phép chiếu tuyến tính không tầm thường (ví dụ 64 bit cao) của trạng thái cũng sinh ra một dãy có độ phức tạp tuyến tính hữu hạn. Thu đủ nhiều mẫu là ta thừa sức khôi phục được quan hệ truy hồi. **Note:** Khi đầu ra là tổ hợp tuyến tính của trạng thái tuân theo một số biến đổi cố định, tồn tại một đa thức triệt tiêu trên dãy $y_t$: $\displaystyle \sum_{i=0}^Lc_iy_{t+i}=0, c_i\in\mathbb{F}_2, \forall t$ $\rightarrow$ Tìm được bộ $c_i$, ta sẽ tính được các $y_t$ tiếp theo từ các giá trị cho trước. Từ đó, ý tưởng của tôi là dựng 1 ma trận `m` có số hàng lớn hơn bậc hữu hạn $O(n)$, số cột là 1 hằng số $k$ sao cho $k\times64 > O(n)+1=194$ là số lượng các đầu ra liên tiếp để sử dụng ràng buộc tuyến tính giống hệt về cấu trúc giữa chúng. ```python k = 4 c = 64 * 3 + 2 m = [] for i in range(c): row = [] for j in range(k): U = values[i + j] for l in range(64): row.append(U & 1) U >>= 1 m.append(row) ``` Sau đó, ta tìm ma trận cột `p` độ dài $k\times64$ sao cho $p\times m = 0$ Ở đây `p` mô tả một quan hệ tuyến tính bất biến theo t giữa k output liên tiếp (từng bit xét độc lập) và nó chính là “đa thức triệt tiêu” trên dãy, áp dụng đồng thời cho 64 bit. ```python p = m.left_kernel().basis()[0] ``` Sau khi có được `p`, ta áp cùng 1 tổ hợp tuyến tính trên $\mathbb{F}_2$ để sinh ra các đầu ra mới và sử dụng chúng để tạo key là có thể giải mã được `msg` của đề bài. ```python # sol.py from sage.all import * from Crypto.Cipher import AES from Crypto.Util.Padding import unpad values = [14841917648858837769, 9575471537143015742, 15844238770919434615, 5212260032608174747, 9233247444323572291, 10459091850700078324, 6348171461039085016, 11275083358377081726, 15851907641654315174, 5642462723638664555, 12166934227670445515, 16652002833255337213, 1100331227495018541, 17310052757528676070, 10647848709476991180, 7381860977210638817, 14749898712454789023, 9867793747622460486, 1616048454543341186, 8104578690941366513, 4083347094007115737, 11760837183986471162, 16862797396993697119, 1174637638031988756, 1804745826465075031, 16276632568687926630, 5043494920323747028, 11409272895120917919, 16265611200345769799, 14970614558261079002, 2250516726687967919, 1197248850350389243, 14603131310865530726, 16548361948476584779, 13353652992939482174, 13336486704510937571, 10441893770111811970, 4107764688249514273, 11772797255441774916, 12518165417933617541, 7010550077174075010, 14001355721098446832, 437777177382829002, 1722164767628433760, 6559647342847449596, 7938756427230732045, 13281366260429048930, 13092183655381323106, 11806529132310996843, 13947964017447178113, 4621312151518731422, 12501874612357040563, 10826458102042992364, 2159902472703790642, 13882393964322032422, 6529148921315953891, 10134887505878190392, 10419951393751420634, 10135848342454860542, 14313363158598253532, 8934368669847669830, 5124101360181491049, 14913219354552074425, 10908282099950928722, 4253964689299281724, 7407726974875151040, 14219905121435863470, 4181337490774743589, 4184184504223738823, 16936150976212002434, 14433936574129760038, 15520881989251542597, 11194070033146759221, 18174433602168311426, 13020442067015339358, 4382990166888652495, 10254297534043164279, 3817100944212579597, 5331877218604250458, 7185825545642354761, 338711531771068892, 10250264203376128253, 7359215208260510617, 14277514750873457414, 3460304295915392143, 3818816217224032441, 4483483566282319717, 2866544513807354462, 12135646457812113066, 8778669990047144231, 12989768322575258896, 8988116933722638298, 15692389573895602242, 4456647617936315332, 6039845569195475247, 14311304340715617600, 17295981409675358596, 11427453308135142971, 9052862849088317754, 6137397312278782739, 9544766266324391091, 16447228426027058890, 13803636818363488657, 16670480602864962535, 3351397243085772246, 5665702184993835815, 16424003006508152011, 13976194469704404585, 401185194999070420, 5699512823604259627, 17357160984615588720, 3670786096561696620, 4323747368693388297, 10876978042181159325, 7883125366518538304, 2344284687610109741, 12391759147373797536, 12590053927293200960, 8879156692797105548, 8461065756503570819, 17573320578141302871, 15642604376730203427, 12749133263385207476, 4133105186664632492, 18028280465459656846, 3067339009189147778, 15204862189705497345, 18132974384436241649, 8091492506662448517, 18147014166549437993, 5201643316892664569, 3104017212583314140, 3368545846626480386, 14752755621090773653, 9816609813405607817, 10989512726787524104, 17941375633925480992, 5969536734982737360, 3324460409144811670, 14364167734200742327, 10841885411537515505, 12033027733536926218, 13019115357341976440, 13723571084095846113, 14963449044276506843, 9241404467774916626, 1173475476199930336, 10007814422905689012, 880823326108631285, 2864914879726840475, 12348071585001057949, 6441302746542868688, 4806991077471213319, 8933141648678945175, 11649694907667295433, 8708277013041751508, 11719418456350910218, 16635157254108725579, 2134377351096125475, 10289961173017455600, 7055962786096505116, 21728601024181168, 16456998880597164728, 17216116874066795215, 3380132322173996284, 13114502767808007100, 4207348709474901930, 12916699834279235125, 15877440687400786825, 4585779573201771030, 3370966433634988692, 6413163656213101252, 8735986678159831147, 14813660282203931848, 4336269858741206951, 7680697319476289935, 3057350045239125021, 437484923622226917, 12182392489340614340, 7344380605957273905, 1047582581395548343, 9669150555048976950, 3445073427932842783, 5988003049280873305, 9984210927208328016, 8558400548885959238, 14932071850589169092, 10998241484982990572, 4061032217328993314, 16299495071317926943, 14159713152585339233, 7943955737745599039, 2708719529779063308, 5915795415723872866, 15610632814203455383, 9659638034966636775, 10804220067446596648, 3339259544022408735, 16353934319513835005, 7564345426168321381] msg = b'\xf9\xb9p\xf1\xa0\xc2\x15-\xa1\x90b\x06\x85\x1e\xa9bH\x15\x02\xcc\xba\x97\xc9.\xd3\xc8#\x1af6N\x96x)\x89\xddrg\xa2\x8b\xdd\x0c\xc9peNzWU\xfc9fJ1\xef\xdd\n\x9c\xa2\tW\x97F\x9a' k = 4 c = 64 * 3 + 2 m = [] for i in range(c): row = [] for j in range(k): U = values[i + j] for l in range(64): row.append(U & 1) U >>= 1 m.append(row) m = matrix(GF(2), m) p = m.left_kernel().basis()[0] for idx in range(len(values), len(values) + 2): ans = 0 for i in range(len(p) - 1): if p[i] == 1: ans ^= values[idx + i - len(p) + 1] values.append(ans) key = values[-2] + values[-1] * pow(2, 64) key = int.to_bytes(int(key), 16) cipher = AES.new(key,AES.MODE_ECB) print(unpad(cipher.decrypt(msg), 16).decode()) ``` Flag: crew{Its_34513r_1n_GF2_d1a83b1addeb0ed863b46a85aef9a293} ### Inverse with errors only ![image](https://hackmd.io/_uploads/ryqySXhnge.png) :::spoiler `chall.py` ```python import hashlib import secrets from Crypto.Cipher import AES from Crypto.Util.number import getPrime as get_prime from Crypto.Util.Padding import pad flag = b"crew{*** REDACTED ***}" bits = 1024 d = get_prime(bits) values = [] for _ in range(30000): n = secrets.randbits(1024) | (1 << 1024) values.append(pow(d, -1, n)) key = hashlib.sha256(str(d).encode()).digest() flag = pad(flag, 16) cipher = AES.new(key, AES.MODE_CBC) iv = cipher.iv.hex() enc = cipher.encrypt(flag).hex() print(f"{values = }") print(f"{iv = }") print(f"{enc = }") ``` ::: Mỗi `value` in ra là $v_i\equiv d^{-1}$ mod một số ngẫu nhiên $n_i$. Với một số nguyên tố $p$ nhỏ, khoảng $\dfrac{1}{p}$ của những giá trị $n_i$ này sẽ chia hết cho $p$. Vì vậy, với những chỉ số này ta cũng sẽ có: $dv_i\equiv 1\mod p\Rightarrow d\equiv (v_i)^{-1}\mod p$. Nếu ta phân tích tần suất $\forall i$, giá trị $d\equiv v^{-1}\mod p$ sẽ xuất hiện lặp lại nhiều lần với p. Khi đó, ta sẽ lọc các ứng viên $d$ có tần suất cao nhất với mỗi modulo $p$. ```python # Quét primes nhỏ lấy ứng viên CAND = [] for p in prime_range(3, 1000): counts = Counter(v % p for v in values).most_common(1) r1 = counts[0][0] a_p = pow(r1, -1, p) CAND.append((p, a_p)) ``` Thu được nhiều $d$ theo các modulo nguyên tố khác nhau, thì ta sử dụng định lý thặng dư trung hoa (CRT) cho các $d$ này với modulo tăng lên với $P=\displaystyle\prod p_i$ cho đến khi nó đạt được 1024 bit thì ta sẽ thử chúng làm key đến khi nhận được flag đúng. ```python # CRT từng ứng viên để lấy d a_mod, mod = 0, 1 for p, a_p in CAND: a_mod, mod = crt([a_mod, a_p], [mod, p]), mod * p if iv_hex and enc_hex and mod.bit_length() >= 1024: try: pt = decrypt_flag(a_mod, iv_hex, enc_hex) if pt.startswith(b"crew{"): print(unpad(pt, 16).decode()) break except Exception: pass ``` Script: ```python import ast import hashlib from Crypto.Cipher import AES from collections import Counter from Crypto.Util.Padding import unpad from sage.all import crt, prime_range def decrypt_flag(d, iv_hex, enc_hex): key = hashlib.sha256(str(d).encode()).digest() iv = bytes.fromhex(iv_hex) enc = bytes.fromhex(enc_hex) cipher = AES.new(key, AES.MODE_CBC, iv=iv) pt = cipher.decrypt(enc) return pt def get_flag(values, iv_hex, enc_hex): # Quét primes nhỏ lấy ứng viên CAND = [] for p in prime_range(3, 1000): counts = Counter(v % p for v in values).most_common(1) r1 = counts[0][0] a_p = pow(r1, -1, p) CAND.append((p, a_p)) # CRT từng ứng viên để lấy d a_mod, mod = 0, 1 for p, a_p in CAND: a_mod, mod = crt([a_mod, a_p], [mod, p]), mod * p if iv_hex and enc_hex and mod.bit_length() >= 1024: try: pt = decrypt_flag(a_mod, iv_hex, enc_hex) if pt.startswith(b"crew{"): print(unpad(pt, 16).decode()) break except Exception: pass if __name__ == "__main__": with open("out.txt", "r") as f: data = {} for line in f: key, rhs = line.strip().split(" = ", 1) key = key.strip() rhs = rhs.strip() data[key] = ast.literal_eval(rhs) values, iv_hex, enc_hex = data["values"], data["iv"], data["enc"] get_flag(values, iv_hex, enc_hex) ``` Flag: crew{1nvertin9_w17h_3rr0rs_4065fad3e73c13b3f467cfc7887ff960}