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