---
# System prepended metadata

title: Symmetric Cryptography

---

---
# Symmetric Cryptography
## Keyed Permutations
### Đề bài
![image](https://hackmd.io/_uploads/ryDi1Ao0eg.png)
> bài này xàm quá, không làm
## Resisting Bruteforce
### Đề bài
![image](https://hackmd.io/_uploads/BJFMlAjRxx.png)
> Bài này cũng xàm, không làm
## Structure of AES
### Đề bài
![image](https://hackmd.io/_uploads/r1SDlAjAgl.png)
:::spoiler matrix.py
```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.  """
    ????

matrix = [
    [99, 114, 121, 112],
    [116, 111, 123, 105],
    [110, 109, 97, 116],
    [114, 105, 120, 125],
]

print(matrix2bytes(matrix))
```
:::
### Ý tưởng
#### Về mặt bản chất: 
**Cấu trúc của AES bao gồm:**
1. **KeyExpansion** (mở rộng khoá)
Như ta thấy thì AES không sử dụng một khoá cho toàn bộ quá trình **AddRoundKey** (thêm khoá vòng) mà ta sẽ sử dụng một thuật toán để biến một khoá có 128 bit thành 11 khoá có 128 bit (Lý do ta tạo 11 khoá vì ta thực hiện **AddRoundKey** 11 lần)

2. **Initial key addition** (cộng khóa ban đầu)
Bước này ta sẽ xor các bytes của khoá vòng đầu tiên với **state**.(state là một ma trận 4x4 và ta sẽ hiểu hơn khi nhìn vào code bên dưới)

3. **Round** (vòng lặp)
Ta sẽ thực hiện lặp **9 lần + 1 vòng lặp cuối**.

    Các vòng lặp sẽ thực hiện quanh các thao tác chính bao gồm:
    
    **a. SubBytes** (thay thế byte):
    Ta sẽ dựa vào một bảng gọi là **Sbox**, từ đó ta sẽ thay thế từng giá trị gốc thành các giá trị trong bảng. (sẽ được đề cập ở những bài sau)
    
    **b. ShiftRows** (dịch hàng)
    Ta đã biết thì state là một ma trận 4x4, vì thế ta sẽ thực hiện dịch giá trị của từng hàng. (sẽ được đề cập ở những bài sau)
    
    **c. MixColumns** (trộn cột)
    Ta sẽ thực hiện nhân các cột của state với một ma trận 4x4 cố định. (sẽ được đề cập ở những bài sau)
    
    **d. AddRoundKey** (cộng khoá vòng)
    Thực hiện tương tự như bước 2 và chỉ khác ở khoá vòng (ta có tổng cộng 11 khoá từ bước 1)

    **Và tại vòng lặp cuối ta chỉ thực hiện a, b, d.**
    
**Về lại bài toán:** 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Bài toán này chỉ yêu cầu ta thực hiện chuyển từ **state** về lại **plantext** gốc

### Code
:::spoiler solve.py
```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.  """
    plaintext = "".join(chr(item) for rows in matrix for item in rows)
    return plaintext

matrix = [
    [99, 114, 121, 112],
    [116, 111, 123, 105],
    [110, 109, 97, 116],
    [114, 105, 120, 125],
]

print(matrix2bytes(matrix))
```
:::
> crypto{inmatrix}
## Round Keys
### Đề bài
![image](https://hackmd.io/_uploads/Syqln0oRle.png)
:::spoiler add_round_key.py
```python=
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):
    ???

print(add_round_key(state, round_key))
```
:::
### Ý tưởng
Bài này thực hiện bước 2 mà ta đã đề cập ở trên **Initial key addition** bằng cách xor các giá trị của state và round key. Sau đó ta sẽ dùng lại hàm matrix2bytes ở bài trên để ra flag.
### Code
:::spoiler solve.py
```python=
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):
    #add_round_key
    state = [[s[i][j] ^ k[i][j] for j in range(4)] for i in range(4)]
    
    #matrix2bytes
    plaintext = "".join(chr(item) for rows in state for item in rows)
    return plaintext

print(add_round_key(state, round_key))
```
:::
> crypto{r0undk3y}
## Confusion through Substitution
### Đề bài
![image](https://hackmd.io/_uploads/Byf2wzTCgl.png)
:::spoiler sbox.py
```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 sub_bytes(s, sbox=s_box):
    ???

print(sub_bytes(state, sbox=inv_s_box))
```
:::
### Ý tưởng
Bài này chính là thực hiện bước 3a, bằng cách thay thế những giá trị trong state là toạ độ của các giá trị sbox cho sẵn.

**Note:** mục đích của hàm này để gây ra "confusion" (sự nhầm lẫn) để khiến cho plaintext và ciphertext không có bất cứ liên hệ nào với nhau. Tức biến nó thành một hàm phi tuyến tính.
### Code
:::spoiler solve.py
```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 sub_bytes(s, sbox=s_box):
    state = [[sbox[s[i][j]] for j in range(4)] for i in range(4)]

    plaintext = "".join(chr(item) for rows in state for item in rows)
    return plaintext

print(sub_bytes(state, sbox=inv_s_box))
```
:::
>crypto{l1n34rly}
## Diffusion through Permutation
### Đề bài
![image](https://hackmd.io/_uploads/ryzz3G60xg.png)
:::spoiler diffusion.py
```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):
    ???

# 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],
]
```
:::
### Ý tưởng
Bài này chính là bước 3b và 3c mà mình đã đề cập.

Với phần **ShiftRows** thì ta chỉ đơn thuần là dịch vị trí của các giá trị trong cùng một hàng. Cụ thể: hàng 0 sẽ giữ nguyên, hàng 1 sẽ dịch sang trái 1 ô, hàng 2 sẽ dịch 2 ô và hàng 3 sẽ dịch 3 ô.

Còn **MixColumns** thì ta sẽ có một ma trận 4x4 cố định như sau:
$$
\begin{bmatrix}
02 & 03 & 01 & 01 \\
01 & 02 & 03 & 01 \\
01 & 01 & 02 & 03 \\
03 & 01 & 01 & 02
\end{bmatrix}
$$
Và với từng cột trong state ta sẽ thực hiện một phép toán:
![image](https://hackmd.io/_uploads/Hkf-Wm6Cgg.png)
(không đánh máy được nên xài đỡ ảnh)

Và từng phép tính đó là:
$$c'_0 = (02 \cdot c_0) \oplus (03 \cdot c_1) \oplus (01 \cdot c_2) \oplus (01 \cdot c_3)$$

Tuy nhiên phép nhân trong đó có chút khác so với thông thường.
Ví dụ:
$$
01 . b = b
$$
Còn 
$$
02 . b
$$
Bản chất là một phép dịch bit: ta sẽ dịch byte b sang trái 1 bit (b << 1).
NHƯNG, nếu bit cao nhất (MSB) trước khi dịch của b là 1 (tức là b >= 128 hay b & 0x80), ta phải XOR kết quả với 0x1B. (Đây là cách xử lý "tràn" trong $GF(2^8)$).

Và 
$$
03 . b = 02 . b\ \oplus \ 01 . b 
$$
Từ những điều trên, ta có thể giải quyết được phần này.

Quay về bài toán ban đầu, nó chỉ yêu cầu ta code lại phần inv_shift_rows đó là đảo ngược của shift_rows nhằm giúp ta decrypt. Và format trong code sẽ có chút khác hơn so với theo lý thuyết của ta nhưng vẫn ra đúng kết quả. 

Ta chỉ cần chạy inv_mix_columns sau đó inv_mix_rows rồi chuyển matrix2bytes là ra.
### Code
:::spoiler solve.py
```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[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][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[1][3], s[2][3], s[3][3], s[0][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)
plaintext = "".join(chr(item) for rows in state for item in rows)
print(plaintext)
```
:::
> crypto{d1ffUs3R}
## Bringing It All Together
### Đề bài
![image](https://hackmd.io/_uploads/S1qiKXTRle.png)
:::spoiler aes_decrypt.py
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'



def expand_key(master_key):
    """
    Expands and returns a list of key matrices for the given master_key.
    """

    # Round constants https://en.wikipedia.org/wiki/AES_key_schedule#Round_constants
    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,
    )

    # Initialize round keys with raw key material.
    key_columns = bytes2matrix(master_key)
    iteration_size = len(master_key) // 4

    # Each iteration has exactly as many columns as the key material.
    i = 1
    while len(key_columns) < (N_ROUNDS + 1) * 4:
        # Copy previous word.
        word = list(key_columns[-1])

        # Perform schedule_core once every "row".
        if len(key_columns) % iteration_size == 0:
            # Circular shift.
            word.append(word.pop(0))
            # Map to S-BOX.
            word = [s_box[b] for b in word]
            # XOR with first byte of R-CON, since the others bytes of R-CON are 0.
            word[0] ^= r_con[i]
            i += 1
        elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
            # Run word through S-box in the fourth iteration when using a
            # 256-bit key.
            word = [s_box[b] for b in word]

        # XOR with equivalent word from previous iteration.
        word = bytes(i^j for i, j in zip(word, key_columns[-iteration_size]))
        key_columns.append(word)

    # Group key words in 4x4 byte matrices.
    return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]


def decrypt(key, ciphertext):
    round_keys = expand_key(key) # Remember to start from the last round key and work backwards through them when decrypting

    # Convert ciphertext to state matrix

    # Initial add round key step

    for i in range(N_ROUNDS - 1, 0, -1):
        pass # Do round

    # Run final round (skips the InvMixColumns step)

    # Convert state matrix to plaintext

    return plaintext


# print(decrypt(key, ciphertext))
:::
### Ý tưởng
Như cái tên của nó, ta chỉ cần gộp mọi thứ lại và follow theo các hint mà nó đã hướng dẫn trong code là ra.
### Code
:::spoiler sovle.py
```python=
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'

def bytes2matrix(text):
    return [list(text[i:i+4]) for i in range(0, len(text), 4)]

def add_round_key(s, k):
    for i in range(4):
        for j in range(4):
            s[i][j] = s[i][j] ^ k[i][j]

def inv_shift_rows(s):
    s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][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[1][3], s[2][3], s[3][3], s[0][3]

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 inv_sub_bytes(s, sbox=inv_s_box):
    for i in range(4):
        for j in range(4):
            s[i][j] = sbox[s[i][j]]

xtime = lambda a: (((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 matrix2bytes(matrix):
    plaintext = "".join(chr(item) for rows in matrix for item in rows)
    return plaintext

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 decrypt(key, ciphertext):
    round_keys = expand_key(key) 

    state = bytes2matrix(ciphertext)

    add_round_key(state, round_keys[N_ROUNDS])

    for i in range(N_ROUNDS - 1, 0, -1):
        inv_shift_rows(state)
        inv_sub_bytes(state)
        add_round_key(state,round_keys[i])
        inv_mix_columns(state)

    inv_shift_rows(state)
    inv_sub_bytes(state)
    add_round_key(state,round_keys[0])
    
    plaintext = matrix2bytes(state)
    return plaintext

print(decrypt(key, ciphertext))
```
:::
> crypto{MYAES128}
## Modes of Operation Starter
### Đề bài
![image](https://hackmd.io/_uploads/S1F5CX6Cll.png)
[**play**](https://aes.cryptohack.org/block_cipher_starter)
### Giải
:::spoiler source
```python=
from Crypto.Cipher import AES


KEY = ?
FLAG = ?


@chal.route('/block_cipher_starter/decrypt/<ciphertext>/')
def decrypt(ciphertext):
    ciphertext = bytes.fromhex(ciphertext)

    cipher = AES.new(KEY, AES.MODE_ECB)
    try:
        decrypted = cipher.decrypt(ciphertext)
    except ValueError as e:
        return {"error": str(e)}

    return {"plaintext": decrypted.hex()}


@chal.route('/block_cipher_starter/encrypt_flag/')
def encrypt_flag():
    cipher = AES.new(KEY, AES.MODE_ECB)
    encrypted = cipher.encrypt(FLAG.encode())

    return {"ciphertext": encrypted.hex()}
```
:::
Trước tiên, ta sẽ đọc source code, nhìn vào ta có thể thấy rằng bài này đơn giản là ta lấy encrypt flag, sau đó ta sẽ ném nó vào ô decrypt và ta sẽ nhận được một hex flag, ta chỉ cần encode nó là được.
![image](https://hackmd.io/_uploads/Hyqd14pCgg.png)
![image](https://hackmd.io/_uploads/H1sYyV6Cll.png)
![image](https://hackmd.io/_uploads/Bk2cJV6Rll.png)
> crypto{bl0ck_c1ph3r5_4r3_f457_!}
## Passwords as Keys
### Đề bài
![image](https://hackmd.io/_uploads/r1NRkVa0xl.png)
[**play**](https://aes.cryptohack.org/passwords_as_keys/)
### Giải
:::spoiler source
```python=
from Crypto.Cipher import AES
import hashlib
import random


# /usr/share/dict/words from
# https://gist.githubusercontent.com/wchargin/8927565/raw/d9783627c731268fb2935a731a618aa8e95cf465/words
with open("/usr/share/dict/words") as f:
    words = [w.strip() for w in f.readlines()]
keyword = random.choice(words)

KEY = hashlib.md5(keyword.encode()).digest()
FLAG = ?


@chal.route('/passwords_as_keys/decrypt/<ciphertext>/<password_hash>/')
def decrypt(ciphertext, password_hash):
    ciphertext = bytes.fromhex(ciphertext)
    key = bytes.fromhex(password_hash)

    cipher = AES.new(key, AES.MODE_ECB)
    try:
        decrypted = cipher.decrypt(ciphertext)
    except ValueError as e:
        return {"error": str(e)}

    return {"plaintext": decrypted.hex()}


@chal.route('/passwords_as_keys/encrypt_flag/')
def encrypt_flag():
    cipher = AES.new(KEY, AES.MODE_ECB)
    encrypted = cipher.encrypt(FLAG.encode())

    return {"ciphertext": encrypted.hex()}
```
:::

Đọc code thì ta có thể thấy rằng key của bài này nằm trong một file words, và ta chỉ cần bruteforce nó là ra.

:::spoiler solve.py
```python=
from Crypto.Cipher import AES
import hashlib
import requests
from binascii import hexlify

r = requests.get("http://aes.cryptohack.org/passwords_as_keys/encrypt_flag/")
ciphertext = "c92b7734070205bdf6c0087a751466ec13ae15e6f1bcdd3f3a535ec0f4bbae66"

with open("words.txt") as f:
    words = [w.strip() for w in f.readlines()]

def decrypt(ciphertext, key):
    ciphertext = bytes.fromhex(ciphertext)

    cipher = AES.new(key, AES.MODE_ECB)
    try:
        decrypted = cipher.decrypt(ciphertext)
    except ValueError as e:
        return {"error": str(e)}

    return decrypted

for i in words:
    key = hashlib.md5(i.encode()).digest()
    plaintext = decrypt(ciphertext, key)
    if b"crypto{" in plaintext:
        print(plaintext.decode())
        break
```
:::
> crypto{k3y5__r__n07__p455w0rdz?}
## ECB Oracle
### Đề bài
![image](https://hackmd.io/_uploads/r1n8HHa0le.png)
[**play**](https://aes.cryptohack.org/ecb_oracle/)
:::spoiler source
```python=
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad


KEY = ?
FLAG = ?


@chal.route('/ecb_oracle/encrypt/<plaintext>/')
def encrypt(plaintext):
    plaintext = bytes.fromhex(plaintext)

    padded = pad(plaintext + FLAG.encode(), 16)
    cipher = AES.new(KEY, AES.MODE_ECB)
    try:
        encrypted = cipher.encrypt(padded)
    except ValueError as e:
        return {"error": str(e)}

    return {"ciphertext": encrypted.hex()}
```
:::
### Giải
Đọc code thì ta có thể hiểu rằng, khi ta tương tác với server một plaintext bất kỳ, server sẽ cộng flag vào và đệm pad vào sao cho nó là bội của 16.

Và mình nảy ra một ý tưởng tấn công bằng cách, vì mình biết format của nó là crypto{...}. Nên ta khi tương tác với server, mình điền 'a'*15 và 'a'*15+'c'. Và kết quả ra là 16 bytes đầu giống hệt nhau.
![image](https://hackmd.io/_uploads/SJeoHrpRll.png)
![image](https://hackmd.io/_uploads/rkCjBHTAee.png)


Vì thế mấu chốt ở đây là khi ta không biết format đề sẵn, thay vì ta có sẵn 'c' thì ta sẽ bruteforce nó để tìm ra.
:::spoiler solve.py
```python=
import requests
import string
url = "http://aes.cryptohack.org/ecb_oracle/"
k = string.printable
flag = ''
for i in range(31,0,-1):
    s1 = i*'a'
    s1 = s1.encode().hex()
    r1 = requests.get(f"{url}/encrypt/{s1}")
    data1 = r1.json()
    cipher1 = data1["ciphertext"]
    cipher1 = bytes.fromhex(cipher1)

    for j in k:
        s2 = i*'a' + flag + j 
        s2 = s2.encode().hex()
        r2 = requests.get(f"{url}/encrypt/{s2}")
        data2 = r2.json()
        cipher2 = data2["ciphertext"]
        cipher2 = bytes.fromhex(cipher2)
        if (cipher2[0:32] == cipher1[0:32]):
            flag +=j
            print(flag)
            break
```
:::
Mình đoán nó sẽ nằm trong khoảng 32 kí tự nên mình đã bruteforce trong khoảng đó =))))
> crypto{p3n6u1n5_h473_3cb}
## ECB CBC WTF
### Đề bài
![image](https://hackmd.io/_uploads/BkyPPraReg.png)
[**play**](https://aes.cryptohack.org/ecbcbcwtf/)
### Giải
Bài này cho ta 2 cách encrypt và decrypt khác nhau, tuy nhiên mấu chốt vẫn là việc nó cùng key. Và mình sẽ giải thực tế luôn, mình có: `{"ciphertext":"41bf7b2cb4dfd947612745f6904ffc0548b5de2a4a114dc8e2eb1af6f8c9d408d851784304e99172a8bef50514a47365"}`
Sau đó mình tách nó ra thành:
`
iv = 41bf7b2cb4dfd947612745f6904ffc05
`
`
block 1 = 48b5de2a4a114dc8e2eb1af6f8c9d408
`
`
block 2 = d851784304e99172a8bef50514a47365
`
Giờ để tìm plaintext 1 tại block 1, ta chỉ cần decrypt block 1, sau đó xor với iv sẽ ra được hex plaintext, rồi ta chuyển nó về thành utf8 thôi.
![image](https://hackmd.io/_uploads/SyQMFSaRlx.png)
Còn với block 2 thì ta sẽ thực hiện xor với block 1.
![image](https://hackmd.io/_uploads/HkKIFHaAll.png)
> crypto{3cb_5uck5_4v01d_17_!!!!!}
## Flipping Cookie
### Đề bài
### Giải
Bài này thì ta đã biết rằng output `admin=False;expiry={expires_at}` và nhiệm vụ của ta là phải chuyển từ `False` thành `True;`.

Và ta có rằng: 'F' = Decrypt(C[6],key) $\oplus$ iv[6]. Và vì thế mình sẽ tạo ra một iv[6]' = iv[6] $\oplus$ ord(F) $\oplus$ ord(T). Và lúc này khi thế iv[6]' vào thì ta sẽ ra được 'T'. Tương tự với những cái kia.

:::spoiler solve.py
```python=
import requests
from Crypto.Util.number import *
url = "http://aes.cryptohack.org/flipping_cookie/"
r = requests.get(f"{url}/get_cookie")
data = r.json()
cookie = data["cookie"]
iv =bytes.fromhex(cookie[:32])
ciphertext =bytes.fromhex(cookie[32:])
new_iv = iv[:6] +bytes([iv[6]^ord('F')^ord('T')])+bytes([iv[7]^ord('a')^ord('r')])+bytes([iv[8]^ord('l')^ord('u')])+bytes([iv[9]^ord('s')^ord('e')])+bytes([iv[10]^ord('e')^ord(';')])+iv[11:]
new_iv = new_iv.hex()
ciphertext = ciphertext.hex()
url1 =  "http://aes.cryptohack.org/flipping_cookie/check_admin"
a = requests.get(f"{url1}/{ciphertext}/{new_iv}")
flag = a.json()
print(flag)
```
:::
> crypto{4u7h3n71c4710n_15_3553n714l}
## Symmetry
### Đề bài
![image](https://hackmd.io/_uploads/HkaJ7Dp0xx.png)
:::spoiler source
```python=
from Crypto.Cipher import AES


KEY = ?
FLAG = ?


@chal.route('/symmetry/encrypt/<plaintext>/<iv>/')
def encrypt(plaintext, iv):
    plaintext = bytes.fromhex(plaintext)
    iv = bytes.fromhex(iv)
    if len(iv) != 16:
        return {"error": "IV length must be 16"}

    cipher = AES.new(KEY, AES.MODE_OFB, iv)
    encrypted = cipher.encrypt(plaintext)
    ciphertext = encrypted.hex()

    return {"ciphertext": ciphertext}


@chal.route('/symmetry/encrypt_flag/')
def encrypt_flag():
    iv = os.urandom(16)

    cipher = AES.new(KEY, AES.MODE_OFB, iv)
    encrypted = cipher.encrypt(FLAG.encode())
    ciphertext = iv.hex() + encrypted.hex()

    return {"ciphertext": ciphertext}
```
:::
### Giải
**Note:** chưa hiểu bản chất
Trước tiên bài này  thì mình sẽ lấy encrypt flag, rồi tách nó thành iv và encrypted text, sau đó mình ném iv lên encrypt ở trên với plaintext là một chuỗi toàn 0 (?) và thứ mình nhận được lúc này là key. Từ key ta xor với encrypted text ra flag.
> crypto{0fb_15_5ymm37r1c4l_!!!11!}
## Bean Counter
### Đề bài
![image](https://hackmd.io/_uploads/ry4EdDpCxl.png)
[**play**](https://aes.cryptohack.org/bean_counter/)
### Giải
**Note:** chưa hiểu bản chất
Nhìn vào code, ta có thể thấy rằng iv không bao giờ thay đổi vì step_up luôn mặc định là False, vì thế keystream cũng không bao giờ thay đổi. Và xuyên suốt bài này ta sẽ xor với cùng một keystream. Và để tìm ra được keystream ta sẽ xor 16 bytes đầu tiên của encrypted và header của png (?)
:::spoiler solve_by_gemini.py
```python=
import binascii

def xor_bytes(a, b):
    return bytes([x ^ y for x, y in zip(a, b)])

PNG_HEADER = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52'

encrypted_hex = b"7eee3c7e4d9291c751e33f20e6d1e7f9f7be731540988bba59e53f2daf20e0d602be722823e2df9525b15e5a8fe9d1c491d71e5c60ecf2bd34c35a55c6ffa3ab8f64b7a02b0aaffadf67c0ea866fabd7769efd7a52385211316c80320d2bf9c1be9da171232e1650de69b5c9ace91b90af318dd6ff7678f2ad888a8808102d958cf18e52f35539356131e8925a27457f296985667907c434b608b0c748d226e0624988cca37014269f16fdd04aea5ed74a59b74cb94dcb1e6e5ce71250fbdeaddd5d41f8a7627c89649c72bd919a9c40c4ee5fa6d9435c02aa88648460381c753547ae467f27f40e9110b6baf92cd742aea5dd94644db5346da9556773b5b5d1b827830499070ab3a210cc50c4f8ee6e9c10fbee51b327929e268c9300d56244f0213954fc7e7114633a15628d41dad5304bbdc6bd414152be46407f51cae9d48b443f93fb64b51e9e18b4c2f473cc9923c1cec6a16da7720d9c41b07daecc1469070d07d5072442c00d70565157f997180b03963f75e5789f75c44afc46f8d7c8cfadb7282554276d779889be582d6c138f5ab04316875d3cfd18e2f3d14066dae52cc026e754826fcf8e388053d882506a0e276e9619fc9a4aaef1b916df2021e7ff6dd7e20b8eb489c69c3965ed9028573c270cf4f2f1f1c40c7549b25c430642e7a2ae1509076881e6c8228c4881a6d7de1b720f785e6354aebcd3a6e23ec07eadb69f66007107d11742379eb8f79e797a9d3fd375c80d1672594adf42565c3ec1e32798c9095b67fac9e59ff164a2ccbd700be1bfaa53da77fdcd70b7fa2e1a57b254fb1a6884397623fa597fd7b04d0952d16afed473c72e7f54ca36773b043a6f2b9af5795cd38b89e58a3285ddfdd4951ee3df95be39bc7c38cfce812deb07d1946d07f78b73eeaa21a845f3447278da8a3c952ac99091e80b1232bc28e0ae8996b63023e786c880d4751a7cde71db9e5da176254884a1486a33f26c21f31fe1f47d524cc3d428c201a7df514dfe9fef9b3657b3a46d04b214e7c7466468ad04be1b27281e35f5b60621f2d49333d9960024c829f1f0b6b13e287199fe69ba79eed7a25ce2d74d468aabb492bc0d222c12c02f8923cf78eb67f950f713586d80bbbdf83f851de0e474da8161889d0176e476a4a9bf1446d60c357ad17210cc20caaa076df7a9ca5c7c9b49d16dc75b5adf22b0a9e0d97e99dcf7e2ea3b2da699d1a2f6bd2214374271e47d159b3304421db22cc80eb6642aa6a26f1dca1fbc2a1d83771d89171dab58b06d451236e0973e6b13704b5a565fc500143e4579aba9a471e0e23e875258bd3f843c49d81c6f5a51d9e8ac866f857fe5f2c54bb5db68f5beafa45dd5ab4730cccc1c92cd9ea8b4baeedbc0fd78d8f7323df2363de5602ec3837d60832d9a7a85791dab586b189a6d052ccc6e2fca4ae3e6e64a51084a32523e17155da05f01ef3a85120a99b728036796ec4fded6d5d4d86bd0d20f088c56b9748154327ff61af8ab663198cab1735d9f6776737543ab58959ac44cea8de540cf789ae95b498bba6df9793c4474fac13c5170e1dafde2232dc26a117a6a9527e827b90e2e14cf7b26dfc65153223bf0042a2348c423d4c3d0f61f61ae7304477856872618cace648d217705a29da2f2a495b926833f8151455d4df2b54b0452da147af642a05935c1b1445f9d6bb10d5f8e53001b4f02d7fa6a16d23c64d7ad4ac6cbbee763d7630922260f2592d5ae11a5ff9ab501554adc40056fdd327f3f65eeaacdb8cafe15d17fbd20a1747aeb14a7427937039e6e8c4940fee2124178cbb9756cc9e425ca14cab4c4b5c92f5769ff2006de0580c883f43c367c901e35ecf470d083ee6711e9233967938b3bab0a93e233e1f63b97d39e5ed5a9470462722c9cc1d9a993bf93138772ab382ac2f779064e786f952a78623456163ba74b6edea63a06b0cd79335e859e2c7eaa4f608e3549084b94933cfa4457c583baed2db1b7ebbd9dc41b3abe4323654ad0e6b5a1db467b7de6d22cfde4041cce25081d4d2b526f9290ca1ea2527a1aa2fc198e72a323b29c19ab5532cd56d050756eaafc761566f154554bf5d06753dd8521ec00d1b0ddbed290b7ac8d13b312d0d65f1fd67655820736397697bc15944e0d5e3cc8891485a292cebd6831c92e12132ce5177e3e176e7f16160f6162d8c8910d81ca6edb4823abf0d743728d042429d43302b2506fae7efc6008ed118475630ce8058832571a0ff627265b3cfed342b758a4e6ae37656f0504609b441afb3476a656bf1c84b3d991cd55059786a860d17dde9c6bfd682f71df4974ae10910cc7108d5725598ceb70af19e557f056e8a0a26e21b0648b117e5e6d306b0f6c8499437bb6a51ba2c198c78b7e8d2074653d1f2fb82bca285963b11c6ee9d7555e5d4096dd687e71ee36cec786627c28c96561473d3389c52de56d3eee7dcaeef5954c53a73f26bc8efabe17f09e91d9be7912b9e970dcded5dbaa3290fc469acb18b7563c055c4f1ab53f98233b221323e34ddee3cfbcf352e1dbcdc0a145b89e7f587967e9ee2233d38f35687e07b9ac9ba8eb656d1b03fc8690d3da8c0d8099dba3bb64f731b6ba4fac37a3eb6151a01dd548d6d852d85a01a0228d211dd897189e57b22b6f406b0700fe8755c1c28e5f49771189ffef5fabc7da42d0fbda98ab882cb992648fa61b7e4cb361603b18eb12e00f5ba0bba3f021be5a1c5b4cddef0f038775d7b199a2bf164d7893209e794ca6f96183f8c755ac3616295cf40437192fa09dd975a4a6c08a76d2349f18e520acc94de9c3a4ea1705a037c16ffe8588a165e48d9b16dba27ad91342d8b459d665272e234587919e470846fea777602643ec587293313446ded2201a8de5347d09466f50582d7a0fde7d39a93c001ab78c21e444a0f8f5a1107ebae35a3129f6644ff40ed162f369a65badce4d68ddc08f27ccad090a292ad5426c5485a98f5b41a978d3b49f3829fcfe5985995dd71a7b449b723970cd1643fc6428f685fbf3fa6ef807c8ecca8be6687bfb7031ea7ff050d9e37ba8aa0eb4c07c7d9e7543382116232a2a521f2d881c68b79e9d8010171bc2a8b6a6bca11e6dcabb0b3ad4a9db3721344305c9e5d2699f155a3b109826b452a2650010878898eb5921e842318acf2b82881f4c54e4a2a113941a144359a2acaf83974b93f95190e12493f0abb8941db4b33d442bf0056e82f96c6ab9ad31d4c73f55fd7280405787c62e52706c3e5c3d70b158cdf539d85af773df59feee6eed395f8c18345fbe8b6955d6282453e1217a81b707f3289d7626f03131a8ae38c0e5145b95cee464cc8d17bfba9ad528a453d3ed51a9d43080f7d76f5f65d864f9830e4a733359d4253babe4ef599dc595db1a970be950c4509a3ece52954494a5f89597e400604347259eb326e3f4ef27f8ac9b34ea5477990b72974d7004d16ea465dae03baf3da34fabb23f23d78eefc93e0a0116929f6319fe414380e7fd2e0503b2bb8d2eb691b2715d9efddbfd80fd2dfc6875c2d50a443a09ea86c37c90484869a0a5718d1b195020f593c20ab0efa052b8bd35d9412636d5fbdbb1ad8b61f261ddab761703720bf027b0d28d478744dbcf585a65c91bbf9c0d44d755f79676eaba5c422aa5925e5662e5714bc33babde05bf8505b1bfe6e91dd22fe9fb80da033cd7d3bb3f7628179e124f4b1a79f1a0299fbe19654a52b8c9c79b3ce42e1d80e5c85ee2cd2e036ffd9b0a905bab5a6a103b39a6c435c567122279a5cd2dfa6a27d6dfda66c8bd7ce8c985acda033e66cca8ddff5915db0daf5f515b1e7e3deac55ed707ef2d582a95f02d7b9506295bf4d2ceffe06917a292dfdad852ed07adeb934d690a01d37c5fd1cf3fb72973811d1887305dcb6d7dbafad3319d5f171b19ad35963640bbbb190b74a1275833e1a013c20a6a222817781d6c176111342bfecbfc2c038ce2bc809fd64c9aa9101b347e35bdb3a090de3097eab0c7de9cfbd4770e0e03c43cfc7e272f22d5483d82eaff7499c3be982cdc336f7be78923e716f88cb1992d83b09cbb0a4ea7f74da62dfe3533f0c2d34b52685cd40700ba4d9f5384dd3062bf0c997a2dbd1d11526a5bbba580508401f0c7f52195c009ea34c1fc6c2f3e26b3f57b4eb2f730d0c846d6df56244cd3ccc278e423301dde32c3fa5aa2faa267f92d2a8105ddb9a1a899cdec34c929e21f6ffcd02089661aaec2cfae39430102e3cbdbde17d08fa29887f3c33768a204070797b5a8c5b719098bcbb1a8db20cf304bdb6f8fccb9a7af78a13d8b8b64c29f3839668f7357b5cca372fb09e5b18be3f2640ac10d81e4e6302cdf6fd5c994774af1faac10e5232de2a2a6b1e907d88df8116c48ce33d1bb09235fce3f0f7a536583f9b83ff7d3b98421a94c2d0356672ddb5b15fdac9113d1527da85ff507a0b95fe9e4416a7bc3c8e9848b6a4e0e12634e00b8b51d7c37cb2ccf5d2dccb31045526fbc0dced2190a9fad45d4b53adcb524a26568b0a3d0087097e5af55d7830c70caeeb64c9eb2d55a6e7e6c76976914d52da9ab564577b720c37110782ece52abdb294b87d4d000ef27f996c533ec496d246e25965a70dd07a784346393d745309864bafa63ee618bca61765694bc2b5a30967d78181fd0eb96ef32b009f49bd8b2f902991b7b69f9ec127bdb4d5afa6dffddaccbfe299df954190d106cfd5efb278016bb41b115e670d084e2dd569fd8cd7b493ad87a697a6a900c7dd9bd58d7058e6ceb094911be56aa194d4249a23173df54550038cd30c9856f65c373c3125be9f02d8c46bd8726129f7834289c55074e0d0809d808abba3dcd5ec6cea1c184e308211ffb7a892f92db581923d7f4787482ac98e498d5f301fdb75110c49ccf7223d3dbc962d6bce36471476f53c0934a09df0efaaf4b9c27dd5ea94b64131dd37cc681b955011c6b3988b2c1b69ffa8ee5f351ddd13636b5fa7c28e82ba2f33f411225b5c63dfd99397579beb930b3871efb24b09caa6bf5f8a430bb5151eccc10f74ce10f46fe72b3225eeead170beb331476fc22bfcbc69a6944578c1233a1f76d3382c57bb0c25018f5c93325d9bd87cf7e40ffad65d3633dd6d1c5c918bfeb97c31a7f8431500224f00c0b3013a64acadc3bf49a10f2af75dc1ced7691af174a2145a848a73036ab7b80e3272b789000d52336ca0d763e010eeecb181b6474972c8368b9ed9f48f8193a6e15a34e4eae21f897a96a15a7eeac053df364d91c42b92923c0040b5b3a4c6fbd3a5744a57d044b464db157b5225f6ad8677a59a649098dbd14d7530b24e596a75317f48b0e9444a3342838a42d4cfb91ab530eb44c300bb3a1a30d3929dc088f0aa43c296ff4646ae495842beea545eac42da3870ae10adb81f3420888e97b75cef2cd2e49efbf6db2c720e55890b9d696c81525d616b0a8d53e44b76ce9ff4154c1971e68f888f252988730a47e2b0b02c5ae263bdd622f66f83f6f984a01e512ff3882d99bd7c8ffbc318fb710fdf7df3033c6187adba8e31c104d39d8111372e082c5ece3dbb8f64246be58ef60c66fdd449048bbeb96623281fa95bd7305993394e37e92c5e3efb17f0420a828b28b170ca8e7b6823ec0a3842c27e69e061e49360f5a89644f09d2d816ac73010e508b68e4f861e4f974ca636cdc0b9bc53bb85186378cc9c6e4ca6abeee82841db07c02b3df6af3de16b50154833d5a4200b63aa223da7cdac74fa872468df21c2bbd32fa7483fd8b3bb0e9274fa5a02f2892b68595cf18de3d0492467d66b78c99a931f6240003081ca9d8cc96f0847c469f2af47d894e18e8d61a0ccf6b5bcee310314023cb7ec89206656298f7975bff8b500b1a93f982b9a34777895bf0d990d093aa41ee00824768debc9f799d93e89deaa0d93b615534b65226a872d3eecfde0e220a9bdef76d86d8e0a7bbeaf612527f0732bf5db128f39fdb54b8e5d7593b529008192adbd95c4192d1a06b98d3c62f7759df13f0cea51cbe3b694bac05e687590ccea894c49000719d4b140cc3927b620a2846c31bed8eb0cb9ab944727e1874ab59410fe6f9ff1ab3d8f439ac92a77a2b5720757fc85384e78813ad30a93f3d11b1798a69df88c71947743a5ff32beb2f6f924c43a586be40124df6af6d5fa95ca06e4278bd49e54bf5eff91b8f9205d78d196f287f036cdb9acf0819f5266e5095d8a2ed0e2d615414420de1e02f4b26ffb06f30a1069c72e197e9f05b7b3e0065e99141cfce7ec40b65c01ac214371f8764dc85d653c9427fcc02a514e0b2d0a363543cfe08968d7f74cd7a40ecb227515db3f7be723f22d3cc8951e33f2daf995ae84cc172394091fb8508903f2d81baa3abd99d7341e5a7fdcd51e33859e6d4e6ac13bd762b5895e106dcaa3f2daf80d7eeafca31562df5eea325e37c5fcaf8d7ce939e055034f0ab8a18ae6f7a2e97b4abf79e727004d9dfb58b0ea254ffcdf4510001cfe874253b7ae32995e57d2b96e923365008c65cafd5f637b63ee8feb1e67b27fe900c0ab906694a8e0b3c5d39cf3d7456b7660bd3bcfd5bb42707b90b1346b7735c47d95dabb845a443d5a04cb4045589bb34bbc07e392d9887040dd85029cdec87a7e344b49f90a1ee8d1db3fbdfaab87a4003114d13426fec0dc98bbfdde66633c5929d481342735e174da73f46de40a020ac91bcae7f32a66a9b2468f6253fe6c58ca195c58a1f7912241ec39dd4305a23bbdfe7446f790edcc58bbfb590e5fa8d5586e1476825a1ed9c48fa9e460f443da8914041eb3bcd5ee170153b3a624d34ec6b2f9468f7e567d9dad2ceaa783c4c9e88be9f21ba269ee255c74f07cbe85f01c9c8bab29e8a089ebab2eeee5a2d386a42eab004d81a421ef2c142a4cb80f9606d563eb36e7c2e1d27afcb4386ea30eaf9af9bcdae658f8b2a43f8f2d3bbb530829b0fbd2139a59cb586ad4a22cd48031056c07ea2a02d1ba10f511810fac69583c35f65829092aa5fc846e6f1c70f39db0c2be7b9ae92a49ec399d074aebbe877c917402e0082b40b015d3cf9b24b86f0216067b8aaecb5b5f8861c3e23e38208dd13654341223cd8e7506b318f68034aadd1a19e6de8de640f62cfce0f641c2b7ae6f46270f209e90e6f8f113fb8e4b5d87a29cbc7ce00ef8d1f4b08cdb66e297ae6f405b0a1bf30a2ab9065cfb66e9b4694934f5daf0e68d6177d1987d651efcfca2e39a268fadfb4b11d6323cd9ec16843f9b22be93ab2c7c1a52e1757ba1ac59f2df1a1cc03b3a9747fb2013ba197c329dfe1a5b45663d947e65ec938a4fbf5cda2bd4f9f81e3a396107f391a359df06cde6f867a49485e970dab4930e80b6ef2c02331b39ffe68a53da5d2b5a89140b908d4ee12e254db18604f81f921ca6a82429082603bacc0c3cb41a00c48e178014a6b7d044c4b4418b972c938a4b3f569a3133becb9b3a7ac7093f32faa12ad61fb1f39a5f8c4ccb07bf80bfd9c2072eefcf2e062b18880b4c70b002af2e11d83313db0e19e2aeff5915d16449d8eec7dcf335e49552218444dc2b40135dd2eb72ea445c5f321306ca5964b79c4d791f130d67df4c228570bc2e6afd63a35734fc9aa27d9efd5db86cfb3bb45937b21e22cfde86a1cb21c5d1458e5faed80b7ecfd3e2cae11292175fe5221115b6025b923b2369849767ef2f9357e845f4cfe821319bea6142e263f71bdbe0e3fc8ad31c35b1fb6a398906621edb6e23aa99904b69e860492a12af7c00c47d051ef45dc6eba9210a65bd7497b8953f5c29fdb4741998ba9c30ebd8ec70b91ef370c8973a2796ba953696171b672b2851a34726e419d8f4f79439b9b23c6636d8291d701b16daf5d6bd17f226bd39ae39b0fdde95985ef685ea4d81172337d049c89cf53c32b2768bc9bff5b96a173f02cd1e02f1dc64a33a3d6a1aee72bd1a8700b6af6c46371f3f9ccc47800cc07a47e6d2f956a2c25a4926038b880fb1edee089138f3d6968248921015d949ee3f59300da7e65b0a2a01de18abdf53948b078caa937859d8a089edf95e8d24422c4883b143d1bc7d5a303999ce80502d842d614f3dc5654fa6d4278b0c9804c362cd0cd6e4036eaf8772eccb47d35f9882d43bdf1a73b5cae6541767e72418cceff54fa8a16a4a312d0874a421edec1e050134eddda265740c25c2eb0c0ce84d51934d37c5388e981eee1db1332ae77841e17397f639fc6c838714840b4d9151eba62719d0dbfb3fb79cf28c93c59818825539d49d6643076882cf926ec891cd984119875250a2d2d6f566ca4a5176639e0ca225dc87ab64e68178295c9a0596eee5c49485a02d7c5e71c245907b5e4f820d8eea8749dc7cca80599defd9f5318182de1d873c11dcd4177adb578b9c763cd2231f7b505d9cf434010c39fd6f277e75b80e2ed63261ec9c1cfa38b806dd7ae769b44158b5341ebd2e8a0cfa27c375fa6073eb62c7c0b3cd88538b694e7bb8f7f48a565a198fe6e5c66ce67b6c2e02ce0f1b773c91527b8b6fd6f0255a5681dc506546284fde361f77ba19c8a68add70f2875e9fb7e6cee112b23ef5c4a0fbf23286e52d60a184fe99b83d990b44a8fa50c3da1930716945acb9f1550af828851420cc0c0785d47498293cf86b2f48d9555138fa0d5e24aebb2b7eb32a5673b95e1784bb491716e0880ece7a864bfd40c9fb52b9965c1ce543d968c666a14edce004fd3e1d3f618969a92cef63a7100d2c1c735d12671f2c462c9466ea7ecc507f433488a564497e4599fe3b477417c8695b2ad122ed8e5b037c2dce6f629688c81a935871f008294222a7666ca48638ea591598458caeee7353b67ae763c4519482cb123be2e97e35783753cf0892451994612c009b6a2fec4cc3b224b2aad756665fb497d68ea9ccd764d3bc75297baa2e4585d8193bf10e74b8507fef684fcb9c048f1b6e8cddde6eed69a563db9617275281019fee71e505a4309c5daa6551025bc0e7a59dbada1a8f4b97b3bcb5de9f36cde02e56157e5fca0a924941a1d8ec647a3133b9ca41f2b77be8ac3ba5aefff300ce190675369690b47812081850370ecdeddcf4d89beb67a509c57a86df1516d9a2d2cae98ebe2beff3a71c8dd945b88b4578091d4eee6bff539fac5939cdd4af8b420b4af838ad6dfe2313be0f3ad874f665ddcea20ed54ff3c774ee4f7b1b10cd0a20f3983117b3880e9802e5f76fb4b97ed566a440a2457c245b9eaa795e1e35143c179d112970dabf507b89bed69db27e639b46009b235522bc9c8d0965a56654056ff5c9c6bcc064d04fbe8ee9f7d02a66355ef8fd19810ccaa4d1b043593485ac8bb1c528b80f67b615ce93cc18219aa6cd8e3ebf75c90db20aa129d01b3bfab299f3be7d115e7ecab6d2365f9a37e6caee5dfd7e7ab673c37effc466cf8ccddfe38f38bd59c704bf9840543df034f15cf28fbcb4467c28af31b14524e751170254973fbb1c209423bb55d9071f30feee66aaf306a236fda502f7fdc59e769aac2a33ea592db1781327d81352ddda1554d90676d31fc50a8c055edfe9328599e8e8b2abbeea7fb5c8b0ecae147c01002a07c464e2757061a3ec0c7eb42d06e6c0ed5a86a1d55997a09d182a38e5db2cd4ff9e7eeb27fabe09972c807b6102e38ba1c94758fbd9d87bbb61de108818a6a6ffb128be7b6522948485214d83fe37117211bcb0e4c97c9b1699238a3f7b6692d60504cd4864a2125fdeee57ac8bc6b0c28d915a51736473ea691d64af22133216953db5cf0008a88001627878fffce5e79444c700ec209688c811984369e9614e261e8c6b65369eb4da3e27ea4465496dbe1e9c7d1ac05ebe6a2dbdae6b6692d6953db733af699e23a89d844f6d7c04284f2f23af5fb433be34a2c21a50b06e338ef0397af30e9934b0877ebd91739a5e1dad23675b9529801b4dbdae6485ad85d646cd4905c37337e477c6b405a238e291018f49f76176eb6f331a0242e3dbfa905abbd854902449d8eec6fdd218002348b83dffe0942fb5d8da565f96e72f0164149954e8b45845f4c0e9a2854f54e09fa27cb824e95d61746ab9232ee3cb187bd1b440d31621b83f1b72b65d2c476d31367482b2090c4372422e1060b2280fe6f33f6f13d76282dd19906b464c1a2c3193ca51736740d95b1f66a02d5c7a5dee4eccd2ad6753d97c064054186943688813abf4ab23e93a467fd5ea0795f8112662968216d7b03db4c21a9868e75dfead0e89bd25e453de5127d505e61124bc510180a489fe2489784b2314bbf741f457e76d62f3a27680445039f93495b29db021bcd9ac40cfd2536a53d8f5bb152bb32c3af45c1616c900e8fe95884ee6e58a25e024ba25174cef095b79b88e361d7ff168dab2b601908c168d968b4ae281e643315075aadb1a86fb8fd3655fc7a4ca6b578b9357e0703a4d060d4171b3414636b379ffcc591ba9bc541f925a76751eb37cac5647c05dd13fa3efd0c6ec9f7dba172bc377c05706224109f4311d3e4deb42f5e4a254edca91c5234f9b452caa12af7f4d72b1a35fd9fd7676580b9b48e8687ce02b904ba921cc179f8ce9ea7973ebddbc395c488e3f94b578a980e340ea6327bb7710bd3c8aa36c461554e9c878cd08d434854521fc4c7da25f6b3ec9e9be563a48918b4b3f66ab8ca5a22212863917db9f24ba9bafc992ef2e85a72743a632a4611c8cd241af245992b6a5c66a3ffb6325d24bc1751cda5eb364beb1aa4e308f3de292d518ca842197f1f15e58843e45f811c7603ee9e2e8006285a1231733162bbda0291b1ae1ec895dc7ead81dd9462255c14a696e783ee8fea0196c021fdc13fd26a83c2582efc18a11e60873226daa742134ecfce2c88e41fdb87b9d698543f337f458808ba2667e5d3037e9db97fa40ad2c19d02b0bce567d16f35cac52a5ebb656eb1c1f3337104aefa72c173cd2ccf5f90180b22be46a4547aeada6bdd86ff87fc228fa6524082cd89be33dc43a5dd05aadfd80b6fbd9de4dff3cba07d7be1888d4e509e4250bc739f1d229261be831914524ef12d3f7f4a70280b420e8df52dba6f8a3fe3a1b4aecdebd3ac53037a42e1adfad620e32ce3ef2fe74a85cee32734d2737257ff3d6852d3cb864b00e127345218e5c70d6324912836be4ca62f29ec0b3352dfc644120ef0405caf4e9f5d51783ab2687a28361c55edf9fd2705fbe82ed5ac157d87bab18744bd259697db2a9ddd3b201e7a00b68fe5b7105a23a9ec034d8582b2f136c4c96274b8f3508291472810d9a07750cc996e1ea351e04b624bfc74ee452bd30629bd1bf08af36b3b7c02552b562ed9a3a4f8b1da6d6aecfdbb6f53a3ed50fb14114d04a2ee979fc4821e68c1145cfd65570e4d83d5f357e93eb72e6792d1a4b7a1f20089d77bc433bd50de02101b6cf30d89f69adda49cc4821e85b5931c2ad07340170b43fd3023c514a67a8503353f1fae0c89425bd9ca8cd968b46eccfa804897e95a2d68b4a7a9cd0e011391babd790349859afd259687f491e7d7586c56ea56eed29ee79fe5a33f7377093cbbfbb97e3afb8d2ff5e68806b4e89d2dfc91322763afb4d60e9dd5dca53b29abc16ea59d48b90078a04b029eecf4ba80def1f9e5945912317159e865d408fef18fc6418ce6108858594a0ca113cf6dc2f48e015c15eb0d654e918c14a63a93536be8472b07619e0e05919c3d2c7b19cfcef4fc3378b4fb8d55868814a6978604dad6dec234f8b350849741b90b57a56a7bb2f42452de863777556d9434c8faf038f30b49006c457e87d5930fc049c534269cbda81f2b89c21a092dba08b4c745f3777c20b18962a9ecb11986349c8301977d7529b67c373ef8a349282b12b8e588b5bdf13736bba67eedb9279571e74b9b26995bde83c03cdccf8971d10d3fa294ae13855b78235a82a7f3cd05f32b98f67d6f2215256815cd4e093fae3adb596f3b908090501a63c9dd9bd75f8311f320d1ae024b87a98b7d28c301c6bb8d192fb52e723b3e93d6636226ba8023d071477da3feb70b594f533761d62cd88e1422b896ca832422bfe848a156e4384b37c2fef67a02ce0574afbc96ed75064e472cc87ca7e32ff40cca07b9292fda5f8e08ebb368e70d46bf2cc161191aba2739402216957b783d459daafe62f0186333d9e6eef2ea582c34a2962eb5d6d8d591e4599f63f457abd20aab5a9a44132e2fa02828ab90f5f820587a2e9692d3f59e1166c9b3f93536ca5591c4b77d7a44c6b0d1d8846fa8e34a05c5cad2223f90f1163d35e9aa642f62b5a6c05cee32a52513e1d38f4671d28f7b7a2db705391290a6f7ffa35ea88c2bf0e3a5d1a99aafe93a22ed54ff1d566fabf7a63e8c8815537bd393ffb07c7f12ca99f16ddfafbc3e18575f8362cf42d7e92726a4995719b5ad979f1752ab8a08d7c44ade6c30bb81c4296b377e2330f8e1c2c344c9f579ce68cabe7c7b2a8d288951168a92d7fd8e65449408ff276c55fae8aeb006da28b11624055990e6add47859681a5b44da8107dd519e12288c4a2bed00cb311a36ed383362152ee4af46960c4ee488e80f8bbdc76f56b2143d429999df43f32e3c2eaac4655714d833720187e37fcdd7c240ded6dec2902e81c07467a19d78082286b08ae7013493096fb10143df25ee8ee8684024c81cc38851eb6c03d4bb13ab6ef923f15af8357e47cca1d843f02cbc33059f8f8230abded80c1f49a71549d15b4aec7348c14d235a82ab5a9ac2aa67ceab3a7b256cf0a277f7a9e67ae8bb3d03c71eb1bc285eb8e33ba8a05077b8721f5562335e17dba179ebad03ccd8b8ef6ddbff108133fbe2718206fdc8afcfdc5e5888044b3c5772d614e3545c68faafe7bde1c79b92763bc1554e409202fffa788e504c38f93c774e8496d040f82426e0d66cfa2835b1d8205e07d4095f471cb384beda0d4a1b5d24fccb6286a090faec34f52832dc13d8821dd37423c6a901f6ab014ac739b17820f8ab9bfebd323b245c8774cccd94355334402c2123de5d4f7c469e1b1048df5e6911f83ad34ccbe3fdc5cdd5834f2459819281be2a5f1d2f5cc1ca39224b710ed64d6f1468dfc644d274a9a5a07ff872af8fa23e8c03d87bca2b217dfc1f546d726127813de12312c7ac1aafb26d265f9c8dcb9334e85ae70b9ec0955b9cd7ce0fd4b348f809412fc98b3767a7473e479fc36803ebba6f2b1d278bc58c607be1e8470e1d94485a936867b8ffb77bf8dda3fc9d0ebcb0b33187abaff54688c362acbfd9af1dc161f50e328be5376ad8f8eb6cc3bb09d5b5ff9802fbffe9256e8c00bace6284f1a9182e541bb4896cb6c7180bcde959850f81b72dda37c9054ec355ed0e94263a9ec954e1cb979ece85fcb42d26f0b321476eab94fd0478eac7be66470b5c05ebe84994b107b2f52922f25515f0caf20f76240c9a675508c42f063b9abc2092a718c44f1bb15f677c98e13983c35f850985af598f20ee77b0560ba5465dc1f33b2ba997194d37ab75df5cfca8549890495b293a00881172eb0aa8e26d006da58de127dbdac3f2d512baf52b38b63119debc994d2a27a66c99ccf8eb6cc3ffbd82a76f59e36f327bff118cf82f8e15b80855f4c7701723d8507d29f8b90dd0d8297c870a6a88127a42214c3024621685590b0532081a875f30f5dda35ceb131874eab3c08c3abf6eabe0c9564583da8a4305bbb3fb68fb4106035d2bd42dccd8485d4341920f521b53156c94c05a7f666b070a0092c623adee08239e7731ad93d0f3928db7bf5708c4af993359d1ca864936ffb1f09dd322dc517482d77627ee6270f2714f47b3df131832eaf92dbb09b5f97bcd6bcfc564c98b95036632333f2900f51d7179a5224c445abe18c4a205f2363bdafd8be79e400b52b6a5e3a5a67e8c2dac335509c014a2712dd1c07f7434b4e31f373fe3922f8334c435e6677802c71734e4b67efaf01c0b0ecb600700f3b49c516b63370701de4675c7d044f63a1290a6394b78fc77d23e74672141f02cee9b97d4dc80ba753e63a8bbbd05c239da8c8f283a632af6e198c08fca56305b58acdd7f603cb55d85b09aa9ef73b36c7e2c370d05b6b00a4af69281a87383aeaad7042692ba0ecbf830a09715883ab276cf6dd99664e8f5faa1d4c34dba20b314e0c1dcd4082ef7e2a6f9aad828e92a01a51c5160195b212a10b09fe709f735a8ed9c28e956eeb39b10b09fe709f735a8ed9c28e956eeb3a35c1694f25ff5b4a842421454d5b31d97bfa36471127c7d47d0934ecaa6f8b8a863df4bafe0455b56999326a1e1db26c5bc6b441959f715753b70ac8af3579bd094889fe70977d662f8ceb485a8003e3401e1a355a3f695cbea2baea6fa860bcb642a397f01ba66a4aa92b54b6ed9ecf8f2fd404b8c3f9d820096b484a9fa246f5636d4725f466da43701bb87a9026af089511b87f4a081fe794f254f13daae833593f2b4286bb0a218e14e3965acba5d72104eaf45f5290f9fa9734d0289d55ce931220cd8f018bd3c0a20510dc45e67fc8044f3aacf2903f3a18b4e467716fbefe776a64ddf473d2897be691d6bd9befde6ca8c50815d4f8f0ce9faaee77bb0049a8bf5260d23bf3c0aa4369cd7b5a6cfa671c54e9dc78ce2749c3b19501c65f8e88ada7ebdfbd8444ec29fed8f0dc73c5c4f0971da0634f1c7fdb80d42ffefbe2e25b234861ae330a62c60609191c3b573fbbc20a27d6fad0f8df67d1b39ae82594a0cb3143133931116c0205cb2b843c03ca34cf92436de73778293db1798f42c9fc458ea6794ddbc3584a8801ea3e4313d3209653b7e6d28c06cffbec04aa7d6d99d01a39b7f8608684e934d94f464acbf595183b13622df287c9fd4181b69b1a2c95112c9db89f61c56c14781a6bfd499de42ea3f0f3ad36a7e5ce95f47b76a6e0655e3aee64a3d1018847683e79ec66d17966c20b21f634c73463d2e863e05030833f12032e02fcf72b5a85f63844c741e1edc227f50152fa7f53e561e678b36936e0f425b4243e6ab6791ccc122d076fa3f3f777b02c366779c4388cc247b4d40d2dd60505a0c76c078798cbd50cadc9a16074d145ca21f52112516fece856f34b67c58e7db777f12fec38cff22e7d018ae0bee8e63e441481477a4941b9f76d33a35930c72447ebd48c58ac9219a0b218c58a839ba8cb2c498d1f9d4a745fc1274e5bc4c7540dc07bb071284ba7220897f74ace163d5055aeeb274df4799cb47db69b951f03a729b714396aa0f10d964356dd6d8d5eb497fdebcf2b185fad9920e8c8e2114ee4ccdb36f160b4da23daa9efd3dad136a6d90a1c1026294672e5a0fc14d9433fdc4ccf8140d433aa2d832aafc4588fdd01f164ab7724bab4ea666a60bd1c389fb1233f79ec5569f2ef38304b62a5e30477d75e629ad2fb6473fa442e27f23b2a3e7dfcda3814ed351cfa38c597a2fad9babab256aa60ded74e446ab34e9fb9d4eece0bca586dbf9e37ce3220e0256599588dd8842912609e4f731e10e181290e67972081f6715407c631cf2a0b13622df51497c916039244735b0126d98880a88a38fd29bf7cdb56d86e6197c43df265c40164a03bebad1051600ad5e978527e04882e3347b96bf1988ca7d7dcce4880693a412430ecc95cf8fcda818e0d2c16cbd4c382115e7944ef1c432f693dc752600087407dd6feb2d73304e9dc7acc3431722ab0504549a41f471a42cf1e389727404d055a4945b6a39e9d573a3b1b39b23676983a95b6fe2bf20360ce1907c575da442dedb6d6a136cb1a22e0829af605368f8396e42a93d1cc55191c5d9c63254138a4544cad64be3f9472c968507b9a3e39fd65a502ff7ace7c46993751e1ba38bc0d1aca5d4625476262cdd1456605afd321ad49fc63110783020948211d3a33b034a0692a58020addc34c10082b46fd6cc84489fb4619046ac8ecad1f65a5f50f50e8aeb5cf770543b18b0b88606867747dff1ecad45d6900ce23f901224205559e8d9c89def54cc515f829002c77847c7eadb6d28f1e27c29ad4f5d9f97bc957b0cc00b3c66722a895c401261059a2858018c2371a9bade0f1ceffdff553f92245c43364f976d124cfe22ae21605850146b2566c5d22e0fdcf808abd32e0a02fec584d5c1cca108ad1acc40f5fff873286ba7ac31623430a7381707851bf5006a4a95566c6e3c28a284b2ac216b425d77a239c2d9d67db2b98536aa34dc920cd2004d10aa58948d7770b2c591b336611d0fe712177a46fa8e2791701499a3402011b5c3b573ac68afe828031b23f26d37b9239a9194b2733f3e05c1762aa494094651af6dc3bd258936947aae994c1600a06e245df5d1ab0a3ba0e64afb54452af63f748526f636e18ce013d7e2d84880018924528a99dd4ffd3162e056cac288e54f463a259534a81a875c4c9fe9e1bdee256e471dc94c8f3d611998aeecf30c273ccf3f708fa67d58e5217359d05811735153d05b6ed5b69186554530041b8c491c39800bc4f3589844d6e6305cdb92a14aa42ef1581fc97b27e54aeb4f7040b1494fac1834726ee3ff459bfb5c550abef2dc8b0f50719d558d52f7c1bb85a800fef58cd497f521e35644c6614b08782691e2f7f74c7b3c38dfc644a20e479835884e36eee936a70ec7db74f6a2ab5ce9ddffa25d002cb5073b33209e36e17209ac432bda51022551c06eb84f530cc8439bb8a74464393d8e52486946f6fea25d5c721bc3905c9887f3d99b99f1f2ae875bcc9f2ee6568490ab678aca6c5905ccf9de5ca0fb4d51c3e05354b4501c054c803e7a434c2e92518ad062aaf0dc186057de8e05a8b2af10a0b47654232bf7ec29627b3567a2da1980da71e2efc37d1b190d75adba3a398e5298c6623aa501c11a4418fb4ad1ad8b417e229e206a77018fd42f20fe2647ed2111676185758e596fb283a8d09e0cbe028f0e0ef38343c6bcab65d38bcd0e9fce28dba3beabd78549a2b1f7c7093fdc899950593d29400945abd20a7b0c56fc9f971583f81d9bff3f744d267433aa8a5ad25e4ffc0aa75edd46bdb38b2d7e9cc6a66a19c7e08919fb27a9d40e3016287c04f76f555d2f0407124038337529bfe817d66b87950c524185bc6a40851cae7a4fcdbb9d5707fd6e073c807433a65c189181f0cac2a794e72fcf95ff19eb5a8674da951c5409455a1168d8c2845819c5d71fe254c57f7b109251645804af354f5ad85ec8044ab8722afaece3e1a7fc50d01422183288e56957d50c1f8138f8a401546681affcbeec46bee18b0dffbe84ab63393a82d891b5222f240afe362d6750143f050db594e7dbbd6f54cade243cb6625f15c62c9ee5f2f1f550fb979a02bcbb8fb5f10bc53c774e98eb0adffefc43e1f315ee8dc448730bd3da9c0062a216d817791cc524860ba32d74681a74cfcb34f69116b6f586ceb79b8bcf53e199b0525ef054f067ce94ed95b0babf87c1429d00cff5105716c607460c8859c205b021f57b1b76be52a276f591d9dc880f35e7f8c28a447ab06bec14cd6e7048688219e35f6162957639d151ef69fc4ef2cb5aaee481501ad6a9b86073ecc00bd988ec29b142c8aecf670fce279510275d1cd664567cfebe206f16ae29bff9b715cb91c6dd52d2adc3547080039ff8c70a189a8f402b31d58deb92ca894d75c519bf4573498a3051af82875cb1c046a409785986ec5642e45fda7365732ede7aff603c50e7e1bcf5bb0a8fa4c8ff62d0a6d6f4c36e9b294841ecb6e7e3ce1624dc19c3f6cc8c1c68df5a3e405432af14dbc01846c57d85bf52e63d6f1608190240d35c06acf78d8d42ebb9d9e03b1728ae0ee80116c111db6e16fd6c9914e890d88d956448f4080c6a303da74c424e84819ed56c49accd65758b3f67f52beae018336306487c5865aec7660b0231a900b0e1d632f506d5a10ec941e17430a6e462de5c569c1c4e531f5cbf695f32bd3a8ce19648727a134ba56667d31ce374e3f7d35262aa8a734293363fb88b05622c036e11665dd0256ca05bf36eb818a7fc14225ae741c599d0aa8e2f87bda08b67ccca607a497e5c569cd706c4d79a6346922daf9c0be2b3ff26259637d44295277ba67240b89c99de99efed0842a9895d42967a4c5b044b4c78f7dea500026e1c2326b9b5e3c19d14bff32eee1bc6d4a19b890b39070ff3993da704f6e5e30bb8649cd262d50f5118501861f9cf9e866829002dc18f2dbd1bb0ffcd83cc283d9ccee8b878404874ad5577a04432bee15488ea4c169c252e0d151a62ba89b32f9d099810406a10917368bdc51daaea76e4e883f25aec940abadf6fcb0013d5ad0020d2effbc1497f5dee9f82666864ae11dae63e0e342c68c08830d9011265fb497d4fcdbb9d5603ed367c05998b132b0812ddd7a53f9488a06b2059c037b5e0aea2c1fcf05c9a602562831565284d034a5b8f0182b9b1175a967dcddaf229b1b12c11b57d454db51b20d8f357b56e33a646490e80e4ecce321288187b71198634e491f3c4fef60f4aa176419844f0a6ff8ad28b423ce98ce6a12b5e170c57cb794e70cc5f2fb2c19321621b83b533a9805b4ac8fa54452a6d1933ba8febb29b077d50f70a277a9f7a1a748dfa3a611febd3f41c9edad28e4693be5f9877a29015a110d4350d7750f26ebd5080060b009cb0ee4e15c886d753136866146a3fd36c3aae6e194f479b1ad69d205511f8290d86ca07c1ee7eac21b72336e9b648a753173965b6b341831922111428a3d95b26e975b2e1b0e97192c80d7b90acef34eaba6f0e15ea1bbb9861d79e2500b6a60d32b7767bd11267f656a90a3dfbb92557860e36db02fbb1932a7fe389aee8743b8256980aeb2732dc331154fa5d610873f4c8e7d5efef97de52d5ac7423d878f7f5f9a68f286e75fa50f82317b6cbd2223f6fa795a5a8ade529a8df0704a6864d9e880a9c1c523ad192d6afd70c4545e95a7f1f8175ebff3f289406c7d4d262c70b35a3714b43b7e8242fb67656c66f7cb0c0742ec04e3d2b24eab7caa3d99b47cb9debbb720bb7fa3031e4caa2750dcd51f0151c171fc32ae5ae63a9ba5c5bf5ba6f368de597618d5d2a31ebf88e31831f59958479712d6ea5ee0735c36c0145a84d36dc2860127cb3f841be18c46b7e5161c4318bbffddbb03c8bb4d10ebccb748cffb170ed47966633c5929d4813426fec0dc98bff479666332522805b685fbee8cdd80c7cb411a56aa7a30e8fc87dbde84c6843431fb6652e57ecffb9f14e9220d5243df165fcd3757f59693ea8eb35272dc58dec92db57971c21096bf7c5e5fe0dc98bb8f959d1b67e62e37912a19e7da7b734205118542d558d4aad6163f8ce9e68fa97e93154eee7c8a3fb36af06f0e2bfb0bffdbbdc67fd127f392507dac86e4278b0d69f53b1ce164d3fb428d64b318da9ce1d39f6f9bfaa05f768da1e018395b2bae6ae78bcf8ec62df1f7e05a86553e941241c2211535a133b7731d4d8073309691e6dd5a8d90182dbda66e0d3592eaba2fe7f9ee0f3b99bc5f9861639654da8bdd6babff48da0fd2149ed23152fc010cd964d94f4d8a7f4f38d3fe3ed486acabda5b4f70b7d9c2072eefcdac84ad1ece4f0be4ec6bc6bc3831f65b4a6a4549fe156ee54ad11b3c7b22edc2536b4c8fdf5791ed21988fcb9f568da6e9c0dc98b78b80e7e3f43ac91c0e5b91209bd907e9670fcd3751f5989332efd131b34949530badddc1154527fd87de9ba6f0e1be60aa7962689688c81d87de9ba6f365721cec5271209580046e5f920a26f0ed5e86ad53a329e933920665aff312a8d08abf7d92928a80239c7c78d02457fac8a9a333214c48f56bdaee8416a7bdd5e68ba1fcc8af77be1d431adc8af191998ae001e422b6314fdc6046bf7c56395df98a6aef22ebb5dc812014f15c19e7b0bc98bf8d0e6349a515d4e08a919d48236344b26b19d08433a38dfe740f62aa8f06449fc7f32fe3dcf5f289bf5b3177e94bd171c40f9327940627036501f10fe07d9832b78317d5d7281490e9202d3c1e3044dbffddba089d8e91fdd537675281ac39e63787b934920ffeccaf0b377fe8fca8b3f1e9f2a2d4a1db9d0f0629c39a51ca9bd72fcd369813adbe505531739a51ca9bd72fcd3e1c8678c22bfd2d8b2fb7600abf7be727005d6cf631383bd" # VÍ DỤ: "a1b2c3d4..."


encrypted_data = binascii.unhexlify(encrypted_hex)

c1 = encrypted_data[:16]


keystream = xor_bytes(c1, PNG_HEADER)

plaintext = b""
block_size = 16

for i in range(0, len(encrypted_data), block_size):
    block = encrypted_data[i:i+block_size]
    decrypted_block = xor_bytes(block, keystream)
    plaintext += decrypted_block

with open("flag.png", 'wb') as f:
    f.write(plaintext)
```
:::
![flag](https://hackmd.io/_uploads/Hy9Cvva0gl.png)



