---
# System prepended metadata

title: KCSC CTF 2024

---

# 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: 