---
# System prepended metadata

title: 'Dreamhack Walkthrough: Part 1 (Tiếng Việt)'

---

<h1 style="text-align:center; color:#3e3031">
Dreamhack Walkthrough: Part 1
</h1>

Dưới đây là lời giải cho một số thử thách trên trang web **Dreamhack** mà mình thấy thú vị.

## Robot Only (Level 1)

>[!Note] Description
>It's a gambling place where only robots can use it.
>Verify that you're a robot and win a match to buy a flag!

:::spoiler <b>robot_only.py</b>
```python=
#!/usr/bin/env python3
import random
import signal
import sys

MENU_GAMBLE     = 1
MENU_VERIFY     = 2
MENU_FLAG       = 3
MENU_LEAVE      = 4

money = 500
verified = False

def show_menu():
    print('=======================================')
    print('1. go to gamble')
    print('2. verify you\'re a robot')
    print('3. buy flag')
    print('4. leave')

def get_randn():
    return random.randint(0, 0xfffffffe)

def gamble():
    global money
    global verified

    if verified is False:
        print('you\'re are not verified as a robot ;[')
        return

    print('greetings, robot :]')

    bet = int(input('how much money do you want to bet (your money: ${0})? '.format(money)))
    if money < bet:
        print('you don\'t have enough money (your money: ${0}).'.format(money))
        return

    randn = get_randn()
    answer = randn % 5 + 1

    print('[1] [2] [3] [4] [5]')
    user_answer = int(input('pick one of the box > '))

    print('answer is [{0}]!'.format(answer))

    if user_answer == answer:
        print('you earned ${0}.'.format(bet))
        money += bet
    else:
        print('you lost ${0}.'.format(bet))
        money -= bet

    if money <= 0:
        print('you busted ;]')
        sys.exit()

class MyTimeoutError(Exception):
    def __init__(self):
        pass

def timeout_handler(signum, frame):
    raise MyTimeoutError()

def verify():
    global verified

    if verified is True:
        print('you have already been verified as a robot :]')
        return

    randn224 = (get_randn() | get_randn() << 32 | get_randn() << 64 |
                get_randn() << 96 | get_randn() << 128 | get_randn() << 160)

    challenge = randn224 ^ 0xdeaddeadbeefbeefcafecafe13371337DEFACED0DEFACED0

    signal.alarm(3)
    signal.signal(signal.SIGALRM, timeout_handler)

    try:
        print('please type this same: "{0}"'.format(challenge))
        user_challenge = input('> ')

        if user_challenge == str(challenge):
            verified = True
            print('you\'re are now verified as a robot :]')
        else:
            print('you\'re not a robot ;[')
        signal.alarm(0)

    except MyTimeoutError:
        print('\nyou failed to verify! robots aren\'t that slow ;[')

def flag():
    global money

    print('price of the flag is $10,000,000,000.')

    if money < 10000000000:
        print('you don\'t have enough money (your money: ${0}).'.format(money))
        return

    with open('./flag', 'rb') as f:
        print(b'flag is ' + f.read())
    sys.exit()

def main():
    while True:
        show_menu()
        menu = int(input('> '))

        if menu == MENU_GAMBLE:
            gamble()

        elif menu == MENU_VERIFY:
            verify()

        elif menu == MENU_FLAG:
            flag()

        elif menu == MENU_LEAVE:
            sys.exit()

        else:
            print('wrong menu :[')

if __name__ == '__main__':
    main()
```
:::

Bài này nói chung sẽ cho chúng ta số vốn ít ỏi là \$$500$ và bắt chúng ta cá cược với tỉ lệ đoán trúng chỉ có $20\%$ để có thể tích cóp \$$10,000,000$ để mua **flag**. Nếu chúng ta chơi theo cách bình thường thì chắc chắn chúng ta sẽ không thể nào tích lũy đủ số tiền lớn đến như vậy để mua **flag** được, vì vậy chúng ta phải "lách luật" và lừa server.

Hãy để ý thật kỹ đoạn mã sau đây:

```python=
def gamble():
    global money
    global verified

    if verified is False:
        print('you\'re are not verified as a robot ;[')
        return

    print('greetings, robot :]')

    bet = int(input('how much money do you want to bet (your money: ${0})? '.format(money)))
    if money < bet:
        print('you don\'t have enough money (your money: ${0}).'.format(money))
        return

    randn = get_randn()
    answer = randn % 5 + 1

    print('[1] [2] [3] [4] [5]')
    user_answer = int(input('pick one of the box > '))

    print('answer is [{0}]!'.format(answer))

    if user_answer == answer:
        print('you earned ${0}.'.format(bet))
        money += bet
    else:
        print('you lost ${0}.'.format(bet))
        money -= bet

    if money <= 0:
        print('you busted ;]')
        sys.exit()
```

Đoạn mã này chỉ kiểm tra xem `money > bet` mà không hề kiểm tra `bet > 0`, như vậy ta có thể gửi số tiền âm thật là nhỏ cho server. Vì chúng ta có 80% xác suất thua và chúng ta chỉ cần làm một lần (vì số nguyên âm rất nhỏ vẫn thỏa mãn điều kiện `money > bet`) để có thể tích lũy đủ tiền mua **flag**, nên cách gian lận này hoàn toàn khả thi.

Dưới đây là toàn bộ lời giải cho thử thách này:
:::spoiler <b style="color:#3e3031">solution.py</b>
```python=
from pwn import remote
import re, random

HOST = "host8.dreamhack.games"; PORT = 8713

tries = 1
while(True):
    print(f"Attempting {tries}...")
    try:
        connection = remote(HOST, PORT)

        connection.sendlineafter(b"> ", b"2")
        message = connection.recvline()

        verify = re.search(rb"[0-9]+", message).group()
        connection.sendlineafter(b"> ", verify)


        connection.sendlineafter(b"> ", b"1")
        connection.recvuntil(b")? ")
        connection.sendline(b"-1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")

        connection.sendlineafter(b"pick one of the box > ", f"{random.randint(1, 5)}".encode())

        connection.sendlineafter(b"> ", b"3")
        connection.recvline()
    except:
        connection.close()
        continue

    message = connection.recvline()
    flag = re.search(rb"DH\{[!-~]+\}", message).group().decode()

    break

connection.success(f"Got flag: {flag}")
# [+] Got flag: DH{0086b1776b2b2dac7aebb790ec005ecf2bcce345c52225f03bb177b47a357a40}

connection.close()
```
:::

:::success
**Flag: DH{0086b1776b2b2dac7aebb790ec005ecf2bcce345c52225f03bb177b47a357a40}**
:::



## ICM2022 (Level 1)

>[!Note] Description
>I think the flag was encrypted using the Integral Cipher Master 2022 algorithm..
>I accidentally deleted an important key and decryption code during decryption. Please decrypt the encrypted flag again!

:::spoiler <b>main.py</b>
```python=
import random
from fractions import Fraction


def enc(p, n, key1, key2):
    q = (Fraction(p, n+1)*key1**(n+1)) - (Fraction(p, n+1)*key2**(n+1))
    print("[OK] plain is encrypted : ", q)
    return q


def dec(q):
    # cencored


def key_make():
    n, key1, key2 = 0, 1, 0
    while key2 < key1:
        n = random.randrange(1, 10)
        key1 = random.randrange(1, 100)
        key2 = random.randrange(1, 100)
    return n, key1, key2


p = ""
emp = input("plain text: ")
for character in emp:
    p += str(ord(character))
n, key1, key2 = key_make()

print(f"[WAR]p = {p} n = {n} key1 = {key1} key2 = {key2}")

q = enc(int(p), n, key1, key2)
dec(q)

```
:::

:::spoiler <b>readme.txt</b>
```text=
n = 3
key 1 = #cencored#
key 2 = 95
q = -200640142664324295933714
p = #cencored# (p is number)


if you get the p(decrypted q)
plz enter the p in the DH{here}


```
:::


Bài này đơn giản sẽ bắt ta tìm lại $p$ khi ta biết được $q$, $n$ và $\text{key}_2$, file thử thách sẽ cho ta biết được mối liên hệ giữa các biến này. Cách làm dưới đây của mình sẽ có thể dùng được kể cả khi các biến số khác $n$ trở nên rất lớn.

Dựa vào file đề bài ta có $\text{key}_1 < \text{key}_2$ và ta có công thức:

$$
\begin{align*}
    q &= \frac{p}{n+1}\text{key}_1^{n+1} - \frac{p}{n+1}\text{key}_2^{n+1} \\[5pt]
    \Leftrightarrow \text{key}_1^{n+1} &= \frac{(n+1)q}{p} + \text{key}_2^{n+1}
\end{align*}
$$

Như vậy ta sẽ tiến hành lặp qua từng ước $p_0$ của $(n+1)q$, nếu $\sqrt[n+1]{\frac{(n+1)q}{p} + \text{key}_2^{n+1}}$ là một số nguyên, thì $p = p_0$ chính là giá trị cần tìm của chúng ta.

Vậy là ta đã tìm ra được cách lấy **flag**, dưới đây là toàn bộ lời giải cho thử thách này:
:::spoiler <b style="color:#3e3031">solution.py</b>
```python=
from sage.all import divisors
from gmpy2 import iroot

key_2 = 95
q = -200640142664324295933714
n = 3

product = (n+1)*q

for divisor in divisors(product):
    expression = (product//divisor) + key_2**(n+1)
    if(expression < 0): continue

    root, is_exact = iroot(expression, n + 1)

    if(is_exact):
        p = divisor
        break

flag = "DH{" + f"{p}" + "}" 
print("[!] Got flag:", flag)
# [!] Got flag: DH{10112210997116104}

```
:::

:::success
**Flag: DH{10112210997116104}**
:::



## likeb64 (Level 1)
>[!Note] Description
>Dream studied base64 and created her own password.
>Get the flags from the following given passphrase!
>
>`IREHWYJZMEcGCODGMMbTENDDGcbGEMJZGEbGEZTFGYaGKNRTMIcGIMBSGRQTSNDDGAaWGYZRHEbGCNRQMUaDOMbEMRTGEYJYGUaWGOJQMYZHa===`
>
>The flag format is DH {...} This is it.
>
>hint: `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef`

Vì **hint** có độ dài là $32$ và văn bản mã này gần giống với **base64** nên rất có thể nó là **base32**, lên [**CyberChef**](https://gchq.github.io/CyberChef/) và kiểm tra thử ta thu được:

![image](https://hackmd.io/_uploads/HySGRDlJZg.png)

:::success
**Flag: DH{a9a8a8fc724c76b1916bfe64e63b8d024a94c05cc196a60e473ddfba855c90f2}**
:::



## No shift please! (Level 1)
>[!Note] Description
>I removed the ShiftRows feature from AES! Is this password really secure?
>The given AES\.py is equivalent to the code presented in Block Cipher: AES.
>- Exploit Tech: This is a problem to practice together in AES without ShiftRows.


:::spoiler <b>AES.py</b>
```python=
#!/usr/bin/env python3
# Name: AES

class AES_implemented:
    rcon = ([0x01, 0, 0, 0], 
            [0x02, 0, 0, 0], 
            [0x04, 0, 0, 0], 
            [0x08, 0, 0, 0], 
            [0x10, 0, 0, 0], 
            [0x20, 0, 0, 0], 
            [0x40, 0, 0, 0], 
            [0x80, 0, 0, 0], 
            [0x1b, 0, 0, 0], 
            [0x36, 0, 0, 0])
            
    gf_mul_2 = (
        0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 
        0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 
        0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 
        0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 
        0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 
        0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 
        0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, 
        0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 
        0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, 
        0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, 
        0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, 
        0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, 
        0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, 
        0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 
        0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, 
        0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5
    )
    
    gf_mul_3 = (
        0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, 
        0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, 
        0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, 
        0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, 
        0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, 
        0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, 
        0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, 
        0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, 
        0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, 
        0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, 
        0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, 
        0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, 
        0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, 
        0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, 
        0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, 
        0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a
    )
    
    gf_mul_9 = (
        0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, 
        0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, 
        0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 
        0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, 
        0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, 
        0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, 
        0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, 
        0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, 
        0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, 
        0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, 
        0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, 
        0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, 
        0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, 
        0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, 
        0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, 
        0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46
    )
    
    gf_mul_11 = (
        0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, 
        0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, 
        0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, 
        0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, 
        0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, 
        0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, 
        0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, 
        0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, 
        0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, 
        0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, 
        0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, 
        0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, 
        0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, 
        0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, 
        0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, 
        0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3
    )
    
    gf_mul_13 = (
        0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, 
        0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, 
        0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, 
        0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, 
        0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, 
        0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, 
        0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, 
        0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, 
        0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, 
        0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, 
        0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, 
        0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, 
        0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, 
        0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, 
        0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, 
        0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97
    )
    
    gf_mul_14 = (
        0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, 
        0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, 
        0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, 
        0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, 
        0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, 
        0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, 
        0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, 
        0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, 
        0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, 
        0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, 
        0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, 
        0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, 
        0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, 
        0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, 
        0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, 
        0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d
    )
    
    sbox =  (
        0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 
        0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 
        0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 
        0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 
        0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 
        0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 
        0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 
        0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 
        0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 
        0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 
        0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 
        0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 
        0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 
        0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 
        0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 
        0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 
    )
    
    sbox_inv = (
        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 __init__(self, key):
        assert isinstance(key, bytes) and len(key) == 16
        self._block_size = 16
        self._round = 10
        self._round_keys = self._expand_key(key)
        self._state = []
        
    def _transpose(self, m):
        return [m[4 * j + i] for i in range(4) for j in range(4)]
        
    def _xor(self, a, b):
        return [x ^ y for x, y in zip(a, b)]
        
    def _expand_key(self, key):
        round_keys = [[c for c in key]]
        for round_n in range(self._round):
            prev_round_key = round_keys[round_n]
            round_key = prev_round_key[-4:]
            round_key = round_key[1:] + round_key[:1]
            round_key = [self.sbox[i] for i in round_key]
            round_key = self._xor(
                self._xor(round_key, prev_round_key[:4]), self.rcon[round_n]
            )
            for i in range(0, 12, 4):
                round_key += self._xor(round_key[i : i + 4], prev_round_key[i + 4 : i + 8])
            round_keys.append(round_key)
            
        for round_n in range(self._round + 1):
            round_keys[round_n] = self._transpose(round_keys[round_n])
            
        return round_keys
        
    def _add_round_key(self, round_n):
        self._state = self._xor(self._state, self._round_keys[round_n])
        
    def _sub_bytes(self):
        self._state = [self.sbox[c] for c in self._state]
        
    def _sub_bytes_inv(self):
        self._state = [self.sbox_inv[c] for c in self._state]
        
    def _shift_rows(self):
        self._state = [
            self._state[0], self._state[1], self._state[2], self._state[3],
            self._state[5], self._state[6], self._state[7], self._state[4],
            self._state[10], self._state[11], self._state[8], self._state[9],
            self._state[15], self._state[12], self._state[13], self._state[14]
        ]
        
    def _shift_rows_inv(self):
        self._state = [
            self._state[0], self._state[1], self._state[2], self._state[3],
            self._state[7], self._state[4], self._state[5], self._state[6],
            self._state[10], self._state[11], self._state[8], self._state[9],
            self._state[13], self._state[14], self._state[15], self._state[12]
        ]
        
    def _mix_columns(self):
        s = [0] * self._block_size
        for i in range(4):
            s[i] = self.gf_mul_2[self._state[i]] ^ self.gf_mul_3[self._state[i + 4]] ^ self._state[i + 8] ^ self._state[i + 12]
            s[i + 4] = self._state[i] ^ self.gf_mul_2[self._state[i + 4]] ^ self.gf_mul_3[self._state[i + 8]] ^ self._state[i + 12]
            s[i + 8] = self._state[i] ^ self._state[i + 4] ^ self.gf_mul_2[self._state[i + 8]] ^ self.gf_mul_3[self._state[i + 12]]
            s[i + 12] = self.gf_mul_3[self._state[i]] ^ self._state[i + 4] ^ self._state[i + 8] ^ self.gf_mul_2[self._state[i + 12]]
        self._state = s
        
    def _mix_columns_inv(self):
        s = [0] * self._block_size
        for i in range(4):
            s[i] = self.gf_mul_14[self._state[i]] ^ self.gf_mul_11[self._state[i + 4]] ^ self.gf_mul_13[self._state[i + 8]] ^ self.gf_mul_9[self._state[i + 12]]
            s[i + 4] = self.gf_mul_9[self._state[i]] ^ self.gf_mul_14[self._state[i + 4]] ^ self.gf_mul_11[self._state[i + 8]] ^ self.gf_mul_13[self._state[i + 12]]
            s[i + 8] = self.gf_mul_13[self._state[i]] ^ self.gf_mul_9[self._state[i + 4]] ^ self.gf_mul_14[self._state[i + 8]] ^ self.gf_mul_11[self._state[i + 12]]
            s[i + 12] = self.gf_mul_11[self._state[i]] ^ self.gf_mul_13[self._state[i + 4]] ^ self.gf_mul_9[self._state[i + 8]] ^ self.gf_mul_14[self._state[i + 12]]
        self._state = s
        
    def _encrypt(self, plaintext):
        self._state = self._transpose(plaintext)
        
        self._add_round_key(0)
        for round_n in range(1, self._round):
            self._sub_bytes()
            self._shift_rows()
            self._mix_columns()
            self._add_round_key(round_n)
        self._sub_bytes()
        self._shift_rows()
        self._add_round_key(self._round)
        
        return bytes(self._transpose(self._state))
        
    def _decrypt(self, ciphertext):
        self._state = self._transpose(ciphertext)
        
        self._add_round_key(self._round)
        self._shift_rows_inv()
        self._sub_bytes_inv()
        for round_n in range(self._round - 1, 0, -1):
            self._add_round_key(round_n)
            self._mix_columns_inv()
            self._shift_rows_inv()
            self._sub_bytes_inv()
        self._add_round_key(0)
        
        return bytes(self._transpose(self._state))
        
    def encrypt(self, plaintext):
        assert len(plaintext) % self._block_size == 0
        
        ciphertext = b''
        for i in range(0, len(plaintext), self._block_size):
            ciphertext += self._encrypt(plaintext[i : i + self._block_size])
        return ciphertext
        
    def decrypt(self, ciphertext):
        assert len(ciphertext) % self._block_size == 0
        
        plaintext = b''
        for i in range(0, len(ciphertext), self._block_size):
            plaintext += self._decrypt(ciphertext[i : i + self._block_size])
        return plaintext
        
        
if __name__ == '__main__':
    import os
    from Crypto.Cipher import AES
    test = 0x1000
    for t in range(test):
        key = os.urandom(16)
        plain = os.urandom(16)
        
        assert AES_implemented(key).encrypt(plain) == AES.new(key, AES.MODE_ECB).encrypt(plain)
```
:::
:::spoiler <b>chall.py</b>
```python=
from AES import AES_implemented
import os

# For real AES without modification, this challenge is unsolvable with modern technology.
# But let's remove a step.
ret = lambda x: None
AES_implemented._shift_rows = ret
AES_implemented._shift_rows_inv = ret
# Will it make a difference?

secret = os.urandom(16)
key = os.urandom(16)

flag = open("flag.txt", "r").read()

cipher = AES_implemented(key)

secret_enc = cipher.encrypt(secret)
assert cipher.decrypt(secret_enc) == secret
print(f"enc(secret) = {bytes.hex(secret_enc)}")

while True:
	option = int(input("[1] encrypt, [2] decrypt: "))

	if option == 1: # Encryption
		plaintext = bytes.fromhex(input("Input plaintext to encrypt in hex: "))
		assert len(plaintext) == 16

		ciphertext = cipher.encrypt(plaintext)
		print(f"enc(plaintext) = {bytes.hex(ciphertext)}")

		if plaintext == secret:
			print(flag)
			exit()

	elif option == 2: # Decryption
		ciphertext = bytes.fromhex(input("Input ciphertext to decrypt in hex: "))
		assert len(ciphertext) == 16
		
		if ciphertext == secret_enc:
			print("No way!")
			continue
			
		plaintext = cipher.decrypt(ciphertext)
		print(f"dec(ciphertext) = {bytes.hex(plaintext)}")
```
:::

Như mô tả, bài này sử dụng **AES** không có thành phần **ShiftRows** để mã hóa. Do đó, ta có thể khai thác lỗ hổng này để khôi phục lại **flag**.
Về kiến thức mật mã khối **AES** thì các bạn nên đọc [ở đây](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) để hiểu những thứ sắp tới!

>[!Note] Cách chuyển chuỗi văn bản thành khối văn bản trong AES
> Trong AES, chúng ta sẽ đệm thêm văn bản rõ một số ký tự sao cho độ dài của văn bản rõ là bội của $16$, sau đó chúng ta sẽ chia văn bản rõ này thành các đoạn có độ dài $16$ và tiến hành biến các đoạn này thành khối văn bản kích thước $4 \times 4$.
> Với mỗi đoạn văn bản, ta sẽ tiến hành biến chúng thành khối văn bản bằng cách viết từng byte của đoạn văn bản thành từng cột, cả hai đều theo thứ tự từ trái sang phải.
> **Ví dụ:** Với đoạn văn bản `00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f`, ta chuyển đổi thành khối văn bản như sau:
> $$
> \begin{bmatrix}
    > 00 & 04 & 08 & 0c \\
    > 01 & 05 & 09 & 0d \\
    > 02 & 06 & 0a & 0e \\
    > 03 & 07 & 0b & 0f
> \end{bmatrix}
> $$

Với mã hóa khối **AES** mà không có **ShiftRows** thì với hai văn bản rõ mà khác nhau ở byte thứ $i$ ($i$ bắt đầu từ $1$) thì hai văn bản mã sẽ khác nhau $4$ byte liên tiếp ở vị trí thứ $4i - 3$ đến $4i$. Điều này đúng đơn giản là vì khi mất thành phần **ShiftRows**, mã hóa khối **AES** không còn thành phần nào xáo trộn các cột với nhau nên mỗi byte khác nhau của cặp văn bản rõ chỉ ảnh hưởng đến 4 byte trong cùng cột của cặp văn bản mã.

Sau khi biết được những tính chất này, ta có thể áp dụng nó để tiến hành giải thử thách này. Cụ thể, ta sẽ chọn ra văn bản mã `chosen_ciphertext_1 = encrypted_secret[:-2] + os.urandom(1).hex()` sao cho nó khác với `encrypted_secret`. Tiếp theo ta sẽ tiến hành gửi cho server để giải mã và thu lại được `plaintext_1`, `plaintext_1` lúc này sẽ có 12 byte đầu giống hệt với `secret`.
Làm tương tự với `chosen_ciphertext_2 = os.urandom(1).hex() + encrypted_secret[2:]`, ta thu được `plaintext_2` có 12 byte cuối giống hệt với `secret`.

Đến đây ta sẽ thu được `secret = plaintext_1[:8] + plaintext_2[8:]`, lúc này ta chỉ cần gửi secret cho server để lấy **flag** là xong!

Dưới đây là toàn bộ lời giải cho thử thách này:
:::spoiler <b style="color:#3e3031">solution.py</b>
```python=
from pwn import remote
import re, os

HOST = "host3.dreamhack.games"; PORT = 15823
connection = remote(HOST, PORT)

receive = connection.recvline().strip().decode()
encrypted_secret = re.search(r"[0-9a-f]{8,}", receive).group()

chosen_ciphertext_1 = encrypted_secret[:-2] + os.urandom(1).hex()
while(chosen_ciphertext_1 == encrypted_secret):
    chosen_ciphertext_1 = encrypted_secret[:-2] + os.urandom(1).hex()

connection.sendlineafter(b"[1] encrypt, [2] decrypt: ", b"2")
connection.sendlineafter(b"Input ciphertext to decrypt in hex: ", chosen_ciphertext_1.encode())

receive_1 = connection.recvline().strip().decode()
plaintext_1 = re.search(r"[0-9a-f]{8,}", receive_1).group()



chosen_ciphertext_2 = os.urandom(1).hex() + encrypted_secret[2:]
while(chosen_ciphertext_2 == encrypted_secret):
    chosen_ciphertext_2 = os.urandom(1).hex() + encrypted_secret[2:]

connection.sendlineafter(b"[1] encrypt, [2] decrypt: ", b"2")
connection.sendlineafter(b"Input ciphertext to decrypt in hex: ", chosen_ciphertext_2.encode())

receive_2 = connection.recvline().strip().decode()
plaintext_2 = re.search(r"[0-9a-f]{8,}", receive_2).group()


secret = plaintext_1[:8] + plaintext_2[8:]

connection.sendlineafter(b"[1] encrypt, [2] decrypt: ", b"1")
connection.sendlineafter(b"Input plaintext to encrypt in hex: ", secret.encode())
connection.recvline()

flag = connection.recvline().strip().decode()

connection.success(f"Got flag: {flag}")
connection.close()

# [+] Got flag: DH{This_is_actually_pretty_safe_for_a_4btye_cipher.._But_unsafe_ofc}
```
:::

:::success
**Flag: DH{This_is_actually_pretty_safe_for_a_4btye_cipher.._But_unsafe_ofc}**
:::



## 40 Birthdays (Level 1 - Brief Explanation)

>[!Note] Description
>Please find two friends with the same birthday!
>- This is a problem we practice together in Exploit Tech: Birthday Paradox.

:::spoiler <b>chall.py</b>
```python=
import hashlib

def birthday_hash(msg):
	return hashlib.sha256(msg).digest()[12:17]

msg1 = bytes.fromhex(input("Input message 1 in hex: "))
msg2 = bytes.fromhex(input("Input message 2 in hex: "))

if msg1 == msg2:
	print("Those two messages are the same! >:(")

elif birthday_hash(msg1) != birthday_hash(msg2):
	print("Those two messages don't have the same birthday! T.T")

else:
	print("Finally! They have the same birthday ^o^")
	print(open("flag.txt").read())
```
:::

Bài này chỉ cần chọn ngẫu nhiên các tin nhắn và lưu trữ các `birthday_hash` vào một dictionary để lưu trữ, nếu hai tin nhắn nào mà có cùng `birthday_hash` thì ta đã tìm được hai tin nhắn cần tìm.

Dưới đây là toàn bộ lời giải cho thử thách này:
:::spoiler <b style="color:#3e3031">solution.py</b>
```python=
import hashlib, random
from Crypto.Util.number import long_to_bytes
from tqdm import trange
from pwn import remote


def birthday_hash(msg: bytes) -> bytes:
    return hashlib.sha256(msg).digest()[12:17]

def find_collision(iterations = 5_000_000) -> tuple:
    archive = {}

    for _ in trange(iterations):
        message = random.randbytes(32).rjust(16, b'\x00')
        hash = birthday_hash(message)

        if hash in archive:
            previous_message = archive[hash]

            if(previous_message != message):
                return message.hex(), previous_message.hex()
        else: archive[hash] = message

message_1, message_2 = find_collision()

# print(message_1)
# print(message_2)

HOST = "host8.dreamhack.games"; PORT = 10082

connection = remote(HOST, PORT)

connection.sendlineafter(b"Input message 1 in hex: ", message_1.encode())
connection.sendlineafter(b"Input message 2 in hex: ", message_2.encode())

connection.recvline()

flag = connection.recvline().strip().decode()
connection.success(f"Got flag: {flag}")

# [+] Got flag: DH{4f5abd8e1667959edf3e143e38c10beda47bc262198ea2e3ffce2c637d708843}
```
:::

:::success
**Flag: DH{4f5abd8e1667959edf3e143e38c10beda47bc262198ea2e3ffce2c637d708843}**
:::



## Insecure Seed (Level 1)

>[!Note] Description
>One day, Dream's car was hacked!!
>The cause is that it is an old car that has been shipped, so it is said that it is vulnerable to security...
>Can we find the vulnerability for the exact cause?
>Flag format: `DH{}`

:::spoiler <b>seed.py</b>
```python=
# DREAMHACK CHALLENGE - INSECURE SEED #

import os
import random
from typing import List

class Seed:
    def __init__ (self) -> None:
        self.a: int = os.urandom(1)

    @staticmethod
    def GenerateSeed() -> List[int]:
        seed: bytearray = bytearray(random.getrandbits(8) for x in range(4))
        return list(seed)
        
    def CalculateKey(self, seed: List[int]) -> bool:
        key: List[int] = [0] * len(seed)
        
        for i in range(len(seed)):
            result: bytes = bytes([self.a[j] ^ seed[i] for j in range(len(self.a))])
            key[i] = int.from_bytes(result, byteorder='little')
        return key
    
    def Authentication(self, seed: List[int], k: List[int]) -> bool:
        key = self.CalculateKey(seed)
        
        if key == k:
            print('Correct!!')
            return True
        else:
            print('Invalid Key!!')
            return False

if __name__ == "__main__":
    s = Seed()
    
    seed = s.GenerateSeed()
    print(f"Seed: {seed}")
    
    while 1:
        k = input("Key: ").strip().split() # input ex) 41 42 43 44
        kl = [int(x) for x in k]
        
        if s.Authentication(seed, kl):
            break
    
    print('DH{fake_flag}')
```
:::

Đề bài đơn giản sẽ tạo một `seed` là một danh sách gồm $4$ số nguyên không vượt quá $8$ bit, sau đó họ sẽ dùng phép **XOR** của từng phần tử với một số bí mật `a` trong đoạn $[0; 255]$ để tạo khóa.
Nhiệm vụ của chúng ta sẽ làm từ `seed` mà server cho để tìm được đúng khóa để lấy **flag**.

Ta sẽ chọn đại một số bí mật `a` nào đó và tính `chosen_key` của ta và gửi cho server, xác suất mà khóa tự chọn của chúng ta trùng với khóa của server là $\frac{1}{256}$. Ta thấy xác suất này không quá lớn nhưng cũng không quá nhỏ nên ta chỉ cần tiến hành ngắt kết nối nếu thất bại và tiến hành kết nối lại và gửi lại cho đến khi khóa của ta trùng với khóa của server là được.
Xác suất để ta có ít nhất một lần khóa của ta trùng với khóa của server sau $n$ lần thử là $1 - \left( \frac{255}{256} \right)^n$, vì vậy ta càng thử nhiều lần thì khả năng thành công của chúng ta lại càng cao.

Dưới đây là toàn bộ lời giải cho thử thách này:
:::spoiler <b style="color:#3e3031">solution.py</b>
```python=
from pwn import *
import json

HOST = "host8.dreamhack.games"; PORT = 20185

while(True):
    connection = remote(HOST, PORT)
    connection.recvuntil(b"Seed: ")
    seed = json.loads(connection.recvline())

    key = ' '.join([str(c) for c in seed])
    connection.sendlineafter(b"Key: ", key.encode())

    receive = connection.recvline().strip().decode()
    if receive == "Correct!!":
        flag = connection.recvline().strip().decode()
        break

    connection.close()

connection.success(f"Got flag: {flag}")
connection.close()

# [+] Got flag: DH{th3s_1s_insecure_seed!!}
```
:::

:::success
**Flag: DH{th3s_1s_insecure_seed!!}**
:::



## Basic of crypto (Level 1 - Brief Explanation)

>[!Note] Description
>Let's go back to the basics. What is the fundamental method of breaking a cryptosystem?

:::spoiler <b>chall.py</b>
```python=
import os
import random

def clock():
    h, m = 0, 0
    H = list(range(8))
    M = list(range(8))
    random.shuffle(H)
    random.shuffle(M)

    while True:
        cal=8*H[h]+M[m]
        m+=1
        if m>=8:
            h+=1; h%=8; m=0
        yield cal


with open("flag.txt", "rb") as f:
    data = f.read()

# Assume that all bytes in flag.txt are printable ASCII letters, with given format.
assert all(((0x41 <= x <= 0x5a) or (0x61<= x <= 0x7a) or (x==0x5f)) for x in data[3:63])
assert data[:3]==b'DH{'
assert data[63]==ord('}')

with open("flag.txt.enc", "wb") as f:
    for a, b in zip(data, clock()):
        f.write(bytes([a ^ b]))

os.remove("flag.txt")
```
:::

:::spoiler <b>flag.txt.enc</b> (Vì định dạng đặc biệt của file này nên không viết ra đây được)
:::

Bài này chúng ta chỉ cần dựa vào định dạng của flag để tiến hành khôi phục một phần của `H` và `M`, sau đó chúng ta sẽ tiến hành brute-force các phần tử còn lại trong hai mảng này và đồng thời khôi phục **flag**.

Dưới đây là toàn bộ lời giải cho thử thách này:
:::spoiler <b style="color:#3e3031">solution.py</b>
```python=
import string, itertools
from Crypto.Util.strxor import strxor
from tqdm import tqdm


with open("flag.txt.enc", "rb") as file:
    encrypted = file.read()

H = [0 for _ in range(8)]
M = [0 for _ in range(8)]

# Khôi phục giai đoạn 1
for i, byte in enumerate(b"DH{"):
    M[i] = (byte ^ encrypted[i]) % 8

H[0] = ((b'D'[0] ^ encrypted[0]) - M[0])//8

M[7] = (b'}'[0] ^ encrypted[63]) % 8
H[7] = ((b'}'[0] ^ encrypted[63]) - M[7])//8

# Khôi phục giai đoạn 2
flag_letters = string.ascii_letters + '_'

M_remain_bytes = list(set(range(8)) - set(M[:3] + [M[-1]]))
H_remain_bytes = list(set(range(8)) - set(H[:1] + [H[-1]]))


for M_permutation in tqdm(itertools.permutations(M_remain_bytes), desc="Recovering data...", total=24):
    five_first_byte_clocks = [8*H[0] + M_permutation[i] for i in range(4)]

    possible_flag_bytes = [five_first_byte_clocks[i] ^ encrypted[i + 3] for i in range(4)]


    for i in range(4):
        M[i+3] = M_permutation[i]

    flag = "DH{" + bytes(possible_flag_bytes).decode() + chr((8*H[0] + M[7]) ^ encrypted[7]) 
    for H_permutation in itertools.permutations(H_remain_bytes):
        for i in range(6):
            H[i + 1] = H_permutation[i]

        possible_flag = flag

        for i in range(1, 8):
            byte_clocks = bytes([8*H[i] + M[j] for j in range(8)])

            possible_flag += strxor(byte_clocks, encrypted[8*i: 8*(i+1)]).decode()
            
        if(all([c in flag_letters for c in possible_flag[3:-1]]) == False): continue

        flag = possible_flag
        break

print("[!] Got flag:", flag)
# [!] Got flag: DH{Bruteforcing_is_the_fundamental_of_cracking_the_cryptography}
```
:::

:::success
**Flag: DH{Bruteforcing_is_the_fundamental_of_cracking_the_cryptography}**
:::



## MD5 Cryptex (Level 1 - Brief Explanation)

>[!Note] Description
>Open the MD5 Cryptex and get a flag!

:::spoiler <b>app.py</b>
```python=
from flask import Flask, render_template, request
import secrets
import hashlib

app=Flask(__name__)

letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
password=''
target=None
key = [secrets.randbelow(36) for _ in range(6)]
for i in range(0,6,1):
    password+=letters[key[i]]
target=hashlib.md5(password.encode()).digest().hex()

@app.route('/')
def index():
    return render_template('main.html',target=target,error='Enter the correct password!')

@app.route('/submit', methods=['POST'])
def submit_code():
    data=str(request.form.get('val1'))+str(request.form.get('val2'))+str(request.form.get('val3'))+str(request.form.get('val4'))+str(request.form.get('val5'))+str(request.form.get('val6'))
    attempt=hashlib.md5(data.encode()).digest().hex()
    if target==attempt:
        return render_template('main.html',target=target,error='DH{**flag**}')
    else:
        return render_template('main.html',target=target,error='Incorrect password')

if __name__=='__main__':
    app.run(debug=False,host='0.0.0.0',port=5000)
```
:::

>[!Note] Thông tin thêm
>Ngoài file `app.py` ra, còn một số file thừa thãi nữa nhưng vì nó không liên quan đến thử thách nên ta chỉ quan tâm đến file này.

Bài này đơn giản chỉ là một bài brute-force để tìm ra được `password`, tuy nhiên ta có thể sử dụng đa tiến trình để tăng tốc độ brute-force.

Dưới đây là toàn bộ lời giải cho thử thách này:
:::spoiler <b style="color:#3e3031">solution.py</b>
```python=
import itertools, hashlib
from tqdm import tqdm
import multiprocessing

letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
target = "698558d0dbffa03b6c9a32643fa46716"
total_attempts = len(letters)**6

# 1. TẠO HÀM CHECK HASH RIÊNG BIỆT
# Hàm này phải nằm ở top-level (không nằm trong hàm khác)
# để multiprocessing có thể "pickle" (gửi) nó cho các tiến trình con.
def check_hash(combo):
    password = ''.join(combo)
    attempt = hashlib.md5(password.encode()).digest().hex()
    
    if attempt == target:
        return password  # Trả về password nếu tìm thấy
    return None          # Trả về None nếu không tìm thấy

# 2. SỬ DỤNG __name__ == "__main__" (RẤT QUAN TRỌNG)
# Cần phải có khối này để ngăn code bị lặp vô tận
# khi các tiến trình con được sinh ra.
if __name__ == "__main__":

    print(f"Bắt đầu brute-force {total_attempts} trường hợp...")
    
    # Tạo iterator (chưa bắt đầu tính toán)
    iterator = itertools.product(letters, repeat=6)
    
    # Ước lượng một kích thước "chunk" hợp lý
    # Chunksize là số lượng công việc gửi cho mỗi tiến trình trong một lần.
    # Con số này cần tinh chỉnh, nhưng 50,000 là một điểm khởi đầu tốt.
    chunksize = 50000 
    
    # 3. TẠO POOL VÀ SỬ DỤNG 'imap_unordered'
    # multiprocessing.Pool() sẽ tự động sử dụng tất cả các nhân CPU của bạn
    with multiprocessing.Pool() as pool:
        
        # pool.imap_unordered là lựa chọn tốt nhất ở đây:
        # - imap: Xử lý iterator (tiết kiệm bộ nhớ)
        # - unordered: Trả về kết quả ngay khi xong, không cần đợi theo thứ tự.
        
        # Bọc 'imap_unordered' trong 'tqdm' để hiển thị progress bar
        # 'total' là bắt buộc cho tqdm khi dùng iterator
        results = tqdm(pool.imap_unordered(check_hash, iterator, chunksize=chunksize), 
                       desc="Brute-forcing hash...", 
                       total=total_attempts)
        
        # 4. LẶP QUA KẾT QUẢ VÀ DỪNG LẠI KHI TÌM THẤY
        for found_password in results:
            if found_password:
                print(f"\n[+] Hash found! Password is: {found_password}")
                
                # Rất quan trọng: Dừng tất cả các tiến trình con
                # ngay khi tìm thấy kết quả để không lãng phí CPU.
                pool.terminate() 
                break
        else:
            # Chỉ chạy nếu vòng for kết thúc bình thường (không 'break')
            print(f"\n[-] Not found!")
```
:::

:::success
**Flag: DH{MD5_I$_Vu1n3r4ble_h4sh_functi0n!}**
:::

## Summary

Dưới đây là những bài đầu tiên của mình trên **Dreamhack**, vì vậy nó rất dễ và ngắn gọn.

Xin chào và hẹn gặp lại các bạn ở các phần tiếp theo 👋 