[TOC] ## Reliable Security Approach I encrypted the flag with RSA, it is now very secure! ### 題目分析 Flag 每個字元都被 RSA 加密了。請找出 flag。 ### 解法 由於每個字元是分開加密的,明文會是 0~127 之間的數字,所以只要檢查 0~127 哪一個數字加密後等於密文就好。 ```py # from output.txt n = ... e = ... encrypted = ... flag = '' for encrypted_char in encrypted: for plaintext_char in range(128): if pow(plaintext_char, e, n) == encrypted_char: flag += chr(plaintext_char) print(flag) ``` ## Reasonably Sophisticated Application If my encryption code is very complex (and slow), then no one will be able to break it! ### 題目分析 有一個看起來非常複雜的加密演算法 `encrypt(plaintext, key, rounds)`,並且有兩個用一樣的 `key` 與 `rounds` 加密的結果。其中一個明文已知,另一個是 flag。 ### 解法 加密流程為用 `key` 算出 `derived`,然後把 `derived` 用某種方法和明文 xor,是一種(很爛的) stream cipher。因為是使用 xor,所以加密和解密的操作是一樣的。 雖然 `key_derive` 裡面的過程比較複雜,但可以發現同個 `key` 被用了兩次,所以兩次得到的 `derived` 結果會一樣。因此其實不需要知道 `key` 是什麼或是 `key_derive` 裡面在幹嘛,只要得到加密時用的 `derived` 就可以解密。 我們發現 `derived` 是長度 16 的 bytes,而且從 ```py for i in range(len(plaintext)): ciphertext[i] ^= derived[(i ^ (i >> 4)) % 16] ``` 可以知道 `derived` 就是 `ciphertext[0:16]` xor `plaintext[0:16]`。利用題目提供的 test plaintext/ciphertext 可以得到 `derived` 解密。 ```py # from output.txt test_crypt = bytes.fromhex(...) flag_crypt = bytes.fromhex(...) test_plain = b'this is a test plaintext' derived = bytearray(16) for i in range(16): derived[i] = test_plain[i] ^ test_crypt[i] flag = bytearray(flag_crypt) for i in range(len(flag)): flag[i] ^= derived[(i ^ (i >> 4)) % 16] print(flag.decode()) ``` ## Revised Signature Algorithm I heard that SHA-1 or similar functions are vulnerable to [LEA](https://en.wikipedia.org/wiki/Length_extension_attack), so I revised my hash algorithm. Can you make a fake signature? ### 題目分析 服務有兩種功能: 1. 提供 `message`,獲得 `sign(message)` 也就是 `hash(secret + nonce + message)` 的值,但 `message` 只能是八位數字 2. 提供 `nonce` 和 `sign('GIVE ME THE FLAG!!')`,獲得 flag Hash 的實作是用 byte 陣列 $s_0s_1s_2\dots s_{n-1}$ 計算 $$ \begin{align*} & (\dots (((s_0b + s_1)b + s_2)b + s_3) \dots)b + s_{n-1} \bmod p\\ \vphantom{}=\vphantom{} &\sum_{i=0}^{n-1} s_i b^{n-1-i} \bmod p \end{align*} $$ 的值,其中 $b, p$ 是未知的隨機整數。 ### 解法 如果知道 $b, p, c$,那就能從 $\operatorname{hash}(s_0s_1 \dots s_{n-1})$ 推出 $\operatorname{hash}(s_0s_1 \dots s_{n-1}c)$ 的值,因為 $$ \begin{align*} & \operatorname{hash}(s_0s_1 \dots s_{n-1}c)\\ \vphantom{}=\vphantom{} &((\dots ((s_0b + s_1)b + s_2) \dots) + s_{n-1})b + c \bmod p\\ \vphantom{}=\vphantom{} &\operatorname{hash}(s_0s_1 \dots s_{n-1}) \times b + c \bmod p\\ \end{align*} $$ 反之也可以得到 $$ \begin{align*} & \operatorname{hash}(s_0s_1 \dots s_{n-1})\\ \vphantom{}=\vphantom{} &(\operatorname{hash}(s_0s_1 \dots s_{n-1}c) - c) \times b^{-1} \bmod p\\ \end{align*} $$ 重複這個流程,就能對於任意字串 $s, t$,在不知道 $s$ 的情況下從 $\operatorname{hash}(s)$ 推出 $\operatorname{hash}(s+t)$ 的值(或是反過來)。 如果令 `s = secret + nonce`,那我們可以從 `sign(b'12345678') = hash(s + b'12345678')` 推得 `hash(s)` 然後再推得 `hash(s + b'GIVE ME THE FLAG!!') = sign(b'GIVE ME THE FLAG!!')`: ```py hash_value = sign(b'12345678') for char in reversed(b'12345678'): hash_value = (hash_value - char) * pow(b, -1, p) % p for char in b'GIVE ME THE FLAG!!': hash_value = (hash_value * b + char) % p # hash_value == sign(b'GIVE ME THE FLAG!!') ``` 所以得到 $b, p$ 就可以拿 flag。得到 $b, p$ 的方法有很多,這裡講其中一種: 我們知道 `secret` 與 `nonce` 長度,並且第 $i$ 次使用服務的 `nonce` 會等於 `str(i).encode() * 8`。因此,當 $m$ 是八位數時,`sign(m)` 的值是 $\operatorname{hash}(s_0s_1 \dots s_{15} cccccccc m_0m_1 \dots m_7)$,其中 $s$ 是 `secret`、$c$ 是 `ord('0') + i`。把他展開可以得到 $$ \begin{align*} & \operatorname{hash}(\color{blue}{s_0s_1 \dots s_{15}} \color{green}{cccccccc} {m_0m_1 \dots m_7})\\ \vphantom{}=\vphantom{} & \color{blue}{s_0b^{31} + s_1b^{30} + \dots + s_{15}b^{16}} + \\ & \color{green}{cb^{15} + cb^{14} + \dots + cb^8} + \\ & {m_0b^7 + m_1b^6 + \dots + m_6b + m_7} \bmod p\\ \end{align*} $$ 如果第 0 次、第 1 次 sign 一樣的 $m = \texttt{"00000000"}$,令得到的值為 $h_0, h_1$,那麼會有 $$ \begin{align*} & h_1 - h_0 \\ \vphantom{}\equiv\vphantom{} & (\color{blue}{s_0b^{31} + s_1b^{30} + \dots + s_{15}b^{16}} + \\ & \color{green}{\texttt{`1'}b^{15} + \texttt{`1'}b^{14} + \dots + \texttt{`1'}b^8} + \\ & \texttt{`0'}b^7 + \texttt{`0'}b^6 + \dots + \texttt{`0'}b + \texttt{`0'}) -\\ & (\color{blue}{s_0b^{31} + s_1b^{30} + \dots + s_{15}b^{16}} + \\ & \color{green}{\texttt{`0'}b^{15} + \texttt{`0'}b^{14} + \dots + \texttt{`0'}b^8} + \\ & \texttt{`0'}b^7 + \texttt{`0'}b^6 + \dots + \texttt{`0'}b + \texttt{`0'}) \\ \vphantom{}\equiv\vphantom{} & (\color{green}{\texttt{`1'}b^{15} + \texttt{`1'}b^{14} + \dots + \texttt{`1'}b^8})-\\ & (\color{green}{\texttt{`0'}b^{15} + \texttt{`0'}b^{14} + \dots + \texttt{`0'}b^8}) \\ \vphantom{}\equiv\vphantom{} & b^{15} + b^{14} + \dots + b^8 \pmod p\\ \end{align*} $$ 令 $b^{15} + b^{14} + \dots + b^8 \bmod p = r$。 因為 $h_0, h_1$ 是 $\vphantom{}\bmod p$ 的結果,$h_1 - h_0$ 的實際值可能是 $r$ 或是 $r - p$(看運氣)。 如果第 2 次 sign 的是 $m = \texttt{"000000}\color{red}{\texttt{1}}\texttt{0"}$,得到 $h_2$,那麼 $$ \begin{align*} & h_2 - h_1 \\ \vphantom{}\equiv\vphantom{} & (\color{blue}{s_0b^{31} + s_1b^{30} + \dots + s_{15}b^{16}} + \\ & \color{green}{\texttt{`2'}b^{15} + \texttt{`2'}b^{14} + \dots + \texttt{`2'}b^8} + \\ & \texttt{`0'}b^7 + \texttt{`0'}b^6 + \dots + \color{red}{\texttt{`1'}b} + \texttt{`0'}) -\\ & (\color{blue}{s_0b^{31} + s_1b^{30} + \dots + s_{15}b^{16}} + \\ & \color{green}{\texttt{`1'}b^{15} + \texttt{`1'}b^{14} + \dots + \texttt{`1'}b^8} + \\ & \texttt{`0'}b^7 + \texttt{`0'}b^6 + \dots + \color{red}{\texttt{`0'}b} + \texttt{`0'}) \\ \vphantom{}\equiv\vphantom{} & (\color{green}{\texttt{`2'}b^{15} + \texttt{`2'}b^{14} + \dots + \texttt{`2'}b^8})-\\ & (\color{green}{\texttt{`1'}b^{15} + \texttt{`1'}b^{14} + \dots + \texttt{`1'}b^8}) +\\ & (\color{red}{\texttt{`1'}b} - \color{red}{\texttt{`0'}b})\\ \vphantom{}\equiv\vphantom{} & b^{15} + b^{14} + \dots + b^8 + b\\ \vphantom{}\equiv\vphantom{} & r + b \pmod p\\ \end{align*} $$ 而 $h_2 - h_1$ 的實際值可能是 $r + b$ 或是 $r + b - p$(看運氣)。 如果第 3 次 sign 一樣的 $m = \texttt{"00000010"}$,得到 $h_3$,那麼與 $h_1 - h_0$ 同理,$h_3 - h_2$ 會是 $r$ 或 $r - p$。 若 $h_3 - h_2 < 0$ 且 $h_1 - h_0 \ge 0$,那麼 $h_3 - h_2 = r - p, h_1 - h_0 = r$,所以 $p = (h_1 - h_0) - (h_3 - h_2)$,並且 $b = (h_2 - h_1) - r \bmod p$。因為有一些運氣成份,這個解法可能要跑幾次才會過。有了 $b, p$ 之後就可以用之前的方法獲得 `b'GIVE ME THE FLAG!!'` 的 signature 來拿 flag。可能有其他更簡單的方法拿 $b, p$,但這個應該比較直觀(?)。 :::spoiler 完整 script ```py import pwn io = pwn.remote('localhost', '33334') def sign_password(password): io.sendlineafter(b'> ', b'1') io.sendlineafter(b'Password? ', password) signature = bytes.fromhex(io.recvline().decode()) _nonce, hash = signature[:8], signature[8:] return int.from_bytes(hash, 'big') def verify_password(password, signature): io.sendlineafter(b'> ', b'2') io.sendlineafter(b'Password? ', password) io.sendlineafter(b'Signature? ', signature.hex().encode()) return io.recvline().decode() h0 = sign_password(b'00000000') h1 = sign_password(b'00000000') h2 = sign_password(b'00000010') h3 = sign_password(b'00000010') # r = b^8 + b^9 + ... + b^15 # h1 - h0 = r (mod p) # h2 - h1 = r + b (mod p) # h3 - h2 = r (mod p) if h1 - h0 > 0 and h3 - h2 < 0: r = h1 - h0 negr = h3 - h2 elif h1 - h0 < 0 and h3 - h2 > 0: negr = h1 - h0 r = h3 - h2 else: print('solve fail') exit(1) p = r - negr b = ((h2 - h1) - r) % p # make h4 = sign('GIVE ME THE FLAG!!') h4 = h3 for char in reversed(b'00000010'): h4 = (h4 - char) * pow(b, -1, p) % p for char in b'GIVE ME THE FLAG!!': h4 = (h4 * b + char) % p h4 = h4.to_bytes(64, 'big') nonce = b'3' * 8 flag = verify_password(b'GIVE ME THE FLAG!!', nonce + h4) print(flag) ``` :::