Trong giải đấu này, mình đã giải được 1 câu trong phần Cryptography và may mắn đứng thứ 8 trên bảng đến cuối (≧▽≦)
Sau đây là bài mình giải được trong giải đấu và tiếp đó là những bài mà mình chưa làm được khi còn trong giải.
Choose!
Link: https://www.youtube.com/watch?v=1lqe8eU48HI
|
|
|
|
Bài này đơn giản là về phần mã hóa AES (Advanced Encryption Standard) và mình có xem lại cấu trúc của nó tại wikipedia và đây để tiếp cận.
Phân tích:
- Hàm
dumb_step
pass 1 phép biển đổi trong 4 phép cần thiết để mã hóa AES. - Server tạo 50 vòng, trong đó mỗi vòng thực thi nhiệm vụ sau:
- Nhập 1 đoạn tin có $length\le48$.
- Random bỏ 1 trong 4 bước để tạo
step
mới chỉ còn 3 bước. - Random sử dụng
init_step
gồm 4 bước haystep
để mã hóa. - In ra
ciphertext
tương ứng. - Yêu cầu nhập vào:
- 0 nếu dùng
init_step
(4 bước) - 1 nếu dùng
step
(3 bước)
- 0 nếu dùng
Bypass được 50 vòng thì ta sẽ nhận được flag. Đề bài là vậy nên khi bắt tay vào làm và code thì mình đã giải thuật được cho 3 bước rồi nhưng còn bị kẹt lại ở phần Skip SubBytes, may sao trong lúc tìm tài liệu thì đã tìm thấy bài này và giải quyết được thử thách.
Ý tưởng:
- Chủ đích của ta là phải từ
plaintext
nhập vào vàciphertext
tương ứng phải biết được nó đã được sử dụng bao nhiêu bước để trả về giá trị đúng trong 50 vòng. - Yêu cầu của
plaintext
là 48 bytes (tức 3 khối AES) nhưng nếu để ý kỹ thì nó đượcpadding
trước khi mã hóa nên nếu gửi đủ 48 bytes thìplaintext
sẽ thêm khối thứ 4 làb'\x10' * 16
và đây cũng là dữ kiện quan trọng để giải bài này. - Để cho mạch suy nghĩ trôi chảy và dễ code thì ta chia nó làm 5 trường hợp chính và giải quyết như ngay sau đây:
- Skip AddRoundKey
Đây là phần dễ nhất mà ta chỉ cần encrypt cái
plaintext
mà mình nhập vào với thuật aes chỉ có 3 bước còn lại vì nó không còn phụ thuộc vào key. Do đó, ta không có điều kiện gì vớiplaintext
nhập vào. Sau đây là script phần này:
|
|
- Skip SubBytes
Phần này đối với mình là thử thách nhất vì khi phân tích, mình thấy rằng bỏ SubBytes đi sẽ làm cho phần mã hóa với 3 bước còn lại trở thành các bước với chuyển đổi và ma trận khá phức tạp. Như ta đã biết, nếu bỏ SubBytes đi thì các vòng của AES sẽ trở thành:
Vòng 0: AddRoundKey
Vòng 1–9: ShiftRows, MixColumns, AddRoundKey
Vòng 10: ShiftRows, AddRoundKey
Để cho gọn thì ta đặt $S, M, k_i$ lần lượt là phép biển đổi ma trận bằng ShiftRows và MixColumns và AddRoundKey ở vòng thứ i. Sau đây là quá trình mã hóa plaintext
($P$):
Vòng 0: $P + k_0$
Vòng 1: $M(S(P+k_0))+k_1$. Đơn giản hóa bằng cách tính $A =MS$.
Vòng 2-9: $…(A(A(A(P+k_0))+k_1)+k_2)+k_3+…$
Vòng 10: $S(…(A(A(A(P+k_0))+k_1)+k_2)+k_3+…)+k_{10}$
$=SA^9P+SA^8k_0+..Sk_9+k_{10}=SA^9P+K$
Ở cuối ta thấy là plaintext
gắn với $SA^9=M^9S^{10}$ mà không phụ thuộc gì tới key.
Do đó, để tấn công thì ta có thể nghĩ rằng với cùng 1 key thì $K$ tính được ở trên sẽ như nhau tức là ciphertext
đặt là $C$ sẽ cho ra 1 hằng số $K=C-SA^9P$.
Và để giải quyết phần này thì ta sẽ phải có 2 khối trong phần plaintext
ngay trước mã hóa để chứng minh và tính $K$ bằng cách tìm được cả $SA^9$.
Với phần tính $SA^9$ thì không hề đơn giản và mình vẫn đang đọc và tìm hiểu qua bài này. Còn thuật toán thì mình sẽ sử dụng 1 phần của implement trong bài báo cho trên.
Tiếp theo là phần 2 khối trong plaintext
thì mình chọn khối 1 và khối 4 để sử dụng với khối 1 mình đưa vào là: b'\x00' * 16
và khối 4 mặc định là: b'\x10' * 16
.
Script:
|
|
- Skip ShiftRows
Khi bỏ phần này thì rõ ràng các cột sẽ luôn giữ nguyên vị trí vì khi thực hiện MixColumns xáo cột chỉ làm thay đổi được vị trí của nó chứ không phải những thành phần bên trong đó. Vì vậy ta sẽ chia 1 khối trong
plaintext
ra thành 4 block và so sánh, nghĩa là hai cặpplaintext - ciphertext
ứng với 1 cặp block tương ứng giống nhau ở mỗi phần. Ở đây, mình sử dụng khối 2 và khối 4 với dữ liệu là:b'\x10' * 4 + b'\x30' * 12
vàb'\x10' * 16
rồi so sánh block đầu củaciphertext
. Script:
|
|
- Skip MixColumns
Ở bước này, nó không những chuyển vị các cột mà còn nhân với 1 hệ số cố định $c(x)$
Do đó, khi ta bỏ bước MixColumns thì 1 khối 16 byte sẽ được mã hóa theo 16 khối khác nhau ứng với từng byte và không phụ thuộc đôi một vào nhau. Điều này nghĩa là tồn tại 1 song ánh ứng với 1 byte của
plaintext
với 1 byte củaciphertext
và mình bypass phần này bằng cách xét chỉ 1 byte khác nhau của 2 khối ởplaintext
và đếm số lượng khác nhau của chúng ởciphertext
Ở bài này, mình sử dụng khối 3 và khối 4 với dữ liệu là:b'\x00' + b'\x10' * 15
vàb'\x10' * 16
rồi so sánh count của chúng. Script:
|
|
Sau đây là script tổng hợp cả 4 phần và cũng là solution mà mình viết ra được cho chall:
|
|
Flag: W1{aAESEEESaEsaAEaSesEEEsAaseseesaSSEaaASeAAESEESSSaASeAsSSAAAAeAsE_baCsDCbtqU}
heartbreak
|
|
Flag của thử thách được chia làm 2 và mã hóa bằng 2 phần khác nhau.
Part 1:
|
|
Bộ khóa RSA được gửi ra sau khi được nhân với 1 số nguyên tố 1024 bit ngẫu nhiên.
Chú ý điều kiện: if hints[_] == 0: hints[_] = (hints[_] - 1) % n
=> Từ đây, ta có thể tính được ngay $n = hint(n) + 1$
Thêm nữa, ta có hệ phương trình:
$$ \begin{cases} p = getPrime(2048) \\ q = getPrime(2048) \\ n = p * q \\ hint(p) = p * getPrime(1024) \end{cases} $$Tức là $n$ có 4096 bit, còn hint(p)
chỉ có 3072 bit và vì nó bé hơn $n$ nên khi lấy đồng dư thì nó sẽ không thay đổi.
Khi đó, ta hoàn toàn có thể lấy được $p$ bằng cách lấy GCD của (n, hint(p))
Cuối cùng, ta khôi phục dữ liệu cần thiết của bộ khóa và giải mã được part 1.
|
|
Part 2:
|
|
Với $e$ nhỏ, thì ta nghĩ ngay tới Low public exponent attack
và khi tìm hiểu thì ta thấy bài này tương đồng với kiểu Franklin–Reiter related-message attack
Cụ thể hơn, $m_2 = m _1 » 8 \Leftrightarrow m_1 = m_2 * (1 » 8) + k$.
Mặt khác, ta biết rằng $m_1$ là Part2 của flag tức là phần tử cuối trong 8 bit của nó khi quy về bảng mã ASCII sẽ là }
tương đương với giá trị 125
=> $m_1=256*m_2+125$
Sau đó, ta viết lại dưới dạng đa thức $f(x)$ và $g(x)$ là:
$$ \begin{cases} f(x) = x^e-c_1, f(m_1) =0 \\ g(x) = x^e-c_2, f(m_2) =0 \end{cases} \Leftrightarrow \begin{cases} f(x) = (256*x+125)^e-c_1, f(m_2) =0 \\ g(x) = x^e-c_2, g(m_2) =0 \end{cases} $$Hai đa thức này có nghiệm chung là $m_2$ nên khi lấy GCD của chúng và nếu nó tuyến tính thì ta sẽ lấy được $m_2$, tính $m_1$ và lấy được Part2 của flag.
|
|
Script:
|
|
Flag: W1{https://www.youtube.com/results?search_query=p0lyn0m1als+9c4+is+good+isn%27t+it+?flag=tru4}