# zer0ptsCTF 2023 writeup - Cuối tuần vừa rồi (15/7 - 16/7) đã diễn ra giải zer0ptsCTF, mình đã đánh cho team ``m1cr0$oft 0ff1c3`` và kết thúc giải ở vị trí thứ 48. ![](https://hackmd.io/_uploads/rJBdBLfq3.png) - Giải lần này thì mình chỉ solve được 2/6 câu crypto, những câu còn lại tương đối khó (chắc là do trình chưa đủ 😥). - Dưới đây là writeup cho các challenge mà mình đã giải được. ## writeup available for: ### Crypto: 1. [SquareRNG](#SquareRNG) 2. [easy-factoring](#easy-factoring) ## SquareRNG >I wrote a pseudorandom number generator. You can't guess the next bit without knowing each other's secret. > >nc crypto.2023.zer0pts.com 10666 > >author: ptr-yudai Attachments: * server.py ```py=0 #!/usr/bin/env python3 import os from Crypto.Util.number import getPrime, getRandomRange def isSquare(a, p): return pow(a, (p-1)//2, p) != p-1 class SquareRNG(object): def __init__(self, p, sa, sb): assert sa != 0 and sb != 0 (self.p, self.sa, self.sb) = (p, sa, sb) self.x = 0 def int(self, nbits): v, s = 0, 1 for _ in range(nbits): self.x = (self.x + 1) % p s += pow(self.sa, self.x, self.p) * pow(self.sb, self.x, self.p) s %= self.p v = (v << 1) | int(isSquare(s, self.p)) return v def bool(self): self.x = (self.x + 1) % self.p t = (pow(self.sa, self.x, self.p) + pow(self.sb, self.x, self.p)) t %= self.p return isSquare(t, self.p) p = getPrime(256) sb1 = int(input("Bob's seed 1: ")) % p sb2 = int(input("Bob's seed 2: ")) % p for _ in range(77): sa = getRandomRange(1, p) r1 = SquareRNG(p, sa, sb1) print("Random 1:", hex(r1.int(32))) r2 = SquareRNG(p, sa, sb2) print("Random 2:", hex(r2.int(32))) guess = int(input("Guess next bool [0 or 1]: ")) if guess == int(r1.bool()): print("OK!") else: print("NG...") break else: print("Congratz!") print(os.getenv("FLAG", "nek0pts{*** REDACTED ***}")) ``` Nhận xét: - Đây là challenge đầu tiên xuất hiện và được gắn tag warm up. - Tóm tắt sơ qua flow của bài: + Ta cần gửi đến server 2 seed **sb1** và **sb2**. + Khi này server sẽ nhận 2 seed ta vừa nhập modulo **p** (p là số nguyên tố) và bắt đầu random 1 số **sa** trong khoảng [1, p]. + Tiếp theo đó server sẽ cho ta biết kết quả của method int(nbits) của 2 object **r1** và **r2** của class SquareRNG. + Sau đó, server yêu cầu ta đoán output của method bool của object **r1**. - Như vậy, toàn bộ các phép tính trong bài này đều được gói gọn trong class SquareRNG. Bây giờ, ta sẽ tiến hành nghiên cứu class SquareRNG. - Đầu tiên là method `__init__` của class, ta có thể hiểu đây là method dùng để khai báo các tham số của object trong class. Quan sát thấy **r1** và **r2** khi được khai báo đều sử dụng chung tham số **sa** và **p**, duy chỉ khác nhau ở seed đầu vào **sb1** và **sb2** do ta nhập (Điều kiện là sa và sb được nhập vô đều phải **không** ≡ 0 (mod p). (Ở đây theo kinh nghiệm của mình thì chắc chắn **r1** và **r2** sẽ có sự liên quan nhất định đến nhau). - Tiếp theo là method `int(self, nbits)` của class, phân tích qua đoạn code ta sẽ thấy được rằng $s = 1+\sum\limits_{x = 1}^{nbits}{sa^{x} * sb^{x}}$ và output của method sẽ cho ta biết với từng x một trong công thức trên sẽ mang giá trị 0 hay 1. - Giá trị 0 hay 1 ở trên được tính thông qua hàm isSquare(a, p) và hàm đó sẽ cho ta biết kết quả của `pow(a, (p-1)//2, p) != p-1`. Nhìn kĩ công thức trong hàm này ta lại thấy ngay đây chính là công thức của [Legendre Symbol](https://en.wikipedia.org/wiki/Legendre_symbol). Như vậy, ta có thể kết luận ngay hàm isSquare(a, p) này cho chúng ta biết số a được nhập vô có phải là 1 số [Quadratic Residues](https://en.wikipedia.org/wiki/Quadratic_residue) (QR) trên modulo p hay không. - Cuối cùng là method `bool(self)`, output của method này sẽ check xem $sa^x + sb^x$ có phải là 1 số QR như đã nói ở trên hay không. - Một điều cần lưu ý khác trong bài này là ta cần đoán output của r1.bool() sau khi đã gọi r1.int(32), tức là ta đang cần check xem $sa^{33} + sb1^{33}$ có phải là QR không. - Ta có thể thấy trong công thức trên có dạng $a^n + b^n$ và n là số lẻ vừa hay thỏa công thức dưới đây. ![](https://hackmd.io/_uploads/rye64vGq2.png) - Như vậy, thay b = 1, n = 33 vào công thức ở trên, ta sẽ thu được $a^{33} + 1^{33} = (a+1)(a^{32} - a^{31} + a^{30} + ... + 1)$ - Như vậy, ta đã có thể phân tích $sa^{33} + sb^{33}$ thành 2 factor và như ta đã biết rằng để $sa^{33} + sb^{33}$ là số QR thì ta cần 2 factor của nó hoặc đồng thời là QR hoặc đồng thời không là QR. ![](https://hackmd.io/_uploads/B1PQ8vfch.png) - Tiếp theo, ta sẽ để ý rằng nếu như ta nhập sb = 1, kết quả của method `int(self, nbits)` thu được sẽ có dạng $s = 1+\sum\limits_{x = 1}^{nbits}{sa^{x}} = 1 + sa^1 + sa^2 + ... + sa^{32}$ - Còn nếu ta nhập sb = -1, ta sẽ thu được $s = 1 - sa^1 + sa^2 - ... - sa^{31} + sa^{32}$ - Đối chiếu với factor thứ hai trong expansion của $a^n + b^n$, ta thấy rằng 2 bọn nó là như nhau. Vì thế nếu nhập sb = -1, thì bit cuối cùng của output của method `int(self, nbits)` sẽ cho ta một nửa dữ kiện để kết luận. Nửa dữ kiện còn lại sẽ đến từ bit đầu tiên của sb = 1. - Như vậy, ở bài toán này, ta chỉ cần đơn giản gửi lên 2 seed 1 và -1. Khi đó dựa vào bit cuối của seed -1 và bit đầu của seed 1 là đủ dữ kiện để ta có thể kết luận số tiếp theo của seed 1 có phải là QR hay không. Script: ```py=0 from pwn import * import time r = remote("crypto.2023.zer0pts.com", 10666) r.recvuntil(b"Bob's seed 1: ") r.sendline(b"1") r.recvuntil(b"Bob's seed 2: ") r.sendline(b"-1") def cal(a, b): # XNOR function """ based on a^n + b^n = (a+b)... and also legendre symbol """ return 1 if a == b else 0 for _ in range(77): print(f"Try {_}") r.recvuntil(b"Random 1: ") r1 = int(r.recvline().rstrip(), 16) r.recvuntil(b"Random 2: ") r2 = int(r.recvline().rstrip(), 16) print(bin(r1), len(str(bin(r1)[2:]))) print(bin(r2), len(str(bin(r2)[2:]))) print(r1>>31) print(r2&1) #r.interactive() res = cal(r1>>31, r2&1) print(res) r.recvuntil(b"Guess next bool [0 or 1]: ") r.sendline(str(res).encode()) print("~"*40) time.sleep(1) r.interactive() #zer0pts{L(a)L(b)=L(ab)} ``` ![](https://hackmd.io/_uploads/rJ9rtDzcn.png) -> Flag: `zer0pts{L(a)L(b)=L(ab)}` ## easy-factoring >The word "decomposition" has multiple meanings. >Can you decompose? > >nc crypto.2023.zer0pts.com 10333 > >author: mitsu Attachment: * server.py ```py=0 import os import signal from Crypto.Util.number import * flag = os.environb.get(b"FLAG", b"dummmmy{test_test_test}") def main(): p = getPrime(128) q = getPrime(128) n = p * q N = pow(p, 2) + pow(q, 2) print("Let's factoring !") print("N:", N) p = int(input("p: ")) q = int(input("q: ")) if isPrime(p) and isPrime(q) and n == p * q: print("yey!") print("Here you are") print(flag) else: print("omg") def timeout(signum, frame): print("Timed out...") signal.alarm(0) exit(0) if __name__ == "__main__": signal.signal(signal.SIGALRM, timeout) signal.alarm(30) main() signal.alarm(0) ``` Nhận xét: - Bài trên là một bài mình làm khá tà đạo (mình sẽ update cách chính đạo sau 😥) - Sơ qua về bài trên, server sẽ cho ta biết $N = p^2 + q^2$ - Nhiệm vụ của ta là từ số **N** đã cho, ta cần tính được số p, q ban đầu. - Đây là một bài toán thuộc dạng **sum of two square** - Và ở đây mình đã sử dụng [Alpertron](https://www.alpertron.com.ar/ECM.HTM) để tính được p, q ban đầu. - Ta sẽ liên tục thu các số N và thử tính p, q đến khi nào thu được p, q là số nguyên tố. ![](https://hackmd.io/_uploads/S1F8iPfqh.png) ![](https://hackmd.io/_uploads/HJIxnPfc2.png) ![](https://hackmd.io/_uploads/rJo-hPfq2.png) ## Kết luận - Ở trên là danh sách 2 bài mảng crypto mình solve được trong giải zer0ptsCTF 2023. - Mọi thắc mắc của các bạn xin vui lòng liên hệ với mình qua discord: ``tranminhprvt01#7535`` - Cuối cùng, cảm ơn các bạn đã đọc. Have a good day! 🥰