Giải khó vãi cả ẻ, mình giải được đúng 2 beginner chall :'( ## yet another login > Yet another login task... Authenticate as admin to get the flag! > > Regards, > joseph > > AU: nc chal.2025.ductf.net 30010 > US: nc chal.2025-us.ductf.net 30010 chall.py ``` #!/usr/bin/env python3 from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes from hashlib import sha256 from secrets import randbits import os FLAG = os.getenv('FLAG', 'DUCTF{FLAG_TODO}') class TokenService: def __init__(self): self.p = getPrime(512) self.q = getPrime(512) self.n = self.p * self.q self.n2 = self.n * self.n self.l = (self.p - 1) * (self.q - 1) self.g = self.n + 1 self.mu = pow(self.l, -1, self.n) self.secret = os.urandom(16) def _encrypt(self, m): r = randbits(1024) c = pow(self.g, m, self.n2) * pow(r, self.n, self.n2) % self.n2 return c def _decrypt(self, c): return ((pow(c, self.l, self.n2) - 1) // self.n) * self.mu % self.n def generate(self, msg): h = bytes_to_long(sha256(self.secret + msg).digest()) return long_to_bytes(self._encrypt(h)) def verify(self, msg, mac): h = sha256(self.secret + msg).digest() w = long_to_bytes(self._decrypt(bytes_to_long(mac))) return h == w[-32:] def menu(): print('1. Register') print('2. Login') return int(input('> ')) def main(): ts = TokenService() print(ts.n) while True: choice = menu() if choice == 1: username = input('Username: ').encode() if b'admin' in username: print('Cannot register admin user') exit(1) msg = b'user=' + username mac = ts.generate(msg) print('Token:', (msg + b'|' + mac).hex()) elif choice == 2: token = bytes.fromhex(input('Token: ')) msg, _, mac = token.partition(b'|') if ts.verify(msg, mac): user = msg.rpartition(b'user=')[2] print(f'Welcome {user}!') if user == b'admin': print(FLAG) else: print('Failed to verify token') else: exit(1) if __name__ == '__main__': main() ``` **1. Giải thích code** 1.1 Một số hàm cần lưu ý * Hàm **encrypt()**: mã hóa message => E(msg) <br> $c = g^m \cdot r^n \ mod\ n^2$ (thực ra cái $r^n$ có hay không cũng được, không ảnh hưởng đến quá trình **decrypt()**) * **generate()**: Mã hóa hash của msg => E(h) * **verify()**: kiểm tra 32 byte cuối của D(E(h)) =? h 1.2 Flow * Server sẽ trả lại n ngay sau khi kết nối, và có 2 lựa chọn ![image](https://hackmd.io/_uploads/B1JABzpLlx.png) * Quá trình Register, sau khi nhập Username server sẽ trả về Token = username + b'|' + E(H(username)), và sử dụng Token vừa được cung cấp ta sẽ đăng nhập được (khi này server sẽ tách token thành msg và mac (chính là E(h)), và verify xem h và D(E(h)) có trùng nhau hay không). ![image](https://hackmd.io/_uploads/ryMBUGaLxx.png) ![image](https://hackmd.io/_uploads/HJ8O8zpLeg.png) * Hàm **rpartition(m)** sẽ tách chuỗi thành 3 phần, trước chuỗi m, chuỗi m và chuỗi sau m, và điều đặc biệt là hàm này sẽ "mò" từ phải sang trái<br> VD: msg = 'user= skibidi user=admin' <br> rpartition('user=') <br> -> [user= skibidi, user=, admin] 2. Solution * Để lấy được flag thì trong msg phải có b'admin', nhưng input không cho phép có b'admin' và do cơ chế khá thú vị của hàm **rpartition(m)**, nên ta sẽ khai thác qua việc gửi token (username = b'user' + b'user=admin' + '|' + mac hợp lệ cho username) * Ta sẽ làm điều đó thông qua Hash length extention attack, nếu biết được msg và Hash(secret | msg), ta có thể tính được Hash(secret | msg | padding | extra) mà không cần biết secret, trong bài chúng ta sẽ là Hash(secret | b'user' | padding | b'user=admin'). <br> **Key 1**: Tìm ra Hash(secrete | b'user') * Đối với các hàm Paillier, có một số tính chất khá thú vị <br> Additive Homomorphism: ![image](https://hackmd.io/_uploads/HJaFiMpLxl.png) Scalar Multiplication ![image](https://hackmd.io/_uploads/SJ1TsGp8lg.png) Do n (1024 bit) >> h (256 bit) nên ta có thể bỏ mod n. Ta sẽ phải lợi dụng tính chất này để tìm ngược lại thứ cần ở **Key 1** **Key 2**: Recover từng bit trong Hash(secrete | b'user'), ta sẽ sử dụng công thức sau ![image](https://hackmd.io/_uploads/Sk-RnzTUel.png) * Tôi sẽ lấy ví dụ trên 4 bit để hình dung rõ hơn tại sao lại có công thức trên * Để check bit_i là 0 hay 1, thì ta sẽ làm những hành động <br> 1. Xử lí phần trước của bit_i (nhân $2^{255 -i}$)) * Nhân với $2^{255 -i}$ là để dịch bit_i sang trái lên đầu, do bit đầu của h luôn luôn là 1 nên khi cộng vào sẽ check được trạng thái (0/1) của bit_i * Nếu bit_i = 1 => kết quả hàm verify sẽ sai <br> bit_i = 0 thì ở bit_i sẽ dịch số 0 lên đầu => kết quả hàm verify đúng ![image](https://hackmd.io/_uploads/ry5xGXTIll.png) 2. Xử lí phần sau của bit bit_i (trừ đi phần đã biết của h) ![image](https://hackmd.io/_uploads/S1Dn7X6Ieg.png)t * Và hàm verify cũng chỉ quan tâm đến 32 byte (256 bit) cuối nên đếch cần quan tâm mấy cái bị dịch lên trước là cái gì <br> Code: ``` from Crypto.Util.number import bytes_to_long, long_to_bytes from pwn import * from tqdm import tqdm import hlextend def adding(c, a, n): return int(c * pow(n+1, a, n**2) % n**2) def multiplying(c, k, n): return int(pow(c, k, n**2)) def adding_ct(c, c2, n): return int(c * c2 % n**2) def get_token(username, conn): conn.sendlineafter(b'> ', b'1') conn.sendlineafter(b'Username: ', username) return bytes_to_long(bytes.fromhex(conn.recvline().decode().split('Token: ')[1]).partition(b'|')[2]) def login(msg, mac, conn): conn.sendlineafter(b'> ', b'2') conn.sendlineafter(b'Token: ', (msg + b'|' + mac).hex().encode()) response = conn.recvline().decode() return 'Welcome' in response def main(): conn = remote('chal.2025.ductf.net', 30010) n = int(conn.recvline().decode()) mac = get_token(b'user', conn) rec_pt = 0 for i in tqdm(range(256)): w = adding_ct(multiplying(adding(mac, -rec_pt, n), 2** (255 - i), n), mac, n) if not login(b'user=user', long_to_bytes(w), conn): rec_pt |= 1 << i hle = hlextend.sha256() ext = hle.extend(b'user=admin', b'user=user', 16, long_to_bytes(rec_pt).hex()) h = bytes.fromhex(hle.hexdigest()) mac = long_to_bytes(pow(n + 1, bytes_to_long(h), n**2)) login(ext, mac, conn) print(conn.recvline().decode()) conn.close() main() flag: DUCTF{now_that_youve_logged_in_its_time_to_lock_in} ```