Wolv CTF 2023

keyexchange

Description: Diffie-Hellman is secure right

Với ba số nguyên tố \(n\), \(s\), \(a\), server gửi mình số \(s^a \pmod n\). Mình cần nhập số \(b\) và server trả về \((s^a)^b \pmod n\). Mình chỉ cần chọn \(b=1\)secret_key sẽ là số \(s^a \pmod n\) được nhận lúc nãy. Từ đó decrypt ra flag.

Z2kDH

Description: Sick of how slow it is to take the modulus of a large prime number? Tired of performing exponentiation over some weird group like ed25519? Just use the integers mod 2^k group with the unit g = 5! Efficient, reliable, doesn't require any hard math!

Với output là

99edb8ed8892c664350acbd5d35346b9b77dedfae758190cd0544f2ea7312e81
40716941a673bbda0cc8f67fdf89cd1cfcf22a92fe509411d5fd37d4cb926afd

Gọi \(a\), \(b\) lần lượt là private key của Alice và Bob. Khi đó output trên tương đương với \((g^a \bmod p) // 4\)\((g^b \bmod p) // 4\) với \(p = 2^{258}\).

Như vậy mình cần bruteforce 2 bit cuối của mỗi public key và từ đó tìm ra private key của từng người. Cuối cùng thế vào hàm Z2kDH_exchange để tìm shared key là flag.

Galois-t is this?

Description: I'm expecting a super secret message... If you can give it to me, I'll give you a flag!

Bài này thực chất là tính tag trong mode GCM của AES. Các bạn có thể xem thêm về mode CTR và GCM trong bài tìm hiểu lại Google CTF 2020 của mình (bài Pythia).

Trong bài này chúng ta có ba lần request với ba nonce khác nhau để encrypt hoặc decrypt.

Xét hàm incr mình thấy counter thực hiện cộng lên 1 ở mỗi bước.

Nếu mình bắt đầu từ \(\mathrm{nonce} = 0\), với ba block plaintext \(P_1\), \(P_2\), \(P_3\) thì ciphertext tương ứng sẽ như sau.

../_images/wolv1.jpg

Tại sao mình cần ba block plaintext trong khi message của mình là hai block (\(32\) byte)? Vì ý tưởng của mình khi decrypt là gửi \(\mathrm{nonce}=1\) lên, khi đó các giá trị trung gian (AES của counter) giữ nguyên với lúc encrypt.

../_images/wolv2.jpg

Lưu ý rằng ở hình 1, mình encrypt với \(P = 0\)\(\mathrm{nonce} = 0\) nên mình sẽ có \(C_i = u_i\). Ở hình 2 mình có \(P'_i \oplus C'_i = P_{i+1} \oplus C_{i+1} = C_{i+1}\), nên mình sẽ tìm được \(C'_i\) ứng với \(P'_i\) là message (\(C'_i = P'_i \oplus C_{i+1}\)).

Vậy còn bước authentication?

Nhắc lại về cách tính tag. Trong \(\mathrm{GF}(2^{128})\), giả sử với các ciphertext \(C_1\), \(C_2\), ..., \(C_{n}\), với \(H = \text{AES}_K(0^{128})\), \(L = \mathrm{len}(A) || \mathrm{len}(C)\)\(I = \text{AES}_K (J_0)\) với \(J_0\) là nơi bắt đầu counter.

Ở đây \(n = 2\), nghĩa là mình cần tìm \(T = C_1 H^4 + C_2 H^3 + A H^2 + L H + I\) với \(A\) là associated data (ở trong bài là header).

Ở đây chúng ta đã có \(C'_1\), \(C'_2\), \(A\), \(L\), \(I\) (\(I\) chính là \(C_1\), các bạn thấy không? :v), vậy còn \(H\)?

Tới đây mình encrypt với \(\mathrm{nonce}=2\) và ciphertext là chuỗi rỗng. Khi đó tag của mình là \(T'' = A H^2 + L'' H + I\), nhưng \(A\) đã bị pad bởi mấy số 0 ở cuối nên phép nhân H_mult luôn cho ra \(0\), do đó \(T'' = L'' H + I\) (\(L'\) là vì lúc này \(\mathrm{len}(C) = 0\)). Như vậy mình có thể tìm \(H\) rồi.

Như vậy quy trình giải bài này là:

  • encrypt với \(\mathrm{nonce}=0\)\(P_i = 0^{128}\) với \(i=1,2,3\);

  • encrypt với \(\mathrm{nonce}=2\)\(P_i = \emptyset\);

  • tính \(H\) từ đó tính tag khi \(\mathrm{nonce} = 1\);

  • decrypt với \(C = \text{message}\)\(\mathrm{nonce} = 1\).

Cám ơn các bạn đã đọc writeup của mình.