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

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