# 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

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

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()
```