# HITCON CTF 2022 ## BabySSS - Crypto - 94 solves ### Description > I implemented a toy Shamir's Secret Sharing for fun. Can you help me check is there any issues with this? > > Author: maple3142 [Attachments](https://github.com/m1dm4n/CTF-WriteUp/blob/main/2022/HITCONCTF2022/babysss/babysss-1068a45edf321eee75c9ceb3241a9941ab8bdc07.tar.gz) ### Solution First look at the source file: ```python= from random import SystemRandom from Crypto.Cipher import AES from hashlib import sha256 from secret import flag rand = SystemRandom() def polyeval(poly, x): return sum([a * x**i for i, a in enumerate(poly)]) DEGREE = 128 SHARES_FOR_YOU = 8 # I am really stingy :) poly = [rand.getrandbits(64) for _ in range(DEGREE + 1)] shares = [] for _ in range(SHARES_FOR_YOU): x = rand.getrandbits(16) y = polyeval(poly, x) shares.append((x, y)) print(shares) secret = polyeval(poly, 0x48763) key = sha256(str(secret).encode()).digest()[:16] cipher = AES.new(key, AES.MODE_CTR) print(cipher.encrypt(flag)) print(cipher.nonce) ``` We can see that it's a Shamir’s Secret Sharing but it's on Integer field ($$\mathbb{ZZ}$$). The challenge give us 8 shares and ask us to recover all 129 coefficients of poly. Each shares give to us have a form like this: $$ (x_i, y_i) = (x_i, a_{129}x_i^{128} + a_{128}x_i^{127}+...+a_{2}x_i + a_{1}) $$ So basically for each share $i$ if you get $y_i \% x_i$, you will get $a_1\%x_i$ . With 8 shares you could use **CRT** (Chinese Remainder Theorem) to recover a1 and then subtract that a1, divide $x_i$ and countinue to do that until you have all 129 coefficients All coefficients in poly are 64-bit integers and all shared *$x_i$ are 16-bit integer but we have 8 shares so it's enough for us to recover each coefficients. - [Script](https://github.com/m1dm4n/CTF-WriteUp/blob/main/2022/HITCONCTF2022/babysss/solve.py) - Output: ```python hitcon{doing_SSS_in_integers_is_not_good_:(} ``` ## Secret - Crypto - 41 solves ### Description > Too many secrets ... > > Author: lyc [Attachments](https://github.com/m1dm4n/CTF-WriteUp/blob/main/2022/HITCONCTF2022/secret/secret-e35f5c21e032b74b1ab8110722c593847c2534cb.zip) ### Solution My basic idea is from this latest [N1CTF](https://tl2cents.github.io/2022/11/08/N1CTF-2022-Crypto-Writeups-By-tl2cents/). We will use lattice to recover the modulus $p$ then recover the modulus $N$ and decrypt flag. #### Recover modulus p Why we need recover $p$ first? Well you can in see the source code that the public key is $p + e_i$ and we don't know $p$ but we know all $e_i$. So if we get $c_i$ (i'th ciphertext) modulo with $p$, we will get $c_i\equiv m^{p+e_i}\equiv m^{e_i + 1}\pmod{p}$. All $e_i$ are 512-bit integer so it's too big for us to apply direct power so we will construce a lattice to narrow down the exponents. The lattice is as follows: $$ M = \begin{bmatrix} e_1 & 1 & 0 &... & 0 \\ e_2 & 0 & 1 &... & 0 \\ \vdots & \vdots & \vdots& \ddots & \vdots \\ e_{64} & 0 & 0 &... & 1 \\ \end{bmatrix} $$ And than apply the LLL algorithm to $M will give you all small linear combinations (list $k_i$) of all rows in $M$. Define $ML=M.LLL()$ and $ML_i$ will be the i'th row of $ML$, $ML_i$ will have the form $[r_i, k_{i1}, k_{i2}, \ldots, k_{i64} ]$ such that: $$ r_i = \sum\limits_{j=1}^{64} e_j*k_{ij} $$ And the most important that $k_i$ is small so we can apply the power direct to $c_i$. Since we don't have $m_i$, we can't apply the exponent $r_i$. But we was given 64 $c_i$ and $e_i$ so we can find a pair $r_i$ and $r_j$ such that $$ \begin{equation} k_1*r_i = k_2*r_j \iff m^{k_1*r_i} = m^{k_2*r_j} \iff m^{k_1*r_i} - m^{k_2*r_j} = K*p = 0 \pmod{p} \end{equation} $$ And $m^{r_{i}}$ can simple calculate since we have linear combination of list $e_i$: $$ \begin{align} e_1*k_{i1} + e_2*k_{i2} + \cdots + e_{64}*k_{i64} &= r_i\\ \iff m^{e_1*k_{i1} + e_2*k_{i2} + \cdots + e_{64}*k_{i64}} &= m^{r_i} \\ \end{align} $$ Note: since $k_{ij}$ and $r_i$ can be negative or positive, we should seperate the list $k_i$ into 2 list: `pos` and `neg`. The equaltion will like this: $$ m^{r_i} = \ \begin{cases} \frac{m^{epos_1*pos_{i1} + epos_2*pos_{i2} + \cdots + epos_{64}*pos_{i64}}}{m^{eneg_1*neg_{i1} + eneg_2*neg_{i2} + \cdots + eneg_{64}*neg_{i64}}} \ \text{if }r_i > 0 \\ \frac{m^{eneg_1*neg_{i1} + eneg_2*neg_{i2} + \cdots + eneg_{64}*neg_{i64}}}{m^{epos_1*pos_{i1} + epos_2*pos_{i2} + \cdots + epos_{64}*pos_{i64}}} \ \text{if }r_i < 0 \end{cases} $$ Code: ```python def compute(coff): pos = mpz(1) neg = mpz(1) for i, cof in enumerate(coff[1:]): if cof > 0: pos = pos * cs[i]**cof else: neg = neg * cs[i]**(-cof) if coff[0] > 0: return ZZ(pos)/ZZ(neg) else: return ZZ(neg)/ZZ(pos) ``` So if we get enough $K_i*p$, we could get **GCD** the list of it and get the modulus $p$ Code: ```python= def solve(ess, bit_need): L = len(ess) M1 = matrix.identity(ZZ, L) mates = matrix(ZZ, L, 1) for i, e in enumerate(ess): mates[i, 0] = e mat = block_matrix(ZZ, [mates, M1], ncols=2) mat = mat.LLL() # for row in mat: # logging.info(row) ns = [] for row1, row2 in combinations(list(mat.rows()), 2): a, b = abs(row1[0]), abs(row2[0]) k1, k2 = 1, 1 if a % b == 0: k2 *= a // b elif b % a == 0: k1 *= b // a else: continue try: logging.info(f"Found a good pair: a = {row1[0]}, b = {row2[0]}") k1 = compute(row1)**k1 k2 = compute(row2)**k2 ns.append(mpz((k1 - k2).numerator())) except ValueError: continue if len(ns) > 2: p = gcd(*ns) logging.debug(f"Found a gcd with {p.bit_length()} bits") if p.bit_length() <= bit_need: return p return p new_es = [] for e in es: new_es.append(e + 1) p = solve(new_es, 1024) logging.debug(f"Found p: {p}") ``` ```python [INFO]: Found a good pair: a = 84, b = 42 [INFO]: Found a good pair: a = -224, b = 112 [INFO]: Found a good pair: a = -224, b = 56 [DEBUG]: Found a gcd with 29601 bits [INFO]: Found a good pair: a = -24, b = -120 [DEBUG]: Found a gcd with 1024 bits [DEBUG]: Found p: 114123489471785231935784934808971699969409921187241213856052699152350022529522625133249122600992294384493330729753558097354310956450782137388609095123051712848950720360020186805006589596948820312938610934162552701552428320073591829720623902109809701883779673050594202312941073709061911680769616320309646800153 ``` #### Recover modulus N Since we have $p$, we just need change the argument for the `solve` function to list of $p + e_i$ and number of bits that we need will be $2048$ ```python= new_es = [] for e in es: new_es.append(e + p) n = solve(new_es, 2048) logging.debug(f"Found n: {n}") ``` ```python [INFO]: Found a good pair: a = 292, b = 4 [INFO]: Found a good pair: a = 66, b = 11 [INFO]: Found a good pair: a = 66, b = 198 [DEBUG]: Found a gcd with 542856 bits [INFO]: Found a good pair: a = 66, b = -330 [DEBUG]: Found a gcd with 408759 bits [INFO]: Found a good pair: a = 52, b = -156 [DEBUG]: Found a gcd with 34762 bits [INFO]: Found a good pair: a = 52, b = -104 [DEBUG]: Found a gcd with 34762 bits [INFO]: Found a good pair: a = 52, b = 4 [DEBUG]: Found a gcd with 34762 bits [INFO]: Found a good pair: a = 52, b = 260 [DEBUG]: Found a gcd with 2048 bits [DEBUG]: Found n: 17724789252315807248927730667204930958297858773674832260928199237060866435185638955096592748220649030149566091217826522043129307162493793671996812004000118081710563332939308211259089195461643467445875873771237895923913260591027067630542357457387530104697423520079182068902045528622287770023563712446893601808377717276767453135950949329740598173138072819431625017048326434046147044619183254356138909174424066275565264916713884294982101291708384255124605118760943142140108951391604922691454403740373626767491041574402086547023530218679378259419245611411249759537391050751834703499864363713578006540759995141466969230839 ``` #### Decrypting flag We have $N$ and $p$ so we just find $e$ in the list $es$ such that $\mathbb{GCD}(p+e, (p-1)*(q-1))=1$ and then using basic RSA decryption to get flag ```python= q = n//p phi = (p-1)*(q-1) for e in es: if gcd(p+e, phi) != 1: continue d = inverse(p+e, phi) logging.debug("FLAG: hitcon{" + long_to_bytes( pow(cs[es.index(e)], d, n)).split(b'hitcon{')[-1].decode().strip()) break ``` ```python [DEBUG]: FLAG: hitcon{K33p_ev3rythIn9_1nd3p3ndent!} ``` [Script](https://github.com/m1dm4n/CTF-WriteUp/blob/main/2022/HITCONCTF2022/secret/solve.sage) ## Superprime - Crypto - 33 solves > Yet another cool prime generation method. > > Author: maple3142 I had solve this challenge lately after the CTF end since i missed the problem of using fixed len string for last binary search. It's a little pity for me but you could read my script [here](https://github.com/m1dm4n/CTF-WriteUp/blob/main/2022/HITCONCTF2022/superprime/solve.py) ## RCE - Web - 157 solves > Hello, I am a Random Code Executor, I can execute r4Nd�M JavaScript code for you >< > > Tips: > Have you ever heard of Infinite monkey theorem? If you click the "RCE!" button enough times you can get the flag 😉 > > Author: splitline [Attachment](https://github.com/m1dm4n/CTF-WriteUp/blob/main/2022/HITCONCTF2022/rce/rce-4bc5d3c73ac0fd8c0b098e9e7ac5a2e1c7a2fcf6.zip) ### Solution app.js: ```javascript= const express = require('express'); const cookieParser = require('cookie-parser') const crypto = require('crypto'); const randomHex = () => '0123456789abcdef'[~~(Math.random() * 16)]; const app = express(); app.use(cookieParser(crypto.randomBytes(20).toString('hex'))); app.get('/', function (_, res) { res.cookie('code', '', { signed: true }) .sendFile(__dirname + '/index.html'); }); app.get('/random', function (req, res) { let result = null; if (req.signedCookies.code.length >= 40) { const code = Buffer.from(req.signedCookies.code, 'hex').toString(); try { result = eval(code); } catch { result = '(execution error)'; } res.cookie('code', '', { signed: true }) .send({ progress: req.signedCookies.code.length, result: `Executing '${code}', result = ${result}` }); } else { res.cookie('code', req.signedCookies.code + randomHex(), { signed: true }) .send({ progress: req.signedCookies.code.length, result }); } }); app.listen(5000); ``` The souce code is short so it just have 2 funtions: home page and random page The home page `/` just have a button and when we click it, the server will send a GET requests to the `/random` The random fuction will check if the length of `code` in the cookie is greater of equal 40 so it will `unhex` and `eval` the code and return `result` to us else it will random a hex char and append it to `code` in cookie We can't manipulate the cookie because the signature but we could make the server sign a cookie we need. Just repeatly send a cookie until the next random character is a char we want. The payload need small than 40 (20 since it will unhex) so normal payload won't work here. My solution is using nest evaluation(`eval(eval(somthing_here))`). Since all the code you eval will directly impact to server so from here will have many way to exploit: + You could asign the code to a variable and then request again (but you will need a lot cookie) + Using `req.query.abcd` (`abcd` to fit the target length or anything you like). Only need 1 cookie and then request server with `/random?abcd={code_excute}` + ... And send the code to read the flag on the server with your signed cookie. [Script](https://github.com/m1dm4n/CTF-WriteUp/blob/main/2022/HITCONCTF2022/rce/solve.py)