# TAMU CTF ## Truncated 1 ![image](https://hackmd.io/_uploads/S1kWsU7lA.png) Ta nhận được 2 file pem chứa publickey và privatekey. Không may thay file privatekey.pem bị cắt cụt phần đầu. ``` ZXPI0zfM5EJkeooRvNr3RKQEoQKBgQD0WrYbxhBveSRYvkOV0+omfutwS6wIoCme CYCq5MboHdZn8NDCHy+Y66b+G/GMZJewqEKQSLwHcAjKHxouneFXp6AxV0rkBWtO RNnjXfthsWXvOgBJzGm8CJQS+xVtUpYc4l1QnYaQpc0/SClSTPG775H5DnJ8t4rK oNQur+/pcwKBgD1BU0AjW6x+GYPXUzA0/tXQpu5XaAMxkinhiiOJWT/AExzJU8Jt eQULJ3EDENG6acSuwMhm0WMLhQ0JG6gIejRyOBZSIqjESWGHPmkU1XbUDz0iLb1h HTqJMAWYKWJs4RnJbx6NGJAhd2Ni4CyOGmujYpqNnp1qfZNhmcj/VOeBAoGBAJgD stU2c9UVlTIMM7mLG1kVjlzPBtha42ko2j32k3Ol1FPXcdfCVPcaa0ockjnX/rJt CvP9+9PYs+8iSESF/cFtS/BGMRYH9Qi9NpwHRLMzDIo2GCXRIFpVL+FbCKp5PV/8 xza2uRdVvolG2EYWDjDvym0Zusmx2YtTYI0m8ObXAoGAZ6T8aF6GAZ0s814ZfEcy zZGrZZQ/MJ7W8ZGdU1/y+204LzfGsW+d+sTPfQPYhn03/qU3SFhP095sYzELeOOZ 3yITOftHEdMP3XffnAudgn3tBHrtu0EsVFL44H7CWe4hx3M49M0lfERD60lPwUG1 8hY5qcthSko1f1WkTgN7Rrs= -----END PRIVATE KEY----- ``` Mình nghĩ chỉ cần thêm `-----BEGIN PRIVATE KEY----- ` thì mình sẽ lấy được thông tin gì đó nhưng không, nó không hề đọc thông tin của khóa. Sau khi stuck quá lâu thì mình đi xin hint của một người anh đẹp trai, và mình nhận được cái blog này [twitter-secrets](https://blog.cryptohack.org/twitter-secrets). Private key được mã hóa trong file PEM sẽ bao gồm các thành phần như sau: $n, e, d, p, q, d \mod (p - 1), d \mod (q-1)$ có format như sau: ``` PrivateKeyInfo ::= SEQUENCE { version Version, privateKeyAlgorithm AlgorithmIdentifier , privateKey PrivateKey, attributes [0] Attributes OPTIONAL } RSAPrivateKey ::= SEQUENCE { version Version, modulus INTEGER, -- n publicExponent INTEGER, -- e privateExponent INTEGER, -- d prime1 INTEGER, -- p prime2 INTEGER, -- q exponent1 INTEGER, -- d mod (p-1) exponent2 INTEGER, -- d mod (q-1) coefficient INTEGER, -- (inverse of q) mod p otherPrimeInfos OtherPrimeInfos OPTIONAL } ``` File PEM được mã hóa theo format DER và sau đó được encode bằng base64. Thử decode base64 để có cái nhìn trực quan. Các thành phần được phân tách tại các bytes đặc biệt. Nhưng ở đây ta thấy được có đến 2 bộ bytes đặc biệt. là `02 81 80` và `02 81 81` - `02` là kiểu dữ liệu, ở đây là integer - `81` là độ dài của giá trị số nguyên sẽ được mã hóa theo 2 byte sau. - `81` hoặc `80` là độ dài thực tế của thành phần đó. ![image](https://hackmd.io/_uploads/Hk1YdPmeR.png) ![image](https://hackmd.io/_uploads/Hk_XFvme0.png) Sau khi check tất cả các trường hợp thì mình nhân được một số nguyên tố và nó chính là `p`. Giờ chỉ việc đi lấy flag thôi. ```python= from Crypto.PublicKey import RSA from Crypto.Util.number import long_to_bytes, bytes_to_long pubkey = RSA.import_key(open("public.pem").read()) #privkey = RSA.import_key(open("private.pem").read()) n = pubkey.n e = pubkey.e p = 171591453807815360055383432361744738571408045745496169801430992882142401100861734451639082772737244851844544019866927162589906672848777094551880156673924037027502474510855110537773122402652662824704382923888088092543963549151216084489617389710494396822238199408480117071568865534432933911118270203872402205043 q = n // p print(n) print(p) print(q) c = open("flag.txt.enc", 'rb').read() phi = (p-1)*(q-1) d = pow(e, -1, phi) m = pow(bytes_to_long(c), d, n) print((long_to_bytes(m))) ``` Flag: `gigem{Q_Fr0M_Pr1V473_K3Y_89JD54}` ## Truncated 2 ![image](https://hackmd.io/_uploads/rk2CovXeC.png) Ta tiếp tục nhận được file privatekey bị cắt phần đầu. Decode base64: ![image](https://hackmd.io/_uploads/H13l6vQxC.png) Ta nhận được 3 phần, theo như format thì phần cuối của file là: ``` exponent1 INTEGER, -- d mod (p-1) exponent2 INTEGER, -- d mod (q-1) coefficient INTEGER, -- (inverse of q) mod p ``` Vậy là ta nhận được 3 phần chính là dp, dq và invQ. Từ $dp, dq$ ta có thể dễ dàng tìm ra được p và q. $dp = d \mod (p-1)$ $ed = 1 \mod (p-1)(q-1)$ $\to ed = 1 + k*(p-1)(q-1)$ lấy modul p-1 cho 2 vế: suy ra: $e*dp = 1 \mod (p-1)$ Ta có thể viết như sau: $e * dp = 1 + k*(p-1)$ Từ với $k < e$ việc tìm p trở nên khá dễ dàng. **script:** ```python= from Crypto.PublicKey import RSA from sympy import isprime from Crypto.Util.number import long_to_bytes, bytes_to_long pubkey = RSA.import_key(open("public.pem").read()) #privkey = RSA.import_key(open("private.pem").read()) n = pubkey.n e = pubkey.e data_hex = '5971f6b5e70281804894e9fa2c26b0e1c631ced2f86be0207a82751d707b018839565e93f551df596e9d16f05599a2bfb0bbb300064139f383de85c793e058da2cce41a9a0398e40be05bb9b82703fe804164f5ff4d76623d0e4c720fd705ce6eface979489a8b3a2bd6630077699c0aa8da6250c1de8840d3e5afc34db865e0650ce08f828b49ad02818054d4d1981870d799334e5ae5174526d2979e14c6ecc74d7b59600fbf7db4c060481c3d38c83aa4048e4c6ad483a416d43aecc58db7fe8b9e3d114187538c02b22c9197fe3afd23a83f6e9ac33fab55c84776b1de23a6057e91c47e36ab2ac7600adbbfeb4159d8b09d81898f9a04e47b679cbe690daf6a60551f2b8227863377028180676ab6ccf3e15bdbfbe61abc7f056f2c68cfc834f8806233d7af1d9c204b2cc1ec36ff32d2dc0a40c63c527a8a04120891a0be8facdd08cfbd094dd21060bf4f041ce624ce1e692715fbaf45837a58a3976d3419248c766831da473e0c665a4739515e1eb998f7fb3ecb6e14da7386f64002cc1cf25ca2393f14f8d1bfe6206b' data_hex = data_hex.split('028180') print(data_hex) ##find p and q # for dp in data_hex[1:]: # dp = int(dp, 16) # for kp in range(3, e): # p_mul = dp * e - 1 # if p_mul % kp == 0: # p = (p_mul // kp) + 1 # if isprime(p) and n % p == 0: # print(f"Possible p: {p}") # break p = 175686589048371524987437716214231436544952421988758977028512309898670634575798354986395679131909077133458646058850182514737865035891817435157438827258054551419621190227752633841294595721590533491316564587316052552895454011463277500314590143225393639686181953221172969420780838402312402869662496440462358754237 q = 146499949876031596595119473544158492278112465965128296098554215599527541093216463269448197726258519438043835011477106576649738579124758942019192335365960513430943680607378942906030476700381704046104806602324753526654817790452245515616252739559328483440653497757953778117237609624419671326102551735126101243367 assert p*q == n c = open("flag.txt.enc", 'rb').read() phi = (p-1)*(q-1) d = pow(e, -1, phi) m = pow(bytes_to_long(c), d, n) print((long_to_bytes(m))) ``` Flag: `gigem{DP_DQ_r54_7rUNC473D_SDA79}` Mình méo hiểu sao câu Truncated 1 lại ra được một số nguyên tố rõ ràng nó là phần cuối của file pem thì làm gì có được cái đấy. ## Criminal ![image](https://hackmd.io/_uploads/SJk3TumxA.png) **server.py** ```python= from base64 import b64encode from Crypto.Random import get_random_bytes from Crypto.Cipher import ChaCha20_Poly1305 from pathlib import Path from zlib import compress flag = Path("flag.txt").read_bytes() key = get_random_bytes(32) try: while True: append = input("Append whatever you want to the flag: ").encode() # gotta save on bandwidth! m = compress(flag + append) cipher = ChaCha20_Poly1305.new(key=key) cipher.update(m) c, tag = cipher.encrypt_and_digest(m) res = cipher.nonce + tag + c print(b64encode(res).decode()) except (KeyboardInterrupt, EOFError): pass ``` Chall này sử dụng mã hóa ChaCha20_Poly1305 như bug của bài này không phải ở mã hóa ChaCha20 nhưng mà hàm compress. Nó được nhắc đến ở đây [CRIME vuln](https://en.wikipedia.org/wiki/CRIME). Cùng xem qua lib `zlib` và hàm `compress`. ![image](https://hackmd.io/_uploads/rJuKej7eA.png) Với các ký tự dùng để compress khác nhau có có thể dẫn đến sự thay đội độ dài của ouput. Ở đây ta dùng `flag{a` khi compress với `flag{b` thì cái len của nó lớn hơn so với khi compress với `flag{a`. ![image](https://hackmd.io/_uploads/S175soXeA.png) Từ hình ảnh trên nếu ta compress `bytes1` và `bytes2` nếu `bytes2` là một phần của `bytes1` thì nó sẽ cho ra một cái len nào đó, còn nếu không phải là một phần của `bytes1` thì nó cho ra một cái len lớn hơn. **script:** ```python= from pwn import * import base64 import string #context.log_level = "debug" io = remote("tamuctf.com", 443, ssl=True, sni="criminal") alphabet = [chr(ord('a')+ i) for i in range(26)] alphabet.append('_') flag = 'gigem{' io.recvuntil(b'Append whatever you want to the flag: ') io.sendline((flag+';').encode()) # vì ';' không thuộc FLAG nên mình sẽ dùng nó để lấy một cái leng chuẩn để so sánh. leng = len(base64.b64decode(io.recvuntil(b'\n', drop = True).decode())) print(leng) while True: for char in alphabet: io.recvuntil(b'Append whatever you want to the flag: ') io.sendline((flag + char).encode()) data = base64.b64decode(io.recvuntil(b'\n', drop = True).decode()) print(len(data)) if len(data) < leng: flag += char break print(flag) ``` Chạy đến đây thì không nhận được thêm ký tự nào phù hợp nữa. ![image](https://hackmd.io/_uploads/HJl56o7lR.png) Flag: `gigem{foiled_again}` ## Smooth Criminal Chall này có source như sau: ```python= from Crypto.Util.number import getPrime,long_to_bytes,bytes_to_long from math import lcm,gcd from secrets import randbelow from hashlib import sha256 NUM_BITS = 2048 def getModulus(bits): n = 1 primes = [] while n.bit_length() < bits: p = getPrime(24) if p not in primes: n *= p primes.append(p) return n, primes def sign(n,msg,d): h = bytes_to_long(sha256(msg).digest()) k = randbelow(q-2)+1 x = pow(h,k,n) r = pow(x,d,n) s = pow(h+x,d,n) return r,s def verify(n,msg,e,r,s): h = bytes_to_long(sha256(msg).digest()) v1 = pow(r,e,n) v2 = pow(s,e,n) return v2 == (v1 + h) % n n,primes = getModulus(NUM_BITS) q = 1 for p in primes: q = lcm(q,p-1) msgs = [] e = 65537 d = pow(e,-1,q) print(f"The modulus is ... a mystery left for you to unfold.") print(f"Your verification exponent {e = }") msg = input("Give the oracle a message to sign: ").encode() msgs.append(msg) r,s = sign(n,msg,d) print(f"Your verification signature is ({r}, {s})") msg = input("Give the oracle another message to sign: ").encode() msgs.append(msg) r,s = sign(n,msg,d) print(f"Your second verification signature is ({r}, {s})") comm = input("Ask the oracle a question: ").encode() r,s = input("Give the verification signature: ").split(",") r,s = int(r),int(s) if comm in msgs: print("Hey, no cheating") exit() if verify(n,comm,e,r,s): if comm == b"What is the flag?": print("The flag is: ",end="") with open("flag.txt","r") as flag: print(flag.read()) else: print("Not the right question.") else: print("Invalid signature") ``` Ta chỉ có 2 lần gửi đến msg để nhận `r, s` và lần cuối ta cần gửi `comm = b"What is the flag?"` và giá trị chính xác của `r` và `s` sau khi thực hiện hàm `sign` với `comm`. Bài này ta đang không có thông tin của `N`, với 2 lần gửi ta có thể lấy lại N bằng `gcd` thông qua dữ kiện sau đây: `v2 == (v1 + h) % n` Tức là: $s^e = r^e +h \mod n$ suy ra: $s^e - r^e - h = m*n$ Với 2 lần gửi ta có được 2 bội của $n$. Nhưng ban đầu mình thử `gcd` nhưng không được vì bội đó quá lớn. Nên mình bỏ sài cách này. Vì N là một smooth number được tạo từ các số nguyên tố có 24 bit. Ta có thể bruteforce được những số nguyên tố này, trông có vẻ khá cơ bắp một tí 💪. Vì $(s^e - r^e - h) \mod n = 0$ suy ra: $(s^e - r^e - h) \mod p_i = 0$ **script:** ```python= from pwn import remote from Crypto.Util.number import getPrime,long_to_bytes,bytes_to_long from secrets import randbelow from math import lcm,gcd from hashlib import sha256 from sympy import nextprime from tqdm import tqdm io = remote("localhost", 1337) io.recvuntil(b'Give the oracle a message to sign: ') io.sendline(b'Obito') io.recvuntil(b'Your verification signature is ') r, s = eval(io.recvuntil(b'\n', drop=True).decode()) io.recvuntil(b'Give the oracle another message to sign: ') h = bytes_to_long(sha256(b'Obito').digest()) io.sendline(b'hahaha') print("Factoring N ...", end=" ") e = 65537 factors = [] p = 2**23 q = 1 n = 1 max = 2**24 while p < max: p = nextprime(p) v1 = pow(r,e,p) v2 = pow(s,e,p) if (v2 - v1 - h) % p == 0: q = lcm(q, p-1) n*=p factors.append(p) print('Done') print(factors) def sign(n,msg,d): h = bytes_to_long(sha256(msg).digest()) k = randbelow(q-2)+1 x = pow(h,k,n) r = pow(x,d,n) s = pow(h+x,d,n) return r,s io.recvuntil(b'Ask the oracle a question: ') io.sendline(b"What is the flag?") io.recvuntil(b'Give the verification signature: ') r, s = sign(n,b"What is the flag?", pow(e, -1, q)) io.sendline(str(r).encode() + b',' + str(s).encode()) io.interactive() ``` ![image](https://hackmd.io/_uploads/rkZyp4rxA.png) Flag: `gigem{sm00th_numb3rs_4r3_345y_70_f4c70r}` ## Jumbled Mô tả: `The RSA Public and Private keys are provided. However, the private key seems to be jumbled in a block size of 10 hex characters. Can you get the flag?` Đề cho ta file flag.enc, public và private **public** ``` 2d 2d 2d 2d 2d 42 45 47 49 4e 20 50 55 42 4c 49 43 20 4b 45 59 2d 2d 2d 2d 2d 0d 0a 4d 49 49 42 49 6a 41 4e 42 67 6b 71 68 6b 69 47 39 77 30 42 41 51 45 46 41 41 4f 43 41 51 38 41 4d 49 49 42 43 67 4b 43 41 51 45 41 71 6d 54 59 68 59 54 37 2b 4e 42 7a 5a 44 72 73 66 4b 44 34 0d 0a 34 4b 2b 39 72 74 4c 63 5a 4c 54 2b 56 61 57 48 59 76 6e 38 42 70 39 58 2f 66 67 37 54 6d 4b 35 6c 35 44 36 4d 73 46 38 39 72 5a 38 74 61 45 47 46 4a 50 79 2b 6b 78 2b 71 55 71 4f 4f 39 35 47 0d 0a 51 68 4d 32 53 58 41 77 6e 30 44 31 54 4a 4b 64 61 53 5a 75 6e 47 30 36 70 63 51 33 62 2b 70 62 35 47 44 59 59 70 34 33 50 37 61 67 55 73 67 48 53 43 77 32 4f 46 43 74 55 2f 4d 73 35 33 45 77 0d 0a 69 32 6a 35 31 64 45 76 2b 38 4b 62 75 71 49 70 32 49 4f 47 7a 4c 79 33 4d 7a 78 34 72 31 54 6a 54 49 6d 31 38 44 6e 70 56 56 65 6f 79 38 73 4e 74 57 62 56 64 6e 43 43 74 49 59 36 4c 6e 50 50 0d 0a 73 6d 61 4f 4a 31 2b 6a 57 72 57 67 76 39 44 6e 64 70 5a 49 65 44 4f 75 6f 7a 64 31 62 4b 6c 74 4c 42 65 49 4b 32 6b 66 46 6e 6f 78 6f 6d 54 67 57 2b 53 41 53 4c 34 72 6e 2f 6f 6a 71 4e 63 30 0d 0a 36 43 5a 35 4c 2b 4b 6e 44 43 42 79 62 68 47 33 73 67 54 69 6d 7a 77 30 51 4d 72 53 35 47 33 35 6b 46 76 32 6c 33 4d 37 2f 38 57 48 4f 69 58 57 70 53 53 5a 4b 6d 4b 71 31 54 73 62 65 76 2b 72 0d 0a 6c 77 49 44 41 51 41 42 0d 0a 2d 2d 2d 2d 2d 45 4e 44 20 50 55 42 4c 49 43 20 4b 45 59 2d 2d 2d 2d 2d ``` **private** ``` 49 45 4e 42 47 2d 2d 2d 2d 2d 20 54 4b 41 45 49 50 56 20 52 0a 2d 4d 2d 0d 2d 59 2d 45 2d 44 42 41 49 41 76 49 41 49 45 47 6b 39 68 69 6b 42 71 4e 67 41 46 53 45 41 41 30 51 77 42 69 67 41 67 53 59 42 77 43 4b 51 42 43 49 41 41 45 6f 67 41 34 50 30 68 76 69 5a 46 71 4e 38 75 6f 4f 78 0d 4e 0a 48 6b 74 75 78 32 30 72 6a 37 50 67 69 59 2b 70 64 35 74 56 6b 50 44 39 74 66 2b 6e 77 31 66 47 79 50 77 6b 6f 6d 59 58 4f 72 51 31 59 79 6f 74 7a 6e 58 32 70 48 0d 54 36 4c 6b 36 55 2f 43 6b 45 33 5a 34 53 37 0a 6f 50 66 56 43 51 63 5a 44 7a 4a 63 6d 62 4a 36 31 6b 70 4d 70 6c 76 76 64 36 78 71 44 54 6c 2f 6a 74 6e 63 68 59 69 6b 4e 44 49 59 64 4c 79 42 41 71 53 79 0a 7a 0d 38 31 55 54 34 4b 56 50 30 61 6e 43 63 4c 6e 54 69 36 6e 75 6f 77 2f 70 53 37 7a 4c 50 76 63 62 67 4d 59 34 62 4d 58 4e 69 56 69 4f 48 76 4c 36 79 56 6a 6c 4f 56 77 65 49 32 4b 56 63 5a 32 74 77 31 38 75 2b 6f 63 68 0d 6a 30 0a 61 36 74 58 4e 34 5a 6e 79 6f 6b 32 68 64 6c 30 43 4f 61 2f 73 33 71 4e 56 36 4d 6a 34 36 52 72 38 67 61 46 30 34 57 73 62 4f 35 5a 42 47 65 69 57 6a 0a 66 2b 75 0d 76 42 69 49 49 6e 6f 6b 54 4a 31 4f 7a 69 6f 75 48 45 49 4a 63 34 4d 76 71 44 62 52 4b 50 42 65 4f 62 79 51 66 57 62 6d 4c 79 6b 41 74 59 2f 63 76 78 63 61 7a 2f 58 71 4a 59 4a 6b 61 4a 6c 36 64 36 78 2f 4f 74 0d 72 0a 71 56 41 42 45 4d 41 41 75 67 36 58 67 55 43 41 38 45 67 41 43 67 57 4b 69 47 2b 55 71 77 4c 53 47 74 79 49 72 61 65 6a 6f 33 78 6b 56 73 44 37 71 65 73 4d 2b 2f 0d 52 36 4d 2b 77 6a 6d 45 77 49 35 6e 47 5a 61 0a 74 64 77 5a 39 37 59 46 70 6b 33 2b 6b 72 4f 38 6b 45 6d 4f 2f 52 6e 63 47 6f 54 53 6f 53 63 33 4f 51 75 53 42 6c 67 65 42 64 42 5a 37 33 57 6e 48 75 31 58 0a 42 0d 75 74 51 6f 78 42 33 52 59 74 6a 71 2b 69 4e 72 42 41 49 52 6e 6a 36 78 4a 56 73 6f 49 31 6a 34 57 61 30 42 70 6d 4e 68 78 7a 70 46 2f 34 78 44 42 2b 71 57 59 6b 71 2f 61 39 47 48 37 57 69 4d 70 4c 32 68 43 51 52 55 0d 63 38 0a 56 2f 38 4f 45 30 4c 39 74 50 68 43 45 4e 74 49 44 31 46 43 43 6b 73 76 57 58 52 39 30 59 68 45 78 51 74 4e 45 39 44 62 55 4a 4b 79 4b 67 38 51 71 6c 0a 71 76 34 0d 59 4b 6c 61 33 73 4b 41 50 67 6a 62 34 32 61 41 4b 59 39 4a 78 48 39 4a 74 74 6b 73 30 59 58 44 70 6b 34 75 45 5a 6a 44 54 4b 4f 57 30 4a 31 78 31 51 68 53 42 50 63 7a 47 2b 52 39 68 71 5a 69 75 65 55 45 54 34 0d 65 0a 67 6f 2b 51 39 37 33 71 50 6a 47 58 58 49 46 71 4d 6a 4b 49 64 48 43 54 58 4a 2b 46 30 4b 45 2f 42 51 67 35 4b 32 5a 33 6e 55 42 74 64 2b 6a 6d 44 63 51 46 53 63 0d 2f 6a 66 77 62 55 4c 46 64 4b 6f 30 51 4d 38 0a 33 55 64 6c 34 42 49 56 45 52 34 7a 46 55 68 4c 4e 52 6a 79 50 46 52 41 68 44 53 7a 63 76 75 66 4d 2b 37 41 55 63 7a 52 4e 39 50 70 4d 2f 4d 42 63 45 41 63 0a 58 0d 4b 79 4b 50 6a 58 42 45 68 4b 49 71 61 6d 43 53 73 42 61 2f 69 55 4e 52 6a 4e 38 42 43 78 75 6f 4e 6b 62 6c 2b 67 66 6c 58 45 73 39 6f 75 33 4b 46 63 44 70 6d 35 38 62 4b 51 6d 31 57 68 6a 38 6e 71 48 4e 56 67 64 4c 0d 74 36 0a 6d 49 49 32 67 6e 6a 7a 48 37 77 70 4b 67 67 58 32 63 61 68 45 4a 68 77 6e 44 67 63 42 51 46 49 63 37 55 72 65 69 69 71 32 4b 78 7a 36 70 66 6e 34 45 0a 79 68 31 0d 36 54 43 74 67 69 4c 53 42 4b 71 55 74 43 6f 6e 36 52 34 74 5a 45 49 65 5a 2f 37 59 59 35 42 45 78 67 5a 62 68 50 4d 50 77 2b 76 71 6e 37 45 57 47 61 48 58 52 73 37 30 72 68 64 34 59 56 39 79 63 69 4e 4e 54 54 0d 4b 0a 31 73 54 6e 6c 34 30 75 77 65 72 66 5a 69 70 2f 38 75 64 76 69 47 30 51 42 64 44 30 78 69 36 53 2b 76 4a 70 49 58 36 72 4c 58 70 7a 69 53 31 56 6f 4b 44 55 39 4e 0d 75 76 4a 6f 39 64 52 64 72 58 45 78 53 75 72 0a 50 33 41 72 78 59 67 4b 42 51 42 50 6e 6b 65 51 74 6e 56 79 70 74 63 62 47 75 31 6a 6f 74 4b 4c 71 6a 42 63 66 43 45 30 73 4e 76 53 42 65 2b 61 51 48 7a 42 0a 73 0d 34 68 4e 79 6c 74 57 35 36 74 37 51 59 38 47 61 7a 69 73 59 5a 69 6b 7a 4a 70 43 59 64 44 63 37 58 79 77 32 45 32 6d 30 4d 7a 33 32 2b 56 63 51 36 74 69 59 67 37 37 44 72 75 42 73 74 4e 78 76 4d 6b 6a 4c 64 42 41 36 0d 59 70 0a 77 4e 42 6b 36 48 50 55 50 77 76 66 47 65 4e 47 4f 50 62 4f 69 69 56 2b 4c 78 32 73 58 35 74 4f 68 53 7a 6d 70 46 61 48 31 6b 41 43 41 68 31 51 30 44 0a 31 45 62 0d 62 47 69 6f 41 4a 41 66 65 74 6c 6c 63 36 56 62 58 4a 4f 42 39 54 48 53 65 4b 71 41 7a 63 4d 30 47 66 6c 36 74 6d 64 67 55 34 4a 62 71 36 4d 57 48 76 50 31 6b 56 78 5a 2f 54 72 76 6f 32 38 67 70 49 72 54 56 65 0d 7a 0a 43 64 37 31 78 50 54 31 69 66 50 50 77 67 62 46 35 75 56 52 6e 2b 2b 56 4f 5a 65 71 6d 53 73 76 41 38 39 56 6b 79 44 35 51 38 56 52 32 39 70 5a 33 6c 32 63 71 62 0d 45 7a 67 6b 6f 54 57 70 72 56 54 35 61 65 75 0a 6e 39 57 37 2b 46 66 54 6d 6a 42 74 30 42 46 37 44 48 4a 58 4b 6b 55 6b 76 37 62 67 6d 7a 4a 62 46 42 2b 64 41 67 7a 43 59 32 50 4a 4b 74 6a 5a 39 63 45 4c 0a 37 0d 68 72 4f 6c 31 38 4a 70 53 69 31 55 36 75 4a 65 65 37 74 32 79 6c 4c 67 6b 63 77 4c 76 71 53 41 46 50 78 6c 2f 52 2b 52 36 67 47 64 35 54 6b 2b 6d 74 4a 69 54 6c 74 2f 33 35 62 49 70 41 50 62 59 54 67 77 59 62 6a 77 0d 46 44 0a 58 2f 49 4c 4b 2b 69 44 68 77 68 71 68 73 71 73 62 35 45 52 4d 7a 54 36 46 42 7a 2b 41 67 2b 79 50 74 77 79 52 50 4b 38 72 59 76 6e 56 37 36 43 43 57 0a 65 70 56 0d 33 32 65 4e 61 46 6a 61 6b 53 44 6c 54 61 49 4f 52 74 77 37 37 79 6f 64 6a 2d 2d 2d 2d 2d 0d 3d 0a 51 3d 41 49 54 52 56 20 4e 50 45 44 2d 2d 2d 2d 2d 45 20 59 45 4b ``` Với `public` mình decode hex thì nó là định dạng file PEM chứa, còn private theo như đề bài mô tả thì nó đã bị `shuffled` nhưng trong phạm vi 10 bytes. Ta cần tìm ra quy luật và đưa nó về nguyên trạng. Với tất cả các file pem chứa private key thì phần mở đầu của nó chính là `-----BEGIN PRIVATE KEY-----`. Từ dữ kiện nãy ta có thể so sánh với các bytes đầu tiên bị xáo trộn để xem quy luật của nó. Bytes đúng: ``` 2d 2d 2d 2d 2d 42 45 47 49 4e 20 50 52 49 56 41 54 45 20 4b 45 59 2d 2d 2d 2d 2d ``` Đã bị trộn: ``` 49 45 4e 42 47 2d 2d 2d 2d 2d 20 54 4b 41 45 49 50 56 20 52 0a 2d 4d 2d 0d 2d 59 2d 45 2d 44 42 41 49 41 76 49 41 49 45 47 6b 39 68 69 6b 42 71 4e 67 41 46 53 45 41 41 30 ``` ![image](https://hackmd.io/_uploads/rJhYBSrxA.png) Với 10 bytes đầu ta tìm chỉ tìm được 1 phần quy luật: `8, 6, 9, 5, 7, ...` Tiếp tục thử với 10 bytes sau xem thế nào: ![image](https://hackmd.io/_uploads/rkbEwHHgA.png) Và thế là ta đã có được quy luật: `8, 6, 9, 5, 7, 3, 1, 4, 0, 2` Các số này có nghĩa là vị trí chính xác của các bytes. Giờ chỉ cần đưa tất cả về đúng vị trí thôi. ```python= from Crypto.PublicKey import RSA from Crypto.Util.number import long_to_bytes, bytes_to_long public = open("public", 'r').read().replace(" ", "") public = RSA.import_key(bytes.fromhex(public)) n = public.n e = public.e private_jumb = open("private", 'r').read().split(' ') private_jumb = [private_jumb[i:i+10] for i in range(0, len(private_jumb), 10)] private_hex = [] qui_luat = [8, 6, 9, 5, 7, 3, 1, 4, 0, 2] for i in private_jumb: for idx in qui_luat: private_hex.append(i[idx]) private = bytes.fromhex(''.join(private_hex)).decode() private = RSA.import_key(private) d = private.d c = bytes_to_long(open('flag.txt.enc', 'rb').read()) print(long_to_bytes(pow(c, d, n))) ``` FLAG: `gigem{jumbl3d_r54_pr1v473_k3y_z93kd74lx}` ## Emoji group Ta có source code: ```python= from secrets import multiply, g, identity, inverse, valid from random import getrandbits def power(p,x): out = identity while x: if x & 1: out = multiply(out,p) p = multiply(p,p) x >>= 1 return out def encrypt(msg,e): generator = power(g,e) out = generator for c in msg: out += power(generator,ord(c)) return out def decrypt(ct,d): chars = [power(g,i) for i in range(256)] plaintext = "" pt = power(ct[0],d) if pt != g: raise Exception("Invalid ciphertext") for c in ct[1:]: pt = power(c,d) plaintext += chr(chars.index(pt)) return plaintext print("Give me a message to encrypt:") msg = input() e = 0 while not valid(e): e = getrandbits(32) ct = encrypt(msg,e) print(f"Your cipher text is:",ct) d = inverse(e) print(f"The original message was:",decrypt(ct,d)) with open("flag.txt","r") as flag: e = 0 while not valid(e): e = getrandbits(32) print("The flag is:",encrypt(flag.read(),e)) ``` Khi ta gửi đến server ta nhận được những thứ như sau: ![image](https://hackmd.io/_uploads/ByCxmtUxR.png) Phần tử đầu tiên của ciphertext là `genarator = g**e`, nhận thấy với mỗi ký tự sẽ ứng với một emoji. Nó có vẻ giống như mã hóa casaer, mỗi ký tự sau khi được mã hóa được ánh xạ đến một emoji tương ứng. Với `msg` mà ta gửi tới ta có thể biết được các ký tự đó ánh xạ như thế nào. Vì `encrypt(msg, e)`, `encrypt(msg, e)` có 2 `e` khác nhau nên ta không thể biết được `flag` được mã hóa như thế nào. Nhưng nếu chúng cùng `generation` thì dễ dàng lấy được FLAG. Mình sẽ gửi tới server là lưu lại tất cả `generation` và ciphertext tương tương ứng với msg mà mình đã gửi. Nếu `generation` của flag_enc có trong những `generation` mà mình đã lưu thì chỉ cần mã hóa ngược lại để lấy FLAG. **script:** ```python= import string from pwn import remote characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~" dict = {} flag = '' while 1: io = remote("localhost", 1337) io.recvuntil(b'Give me a message to encrypt:\n') io.sendline(characters) io.recvuntil(b'Your cipher text is: ') c = io.recvuntil(b'\n', drop=True).decode() dict[c[0]] = c[1:] print(c[0]) io.recvuntil(b'The flag is: ') flag_enc = io.recvuntil(b'\n', drop=True).decode() print(flag_enc) if flag_enc[0] in dict: for i in flag_enc[1:]: # print(i) flag += characters[dict[flag_enc[0]].index(i)] print(flag) break ``` ![image](https://hackmd.io/_uploads/SJzrrqIgC.png) FLAG: `gigem{h0p3_y0u_d1dn7_s0lv3_by_h4nd}` ## PCG Ta có source code như sau: ```python= from secrets import randbelow from Crypto.Util.number import getPrime import sys SIZE = 256 class PCG: # Polynomial Congruential Generator def __init__(self): self.m = getPrime(256) self.coeff = [randbelow(self.m-1) for _ in range(SIZE)] self.x = randbelow(self.m-1) def __call__(self): newx = 0 for c in self.coeff: newx *= self.x newx += c newx %= self.m self.x = newx return self.x def printm(self): print(self.m) return pcg = PCG() print(pcg.m) for i in range(SIZE*3): print(pcg()) sys.stdout.flush() correct = True for i in range(SIZE // 2): guess = int(input()) if guess != pcg(): correct = False if correct: print(open('flag.txt','r').read()) else: print("you failed") sys.stdout.flush() ``` Để lấy được flag ta cần predict được (256/2) giá trị tiếp theo của hàm `pcg` sau khi đã nhận được (256*3) giá trị từ hàm `pcg`. Ta có: $x_{i+1} = a_{0}*x_{i}^{255} + \dots + a_{254}*x_{i} ^ 1 + a_{255} \mod (m)$ Với `m` đã có, ta chỉ cần khôi phục lại hệ số và gen thêm các giá trị tiếp theo để lấy Flag thôi. Gọi: $A = \begin{bmatrix} a_0 & a_1 & a_2 & a_3 & .. & a_{255} \\ \end{bmatrix}$ $B = \begin{bmatrix} x_0^{255} & x_1^{255} & .. & x_{255}^{255} \\ ... & ... & .. & ... \\ x_0 & x_1 & .. & x_{255} \\ 1 & 1 & .. & 1 \\ \end{bmatrix}$ Ta có: $X_i*A = \begin{bmatrix} x_0 & x_1 & x_2 & .. & x_{255} \\ \end{bmatrix}$ với $i \in N$ Giải nghiệm phương trình trên ta sẽ tìm được các hệ số. Và tiếp tục gen tiếp hàm `pcg()` và gửi đến server và nhận lại flag. ```python= from pwn import remote from sage.all import * io = remote("localhost", 1337) SIZE = 256 data = io.recvlines(SIZE*3 + 1) X = list(map(int, data)) print(X) m = X[0] X = X[1:] print(len(X)) F = GF(m) A = Matrix(F,[[x**(SIZE-1-i) for i in range(SIZE)] for x in X[:-1]]) y = vector(F,X[1:]) arr = A.solve_right(y) coeff = [int(x) for x in arr] print(coeff) class PCG: # Polynomial Congruential Generator def __init__(self): self.m = getPrime(256) self.coeff = [randbelow(self.m-1) for _ in range(SIZE)] self.x = randbelow(self.m-1) def __call__(self): newx = 0 for c in self.coeff: newx *= self.x newx += c newx %= self.m self.x = newx return self.x def printm(self): print(self.m) return pcg = PCG() pcg.m = m pcg.coeff = coeff pcg.x = X[-1] for i in range(SIZE // 2): io.sendline(str(pcg()).encode()) io.interactive() ``` FLAG: `gigem{p0lyn0m1al5_4r3_funny}`