# Câu cúi: - Do bài này khác với bài trước ở 2 chỗ, 1 là nó dùng mode CTR, 2 là mỗi lần lặp nó sẽ tạo key khác, do đó để làm bài này thì bắt buộc phải cryptanalysis thôi :v - Đầu tiên, mình dùng z3 để symbolic xem từ plaintext qua đến ciphertext nó sẽ thực hiện như thế nào. Có 2 thứ để symbolic: - Symbolic các round_keys -> đặt từ `k0` -> `k6`: ```python def _expand_key(self, key: bytes) -> None: keys = BitVecs(' '.join([f'k{i}' for i in range(self.rounds)]), 8) self._round_keys = keys ``` - Symbolic 2 hàm sbox và permutation thành 1 hàm P nào đó (tí nữa soi 2 hàm này sau :v), tức khi này `P(a) = self.permutation(self.sbox(a))`: ```py P = Function('P',BitVecSort(8), BitVecSort(8)) def _f(self, l: int, r: int, key: int) -> int: # a = bytearray(int(r ^ key).to_bytes(4, "big")) a = r^key # return l ^ int.from_bytes(self.permutation(self.sbox(a)), "big") return l ^ P(a) ``` - Sau đó chỉnh hàm `_encrypt_block` để chỉ cần in ra các block sau khi qua 7 round xem nó như thế nào: ```python def _encrypt_block(self, pt: bytes) -> bytes: assert len(pt) == self.block_size blocks = [int.from_bytes(pt[(self.block_size // 4) * i : (self.block_size // 4)*(i + 1)], "big") for i in range(4)] for i in range(self.rounds): blocks[1] = self._f(blocks[1], blocks[0], self._round_keys[i]) blocks = blocks[1:] + [blocks[0]] print(blocks[0]) print() print(blocks[1]) print() print(blocks[2]) print() print(blocks[3]) ``` thì đc như thế này ![image](https://hackmd.io/_uploads/ByuwEdcIll.png) - Ở đây nó chỉ đang xử lí block đầu tiên, tức là nó đang encrypt counter 1. Cop ra bên ngoài viết cho đẹp lại, vứt mấy số 0 vô nghĩa đi thì đc như sau: ``` 1^P(P(P(k0)^k1)^k2)^P(P(P(k0)^k1)^P(P(k0)^P(P(1^P(P(P(k0)^k1)^k2)^k3)^k4)^k5)^k6) P(1^P(P(P(k0)^k1)^k2)^k3) P(k0)^P(P(1^P(P(P(k0)^k1)^k2)^k3)^k4) P(P(k0)^k1)^P(P(k0)^P(P(1^P(P(P(k0)^k1)^k2)^k3)^k4)^k5) ``` - Ở đây mình thấy được thằng `blocks[1]` đơn giản nhất, nên tập trung vào nó thôi. Viết lại cho gọn thì ta xem như `blocks[1] = P(1^X)`. Số 1 ở đây phụ thuộc vào giá trị của counter bị encrypt, ví dụ encrypt counter 2 thì chỗ đó là số 2. Còn X đây chỉ phụ thuộc vào các `ki` nên cố định nếu ta encrypt nhiều block cùng lúc. - Bài này chỉ cần mỗi lần gởi 2 blocks, từ đó lấy được 2 giá trị là `enc(counter1)` và `enc(counter2)`, từ đó mình lấy đc giá trị của `P(1^X)` và `P(2^X)`, **để ý là `1^X` và `2^X` chỉ khác nhau 1 byte cuối**, do đó `Sbox(1^X)` và `Sbox(2^X)` cũng chỉ khác nhau 1 byte cuối. - Tiếp theo mình sẽ symbolic tiếp hàm P coi thử nếu 2 input chỉ khác 1 byte cuối thì ra output có gì vui ko :v : ```py xtime = Function('xtime',BitVecSort(8), BitVecSort(8)) def permutation(a): t = a[0] ^ a[1] ^ a[2] ^ a[3] u = a[0] a[0] ^= t ^ xtime(a[0] ^ a[1]) a[1] ^= t ^ xtime(a[1] ^ a[2]) a[2] ^= t ^ xtime(a[2] ^ a[3]) a[3] ^= t ^ xtime(a[3] ^ u) return a a = BitVecs('a0 a1 a2 a3',8) b = BitVecs('a0 a1 a2 b3',8) print(permutation(a)) print(permutation(b)) ``` Kết quả ra như thế này: ![image](https://hackmd.io/_uploads/BJmGuu5Lgg.png) Quá ngon luôn, thế là nếu mình xor 2 byte đầu của output này với 2 byte đầu của output kia, kết quả ra luôn = 0. End game. Script solve: ```python= from pwn import * from chall import * from Crypto.Util.number import * import os import random # io = process(["python3", "chall.py"]) io = remote('chall.blackpinker.com', 32869) def get_enc(pt): io.sendlineafter(b'Plaintext (hex) ', pt.hex().encode()) res = bytes.fromhex(io.recvlineS().strip()) return res def split(arr, size_per_chunk): return [arr[i:i+size_per_chunk] for i in range(0, len(arr), size_per_chunk)] for i in range(3*37): pt = b'\0'*16 res = get_enc(pt) blocks = split(res, 16) blocks[1] = xor(blocks[1], bytes([16]*16)) blocks = [split(b, 4) for b in blocks] if blocks[0][1][0] ^ blocks[0][1][1] ^ blocks[1][1][0] ^ blocks[1][1][1] == 0: guess = 1 else: guess = 0 io.sendlineafter(b'Guess what? ', str(guess).encode()) io.interactive() ```