# HOW AES WORKS
## Keyed Permutations

- Chall giới thiệu về hoán vị khóa.
- Search gg ta thu được flag.

## Resisting Bruteforce

- Search gg ta nhận được thông tin cần biết, đó chính là flag.

- Biclique attack là một biến thể của meet in the middle
- Flag: `crypto{Biclique}`
## Structure of AES

- Chall cung cấp thông tin về AES cho ta, cũng như cách chuyển 1 đoạn plantext thành từng phần mỗi phần 16 bytes, rồi xếp mỗi phần đó thành ma trận 4x4.
```Python
def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
return bytes(sum(matrix, []))
matrix = [
[99, 114, 121, 112],
[116, 111, 123, 105],
[110, 109, 97, 116],
[114, 105, 120, 125],
]
print(matrix2bytes(matrix))
```
- Flag: `crypto{inmatrix}`
## Round Keys

- Chall cung cấp thông tin về AddRoundKey .
- Với chall này ta chỉ cần xor từng phần tử trong matrix với phần từ tương ứng trong matrix state.
```Python
from pwn import *
state = [
[206, 243, 61, 34],
[171, 11, 93, 31],
[16, 200, 91, 108],
[150, 3, 194, 51],
]
round_key = [
[173, 129, 68, 82],
[223, 100, 38, 109],
[32, 189, 53, 8],
[253, 48, 187, 78],
]
def add_round_key(s, k):
x= str()
for i in range(4):
for j in range(4):
s[i][j]= (xor(state[i][j], round_key[i][j]))
a= (s[i][j]).decode()
x+= str(a)
return x
print(add_round_key(state, round_key))
```
- Flag: `crypto{r0undk3y}`
## Confusion through Substitution

- Chall cho ta hiểu về SubBytes với S-Box.
```Python
s_box = (
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,
)
inv_s_box = (
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,
)
state = [
[251, 64, 182, 81],
[146, 168, 33, 80],
[199, 159, 195, 24],
[64, 80, 182, 255],
]
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
return bytes(sum(matrix, []))
def sub_bytes(s, sbox):
for i in range(4):
for j in range(4):
s[i][j] = sbox[s[i][j]]
return s
print(matrix2bytes(sub_bytes(state, inv_s_box)))
```
- Flag: `crypto{l1n34rly}`
## Diffusion through Permutation

- Chall cung cấp thông tin về ShiftRows và MixColumn.
- Tuy nhiên hàm ShiftRows chall này lại dịch chuyển theo hàng ngang thay vì dọc như ta từng biết.
```Python
def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
def inv_shift_rows(s):
s[1][1], s[2][1], s[3][1], s[0][1] = s[0][1], s[1][1], s[2][1], s[3][1]
s[2][2], s[3][2], s[0][2], s[1][2] = s[0][2], s[1][2], s[2][2], s[3][2]
s[3][3], s[0][3], s[1][3], s[2][3] = s[0][3], s[1][3], s[2][3], s[3][3]
# learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
def mix_single_column(a):
# see Sec 4.1.2 in The Design of Rijndael
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for i in range(4):
mix_single_column(s[i])
def inv_mix_columns(s):
# see Sec 4.1.3 in The Design of Rijndael
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v
mix_columns(s)
state = [
[108, 106, 71, 86],
[96, 62, 38, 72],
[42, 184, 92, 209],
[94, 79, 8, 54],
]
inv_mix_columns(state)
inv_shift_rows(state)
print(bytes(sum(state, [])))
```
- Flag: `crypto{d1ffUs3R}`
## Bringing It All Together

- Chall này là tổng hợp của các chall trên.
```Python
from pwn import*
N_ROUNDS = 10
key = b'\xc3,\\\xa6\xb5\x80^\x0c\xdb\x8d\xa5z*\xb6\xfe\\'
ciphertext = b'\xd1O\x14j\xa4+O\xb6\xa1\xc4\x08B)\x8f\x12\xdd'
s_box = (
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,
)
inv_s_box = (
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 shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
def inv_shift_rows(s):
s[1][1], s[2][1], s[3][1], s[0][1] = s[0][1], s[1][1], s[2][1], s[3][1]
s[2][2], s[3][2], s[0][2], s[1][2] = s[0][2], s[1][2], s[2][2], s[3][2]
s[3][3], s[0][3], s[1][3], s[2][3] = s[0][3], s[1][3], s[2][3], s[3][3]
def matrix2bytes(matrix):
s=""
for i in matrix:
for j in i:
s=s+chr(j)
return s
def bytes2matrix(text):
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
def expand_key(master_key):
r_con = (
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)
key_columns = bytes2matrix(master_key)
iteration_size = len(master_key) // 4
i = 1
while len(key_columns) < (N_ROUNDS + 1) * 4:
word = list(key_columns[-1])
if len(key_columns) % iteration_size == 0:
word.append(word.pop(0))
word = [s_box[b] for b in word]
word[0] ^= r_con[i]
i += 1
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
word = [s_box[b] for b in word]
word = bytes(i^j for i, j in zip(word, key_columns[-iteration_size]))
key_columns.append(word)
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]
def xtime(a): return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
def mix_single_column(a):
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for i in range(4):
mix_single_column(s[i])
def inv_mix_columns(s):
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v
mix_columns(s)
def sub_bytes(s, sbox):
for i in range(4):
for j in range(4):
s[i][j] = sbox[s[i][j]]
return s
def add_round_key(s, k):
return xor(s, k)
def decrypt(key, ciphertext):
round_keys = expand_key(key)
state = bytes2matrix(ciphertext)
state = bytes2matrix(add_round_key(state, round_keys[10]))
for i in range(N_ROUNDS - 1, 0, -1):
inv_shift_rows(state)
state = bytes2matrix(sub_bytes(state, inv_s_box))
state = bytes2matrix(add_round_key(state, round_keys[i]))
inv_mix_columns(state)
inv_shift_rows(state)
state = bytes2matrix(sub_bytes(state, inv_s_box))
state = bytes2matrix(add_round_key(state, round_keys[0]))
return matrix2bytes(state)
print(decrypt(key, ciphertext))
```
- Flag: `crypto{MYAES128}`
# SYMMETRIC STARTER
## Modes of Operation Starter

- Chall có liên quan đến AES mode ECB.
- Đầu tiên ta sẽ lấy output của encrypt chính là ciphertext, sau đó gửi ciphertext đi để decrypt là ta thu được flag.
```Python
import requests
BASE_URL = "http://aes.cryptohack.org/block_cipher_starter"
r = requests.get(f"{BASE_URL}/encrypt_flag")
data = r.json()
ciphertext = data["ciphertext"]
r = requests.get(f"{BASE_URL}/decrypt/{ciphertext}")
data = r.json()
plaintext = data["plaintext"]
print(bytearray.fromhex(plaintext).decode())
```
- Flag: `crypto{bl0ck_c1ph3r5_4r3_f457_!}`
## Passwords as Keys

- Chall này là AES mode ECB với key là md5 của 1 key có trong [link sau](https://gist.githubusercontent.com/wchargin/8927565/raw/d9783627c731268fb2935a731a618aa8e95cf465/words).
- Ta lưu nội dung trong link về và tiến hành bruteforce.
```Python
from Crypto.Cipher import AES
import hashlib
import requests
url = 'http://aes.cryptohack.org/passwords_as_keys/'
r = requests.get(f'{url}encrypt_flag/')
first_request = r.json()
ciphertext = first_request['ciphertext']
ciphertext = bytes.fromhex(ciphertext)
with open("words.txt") as f:
words = [w.strip() for w in f.readlines()]
for w in words:
key = hashlib.md5(w.encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
decrypted = cipher.decrypt(ciphertext)
if b'crypto' in decrypted:
print("plaintext", decrypted)
exit
```
- Flag: `crypto{k3y5__r__n07__p455w0rdz?}`
# Block Cipher
## ECB CBC WTF

- Chall cung cấp thông tin về CBC, ECB. Source chall cho ta biết flag được mã hóa bằng CBC như lại được giải mã bằng ECB.
- Ta có sơ đồ encrypt CBC:

- Sơ đồ decrypt ECB:

- Từ source ta biết output của encrypt sẽ là IV(16Bytes)+ encrypt.
- Từ đó ta có được IV. Ta thấy CBC với ECB khác nhau ở bước xor.
```Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import*
from pwn import xor
import requests
url = 'https://aes.cryptohack.org/ecbcbcwtf/'
def encrypt_flag():
r = requests.get(f"{url}/encrypt_flag")
js = r.json()
return (js["ciphertext"])
def decrypt(ciphertext):
r = requests.get(f"{url}/decrypt/{ciphertext}")
js = r.json()
return (js["plaintext"])
cipher = encrypt_flag()
plaintext = decrypt(cipher)
iv1 = bytes.fromhex(cipher[:32])
ciphertext = cipher[32:]
iv2 = bytes.fromhex(ciphertext[:32])
plaintext1 = bytes.fromhex(plaintext[32:64])
plaintext2 = bytes.fromhex(plaintext[64:])
print(xor(plaintext1, iv1) + xor(plaintext2, iv2))
```
- Flag: `crypto{3cb_5uck5_4v01d_17_!!!!!}`
## ECB Oracle

- ECB có điểm yếu là với những block plaintext giống nhau thì sẽ cho ra những ciphertext giống nhau.
- Ta sẽ sẽ làm như sau:
- Gửi plaintext 32 bytes với: 16 bytes đầu là các số 0+ 'crypto{' , 16 bytes sau: byte đầu chạy từ 33-> 128 các bytes còn lại là 0.
- Sau đó ta so sánh 2 phần đầu cuối của cipher thu được, nếu nó giống nhau thì byte ta bruteforce là byte thuộc flag.
```Python
import requests
from Crypto.Cipher import AES
flag = b'crypto{'
def encrypt(plaintext):
url = 'https://aes.cryptohack.org/ecb_oracle/encrypt/' + plaintext.hex()
r = requests.get(url).json()
return bytes.fromhex(r['ciphertext'])
b = ''.join([chr(i) for i in range(32, 127)])
while True:
plaintext = b'1' * (31 - len(flag))
target = encrypt(plaintext)[16:32]
plaintext += flag
found = False
for j in b:
plaintext_temp = plaintext[:31] + j.encode()
bullet = encrypt(plaintext_temp)[16:32]
if bullet == target:
flag += j.encode()
print(flag)
found = True
break
if not found:
break
```
- Flag: `crypto{p3n6u1n5_h473_3cb}`
## Flipping Cookie

- Chall liên quan đến mode CBC. Từ source ta biết được sever sẽ trả cho ta flag khi mà b"admin=True" nằm trong cookie.
- Cookie ban đầu là `admin=False;expiry={expires_at}`. Ta cần chuyển về `admin=True`.
- Đây là CBC decrypt:

- Ta có:
$$ P= IV \oplus D(C) <=> (b'admin=False') =IV \oplus D(C)$$ $$<=> (b'admin=False') \oplus (b'admin=True') \oplus IV= D(C) \oplus (b'admin=True')$$
```Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import*
from pwn import xor
import requests
def get_cookie():
url = "https://aes.cryptohack.org/flipping_cookie/get_cookie/"
r = requests.get(url)
js = r.json()
return bytes.fromhex(js["cookie"])
def check_admin(cookie, iv):
url = "http://aes.cryptohack.org/flipping_cookie/check_admin/"
url += cookie.hex() + "/" + iv.hex() + "/"
r = requests.get(url)
js = r.json()
print(js)
cookie = get_cookie()
adminfalse= (b"admin=False;expiry=")[:16]
admintrue= pad(b"admin=True;", 16)
IV= cookie[:16]
IV= xor(xor(adminfalse, admintrue), IV)
check_admin(cookie[16:32], IV)
```
- Flag: `crypto{4u7h3n71c4710n_15_3553n714l}`
## Lazy CBC

- Chall này là mode CBC, từ source ta biết được sever sẽ đưa flag cho ta khi mà ta gửi key đúng bằng KEY của chall.
- `cipher = AES.new(KEY, AES.MODE_CBC, KEY)` ta thấy cipher mà hóa với IV= KEY. Dựa vào điều này ta sẽ tìm KEY để gửi.
- Đây là CBC decrypt:

- Theo sơ đồ ta có:
$$P_{i+1}= C_i \oplus D(C_{i+1}); C_0= IV$$$$=> P_1 \oplus P_2= C_0 \oplus D(C_1) \oplus C_1 \oplus D(C_2)$$
- Khi này nếu ta gửi tới sever 1 chuỗi với 32 bytes '0' thì ta có được:
$$P_0 \oplus P_1= D(0) \oplus C_0 \oplus D(0) \oplus D(0)= C_0= IV= KEY$$
```Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import *
from Crypto.Util.number import *
from pwn import xor
import requests
def encrypt(plaintext):
url = "https://aes.cryptohack.org/lazy_cbc/encrypt/"
url += plaintext.hex()
r = requests.get(url)
js = r.json()
return (js["ciphertext"])
def get_flag(key):
url = "https://aes.cryptohack.org/lazy_cbc/get_flag/"
url += key.hex() + "/"
r = requests.get(url)
js = r.json()
print(bytes.fromhex(js["plaintext"]))
def receice(ciphertext):
url = "https://aes.cryptohack.org/lazy_cbc/receive/"
url += ciphertext.hex() + "/"
r = requests.get(url)
js = r.json()
return bytes.fromhex(js["error"][len("Invalid plaintext: "):])
ciphertext= long_to_bytes(0)* 32
plaintext = receice(ciphertext)
get_flag(xor(plaintext[:16], plaintext[16:32]))
```
- Flag: `crypto{50m3_p30pl3_d0n7_7h1nk_IV_15_1mp0r74n7_?}`
## Triple DES

- Chall này là Triple DES, với chall này ta biết được điểm yếu của DES chính là các khóa yếu. Nên ta tiến hành brute force với các khóa yếu.
- Source challenge:
```Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import*
from pwn import xor
import requests
def encrypt_flag(key):
url = "https://aes.cryptohack.org/triple_des/encrypt_flag/"
url += key
r = requests.get(url)
js = r.json()
return (js["ciphertext"])
def encrypt(key, plaintext):
url = "https://aes.cryptohack.org/triple_des/encrypt/"
url += key + "/" + plaintext + "/"
r = requests.get(url)
js = r.json()
print(bytes.fromhex(js["ciphertext"]))
a= ['01010101', '01010101','FEFEFEFE', 'FEFEFEFE','E0E0E0E0', 'F1F1F1F1','1F1F1F1F', '0E0E0E0E']
for i in a:
for j in a:
for k in a:
for f in a:
try:
key = i+ j+ k+ f
ciphertext = encrypt_flag(key)
flag = encrypt(key, ciphertext)
except:
continue
```
- Flag: `crypto{n0t_4ll_k3ys_4r3_g00d_k3ys}`
# AUTHENTICATED ENCRYPTION
## Paper Plane


```Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os
KEY = ?
FLAG = ?
class AesIge:
def __init__(self, key):
self.cipher = AES.new(key, AES.MODE_ECB)
def encrypt(self, data, m0=os.urandom(16), c0=os.urandom(16)):
data = pad(data, 16, 'pkcs7')
last_block_plaintext = m0
last_block_ciphertext = c0
result = b''
for i in range(0, len(data), 16):
block = data[i: i + 16]
x = AesIge._xor_blocks(block, last_block_ciphertext)
x = self.cipher.encrypt(x)
x = AesIge._xor_blocks(x, last_block_plaintext)
result += x
last_block_plaintext = block
last_block_ciphertext = x
return result, m0, c0
def decrypt(self, data, m0, c0):
last_block_plaintext = m0
last_block_ciphertext = c0
result = b''
for i in range(0, len(data), 16):
block = data[i: i + 16]
x = AesIge._xor_blocks(block, last_block_plaintext)
x = self.cipher.decrypt(x)
x = AesIge._xor_blocks(x, last_block_ciphertext)
result += x
last_block_ciphertext = block
last_block_plaintext = x
if AesIge._is_pkcs7_padded(result):
return unpad(result, 16, 'pkcs7')
else:
return None
@staticmethod
def _is_pkcs7_padded(message):
padding = message[-message[-1]:]
return all(padding[i] == len(padding) for i in range(0, len(padding)))
@staticmethod
def _xor_blocks(a, b):
return bytes([x ^ y for x, y in zip(a, b)])
@chal.route('/paper_plane/encrypt_flag/')
def encrypt_flag():
ciphertext, m0, c0 = AesIge(KEY).encrypt(FLAG.encode())
return {"ciphertext": ciphertext.hex(), "m0": m0.hex(), "c0": c0.hex()}
@chal.route('/paper_plane/send_msg/<ciphertext>/<m0>/<c0>/')
def send_msg(ciphertext, m0, c0):
ciphertext = bytes.fromhex(ciphertext)
m0 = bytes.fromhex(m0)
c0 = bytes.fromhex(c0)
if len(ciphertext) % 16 != 0:
return {"error": "Data length must be a multiple of the blocksize!"}
if len(c0) != 16 or len(m0) != 16:
return {"error": "m0 and c0 must be 16 bytes long!"}
plaintext = AesIge(KEY).decrypt(ciphertext, m0, c0)
if plaintext is not None:
return {"msg": "Message received"}
else:
return {"error": "Can't decrypt the message."}
```
- Trước hết thì ta sẽ tìm hiểu về `Padding Oracle Attack`. Hãy xem qua video [sau](https://www.youtube.com/watch?v=uDHo-UAM6_4).
- Padding Oracle là một kỹ thuật lợi dụng việc xác thực padding của một thông điệp được mã hóa để giải mã nội dung thông điệp đó.
- Ta có hiểu như sau:
- Lấy ciphertext được mã hóa.
- Sửa đổi một byte trong ciphertext.
- Gửi ciphertext được chỉnh sửa đến sever.
- Sever sẽ phản hồi thông tin về việc padding có hợp lệ hay không.
- Phân tích phản hồi để suy ra vị trí byte bị lỗi trong padding.
- Sửa đổi ciphertext một lần nữa để sửa lỗi padding.
- Lặp lại quá trình này cho đến khi giải mã được toàn bộ plaintext.
- Source solve:
```Python
import requests
from pwn import xor
from Crypto.Util.number import *
def encrypt_flag():
url = 'https://aes.cryptohack.org/paper_plane/encrypt_flag/'
r = requests.get(url).json()
return bytes.fromhex(r['ciphertext']), bytes.fromhex(r["m0"]), bytes.fromhex(r["c0"])
def send_msg(ciphertext, m0, c0):
url = 'https://aes.cryptohack.org/paper_plane/send_msg/'
url += ciphertext.hex() + "/" + m0.hex() + "/" + c0.hex()
r = requests.get(url).json()
return 'error' not in r
def decrypt_block(ctt, m0, c0):
plaintext = b""
new_xor = b""
for i in range(1, 17):
tmp = c0[:16-i]
for j in range(255, -1, -1):
if len(plaintext) > 0:
pad = long_to_bytes(i)*(i-1)
send = tmp + long_to_bytes(j) + xor(pad, new_xor)
else:
send = tmp + long_to_bytes(j)
if send_msg(ctt, m0, send):
new_xor = xor(long_to_bytes(i),(j)) +new_xor
plaintext = xor(xor(long_to_bytes(i),(j)), (c0[16-i:17-i])) + plaintext
print(plaintext)
break
return plaintext
ciphertext, m0, c0 = encrypt_flag()
print(c0.hex())
ciphertext1 = ciphertext[:16]
ciphertext2 = ciphertext[16:]
pt1 = decrypt_block(ciphertext1, m0, c0)
print("block1 done")
print(f"{pt1 = }")
pt2 = decrypt_block(ciphertext2, pt1, ciphertext1)
print("flag: " , pt1 + pt2 )
#Source: ldv
```
- Flag: `crypto{h3ll0_t3l3gr4m}`
## Forbidden Fruit

- Trước hết ta sẽ tìm hiểu về Galois Counter Mode (GCM).
- Tham khảo ở [đây](https://eprint.iacr.org/2016/475.pdf) và [đây](https://www.youtube.com/watch?v=V2TlG3JbGp0&t=40s) trước nha.
- Xét $T= g(X)$, $g(X)$ là hàm GHASH.
- Ta có:
$$g(X)= A_1X^{m+n+1}+ ...+ A_mX^{n+2}+ C_1X^{n+1}+...+ C_nX^2+ LX+ S$$
- Với $A_i,\ C_i$ là các data blocks và cipher blocks, L= len(message), S là nonce value. Các block đều là 128bits trong trường hữu hạn $GF(2^{128})$
$$f_1(X)= A_{1,\ 1}X^5+ C_{1,\ 1}X^4+ C_{1,\ 2}X^3+ C_{1,\ 3}X^2+ LX+ S$$ $$f_2(X)= A_{2,\ 1}X^5+ C_{2,\ 1}X^4+ C_{2,\ 2}X^3+ C_{2,\ 3}X^2+ LX+ S$$
- Mặt khác:
$$f'_1(X)=A_{1,1}X^5+C_{1,1}X^4+C_{1,2}X^3+C_{1,3}X^2+LX+S+T_1$$ $$f'_2(X)=A_{2,1}X^5+C_{2,1}X^4+C_{2,2}X^3+C_{2,3}X^2+LX+S+T_2$$
$$=> g(X)= f'_1(X)+f'_2(X)$$ $$= (A_{1,1}+ A_{2,1})X^5+ (C_{1,1}+ C_{2,1})X^4+...+ LX +T1 +T2$$
- Áp dụng vào ta có:
$$TAG = (((((((((((H * AD0) + AD1) * H) + c0) * H) + c1) * H) + c2) * H) + L) * H) + S$$
$$TAG = AD0*H^6 + AD1*H^5 + c0*H^4 + c1*H^3 + c2*H^2 + L*H + S$$
- Nhập 2 block ta thu được:
$$TAG = (((((H * c_0) + c_1) * H) + L) * H + S)$$ $$<=> TAG = c_0*H^3 + c_1*H^2 + L*H + S$$
- Ta có:
$$TAG_1 = A*H^3 + c_1*H^2 + L*H + S$$ $$TAG_2 = A*H^3 + c_2*H^2 + L*H + S$$ $$ TAG_1 - TAG_2 = (c_1-c_2)*H^2$$ $$TAG_1 - c_1*H^2 = TAG_2 - c_2*H^2$$
- X là TAG giả mạo. Ta có:
$$X = TAG_1 - c_1*H^2 == TAG_2 - c_2*H^2$$
- Mặt khác:
$$ forge(tag) = c*H^2 + tag_1 - c_1*H^2 $$ $$ forge(tag) = c*H^2 + X $$
- Giờ chỉ cần gửi nonce, ciphertext, forge(tag), AD("Cryptohack") vào server và get flag.
```Python
from Crypto.Util.number import *
from Crypto.Util.number import *
from sage.all import *
import struct
import requests
import json
F.<x> = GF(2^128, x^128+x^7+x^2+x+1)
def polynomial_to_bytes(X):
return int(f"{X.integer_representation():0128b}"[::-1], 2)
def bytes_to_polynomial(X):
return F.fetch_int(int(f"{X:0128b}"[::-1], 2))
def ENCRYPT(plaintext):
url = 'http://aes.cryptohack.org/forbidden_fruit/encrypt/'
url += plaintext.hex()
r = requests.get(url).json()
if "error" in r:
return None
return bytes.fromhex(r["nonce"]), bytes.fromhex(r["ciphertext"]), bytes.fromhex(r["tag"]), bytes.fromhex(r["associated_data"])
def DECRYPT(nonce, ciphertext, tag, associated_data):
url = 'http://aes.cryptohack.org/forbidden_fruit/decrypt/'
url += nonce.hex() + '/' + ciphertext.hex() + '/' + tag + '/' + associated_data.hex()
r = requests.get(url).json()
if "plaintext" in r:
return bytes.fromhex(r["plaintext"])
return None
payload = b"\x00"*16
nonce, c1, tag1, AD = ENCRYPT(payload)
payload = b"\x01"*16
_nonce, c2, tag2, _AD = ENCRYPT(payload)
c1 = bytes_to_polynomial(int.from_bytes(c1, 'big'))
c2 = bytes_to_polynomial(int.from_bytes(c2, 'big'))
tag1 = bytes_to_polynomial(int.from_bytes(tag1, 'big'))
tag2 = bytes_to_polynomial(int.from_bytes(tag2, 'big'))
print(f"{c1 = }\n")
print(f"{c2 = }\n")
print(f"{tag1 = }\n")
print(f"{tag2 = }\n")
H2= (tag1-tag2)/(c1-c2)
X = tag1 - c1*H2
assert X == tag2-c2*H2
ciphertext = requests.get("http://aes.cryptohack.org/forbidden_fruit/encrypt/" + (b"give me the flag").hex())
ciphertext = json.loads(ciphertext.text)
ciphertext = bytes.fromhex(ciphertext["ciphertext"])
tag = bytes_to_polynomial(int.from_bytes(ciphertext, 'big'))*H2 + X
tag = polynomial_to_bytes(tag)
print(f"{tag = }\n")
print(f"{nonce.hex() = }\n")
print(f"{AD.hex() = }\n")
print(DECRYPT(nonce, ciphertext, hex(tag)[2:], AD))
#Source: ldv
```
# Chall
## Challenge 1:
Source chall:
```Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import md5
from os import urandom
FLAG = b"KCSC{???????????????????????????}"
assert len(FLAG) % 16 == 1 # hint
key1 = md5(urandom(3)).digest()
key2 = md5(urandom(3)).digest()
cipher1 = AES.new(key1, AES.MODE_ECB)
cipher2 = AES.new(key2,AES.MODE_ECB)
enc = cipher1.encrypt(pad(FLAG,16))
enc = cipher2.encrypt(enc)
print(key1, key2)
print(enc.hex())
#ouput= 21477fac54cb5a246cb1434a1e39d7b34b91e5c135cd555d678f5c01b2357adc0c6205c3a4e3a8e6fb37c927de0eec95
```
- Chall này ta biết được block cuối là b'}' pad thêm 15 bytes.
- Từ thông tin đó ta có thể brute force key1, key2.
- Ta có output khi decryt block cuối với key2 của output chall sẽ bằng với output khi encrypt block đã biết với key1. Ta sẽ brute force key1, key2 thỏa điều kiện trên.
```Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import md5
from os import urandom
c = '21477fac54cb5a246cb1434a1e39d7b34b91e5c135cd555d678f5c01b2357adc0c6205c3a4e3a8e6fb37c927de0eec95'
enc = bytes.fromhex(c)
c1 = enc[:16]
c2 = enc[16:32]
c3 = enc[32:]
p3 = b"}"
x = pad(p3, 16)
key_list = []
list_enc = []
for i in range(256):
for j in range(256):
for k in range(256):
key1 = md5(bytes([i, j, k])).digest()
key_list.append(key1)
cipher3 = AES.new(key1, AES.MODE_ECB)
enc3 = cipher3.encrypt(x).hex()
list_enc.append(enc3)
for i in range(256):
for j in range(256):
for k in range(256):
key2 = md5(bytes([i, j, k])).digest()
plan3 = AES.new(key2, AES.MODE_ECB)
dec3 = plan3.decrypt(c3).hex()
if dec3 in list_enc:
index = list_enc.index(dec3)
print(f"Key2: {key2}")
print(f"Key1: {key_list[index]}")
```
-Flag: `KCSC{MeEt_In_tHe_mIdDLe_AttaCk__}`
## Challenge 2:
**Source Chall**
```Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from random import choice
from os import urandom
import socket
import threading
FLAG = b'KCSC{Bingo!_PKCS#7_padding}'
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
for i in range(100):
x = choice(['ECB','CBC'])
if x == 'ECB':
cipher = AES.new(urandom(16), AES.MODE_ECB)
else:
cipher = AES.new(urandom(16), AES.MODE_CBC, urandom(16))
try:
msg = bytes.fromhex(client.recv(size).strip().decode())
assert len(msg) <= 16
client.send(cipher.encrypt(pad(msg,16)).hex().encode() + b'\n')
ans = client.recv(size).strip().decode()
assert ans == x
client.send(b'Correct!\n')
except:
client.send(b"Exiting...\n")
client.close()
return False
client.send(FLAG)
client.close()
return False
if __name__ == "__main__":
ThreadedServer('',2000).listen()
```
- Chall yêu cầu ta gửi 1 chuỗi x, với len(x)<= 16. Sau đó cho ta output của encrypt và yêu cầu ta đoán xem đó là 'ECB' hay 'CBC'. Đoán đúng 100 lần thì sẽ cho ta flag.
- Như ta đã biết thì khi encrypt bằng 'ECB' với cùng 1 đoạn plantext thì output sẽ cho ra giống nhau. Dựa vào điều này ta sẽ giải quyết chall này.
```Python
from pwn import *
r= remote("localhost", 1337)
for i in range(100):
r.sendline(b'10'*16)
a= r.recvline().decode()
print(a)
a1= a[:32]
a2= a[32:]
if a1== a2:
r.sendline(b'ECB')
else:
r.sendline(b'CBC')
r.recvutil()
r.recvuntil()
```
## Challenge 3:
**Source chall**
```Python
from Crypto.Cipher import AES
from os import urandom
from base64 import b64encode
import string
import socket
import threading
chars = string.ascii_lowercase + string.ascii_uppercase + '!_{}'
FLAG = b'KCSC{Chosen_Plaintext_Attack___ECB_ECB_ECB___you_made_it!}'
assert all(i in chars for i in FLAG.decode())
def pad(msg, block_size):
if len(msg) % block_size == 0:
return msg
return msg + bytes(block_size - len(msg) % block_size)
def chall(usrname):
key = urandom(16)
cipher = AES.new(key, AES.MODE_ECB)
msg = b'Hello ' + usrname + b', here is your flag: ' + FLAG + b'. Have a good day, we hope to see you soon.'
enc = cipher.encrypt(pad(msg,16))
return b64encode(enc)
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
while True:
try:
usrname = client.recv(size).strip()
client.send(chall(usrname) + b'\n')
except:
client.close()
return False
if __name__ == "__main__":
ThreadedServer('',2003).listen()
```
- Chall cho ta gửi tới sever một chuỗi msg bất kỳ, sau đó encrypt msg với cấu trúc sau `msg = b'Hello ' + usrname + b', here is your flag: ' + FLAG + b'. Have a good day, we hope to see you soon.'` bằng ECB.
- Ta sẽ vẫn dựa vào việc ECB trả lại cùng 1 giá trị với input giống nhau để giải quyết chall. Ta sẽ check từng byte của flag nếu giống nhau thì sẽ nhận byte đó.
```Python
from pwn import *
from base64 import b64decode
r = remote('localhost', 1337)
flag = 'KCSC{'
msg = 'a'*32+ 'ahere is your flag: KCSC{'
for x in range(80):
b = msg
for i in range(33, 129):
a = b + chr(i) + 'a' * (5 + 64-x)
r.sendline(a.encode())
cipher = r.recvuntil(b'\n').decode()
m= cipher[2:len(cipher)-2]
cipher= m+ '=='
cipher = b64decode(cipher)
c1 = cipher[48:64]
c2 = cipher[144:160]
if c1 == c2:
flag += chr(i)
msg= msg[1:]+ chr(i)
break
if chr(i)== '.':
break
print(flag)
```

## Challenge 4
**Source chall:**
```Python
import socket
import threading
from Crypto.Cipher import AES
from os import urandom
import string
chars = string.ascii_lowercase + string.ascii_uppercase + string.digits + '_{}'
FLAG = b'KCSC{CBC_p4dd1ng_0racle_}'
assert all(i in chars for i in FLAG.decode())
def pad(msg, block_size):
pad_len = 16 - len(msg) % block_size
return msg + bytes([pad_len])*pad_len
def encrypt(key):
iv = urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
return (iv + cipher.encrypt(pad(FLAG,16)) ).hex().encode()
def decrypt(enc,key):
enc = bytes.fromhex(enc)
iv = enc[:16]
ciphertext = enc[16:]
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(ciphertext)
pad_len = decrypted[-1]
if all(i == pad_len for i in decrypted[-pad_len:]):
return b'Decrypted successfully.'
else:
return b'Incorrect padding.'
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)
while True:
try:
choice = client.recv(size).strip()
if choice == b'encrypt':
client.send(encrypt(key) + b'\n')
elif choice == b'decrypt':
client.send(b'Ciphertext: ')
c = client.recv(size).strip().decode()
client.send(decrypt(c,key) + b'\n')
except:
client.close()
return False
if __name__ == "__main__":
ThreadedServer('',2004).listen()
```
**Solve:**
- Ta sẽ build lại để chạy local nha.
- Với chall này thì ta sẽ thấy flag của ta đã được pad sau khi decrypt. Ta sẽ dựa vào điểm này để giải mã.
- Tương tự các bài trước bài này ta sẽ bruteforce từng byte, nếu output khi decrypt là `Decrypted successfully.` thì ta sẽ nhận byte đó.
```python
from pwn import *
r= remote("localhost", 1337)
r.sendline(b'encrypt')
output= r.recvuntil(b'\n').decode()
output= bytes.fromhex(output)
iv= output[:16]
cipher= output[16:]
flag = str()
for k in range(2):
cipher0 = cipher[16* k: 16* k+ 16]
plantext = b''
fakeplaintext = [0]*16
fakecipher = bytes(fakeplaintext)
x= b''
for i in range(1, 17):
for j in range(256):
fakecipher= fakecipher[:len(fakeplaintext)- i] + bytes([j]) + plantext
x= iv + fakecipher + cipher0
r.sendline(b'decrypt')
r.recvuntil(b'Ciphertext: ')
r.sendline(x.hex().encode())
a= r.recvuntil(b'\n').decode()
a= a[2:len(a)- 2]
if a== "Decrypted successfully.":
fakeplaintext[len(fakeplaintext)- i] = j ^ i
plantext= b''
plantext= xor(bytes([i+ 1])*i , bytes(fakeplaintext[len(fakeplaintext)- i:]))
break
flag += str(xor(bytes(fakeplaintext),output[16* k: 16* k+ 16]))
print(flag)
r.interactive()
```