do ~~kém~~ đợt này choke quá nên mình chỉ làm được 2 câu :crying_cat_face: (**mém** giải được web 1 và foren 2) # Crypto ## Flipping login (250) > **Description**: *Login System using authenticated token. Only admin can read the secret!* `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()) print(enc_token) token = decrypt(enc_token[:16], enc_token[16:]) token = json.loads(token) print(token) return token['name'], token['admin'] def register(): name = input("Nickname: ").strip() token = { "admin": False, "name": name } token = json.dumps(token).encode() print(token) 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)) ``` Server phép nhận một token ở **2. Register**, sau đó nhập một token ở **1. Login**, nếu `admin = True` thì sẽ in flag, tuy nhiên có một vấn đề. Token được tạo ra bằng việc encrypt trong register(). Ta có thể thấy `admin = False`, tức dù có encrypt bao nhiêu lần cũng sẽ không in ra flag. Vậy chung ta cần chỉnh sửa gì đấy để Token xuất hiện `admin = True` thôi. Khi phân tích đoạn code, ta thấy ở hàm `encrypt()` trả về iv + ciphertext (có padding nhưng không quan trọng vì ở `decrypt()` cũng sẽ unpad ). ```python= def encrypt(msg): iv = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC, iv=iv) return iv+cipher.encrypt(pad(msg, 16)) ``` Đây là dữ kiện quan trọng của bài toán vì khi có `IV`, ta có thể đưa giá trị `admin = True` vào Token thông qua toán tử xor. Để hiểu rõ hơn, sau đây là sơ đồ decryption CBC mode: ![](https://hackmd.io/_uploads/rJXB3wtcn.png) Ở đây, `dec(C0) ⊕ IV = P0` mà C0 (Token hiện tại) sau khi decrypt sẽ cho `admin = False`. Để điều chỉnh sang `True`, ta có: $$\begin{array}{lcr} dec(C_0)⊕ IV= P_0 \end{array}$$ $$\begin{array}{lcr} dec(C_0) = IV ⊕ P_0 \end{array}$$ Cho rằng: $$\begin{array}{lcr}P_0 = dec(C_0) ⊕ IV' \mbox{ (thay đổi IV)}\end{array}$$ Thế vào công thức trên, ta có: $$\begin{array}{lcr}P_0' = P_0 ⊕ IV ⊕ IV' \\ \mbox{hay:} \\ IV' = P_0' ⊕ P_0 ⊕ IV \end{array}$$ Ta đã có IV, nhưng còn P0' và P0 ? Vì AES là mã hóa khối (block cipher) nên ta hoàn toàn có thể tự cho P0 và P0' phù hợp sau khi đã biết số byte mỗi block (16 bytes) $$\begin{array}{lcr} P_0 = \mbox{b'\{"admin": False,'} \\ P_0' = \mbox{b'\{"admin": True, '} \end{array}$$ Ta đã có đầy đủ 3 thành phần, chỉ việc xor và gửi kết quả để lấy flag. `solve.py`: ```python= from base64 import b64decode, b64encode data = 'drj8tHvXKSPYSG6PyVN35xV20qHdhJBpA0ub6PiQOm8qWFZKNsePZx1mpVVnxOVwn7Ico0sqgZuKdeaurYJaWQ==' #do mình lười viết code tương tác vs server nên mình quyết định làm code xor thôi :D data = b64decode(data) iv = data[:16] def custom_xor(a, b): return bytes([x^y for x, y in zip(a, b)]) real_text = b'{"admin": False, "name": "admin"}' force_text = b'{"admin": True, "name": "admin"}' real_text = real_text[:16] #print((real_text)) force_text = force_text[:16] #print((force_text)) x=custom_xor(iv,force_text) y=custom_xor(x,real_text) #do ngựa ngựa viết hàm xor, các bạn có thể dùng xor trong pwn ! h = b64encode(y+data[16:]) print ((h)) # đem cái củ này quăng lên server, thế là xong ! ``` Đây là kết quả: ![](https://hackmd.io/_uploads/HJcYRQF9h.png) # Forensic ## Dimension (50) > **Description**: *Yet Another Stego Challenge* ô kê bài này nói thẳng ra dùng tool cho nhanh. (xin lỗi trainer :crying_cat_face:) Attackment file: [final.png](https://anonfiles.com/YdV4B73bzb/final_png) Khi cố gắng mở file này ra, ta sẽ thấy nó không hiển thị gì cả, và lúc này thì những việc cần làm là: `binwalk -e, exiftool, pngcheck`. ![](https://hackmd.io/_uploads/H1efB_Fch.png) ![](https://hackmd.io/_uploads/Hy1tHdtch.png) ![](https://hackmd.io/_uploads/HJD5SOKcn.png) Hmm, tuy bài này binwalk ra kết quả nhưng ta sẽ không cần đụng tới nó đâu. Khi ta exiftool file ảnh, ta sẽ để ý các giá trị `Height` và `Width` là 0x0, vì thế ta không nhìn thấy gì. Để nhìn thấy ảnh thì đơn giản chỉ cần chỉnh cho nó về đúng ban đầu là được! Nhưng làm thế nào? Ta sẽ bruteforce cho tới khi tới đúng giá trị cần tìm. Ví dụ cho một trường hợp khi bruteforce: ![](https://hackmd.io/_uploads/S1lhLut5h.png) Ta chỉ cần tính tới đúng giá trị thôi! `solve.py` thì.. do ~~lười~~ tiết kiệm thời gian nên mình đã tìm tool để nó bruteforce hộ mình. ![](https://hackmd.io/_uploads/rys9DdK5n.png) Nguồn tool: https://github.com/Ge0rg3/ctf-png-size-solver/blob/master/fix_png_size.py Kết quả: ![](https://hackmd.io/_uploads/Sy8Mudt92.png) P/s: những bài mình có ý tưởng nhưng chưa làm được sẽ thêm vào đây sau!