# KCSC CTF 2024 * Trong thời gian diễn ra giải, 8 tiếng , mình chỉ làm được 2/5 bài (có 4 bài Crypto và 1 bài Misc) . Mình xin phép được trình bày toàn bộ ở dưới đây. ## ECB ```python from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from os import urandom import json import socket import threading flag = 'KCSC{s0m3_r3ad4ble_5tr1ng_like_7his}' menu = ('\n\n|---------------------------------------|\n' + '| Welcome to Evil_ECB! |\n' + '| Maybe we can change the Crypto world |\n' + '| with a physical phenomena :D |\n' + '|---------------------------------------|\n' + '| [1] Login |\n' + '| [2] Register ^__^ |\n' + '| [3] Quit X__X |\n' + '|---------------------------------------|\n') bye = ( '[+] Closing Connection ..\n'+ '[+] Bye ..\n') class Evil_ECB: def __init__(self): self.key = urandom(16) self.cipher = AES.new(self.key, AES.MODE_ECB) self.users = ['admin'] def login(self, token): try: data = json.loads(unpad(self.cipher.decrypt(bytes.fromhex(token)), 16).decode()) if data['username'] not in self.users: return '[-] Unknown user' if data['username'] == "admin" and data["isAdmin"]: return '[+] Hello admin , here is your secret : %s\n' % flag return "[+] Hello %s , you don't have any secret in our database" % data['username'] except: return '[-] Invalid token !' def register(self, user): if user in self.users: return '[-] User already exists' data = b'{"username": "%s", "isAdmin": false}' % (user.encode()) token = self.cipher.encrypt(pad(data, 16)).hex() self.users.append(user) return '[+] You can use this token to access your account : %s' % token class ThreadedServer(object): def __init__(self, host, port): self.host = host self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.bind((self.host, self.port)) def listen(self): self.sock.listen(5) while True: client, address = self.sock.accept() client.settimeout(60) threading.Thread(target = self.listenToClient,args = (client,address)).start() def listenToClient(self, client, address): size = 1024 chal = Evil_ECB() client.send(menu.encode()) for i in range(10): try: client.send(b'> ') choice = client.recv(size).strip() if choice == b'1': client.send(b'Token: ') token = client.recv(size).strip().decode() client.send(chal.login(token).encode() + b'\n') elif choice == b'2': client.send(b'Username: ') user = client.recv(size).strip().decode() client.send(chal.register(user).encode() + b'\n') elif choice == b'3': client.send(bye.encode()) client.close() else: client.send(b'Invalid choice!!!!\n') client.close() except: client.close() return False client.send(b'No more rounds\n') client.close() if __name__ == "__main__": ThreadedServer('',2003).listen() ``` * Bài này thì ý tưởng cách làm không có gì khó khăn cả. Tuy nhiên do mình ngựa ngựa nên cũng mất khá lâu mới xong chall này. * Bài này thì mã hóa AES ECB. Mình sẽ có 2 phương thức để thao tác là đăng kí tài khoản và đăng nhập tài khoản. * Về phương thức đăng kí: Mình sẽ nhập 1 chuỗi bất kì ( ở đây gọi là **user**). Tuy nhiên **user** không được là **admin**. Ví dụ mình nhập **user = Giang** thì không sao cả. * Sau đó thì server sẽ mã hóa ECB cái này ![image](https://hackmd.io/_uploads/Sk2e1ckQA.png). Mình sẽ gọi nó là **token** * Tới phương thức đăng nhập: Mình nhập giá trị **token** sau khi đăng kí để server decrypt lại , nếu thu được chuỗi ![image](https://hackmd.io/_uploads/HkdgeqymC.png) thì mình có flag. * Tuy nhiên thì **user** thì không cho đặt là **admin**, **isAdmin** lúc đầu thì lại là **False**. Vậy thì làm thế nào đây :)) * Bài này là ECB. Vậy nên mình hoàn toàn có thể khai thác vào điểm yếu của nó là các block 16 byte được mã hóa hoàn toàn độc lập. * Vậy mình sẽ gửi **user** như sau:![image](https://hackmd.io/_uploads/BJ30xqJQA.png) * Giải thích nhé :accept: * ![image](https://hackmd.io/_uploads/SJIibqJmC.png) * Đây là giá trị **data** mà **server** sẽ đem đi mã hóa. Sau đó 6 block này sẽ được mã hóa bằng ECB. Lúc này nếu muốn token của mình thỏa mãn điều kiện làm admin thì mình chỉ cần gửi cho server chuỗi mã hóa của data lấy block 2,3,4 thuii. * Ở **user** thì 2 giá trị $b'\x00'$ ở đầu sẽ pad cho chuỗi username đủ 16 bytes để làm block1, còn 2 giá trị $b'\x00'$ ở cuối thì cũng tương tự vậy, để cho isAdmin đủ 16 bytes để làm block5, còn $b'\n\n....'$ thì để unpad khi server decrypt là vừa đẹp. * Script thì không dài , chỉ là mình phải tốn thời gian tìm cái $user$ hợp lí thôi. Thử hỏi nếu bài này không phải ECB mà là CBC hoặc cái khác thì có làm được như này không :satisfied: * Full script: ```python from Crypto.Util.number import * from gmpy2 import * import math from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from hashlib import md5 HOST = "103.163.24.78" PORT = 2003 io = remote(HOST,PORT) io.sendlineafter(b'> ',b'2') io.sendlineafter(b'Username: ',b'\x00\x00{"username": "admin", "isAdmin": true}\n\n\n\n\n\n\n\n\n\n\x00\x00') io.recvuntil(b'[+] You can use this token to access your account : ') token = bytes.fromhex(io.recvuntil(b'\n',drop=True).decode())[16:64] print(token.hex()) io.sendlineafter(b'> ',b'1') io.sendline(str(token.hex()).encode()) res = io.recvuntil(b'\n',drop=True) print(res) ``` * **Flag: KCSC{eCb_m0de_1s_4lways_1nSecUre_:))}** ## Square * Server.py ```python from os import urandom from aes import AES import socket import threading flag = 'KCSC{s0m3_r3ad4ble_5tr1ng_like_7his}' menu = ('\n\n|---------------------------------------|\n' + '| Welcome to KCSC Square! |\n' + '| I know it\'s late now but |\n' + '| Happy Reunification Day :D |\n' + '|---------------------------------------|\n' + '| [1] Get ciphertext |\n' + '| [2] Guess key ^__^ |\n' + '| [3] Quit X__X |\n' + '|---------------------------------------|\n') bye = ( '[+] Closing Connection ..\n'+ '[+] Bye ..\n') class ThreadedServer(object): def __init__(self, host, port): self.host = host self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.bind((self.host, self.port)) def listen(self): self.sock.listen(5) while True: client, address = self.sock.accept() client.settimeout(60) threading.Thread(target = self.listenToClient,args = (client,address)).start() def listenToClient(self, client, address): size = 1024 key = urandom(16) chal = AES(key) client.send(menu.encode()) for i in range(8888): try: client.send(b'> ') choice = client.recv(size).strip() if choice == b'1': client.send(b'Plaintext in hex: ') hex_pt = client.recv(size).strip().decode() try: pt = bytes.fromhex(hex_pt) assert len(pt) == 16 except: client.send(b'Something wrong in your plaintext' + b'\n') continue client.send(chal.encrypt(pt).hex().encode() + b'\n') elif choice == b'2': client.send(b'Key in hex: ') hex_key = client.recv(size).strip().decode() try: guess_key = bytes.fromhex(hex_key) assert guess_key == key except: client.send(b'Wrong key, good luck next time =)))' + b'\n') client.close() client.send(b'Nice try, you got it :D!!!! Here is your flag: ' + flag.encode() + b'\n') client.close() elif choice == b'3': client.send(bye.encode()) client.close() else: client.send(b'Invalid choice!!!!\n') client.close() except: client.close() return False client.send(b'No more rounds\n') client.close() if __name__ == "__main__": ThreadedServer('',2004).listen() ``` * aes.py ```python class AES: sbox = ( 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, ) rcon = (0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36) gmul2 = ( 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5 ) gmul3 = ( 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a ) def __init__(self, key): self._block_size = 16 self._round_keys = self._expand_key([i for i in key]) self._state = [] def _transpose(self, m): return [m[4 * j + i] for i in range(4) for j in range(4)] def _xor(self, a, b): return [x ^ y for x, y in zip(a, b)] def _expand_key(self, key): round_keys = [key] for i in range(4): round_key = [] first = round_keys[i][:4] last = round_keys[i][-4:] last = last[1:] + [last[0]] last = [self.sbox[i] for i in last] round_key.extend(self._xor(self._xor(first, last), [self.rcon[i+1], 0, 0, 0])) for j in range(0, 12, 4): round_key.extend(self._xor(round_key[j:j + 4], round_keys[i][j + 4:j + 8])) round_keys.append(round_key) for i in range(len(round_keys)): round_keys[i] = self._transpose(round_keys[i]) return round_keys def _add_round_key(self, i): self._state = self._xor(self._round_keys[i], self._state) def _mix_columns(self): s = [0] * self._block_size for i in range(4): s[i] = self.gmul2[self._state[i]] ^ self.gmul3[self._state[i + 4]] ^ self._state[i + 8] ^ self._state[i + 12] s[i + 4] = self._state[i] ^ self.gmul2[self._state[i + 4]] ^ self.gmul3[self._state[i + 8]] ^ self._state[i + 12] s[i + 8] = self._state[i] ^ self._state[i + 4] ^ self.gmul2[self._state[i + 8]] ^ self.gmul3[self._state[i + 12]] s[i + 12] = self.gmul3[self._state[i]] ^ self._state[i + 4] ^ self._state[i + 8] ^ self.gmul2[self._state[i + 12]] self._state = s def _shift_rows(self): self._state = [ self._state[0], self._state[1], self._state[2], self._state[3], self._state[5], self._state[6], self._state[7], self._state[4], self._state[10], self._state[11], self._state[8], self._state[9], self._state[15], self._state[12], self._state[13], self._state[14] ] def _sub_bytes(self): self._state = [self.sbox[i] for i in self._state] def _encrypt_block(self): self._add_round_key(0) for i in range(1, 4): self._sub_bytes() self._shift_rows() self._mix_columns() self._add_round_key(i) self._sub_bytes() self._shift_rows() self._add_round_key(4) def encrypt(self, plaintext): ciphertext = b'' self._state = self._transpose([c for c in plaintext]) self._encrypt_block() ciphertext += bytes(self._transpose(self._state)) return ciphertext ``` * Bài này thì mã hóa AES tiếp. Tuy nhiên bình thường thì ![image](https://hackmd.io/_uploads/BySgE9yXA.png) * Tức là key = 16 bytes thì 10 vòng lặp. Ở đây chỉ có 4. Sau 1 hồi vật vờ đi tìm thử xem AES chỉ có 4 vòng lặp thì weak chỗ nào thì có tìm được [cái này](http://cse.iitkgp.ac.in/~debdeep/courses_iitkgp/Crypto/papers/square.pdf). Sau khi biết dùng **Square Attack** thì mình có tìm được luôn 1 chall tương tự . Mình để ở [đây nhé](https://hackmd.io/@Giapppp/square_attack#AES-4-Round) * Cách tấn công này thì sẽ tìm lại từng bytes của key. Dù hơi lâu nhưng kiếm được script thì quá ngon rùi :satisfied: * Full script: ```py from Crypto.Util.number import * from gmpy2 import * import math from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from aeskeyschedule import reverse_key_schedule HOST = "103.163.24.78" PORT = 2004 io = remote(HOST,PORT) inv_sbox = [ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, ] def encrypt(pt:bytes): io.sendlineafter(b'> ',b'1') io.sendlineafter(b'Plaintext in hex: ',pt.hex().encode()) ct = bytes.fromhex(io.recvuntil(b'\n',drop=True).decode()) return ct def find_key_bytes(idx:int): real_ans = set(list(range(256))) key_pos = [0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11] while True: ans = set() A_set = [] init = os.urandom(16) for i in range(256): temp = bytearray(init) temp[idx] = i A_set += [encrypt(temp)] for i in tqdm(range(256)): A_set_dec = 0 for ele in A_set: # temp = bytearray(ele) # temp[idx] ^= i # ele_dec_arr = list(temp) # InvShiftRows(ele_dec_arr) # InvSubBytes(ele_dec_arr) # A_set_dec ^= ele_dec_arr[key_pos[idx]] A_set_dec ^= inv_sbox[ele[idx] ^ i] if A_set_dec == 0: ans.add(i) real_ans.intersection_update(ans) if len(real_ans) == 1: return real_ans.pop() key = [] for i in tqdm(range(16)): ans = find_key_bytes(i) key.append(ans) print(key) hexkey = reverse_key_schedule(bytes(key), 4).hex() io.sendlineafter(b'> ',b'2') io.sendlineafter(b'Key in hex: ',hexkey.encode()) res = io.recvuntil(b'\n',drop=True) print(res) ``` * **Flag:KCSC{Sq4re_4tt4ck_ez_2_uNderSt4nd_4nD_1mPlement_R1ght?_:3}** ## Miscrypt * chall.py ```py from PIL import Image import numpy as np import galois GF256 = galois.GF(2**8) img = Image.open('qr_flag_rgb.png') pixels = img.load() width, height = img.size M = GF256(np.random.randint(0, 256, size=(3, 3), dtype=np.uint8)) # scan full height -> weight for x in range(width): for y in range(0,height,3): A = GF256([pixels[x, y], pixels[x, y+1], pixels[x, y+2]]) M = np.add(A, M) pixels[x, y], pixels[x, y+1], pixels[x, y+2] = [tuple([int(i) for i in j]) for j in M] img.save('qr_flag_encrypt.png') ``` * gen_qr_flag.py ```py import qrcode from PIL import Image # Define the text you want to encode text = "flag hereeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" # Generate the QR code qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=1, border=1, ) # Add the data to the QR code qr.add_data(text) qr.make(fit=True) # Create an image from the QR code img = qr.make_image(fill_color="black", back_color="white") # Resize the image to the desired size img = img.resize((999, 999), resample=Image.NEAREST) # Convert the image to RGB mode img = img.convert("RGB") # Create a new image with RGB values for each pixel new_img = Image.new("RGB", img.size) # Iterate over each pixel and set RGB values for x in range(img.width): for y in range(img.height): r, g, b = img.getpixel((x, y)) new_img.putpixel((x, y), (r, g, b)) # Save the image new_img.save("qr_flag_rgb.png") ``` * qr_flag_encrypt.png: ![qr_flag_encrypt](https://hackmd.io/_uploads/Sy-385kQA.png) * requirements.txt ```py galois==0.3.8 numpy==1.26.4 pillow==10.3.0 qrcode==7.4.2 ``` * Đây là 1 bài **Misc** mình nghĩ là rất hay. Tuy nhiên do mình gà nên cứ nghĩ là mấy bài không phải Crypto thì mình không làm được :)))) * Bài này ấy thì sau khi end giải và có đi bú hint của mọi người thì mình nhận ra 1 điều là : từng pixel của qr gốc nó có bị chuyển sang màu bất kì thì chỉ cần đưa lại về đúng 2 màu đen trắng là xong :)))))))))). Không hiểu sao lúc đó đầu tối với chậm tiêu thật sự luôn ấy . :satisfied: * Dễ hiểu hơn thì nó như ECB ấy ![image](https://hackmd.io/_uploads/Hk3cOcym0.png) * Nhưng mà ECB thì nếu chỉ đưa lại về đen trắng thì cũng khó nhận ra đây là con chim cánh cụt. Nhưng qr thì có màu nào khác 2 màu đen trắng đâu :)) * Full script: ```py! from Crypto.Util.number import * from gmpy2 import * import math from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from hashlib import md5 from PIL import Image img = Image.open(r"qr_flag_encrypt.png") px = img.load() def is_subarray(arr, subarr): a = set(arr) b = set(subarr) return a==b def getblock(n0,n1,n2,n3): block = [] for i in range(n0,n1): for j in range(n2,n3): r, g, b= px[i,j] block.append("[" + str(r) + "," + str(g) + "," + str(b) + "]") return block pixels = [] for t1 in range(27): pixel = [0] block0 = getblock(37*t1, 37*(t1+1), 0, 37*1) for t2 in range(1,27): block = getblock(37*t1, 37*(t1+1), 37*t2, 37*(t2+1)) if is_subarray(block0,block) and sorted(block0[:6]) == sorted(block[:6]): pixel.append(0) else: pixel.append(1) pixels.append(pixel) black = "[0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0] [0,0,0]" white = "[255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255] [255,255,255]" with open ("rgb_origin111.txt", "w") as f: for i in range(27): for j in range(27): if pixels[i][j] == 0: f.write(white) else: f.write(black) f.write("\n") with open("rgb_origin111.txt","r") as f: lines = f.readlines() width = len(lines) tmp = lines[1].split(" ") length = len(tmp) - 1 imgsize = (width,length) img = Image.new("RGB", imgsize) pix = img.load() for i in range (width): temp = lines[i].split(" ") for j in range (length): temp[j] = temp[j].replace('[','') temp[j] = temp[j].replace(']','') t = temp[j].split(",") t2 = (int(t[0]), int(t[1]), int(t[2])) pix[i, j] = t2 img.save("flag.png") img = Image.open('flag.png') resized_img = img.resize((999, 999)) resized_img.save("flag_resized.png") ``` * ![image](https://hackmd.io/_uploads/B134K51XC.png) * Ơ qr hơi hơi mờ , không biết có sao không. * ![image](https://hackmd.io/_uploads/S1RDF51QR.png) * **Flag:KCSC{CrYpt0-l1k3-St3g4n0???}** ## Don Copper * Chall.py: ```python import random from Crypto.Util.number import getPrime NBITS = 2048 def pad(msg, nbits): """msg -> trash | 0x00 | msg""" pad_length = nbits - len(msg) * 8 - 8 assert pad_length >= 0 pad = random.getrandbits(pad_length).to_bytes((pad_length+7) // 8, "big") return pad + b"\x00" + msg if __name__ == '__main__': p = getPrime(NBITS//2) q = getPrime(NBITS//2) n = p*q e = 3 print('n =',n) flag = b'KCSC{s0m3_r3ad4ble_5tr1ng_like_7his}' flag1 = int.from_bytes(pad(flag[:len(flag)//2], NBITS-1), "big") flag2 = int.from_bytes(pad(flag[len(flag)//2:], NBITS-1), "big") print('c1 =', pow(flag1, e, n)) print('c2 =', pow(flag2, e, n)) print('c3 =', pow(flag1 + flag2 + 2024, e, n)) ''' n = 20309506650796881616529290664036466538489386425747108847329314416833872927305399144955238770343216928093685748677981345624111315501596571108286475815937548732237777944966756121878930547704154830118623697713050651175872498696886388591990290649008566165706882183536432074074093989165129982027471595363186012032012716786766898967178702932387828604019583820419525077836905310644900660107030935400863436580408288191459013552406498847690908648207805504191001496170310089546275003489343333654260825796730484675948772646479183783762309135891162431343426271855443311093315537542013161936068129247159333498199039105461683433559 c1 = 4199114785395079527708590502284487952499260901806619182047635882351235136067066118088238258758190817298694050837954512048540738666568371021705303034447643372079128117357999230662297600296143681452520944664127802819585723070008246552551484638691165362269408201085933941408723024036595945680925114050652110889316381605080307039620210609769392683351575676103028568766527469370715488668422245141709925930432410059952738674832588223109550486203200795541531631718435391186500053512941594901330786938768706895275374971646539833090714455557224571309211063383843267282547373014559640119269509932424300539909699047417886111314 c2 = 15650490923019220133875152059331365766693239517506051173267598885807661657182838682038088755247179213968582991397981250801642560325035309774037501160195325905859961337459025909689911567332523970782429751122939747242844779503873324022826268274173388947508160966345513047092282464148309981988907583482129247720207815093850363800732109933366825533141246927329087602528196453603292618745790632581329788674987853984153555891779927769670258476202605061744673053413682672209298008811597719866629672869500235237620887158099637238077835474668017416820127072548341550712637174520271022708396652014740738238378199870687994311904 c3 = 18049611726836505821453817372562316794589656109517250054347456683556431747564647553880528986894363034117226538032533356275073007558690442144224643000621847811625558231542435955117636426010023056741993285381967997664265021610409564351046101786654952679193571324445192716616759002730952101112316495837569266130959699342032640740375761374993415050076510886515944123594545916167183939520495851349542048972495703489407916038504032996901940696359461636008398991990191156647394833667609213829253486672716593224216112049920602489681252392770813768169755622341704890099918147629758209742872521177691286126574993863763318087398 ''' ``` * Hmm... nói thế nào nhỉ. Bài này phần miêu tả có nói bóng gió là nếu như không biết CopperSmith thì không phải là 1 người chơi Crypto thật sự :))) . Thế là mình đâm đầu vào làm Coppersmith mới cay chứ. Mình không biết là có làm được theo Coppersmith thật không nhưng trong 8 tiếng mình chơi giải thì bài này mình ngồi lâu nhất. :satisfied: . Nháp chục tờ tìm GCD rồi dùng cả [defund_multivariate](https://github.com/defund/coppersmith/blob/master/coppersmith.sage) vẫn no hope . Đến bây giờ khi mà end giải rồi thì mình mới nhớ ra là trước mình có gặp 1 bài như này rồi :))). Kiểu chả hiểu sao đầu óc lúc đó tối tăm vậy. * Mình để wu bài đó ở [đây nhé](https://hackmd.io/53oprdnoQmyQ9BWo4QtfbQ?view). Bài **QCG** ở phần note cách làm mở rộng của anh Quốc đó ạ. ```python from Crypto.Util.number import * from gmpy2 import * import math from pwn import * from tqdm import tqdm n = 20309506650796881616529290664036466538489386425747108847329314416833872927305399144955238770343216928093685748677981345624111315501596571108286475815937548732237777944966756121878930547704154830118623697713050651175872498696886388591990290649008566165706882183536432074074093989165129982027471595363186012032012716786766898967178702932387828604019583820419525077836905310644900660107030935400863436580408288191459013552406498847690908648207805504191001496170310089546275003489343333654260825796730484675948772646479183783762309135891162431343426271855443311093315537542013161936068129247159333498199039105461683433559 c1 = 4199114785395079527708590502284487952499260901806619182047635882351235136067066118088238258758190817298694050837954512048540738666568371021705303034447643372079128117357999230662297600296143681452520944664127802819585723070008246552551484638691165362269408201085933941408723024036595945680925114050652110889316381605080307039620210609769392683351575676103028568766527469370715488668422245141709925930432410059952738674832588223109550486203200795541531631718435391186500053512941594901330786938768706895275374971646539833090714455557224571309211063383843267282547373014559640119269509932424300539909699047417886111314 c2 = 15650490923019220133875152059331365766693239517506051173267598885807661657182838682038088755247179213968582991397981250801642560325035309774037501160195325905859961337459025909689911567332523970782429751122939747242844779503873324022826268274173388947508160966345513047092282464148309981988907583482129247720207815093850363800732109933366825533141246927329087602528196453603292618745790632581329788674987853984153555891779927769670258476202605061744673053413682672209298008811597719866629672869500235237620887158099637238077835474668017416820127072548341550712637174520271022708396652014740738238378199870687994311904 c3 = 18049611726836505821453817372562316794589656109517250054347456683556431747564647553880528986894363034117226538032533356275073007558690442144224643000621847811625558231542435955117636426010023056741993285381967997664265021610409564351046101786654952679193571324445192716616759002730952101112316495837569266130959699342032640740375761374993415050076510886515944123594545916167183939520495851349542048972495703489407916038504032996901940696359461636008398991990191156647394833667609213829253486672716593224216112049920602489681252392770813768169755622341704890099918147629758209742872521177691286126574993863763318087398 e = 3 P.<x,y> = PolynomialRing(Zmod(n),order='lex') P = x^e-c1 Q = y^e-c2 S = (x+y+2024)^e-c3 I = ideal([P,Q,S]) m = I.groebner_basis() m ``` * Chạy trên sage thì mình thu được cái này: ![image](https://hackmd.io/_uploads/Skj81zWQ0.png) * Đây là nghiệm hay nói chính xác hơn là **flag1** và **flag2** đó ạ. Nhưng mà $( flag_1 + blabla...) (mod n) = 0$ nên $flag_1 = -blabla...(modn)$ * ![image](https://hackmd.io/_uploads/SkNfgf-XA.png) * Đoạn này ghép lại flag bằng tay thôi cho nhanh. * **Flag:KCSC{W0rk1ng_w1th_p0lyn0m14ls_1s_34sy_:D}** * Đến đây cũng chán chả muốn nghĩ lại tại sao lúc đó lại choke đến vậy nữa. :see_no_evil: ## Tổng kết * Mình đã trình bày lại 4/5 chall của tag Crypto giải KCSC CTF lần này, 2 bài làm được trong thời gian thi và 2 bài còn lại thì end giải rồi mới làm được . ( Chall cuối cùng là dùng kĩ thuật crack math random của node js nhưng do mình chưa tìm hiểu sâu nên thôi :)) * Team mình dừng lại ở top 15. Thực ra trước lúc thi thì mình cũng không có đặt aim , chỉ là hết sức mà làm thôi. Nhưng thi xong và đến bây giờ lúc viết nốt wu này thì cảm xúc vẫn lẫn lộn. Cũng hơi thất vọng 1 chút nhưng mình sẽ lấy đó làm bài học để try hard nhiều hơn . Mình cũng được biết các bạn ở team khác giỏi như nào để mình nhìn nhận bản thân mình đang ở đâu. Cố gắng nhiều hơn để sau có đọc lại wu này thì thấy mình có sự tiến bộ thật .Cảm ơn mọi người đã đọc tới đây ạ. :relieved: