# KCSC CTF 2024
## Evil CBC
```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()
```
Challenge này ban đầu mình khá bối rối, tưởng là phải flip nhưng mà lại là mode ECB, sau một thời gian thì mình có thể gửi được cả thành nhiều khối xong rồi lấy thêm phần padding nữa là có thể thu được phần signature đúng.
```python
from pwn import*
# io = remote("localhost", 2003)
io = remote("103.163.24.78", 2003)
# io = process(["python3","b.py"])
io.recvuntil(b'> ')
io.sendline(b'2')
io.sendline(b'{{{"username": "admin","isAdmin": true}')
io.recvuntil(b'[+] You can use this token to access your account : ')
token1 = io.recvuntil(b'\n',drop=True).decode()
io.recvuntil(b'> ')
io.sendline(b'2')
io.sendline(b'a'*14)
io.recvuntil(b'[+] You can use this token to access your account : ')
token2 = io.recvuntil(b'\n',drop=True).decode()
a = bytes.fromhex(token1)
a = a[16:16*4].hex()
b = bytes.fromhex(token2)
b = b[-16:].hex()
print(a)
print(b)
io.recvuntil(b'> ')
io.sendline(b'1')
io.recvuntil(b'Token: ')
io.sendline(a.encode() + b.encode())
io.interactive()
```
**Flag: KCSC{eCb_m0de_1s_4lways_1nSecUre_:))}**
## Miscrypt
``chal.py``
```python
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``
```python
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")
```

Thành thật xin lỗi tác giả của bài này vì đã không làm theo hướng của tác giả, so sorry.
Bài này mình nhìn sơ qua, thì chiều dài của ảnh là 999x999 pixels, và chia ra thành 27 block, mỗi block có 37x37 pixels.
Thì mỗi cột, block đầu tiên sẽ là ô trắng, và thấy rằng, các ô trắng cùng cột đó sẽ tương tự với ô trắng đầu tiên. Thế nên, thuật toán của mình sẽ là lấy các block, so sánh rgb với block 0 và sử dụng set() để lấy các giá trị. Nếu cùng set() thì sẽ được đánh dầu là ô trắng, còn khác thì sẽ đánh dấu là ô đen.
Thế nhưng, ở cột thứ 22, thì set() của tất cả các block đều giống nhau, và dựa vào việc phân tích, mình phát hiện ra rằng là các ô trắng sẽ có 6 pixels đầu tiên có giá trị rgb giống nhau, chỉ là khác thứ tự, thế nên chỉ cần thêm điều kiện sắp xếp vào nữa là xong.
Thành thật xin lỗi tác giả vì không làm theo kiểu toán mà làm theo kiểu stego :cry:
```python
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)
# print(a)
# print(b)
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")
```


**Flag: KCSC{CrYpt0-l1k3-St3g4n0???}**
## KCSC Square
``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
```
``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()
```
Bài này là một dạng tấn công Square Attack AES, là một cuộc tấn công cấu trúc. Nó nhắm vào các thuộc tính cấu trúc qua các vòng mã hóa trong mật mã khối.
Dạng tấn công này chỉ tồn tại trong 3 rounds trong AES-128 và mở rộng ra có thể phá vỡ từ 4,5 có thể tới 6.
Giờ mình sẽ bắt đầu phân tích cách tấn công.
Bạn cũng có thể tham khảo nguồn tài liệu [**này**](https://www.davidwong.fr/blockbreakers/square.html), và có thể tham khảo kiến thức AES tại [**bài viết**](https://github.com/trananhnhatviet/AES_cipher/blob/main/AES_Cipher_Knowledge.md) của mình
Bạn cũng có thể đọc bài viết của mình [**tại đây**]()
### Quay lại challenge
Giờ quay trở lại bài nào, ta gặp khó khăn ở hàm mix_columns, nhưng mà theo lý thuyết ở trên, thì chắc chắn dùng code vẫn đúng. Và mình đã sửa lại một chút để tăng tốc độ thuật toán. Giờ kết nối với server nào.
```python
from pwn import *
from tqdm import *
import os
from aeskeyschedule import *
key_pos = [0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11] # get from https://hackmd.io/@Giapppp/square_attack#AES-4-Round
io = remote("103.163.24.78", 2004)
# io = remote("localhost", 2004)
def encrypt(pt:bytes):
io.sendlineafter(b'> ', b'1')
io.sendlineafter(b"Plaintext in hex: ", pt.hex().encode())
ct = io.recvuntil(b'\n',drop=True).decode()
return bytes.fromhex(ct)
def get_ciphertexts(index):
origin = os.urandom(16)
A_set = []
for i in range(256):
temp = bytearray(origin)
temp[index] = i
A_set.append(encrypt(temp))
return A_set
def guess_key_byte(index):
true_answer = set(list(range(256)))
while True:
A_set = get_ciphertexts(index)
answer = set()
for i in range(256):
target = 0
for state in A_set:
# state = reverse_state(i,state,index)
# target = target ^ state[key_pos[index]]
target ^= inv_sbox[state[index]^i]
if target == 0:
answer.add(i)
true_answer.intersection_update(answer)
if len(true_answer) == 1:
return true_answer.pop()
key = []
for i in tqdm(range(16)):
ans = guess_key_byte(i)
key.append(ans)
print((key))
hexkey = reverse_key_schedule(bytes(key), 4).hex()
print(hexkey)
io.sendlineafter(b"> ",b"2")
io.sendlineafter(b"Key in hex: ", hexkey.encode())
io.interactive()
```
**Flag: KCSC{Sq4re_4tt4ck_ez_2_uNderSt4nd_4nD_1mPlement_R1ght?_:3}**
## Don Copper
```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
'''
```
Mình lại phải bất ngờ về ``groebner_basis``, chắc cũng tại mình chưa có hiểu rõ về nó thế nên mình chưa áp dụng được trong lúc thi. Sau khi đọc solution của **Giang** và anh **Quốc** thì mới thấy được sức mạnh của hàm này.
Mình có từng writeup 1 bài tương tự, mình để ở [**đây**](https://nhatzittt.wan.io.vn/posts/TAMUCTF2024-Writeups/#qcg) nhé.
Thực sự là mình đã thử coppersmith, mình cũng đã thử viết phương trình các kiểu nhưng không có hướng.
Lời giải sage của bài này đây.
```sage
from Crypto.Util.number import *
n = 20309506650796881616529290664036466538489386425747108847329314416833872927305399144955238770343216928093685748677981345624111315501596571108286475815937548732237777944966756121878930547704154830118623697713050651175872498696886388591990290649008566165706882183536432074074093989165129982027471595363186012032012716786766898967178702932387828604019583820419525077836905310644900660107030935400863436580408288191459013552406498847690908648207805504191001496170310089546275003489343333654260825796730484675948772646479183783762309135891162431343426271855443311093315537542013161936068129247159333498199039105461683433559
c1 = 4199114785395079527708590502284487952499260901806619182047635882351235136067066118088238258758190817298694050837954512048540738666568371021705303034447643372079128117357999230662297600296143681452520944664127802819585723070008246552551484638691165362269408201085933941408723024036595945680925114050652110889316381605080307039620210609769392683351575676103028568766527469370715488668422245141709925930432410059952738674832588223109550486203200795541531631718435391186500053512941594901330786938768706895275374971646539833090714455557224571309211063383843267282547373014559640119269509932424300539909699047417886111314
c2 = 15650490923019220133875152059331365766693239517506051173267598885807661657182838682038088755247179213968582991397981250801642560325035309774037501160195325905859961337459025909689911567332523970782429751122939747242844779503873324022826268274173388947508160966345513047092282464148309981988907583482129247720207815093850363800732109933366825533141246927329087602528196453603292618745790632581329788674987853984153555891779927769670258476202605061744673053413682672209298008811597719866629672869500235237620887158099637238077835474668017416820127072548341550712637174520271022708396652014740738238378199870687994311904
c3 = 18049611726836505821453817372562316794589656109517250054347456683556431747564647553880528986894363034117226538032533356275073007558690442144224643000621847811625558231542435955117636426010023056741993285381967997664265021610409564351046101786654952679193571324445192716616759002730952101112316495837569266130959699342032640740375761374993415050076510886515944123594545916167183939520495851349542048972495703489407916038504032996901940696359461636008398991990191156647394833667609213829253486672716593224216112049920602489681252392770813768169755622341704890099918147629758209742872521177691286126574993863763318087398
e = 3
P.<a,b> = PolynomialRing(Zmod(n),order='lex')
equa1 = a^e - c1
equa2 = b^e - c2
equa3 = (a + b + 2024)^e - c3
I = ideal([equa1,equa2,equa3])
solved = I.groebner_basis()
a = int(n-int(solved[0]-a))
b = int(n-int(solved[1]-b))
flag = long_to_bytes(a).split(b'\x00')[-1] + long_to_bytes(b).split(b'\x00')[-1]
print(flag)
```
**Flag: KCSC{W0rk1ng_w1th_p0lyn0m14ls_1s_34sy_:D}**
## KCSC Lottery v3
``server.py``
```python
import sys
import string
import random
import hashlib
import time
import subprocess
import os
flag = 'KCSC{s0m3_r3ad4ble_5tr1ng_like_7his}'
NUM_TRIALS = 50
USE_POW = True
if USE_POW:
# proof of work
prefix = ''.join(random.choice(string.digits) for i in range(5))
suffix = os.urandom(3).hex()
print("Give me a string starting with \"{}\" (no quotes) so its sha256sum ends in {}".format(prefix, suffix), flush=True)
l = input().strip()
if not l.startswith(prefix) or hashlib.sha256(l.encode()).hexdigest()[-6:] != suffix:
print("Nope.", flush=True)
sys.exit(1)
for trial in range(NUM_TRIALS):
print(f'KCSC Lottery v3: trial {trial+1}/{NUM_TRIALS}', flush=True)
tick = time.time()
p = subprocess.run(['node', 'lottery.js'])
tock = time.time()
if abs(tock-tick) > 15:
print(f'⌛️❗️ ({tock-tick:.3f})', flush=True)
sys.exit(1)
if p.returncode != 42:
print(f'🔮️🚫️❗️', flush=True)
sys.exit(1)
print('congrats!', flush=True)
print(flag)
```
``lottery.js``
```javascript
const { randomInt, createHash } = require('crypto');
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
});
const prefix_len = 192;
const alphabet = 'abcdefghijklmnopqrstuvwxyz';
let output = '';
for (let i = 0; i < prefix_len+128; i++) {
output += alphabet[Math.floor(Math.random() * alphabet.length)];
}
const prefix = output.substring(0, prefix_len);
const expected = output.substring(prefix_len);
console.log(prefix);
console.log(createHash('sha256').update(expected, 'utf8').digest('hex'));
readline.question('❓️\n', guess => {
readline.close();
if (guess === expected) {
console.log('✅');
process.exit(42);
} else {
console.log('❌');
process.exit(1);
}
});
```
Challenge này thực sự là mình chưa tìm hiểu kỹ, chắc báo cáo lần sau, mình sẽ tìm hiểu thêm về vấn đề này.
Mình có được anh Thắng và anh Quốc bảo là giải PlaidCTF2023 có các challenge tương tự nên là mình có tìm hiểu và kiếm được 2 quả link này
https://imp.ress.me/blog/2023-04-17/plaidctf-2023/
https://hackmd.io/@m1dm4n/plaidctf2023
Thì mình ngồi code và có được solution như sau
``x.py``
```python
import string
import hashlib
import random
from pwn import *
DEBUG = False
def find_l(prefix, suffix, length):
for i in range(10**length):
l = prefix + str(i).zfill(length)
if hashlib.sha256(l.encode()).hexdigest()[-6:] == suffix:
return l
r = None
charset = None
def connect_to_challenge(use_charset):
global r, charset
# r = process(["python3", "server.py"])
r = remote("103.163.24.78", 2005)
data = r.recvuntil(b'\n',drop=True).decode()
print(data)
suffix = data[-6:]
data = data.replace('Give me a string starting with "','')
prefix = data[:5]
print(suffix, prefix)
for length in range(7,10):
found_l = find_l(prefix, suffix, length)
if found_l:
print("Found l:", found_l)
break
else:
print("Could not find l.")
r.sendline(found_l.encode())
charset = use_charset
def moon_to_buckets(moon):
return [ charset.index(i) for i in moon ]
def buckets_to_moon(buckets):
return "".join([ charset[i] for i in buckets ])
chal_hash = None
def get_challenge():
global chal_hash
r.recvuntil(b"trial")
r.recvline()
chal_prefix = r.recvline().decode().strip()
print("challenge:", chal_prefix[:20])
chal_prefix = moon_to_buckets(chal_prefix)
chal_hash = r.recvline().decode().strip()
return chal_prefix
def answer_challenge(ans, do_send=False):
ans = buckets_to_moon(ans)
if hashlib.sha256(ans.encode()).hexdigest() == chal_hash:
print("answer:", ans[:20])
r.sendline(ans.encode())
return True
else:
return False
def claim_flag():
global r
r.recvuntil(b"congrats!\n")
flag = r.recvline().decode()
r = r.close()
print(flag)
return flag
****
```
``solve.py``
```python
class BV:
def __init__(self, data):
# data is 64-long list of 128 bit ints
# data[0] is a 128 bit int indicating the mask of bits in s0s1
# which are xor-ed together to generate the 0th (LSB) bit of
# this state
# Low 64 bits of s0s1 come from s0, high 64 bits come from s1
assert(len(data) == 64)
self.data = data
def __xor__(self, other):
return BV([i ^ j for i, j in zip(self.data, other.data)])
def __lshift__(self, other):
# After left shift the least significant bits of the state
# should be empty
return BV([0] * other + self.data[:-other])
def __rshift__(self, other):
# After right shift the most significant bits of the state
# should be empty
return BV(self.data[other:] + [0] * other)
def coef(self, pos):
# Converts 128 bit mask into list of ints
coef = f"{self.data[pos]:0128b}"
coef = [int(i) for i in coef[::-1]]
return coef
def eval_one(self, s0s1, pos):
# From a known s0s1, evalute the bit at one position
val = sum([i * j for i, j in zip(self.coef(pos), s0s1)]) % 2
return val
def eval_all(self, s0s1):
# From a known s0s1, evalute the bit at all positions and get
# the u64 output
vals = [self.eval_one(s0s1, pos) for pos in range(64)]
vals = "".join([str(i) for i in vals[::-1]])
vals = int(vals, 2)
return vals
def xs128p(state0, state1):
s1 = state0
s0 = state1
s1 = s1 ^ (s1 << 23)
s1 = s1 ^ (s1 >> 17)
s1 = s1 ^ s0
s1 = s1 ^ (s0 >> 26)
output_state = state0
state0 = state1
state1 = s1
return state0, state1, output_state
# Initial symbolic values of s0 and s1
BV.s0 = BV([1 << i for i in range(64)])
BV.s1 = BV([1 << i for i in range(64, 128)])
s0, s1 = BV.s0, BV.s1
s0, s1, output_state_0 = xs128p(s0, s1)
s0, s1, output_state_1 = xs128p(s0, s1)
s0, s1, output_state_2 = xs128p(s0, s1)
def schedule_sequence(seq):
assert(len(seq) % 64 == 0) # ensure block aligned
return [
j
for i in range(0, len(seq), 64)
for j in seq[i:i+64][::-1]
]
n_steps = 64 * 70
s0, s1 = BV.s0, BV.s1
prng_states = []
for _ in range(n_steps):
s0, s1, output_state = xs128p(s0, s1)
prng_states.append(output_state)
prng_states = schedule_sequence(prng_states)
moon_to_fixed_msb = {0: '0000', 1: '000', 2: '0001', 3: '00', 4: '001', 5: '0011', 6: '0', 7: '0100', 8: '010', 9: '01', 10: '0110', 11: '011', 12: '', 13: '1000', 14: '100', 15: '1001', 16: '10', 17: '101', 18: '1011', 19: '1', 20: '1100', 21: '110', 22: '11', 23: '1110', 24: '111', 25: '1111'}
from sage.all_cmdline import Matrix, vector, GF
from x import*
import struct
def f64_to_u64(f):
# f64 in [0, 1) to u64 XorShift128 state
if f == 1.0:
return 0xffffffffffffffff
buf = struct.pack("d", f + 1)
u52 = struct.unpack("<Q", buf)[0] & 0x000fffffffffffff
u64 = u52 << 12
return u64
def u64_to_f64(u):
# u64 XorShift128 state to f64 in [0, 1)
buf = struct.pack("<Q", (u >> 12) | 0x3FF0000000000000)
f64 = struct.unpack("d", buf)[0] - 1
return f64
moon_to_fixed_msb_precomp = {
k: [(63 - idx, int(fix_bit)) for idx, fix_bit in enumerate(v)]
for k, v in moon_to_fixed_msb.items()
}
F = GF(2)
connect_to_challenge("abcdefghijklmnopqrstuvwxyz")
for trial in range(50):
print(f"trial {trial}")
moons = get_challenge()
ok = False
for offset in range(64):
system_mat = []
system_vec = []
math_random = iter(prng_states)
for _ in range(offset):
next(math_random)
for moon in moons:
moon_prng_state = next(math_random)
for bit_pos, fix_value in moon_to_fixed_msb_precomp[moon]:
system_mat.append(moon_prng_state.coef(bit_pos))
system_vec.append(fix_value)
try:
s0s1 = Matrix(F, system_mat).solve_right(vector(F, system_vec))
print(f"Found s0s1 with {offset = }")
except ValueError:
# no solution, offset is wrong
continue
soln = [
int(u64_to_f64(prng_state.eval_all(s0s1)) * 26)
for prng_state in prng_states[offset + 192: offset + 192 + 128]
]
if answer_challenge(soln):
ok = True
break
if not ok:
raise ValueError("No solution found")
flag = claim_flag()
```
**Flag: KCSC{I_ch4ngeD_tHe_4lph4bet_bUt_sEEm_n0t_4_pr0blEm_f0r_U_@-@}**