> Em tên là Thái Vĩnh Đạt (tên discord là tvdat20004), hôm 22/7 vừa rồi em có tham gia miniCTF do clb Wanna.W1n tổ chức và em được hạng 3. Em làm được 2 bài thuộc mảng Crypto và sau đây em xin trình bày write-up cho 2 bài đó ạ. # flipping login ![](https://hackmd.io/_uploads/HJBcEZq5n.png) - [Attachment](https://cnsc.uit.edu.vn/ctf/files/39d2533105fc81e83ec4ddb273f18655/public.zip?token=eyJ1c2VyX2lkIjo2MDYsInRlYW1faWQiOm51bGwsImZpbGVfaWQiOjEwOH0.ZLyC1w.AmNd3c4YyyzDnZF2J6vxES08L_Q) - Server.py: ```python= #!/usr/bin/env python3 import json import os import signal from base64 import b64decode, b64encode from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from secret import flag key = os.urandom(32) def menu_choice() -> str: print( """Valid choices: 1. Login 2. Register 3. Quit""" ) return input(">> ").strip() def handler(_signum, _frame): print("Time out!") exit(0) def encrypt(msg): iv = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC, iv=iv) return iv+cipher.encrypt(pad(msg, 16)) def decrypt(iv, msg): cipher = AES.new(key, AES.MODE_CBC, iv=iv) return unpad(cipher.decrypt(msg), 16) def login(): enc_token = b64decode(input("Login token: ").strip()) token = decrypt(enc_token[:16], enc_token[16:]) token = json.loads(token) return token['name'], token['admin'] def register(): name = input("Nickname: ").strip() token = { "admin": False, "name": name } token = json.dumps(token).encode() enc_token = b64encode(encrypt(token)).decode() print("Here is your login token: " + enc_token) def main(): signal.signal(signal.SIGALRM, handler) signal.alarm(60) name = 'N.A' is_admin = False print("Welcome to my under-development program. Who are you?") while True: choice = menu_choice() match choice: case '1': name, is_admin = login() break case '2': register() print('') case '3': print("Bye bye!") break case _: print("??????????????") raise RuntimeError("I found a hacker") if is_admin: print(f"Hello {name}! Here is your secret: " + flag) else: print(f"Hello {name}! Sorry, only admin can read my secret!") try: main() except Exception as E: print(str(E)) ``` - Tóm tắt đoạn code: - Server sẽ cho ta 3 lựa chọn: Login(1), Register(2), Quit(3). - Khi chọn Register, server cho phép ta nhập `name`, sau đó nó sẽ tạo một `login_token`. `login_token` chính là base64 của kết quả sau khi mã hóa AES_CBC của một json:`{ "admin": False, "name": name }` - Tùy chọn login cho phép ta nhập vào một `login_token`. Server sẽ encode base64, sau đó decrypt AES_CBC và loads json `token`. Để nhận được flag thì `token` phải có key `admin` ứng với giá trị True. - Vậy điểu mình cần làm để lấy được flag là phải gởi token như yêu cầu ở trên. Với tên đề bài là `Flipping login` cộng với việc đã làm bài flipping cookie trên Cryptohack thì em nghĩ ngay đến kĩ thuật bit flipping attack. Trong quá trình làm bài em có tham khảo ở [link này](https://crypto.stackexchange.com/questions/66085/bit-flipping-attack-on-cbc-mode) - Nhìn chung thì kĩ thuật này cho phép ta modify một vài byte trên chuỗi ciphertext (thực ra là iv + ciphertext) để thu được ciphertext mới và sau khi decrypt ta thu được plaintext như mong muốn. Ở đây mình cần modify sao cho từ plaintext là `{ "admin": false, "name": name }` thành plaintext `{ "admin": true, "name": name }`, tức là chuyển chuỗi `false` thành `true`. Mình sẽ tiến hành flip từng byte như sau: - Ví dụ để chuyển 'f' thành 't', em sẽ xor bit thứ 10 (vị trí của 'f' trong plaintext) với `(ord(f)^ord(t))`. - Lặp lại quá trình đồi với các bit tiếp theo cho đến khi flip hết chuỗi 'false' thành 'true '. - Python implementation: ```python= import base64 import json from pwn import * r = remote('45.122.249.68', 20022) def login(token : str): data = r.recvuntil(b'>> ') r.sendline(b'1') data = r.recvuntil(b'Login token: ') r.sendline(token.encode()) flag = r.recvline() print(flag) def register() -> str: data = r.recvuntil(b'>> ') r.sendline(b'2') data = r.recvuntil(b'Nickname: ') r.sendline(b'tvdat20004') data = r.recvuntil(b'token: ') token = r.recvuntil(b'\n').decode().strip() return token def flipping_bits(ciphertext, pos, plaintext : bytes, new_plaintext : bytes): assert len(plaintext) == len(new_plaintext) ciphertext = list(ciphertext) for i in range(pos, pos + len(plaintext)): ciphertext[i] = ciphertext[i] ^ (plaintext[i-pos] ^ new_plaintext[i-pos]) iv = ciphertext[:16] ct = ciphertext[16:] return base64.b64encode(bytes(iv)+bytes(ct)) token = base64.b64decode(register()) ans = flipping_bits(token,10,b" true",b"false") login(ans.decode()) r.interactive() ``` ![](https://hackmd.io/_uploads/rkAtFG9ch.png) - Flag: `W1{CBC_bit_flipping_attack_can_flip_your_system_https://tsublogs.wordpress.com/2015/07/18/bit-flipping-attack-on-cbc-mode}` # Weird Primes ![](https://hackmd.io/_uploads/ryZH9G5ch.png) - [chall.py](https://cnsc.uit.edu.vn/ctf/files/98af14b1a283d388b47f7e5f9eee2bf9/chall.py?token=eyJ1c2VyX2lkIjo2MDYsInRlYW1faWQiOm51bGwsImZpbGVfaWQiOjEwOX0.ZLyaIQ.HLODE-JrfZuY7cm45CJ-uHXOpWo),[ output.txt](https://cnsc.uit.edu.vn/ctf/files/eb0605b499533756b713a7c5c6197679/output.txt?token=eyJ1c2VyX2lkIjo2MDYsInRlYW1faWQiOm51bGwsImZpbGVfaWQiOjExMH0.ZLyaIQ.KkSAy8ATjTZI6y7j6UP4ebB3yLY) - Hint: 'Bruteforcing each digit since number of possible bytes for each prime is small'. - Sau khi đọc xong hint và một hồi suy nghĩ, em cảm thấy nó "khá giống" với parital bit leak dạng "random known bit of p and q" mà em đã làm trước đây (chi tiết về nó em để ở [đây](https://hal.science/hal-03045663/document), 4.3.1, trang 21). Tuy thoạt nhìn thì thấy 2 bài có vẻ rất khác, nhưng ý tưởng brute-force lại hoàn toàn giống nhau, đều sử dụng "branch and prune algorithm". - Cụ thể, em sẽ brute-force từng byte(do p và q được tạo từ chuỗi byte gồm 128 số) của p và q theo thứ tự từ LSB đến MSB. - Mô tả thuật toán: - Xét cặp LSB của p và q, có tất cả 10x10 cặp có thể có. Bruteforce hết không gian mẫu đó cho đến khi gặp `p.q mod 256 = n mod 256` (p,q đang chỉ có 1 byte LSB) - Xét tiếp byte sau đó, gọi là dp và dq, cũng có 10x10 cặp có thể có. Bruteforce hết không gian mẫu đó cho đến khi gặp `p.q mod (256**2)= n mod (256**2)`(khi này p,q có 2 byte LSB) - Lặp lại quá trình cho đến khi tìm được 128 byte của p và q. - Python implementation: ``` python= n = 1193924133214231889546351753346545092386694958282484590742357919398336185127955297065849705214714735049853117873865465248652047796382636343398101815207127117475834303652582611557916482763041203527984476566898846821511189884575355212170099724076171155888215024629846800791116844007098877699954543318380357268011069937195402719153407503698044969734971221799699731859308452701292314029495280470102667890355711974564903038932606840517852702768264359240118381178825407856623508271302891317946429909210917646516863004718071228523457611307993744722272634542518670009533519107837470331125580871788354030347986972475782995433 e = 65537 enc_flag = '0255ad928673b69d2c1998cafaadda7028d339dd82483c9d01363231a01def067bcea9d2cc378e2b9054feadf31412b1f52e9b9061fc1ba8863cfffff840979e3e44743409e3d5a19ab2bcb2f570776b028ea5f5422962d103e2ea2e59c9c479e5a46c514e2196dd7d3f1c30d588879ef029e0fd41d278c6715998ed746817a29f9d09df775368c038cd616b1d17b0bb318fa4501a6375a98497ceda9a827e0f25a55790bb0a259305dc696e140a6f14a901148113ca5ae3a78195d2d06005a036a19478e7cf1d30d38791479169092745bbfc325d73c162e602e4918dab99221f2f64a50c4b349124d7ca8fb56d8358614f20dd2d445debfe711a6d3b441bb5' from Crypto.Util.number import * from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA p = '' q = '' for i in range(128): sign = False for dp in '0123456789': for dq in '0123456789': tmp_p = dp + p tmp_q = dq + q if bytes_to_long(tmp_p.encode()) * bytes_to_long(tmp_q.encode()) % pow(256,i+1) == n % pow(256,i+1): p = tmp_p q = tmp_q sign = True break if sign: break print(p) print(q) p = bytes_to_long(p.encode()) q = bytes_to_long(q.encode()) assert isPrime(p) and isPrime(q) and p*q == n d = pow(e,-1,(p-1)*(q-1)) key = RSA.construct((n, e, d, p, q)) cipher = PKCS1_OAEP.new(key) flag = cipher.decrypt(bytes.fromhex(enc_flag)) print(flag) ``` ![](https://hackmd.io/_uploads/SJATGX9qn.png) - Flag: `W1{branch_and_prune_is_sometime_very_useful!!!!}`