# I. Crypto
## 1. eXciting Outpost Recon
### Description
- Challenge source:
```python=
from hashlib import sha256
import os
LENGTH = 32
def encrypt_data(data, k):
data += b'\x00' * (-len(data) % LENGTH)
encrypted = b''
for i in range(0, len(data), LENGTH):
chunk = data[i:i+LENGTH]
for a, b in zip(chunk, k):
encrypted += bytes([a ^ b])
k = sha256(k).digest()
return encrypted
key = os.urandom(32)
with open('plaintext.txt', 'rb') as f:
plaintext = f.read()
assert plaintext.startswith(b'Great and Noble Leader of the Tariaki') # have to make sure we are aptly sycophantic
with open('output.txt', 'w') as f:
enc = encrypt_data(plaintext, key)
f.write(enc.hex())
```
- Challenge output:
```python=
fd94e649fc4c898297f2acd4cb6661d5b69c5bb51448687f60c7531a97a0e683072bbd92adc5a871e9ab3c188741948e20ef9afe8bcc601555c29fa6b61de710a718571c09e89027413e2d94fd3126300eff106e2e4d0d4f7dc8744827731dc6ee587a982f4599a2dec253743c02b9ae1c3847a810778a20d1dff34a2c69b11c06015a8212d242ef807edbf888f56943065d730a703e27fa3bbb2f1309835469a3e0c8ded7d676ddb663fdb6508db9599018cb4049b00a5ba1690ca205e64ddc29fd74a6969b7dead69a7341ff4f32a3f09c349d92e0b21737f26a85bfa2a10d
```
### Solution
- Trước hết ta cần hiểu được thuật toán mã hóa trong bài, ở đây đơn giản là `xor` với một `key` 32 bytes cho trước. Tất nhiên là `plaintext` sẽ được pad lên để `len(pt) % 32 == 0`.
- Tuy nhiên, `plaintext` được mã hóa bắt đầu bằng `Great and Noble Leader of the Tariaki` (37 bytes) nên mình có thể leak `key` từ đoạn này. Sau khi có được `key` chỉ cần `xor` ngược lại là có được `flag`.
- Code:
```python=
from hashlib import sha256
LENGTH = 32
def exploit(leak, data):
key = b''
for i in range(0, len(data), LENGTH):
chunk = data[i:i+LENGTH]
for a, b in zip(chunk, leak):
key += bytes([a ^ b])
return key
def decrypt(data, k):
pt = b''
for i in range(0, len(data), LENGTH):
chunk = data[i:i+LENGTH]
for a, b in zip(chunk, k):
pt += bytes([a ^ b])
k = sha256(k).digest()
return pt
leak = b'Great and Noble Leader of the Tariaki'
text = 'fd94e649fc4c898297f2acd4cb6661d5b69c5bb51448687f60c7531a97a0e683072bbd92adc5a871e9ab3c188741948e20ef9afe8bcc601555c29fa6b61de710a718571c09e89027413e2d94fd3126300eff106e2e4d0d4f7dc8744827731dc6ee587a982f4599a2dec253743c02b9ae1c3847a810778a20d1dff34a2c69b11c06015a8212d242ef807edbf888f56943065d730a703e27fa3bbb2f1309835469a3e0c8ded7d676ddb663fdb6508db9599018cb4049b00a5ba1690ca205e64ddc29fd74a6969b7dead69a7341ff4f32a3f09c349d92e0b21737f26a85bfa2a10d'
text = bytes.fromhex(text)
leak = leak[:LENGTH]
key = exploit(leak, text[:LENGTH])
print(key)
print(decrypt(text, key).decode())
```
### Flag
> ~~`HTB{x0r_n0t_s0_s4f3!}`~~
---
## 2. Bloom Bloom
### Description
- Challenge source:
```python=
from random import randint, shuffle
from Crypto.Util.number import getPrime
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import sha256
from secret import *
import os
assert sha256(KEY).hexdigest().startswith('786f36dd7c9d902f1921629161d9b057')
class BBS:
def __init__(self, bits, length):
self.bits = bits
self.out_length = length
def reset_params(self):
self.state = randint(2, 2 ** self.bits - 2)
self.m = getPrime(self.bits//2) * getPrime(self.bits//2) * randint(1, 2)
def extract_bit(self):
self.state = pow(self.state, 2, self.m)
return str(self.state % 2)
def gen_output(self):
self.reset_params()
out = ''
for _ in range(self.out_length):
out += self.extract_bit()
return out
def encrypt(self, msg):
out = self.gen_output()
key = sha256(out.encode()).digest()
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
return (iv.hex(), cipher.encrypt(pad(msg.encode(), 16)).hex())
encryptor = BBS(512, 256)
enc_messages = []
for msg in MESSAGES:
enc_messages.append([encryptor.encrypt(msg) for _ in range(10)])
enc_flag = AES.new(KEY, AES.MODE_ECB).encrypt(pad(FLAG, 16))
with open('output.txt', 'w') as f:
f.write(f'{enc_messages}\n')
f.write(f'{enc_flag.hex()}\n')
```
### Solution
- Chall cho một file `output` siêu dài được gen bằng thuật toán BBS (là một thuật toán gen random). Flow là chall gen ra `key` (`key` 256 bit từ BBS trước khi băm bằng SHA256) và `iv` (được leak) rồi mã hóa các `msg` bằng AES-CBC. `Flag` thì được mã hóa bằng AES-ECB và `KEY` được hint trong các `msg`.
- Lần đầu tiên đọc class này mình cũng khá choáng do hơi dài, tuy nhiên sau khi thử chạy mình nhận ra mỗi `msg` trong `MESSAGES` được mã hóa tới 10 lần, và xác suất để BBS gen ra một `key` 256 bit 0 là khá lớn. Đây cũng là cách để chúng ta giải mã các `msg` trong file `output`. Code như sau:
```python=
from output import *
from Crypto.Cipher import AES
from hashlib import sha256
out = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
key_sha = sha256(out.encode()).digest()
lst = []
for i in enc_messages:
for j in i:
try:
iv = bytes.fromhex(j[0])
cipher = AES.new(key_sha, AES.MODE_CBC, iv)
lst.append(cipher.decrypt(bytes.fromhex(j[1])).decode())
break
except:
pass
for i in lst:
print(i, end='\n\n')
```
sẽ nhận được các đoạn text:
```=
Welcome! If you see this you have successfully decrypted the first message. To get the symmetric key that decrypts the flag you need to do the following:
1. Collect all 5 shares from these messages
2. Use them to interpolate the polynomial in a finite field that will be revealed in another message
3. Convert the constant term of the polynomial to bytes and use it to decrypt the flag. Here is your first share!
Share#1#: (1, 27006418753792019267647881709336369603809025474153761185424552629526746515909)
Keep up the good work! Offered say visited elderly and. Waited period are played family man formed. He ye body or made on pain part meet. You one delay nor begin our folly abode. By disposed replying mr me unpacked no. As moonlight of my resolving unwilling. Turned it up should no valley cousin he. Speaking numerous ask did horrible packages set. Ashamed herself has distant can studied mrs.
Share#2#: (2, 76590454267924193303526931251420387908730989759486987968207839464816350274449)
Only a few more are left! Of be talent me answer do relied. Mistress in on so laughing throwing endeavor occasion welcomed. Gravity sir brandon calling can. No years do widow house delay stand. Prospect six kindness use steepest new ask. High gone kind calm call as ever is. Introduced melancholy estimating motionless on up as do. Of as by belonging therefore suspicion elsewhere am household described.
Share#3#: (3, 67564500698667187837224046797217120599664632018519685208508601443605280795068)
You are almost there! Not him old music think his found enjoy merry. Listening acuteness dependent at or an. Apartments thoroughly unsatiable terminated sex how themselves. She are ten hours wrong walls stand early. Domestic perceive on an ladyship extended received do. Why jennings our whatever his learning gay perceive. Is against no he without subject. Bed connection unreserved preference partiality not unaffected.
Share#4#: (4, 57120102994643471094254225269948720992016639286627873340589938545214763610538)
Congratulations!!! Not him old music think his found enjoy merry. Listening acuteness dependent at or an. Apartments thoroughly unsatiable terminated how themselves. She are ten hours wrong walls stand early. Domestic perceive on an ladyship extended received do. You need to interpolate the polynomial in the finite field GF(88061271168532822384517279587784001104302157326759940683992330399098283633319).
Share#5#: (5, 87036956450994410488989322365773556006053008613964544744444104769020810012336)
```
- Đại khái chúng ta sẽ phải gom các shares trên rồi `interpolate the polynomial` trong trường `GF(88061271168532822384517279587784001104302157326759940683992330399098283633319)`, sau đó chuyển phần tử cố định trong đa thức đó thành bytes rồi sử dụng nó để giải mã `flag`. Mình sẽ tóm tất cả các shares lại thành một đoạn cho dễ nhìn nhé:
```=
Share#1#: (1, 27006418753792019267647881709336369603809025474153761185424552629526746515909)
Share#2#: (2, 76590454267924193303526931251420387908730989759486987968207839464816350274449)
Share#3#: (3, 67564500698667187837224046797217120599664632018519685208508601443605280795068)
Share#4#: (4, 57120102994643471094254225269948720992016639286627873340589938545214763610538)
Share#5#: (5, 87036956450994410488989322365773556006053008613964544744444104769020810012336)
```
- Btw, về định nghĩa của `Polynomial Interpolation` mình sẽ để ở [đây](https://en.wikipedia.org/wiki/Polynomial_interpolation). Nếu dịch ra Tiếng Việt thì `interpolate polynomial` có nghĩa là nội suy đa thức, nói cách khác là bằng cách nào đó chúng ta suy đoán ra một hàm số mà đồ thị của nó đi qua gần đúng tất cả các điểm dữ liệu cho trước, giúp nhận xét được quy luật và dự đoán các điểm dữ liệu tiếp theo. Tóm lại, với 5 `shares` cho trước, chúng ta có thể tìm lại được ít nhất một hàm số bậc cao nhất là 4 thỏa mãn, về phương diện này thì có khá nhiều phương pháp, mình sẽ trình bày 2 phương pháp chính là giải hệ phương trình hệ số đa thức và sử dụng `Lagrange Polynomial`. Sau khi tìm được đa thức này, `KEY` sẽ chính là hệ số tự do.
#### Giải hệ phương trình
- Giả sử đa thức bậc 4 của chúng ta có dạng:
```=
p(X) = a * X^4 + b * X^3 + c * X^2 + d * X + e
```
- Vậy từ 5 `shares` kia mình có thể lập được 5 phương trình thành hệ và giải ra `(a, b, c, d, e)`, trong đó `e` chính là `KEY` (viết hệ hơi dài nên mình giải bằng ma trận cho nhanh). À nhớ là giải hệ trong trường `GF(88061271168532822384517279587784001104302157326759940683992330399098283633319)`.
- Code:
```python=
from Crypto.Util.number import *
from hashlib import sha256
from output import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import *
P = GF(88061271168532822384517279587784001104302157326759940683992330399098283633319)
s1 = (1, 27006418753792019267647881709336369603809025474153761185424552629526746515909)
s2 = (2, 76590454267924193303526931251420387908730989759486987968207839464816350274449)
s3 = (3, 67564500698667187837224046797217120599664632018519685208508601443605280795068)
s4 = (4, 57120102994643471094254225269948720992016639286627873340589938545214763610538)
s5 = (5, 87036956450994410488989322365773556006053008613964544744444104769020810012336)
'''
p(X) = a * X^4 + b * X^3 + c * X^2 + d * X + e
B = A * coef
-> coef = A.solve_right(B)
-> coef = A.inverse()*B
'''
A = matrix(P, [
[s1[0]**4, s1[0]**3, s1[0]**2, s1[0], 1],
[s2[0]**4, s2[0]**3, s2[0]**2, s2[0], 1],
[s3[0]**4, s3[0]**3, s3[0]**2, s3[0], 1],
[s4[0]**4, s4[0]**3, s4[0]**2, s4[0], 1],
[s5[0]**4, s5[0]**3, s5[0]**2, s5[0], 1]
])
B = vector([s1[1], s2[1], s3[1], s4[1], s5[1]])
coef = A.solve_right(B) # coef = A.inverse()*B
e = int(coef[-1])
KEY = long_to_bytes(e)
assert sha256(KEY).hexdigest().startswith('786f36dd7c9d902f1921629161d9b057')
flag = AES.new(KEY, AES.MODE_ECB).decrypt(bytes.fromhex(enc_flag))
print(unpad(flag, 16).decode())
```
#### Phương pháp đa thức Lagrange
- Về phương pháp này thì có thể tham khảo ở [đây](https://en.wikipedia.org/wiki/Lagrange_polynomial). Còn một phương pháp đa thức Newton nữa nhưng không được implement trong sage nên tạm thời mình không viết hi :monkey:
- Code:
```python=
from Crypto.Util.number import *
from hashlib import sha256
from output import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import *
P = GF(88061271168532822384517279587784001104302157326759940683992330399098283633319)
s1 = (1, 27006418753792019267647881709336369603809025474153761185424552629526746515909)
s2 = (2, 76590454267924193303526931251420387908730989759486987968207839464816350274449)
s3 = (3, 67564500698667187837224046797217120599664632018519685208508601443605280795068)
s4 = (4, 57120102994643471094254225269948720992016639286627873340589938545214763610538)
s5 = (5, 87036956450994410488989322365773556006053008613964544744444104769020810012336)
R = P['x']
e = R.lagrange_polynomial([s1, s2, s3, s4, s5]).constant_coefficient()
KEY = long_to_bytes(int(e))
assert sha256(KEY).hexdigest().startswith('786f36dd7c9d902f1921629161d9b057')
flag = AES.new(KEY, AES.MODE_ECB).decrypt(bytes.fromhex(enc_flag))
print(unpad(flag, 16).decode())
```
### Flag
> ~~`HTB{what_a_cool_random_number_generator_by_bluuuuuum_bluuuuuum_and_shuuuuuub_i_implemented_it_securely_didnt_i?}`~~
---
## 3. Living with Elegance
### Description
- Challenge server:
```python=
from secrets import token_bytes, randbelow
from Crypto.Util.number import bytes_to_long as b2l
class ElegantCryptosystem:
def __init__(self):
self.d = 16
self.n = 256
self.S = token_bytes(self.d)
def noise_prod(self):
return randbelow(2*self.n//3) - self.n//2
def get_encryption(self, bit):
A = token_bytes(self.d)
b = self.punc_prod(A, self.S) % self.n
e = self.noise_prod()
if bit == 1:
return A, b + e
else:
return A, randbelow(self.n)
def punc_prod(self, x, y):
return sum(_x * _y for _x, _y in zip(x, y))
def main():
FLAGBIN = bin(b2l(open('flag.txt', 'rb').read()))[2:]
crypto = ElegantCryptosystem()
while True:
idx = input('Specify the index of the bit you want to get an encryption for : ')
if not idx.isnumeric():
print('The index must be an integer.')
continue
idx = int(idx)
if idx < 0 or idx >= len(FLAGBIN):
print(f'The index must lie in the interval [0, {len(FLAGBIN)-1}]')
continue
bit = int(FLAGBIN[idx])
A, b = crypto.get_encryption(bit)
print('Here is your ciphertext: ')
print(f'A = {b2l(A)}')
print(f'b = {b}')
if __name__ == '__main__':
main()
```
### Solution
- Trước tiên chúng ta cần hiểu được flow của chương trình. Chall sẽ thực hiện convert `flag` từ bytes sang nhị phân (`flagbin`), và cung cấp cho chúng ta một chức năng duy nhất, đó là xem tính chất của từng bit, tất nhiên `idx` của bit đó phải nằm trong đoạn `len(flagbin)`.
- Mình sẽ phân tích thuật toán của bài. Chall sẽ gen ra một thằng `S` cố định 16 bytes, thằng `A` 16 bytes ngẫu nhiên. Số `b` là kết quả của phép nhân `A` và `S` mod 256, bên cạnh đó còn có số `e` là số nhiễu. Nếu bit chúng ta muốn xem là `1` thì chall nhả ra `A` và `b + e`, ngược lại nếu bit là `0` thì nhả ra `A` và một số random trong đoạn `[0, 255]`.
- Flow khá đơn giản nhưng cách giải bài có lẽ còn đơn giản hơn. Tạm thời mình sẽ bỏ qua vấn đề bit là `0` hay `1` và phân tích `b + e` kia. Số `b` chắc chắn luôn nhỏ hơn 256 và số `e` nhiễu kia được random bằng cách này:
```
e = randbelow(2*self.n//3) - self.n//2
Với n = 256 thì:
e = randbelow(170) - 128
```
- Từ đây mình nhận ra không gian mẫu của `b + e` có chứa cả số âm, điều này giúp chúng ta biết được bit tại đó chắc chắn là `1` và phân biệt với trường hợp `0` kia. Btw sau khi test thì mình biết được `len(flagbin) = 470`.
- Vậy hướng của mình sẽ là brute force từng `idx` khoảng 30-50 lần, nếu xuất hiện số âm thì đó là bit `1`, ngược lại là bit `0`. Tuy nhiên nếu gửi riêng lẻ thì thời gian brute sẽ rất lâu và bị server chặn nữa nên mình sẽ gửi gộp nhiều lần vào rồi phân tích cho nhanh.
- Code:
```python=
from Crypto.Util.number import long_to_bytes as l2b
from tqdm import tqdm
from pwn import *
HOST = '94.237.63.201'
PORT = 46362
LENGTH = 471
FLAG = ['0'] * LENGTH
msg = b''
for i in range(LENGTH):
msg += b''.join([str(i).encode() + b'\n' for _ in range(30)])
r = remote(HOST, PORT)
r.sendlineafter(b' : ', msg[:-1])
for idx in tqdm(range(LENGTH)):
for i in range(30):
r.recvuntil(b'b = ')
b = int(r.recvuntil(b'\n', drop=True))
if b<0:
FLAG[idx] = '1'
continue
flag = ''.join(FLAG)
flag = l2b(int(flag, 2)).decode()
print(flag)
```
### Flag
> ~~`HTB{distributed_error_not_a673544b1ea303eaadb7d9a9fec3b0ef}`~~
---
## 4. Not that random
### Description
- Challenge server:
```python=
from Crypto.Util.number import *
from Crypto.Random import random, get_random_bytes
from hashlib import sha256
from secret import FLAG
def success(s):
print(f'\033[92m[+] {s} \033[0m')
def fail(s):
print(f'\033[91m\033[1m[-] {s} \033[0m')
MENU = '''
Make a choice:
1. Buy flag (-500 coins)
2. Buy hint (-10 coins)
3. Play (+5/-10 coins)
4. Print balance (free)
5. Exit'''
def keyed_hash(key, inp):
return sha256(key + inp).digest()
def custom_hmac(key, inp):
return keyed_hash(keyed_hash(key, b"Improving on the security of SHA is easy"), inp) + keyed_hash(key, inp)
def impostor_hmac(key, inp):
return get_random_bytes(64)
class Casino:
def __init__(self):
self.player_money = 100
self.secret_key = get_random_bytes(16)
def buy_flag(self):
if self.player_money >= 500:
self.player_money -= 500
success(f"Winner winner chicken dinner! Thank you for playing, here's your flag :: {FLAG}")
else:
fail("You broke")
def buy_hint(self):
self.player_money -= 10
hash_input = bytes.fromhex(input("Enter your input in hex :: "))
if random.getrandbits(1) == 0:
print("Your output is :: " + custom_hmac(self.secret_key, hash_input).hex())
else:
print("Your output is :: " + impostor_hmac(self.secret_key, hash_input).hex())
def play(self):
my_bit = random.getrandbits(1)
my_hash_input = get_random_bytes(32)
print("I used input " + my_hash_input.hex())
if my_bit == 0:
my_hash_output = custom_hmac(self.secret_key, my_hash_input)
else:
my_hash_output = impostor_hmac(self.secret_key, my_hash_input)
print("I got output " + my_hash_output.hex())
answer = int(input("Was the output from my hash or random? (Enter 0 or 1 respectively) :: "))
if answer == my_bit:
self.player_money += 5
success("Lucky you!")
else:
self.player_money -= 10
fail("Wrong!")
def print_balance(self):
print(f"You have {self.player_money} coins.")
def main():
print("Welcome to my online casino! Let's play a game!")
casino = Casino()
while casino.player_money > 0:
print(MENU)
option = int(input('Option: '))
if option == 1:
casino.buy_flag()
elif option == 2:
casino.buy_hint()
elif option == 3:
casino.play()
elif option == 4:
casino.print_balance()
elif option == 5:
print("Bye.")
break
print("The house always wins, sorry ):")
if __name__ == '__main__':
main()
```
### Solution
- Trước hết mình vẫn luôn phải hiểu chall đã. Trong chall này mình được tham gia vào một casino với 100 coins cho trước, và bằng cách nào đó phải kiếm được 500 coins để mua `flag`. Điều kiện khá đơn giản, tuy nhiên mấu chốt nằm ở cách kiếm tiền mình sẽ phân tích dưới đây.
- Chall cung cấp cho chúng ta 3 chức năng chính (tổng có 5 cái):
- Buy flag (-500)
- Buy hint (-10)
- Play (+5 / -10)
#### Buy hint
- Chức năng này thó của mình 10 coins và cho phép chúng ta nhập `input` vào rồi sẽ nhả ngược lại cho mình hoặc là `custom_hmac` hoặc là `impostor_hmac`. `impostor_hmac` thì không có gì đáng nói, chỉ là 64 bytes ngẫu nhiên vô giá trị. `custom_hmac(input)` là 64 bytes có quy luật.
#### Play
- Chức năng này random ra `my_hash_input` 32 bytes cho mình. Sau đó cũng nhả ra một dãy 64 bytes và bắt chúng ta phải đoán xem đó là `custom_hmac(my_hash_input)` hay `impostor_hmac` , nếu đúng thì ting ting 5 coins, ngược lại nếu sai thì pay 10 coins.
#### Phương hướng
- Để biết được đó là `custom_hmac` hay `impostor_hmac` thì cần dựa vào `input` của chúng ta. Bởi `impostor_hmac` chỉ là dãy 64 bytes ngẫu nhiên, còn `custom_hmac` có quy luật như sau:
```=
custom_hmac(input) = sha256(sha256(key + leak) + input) + sha256(key + input)
-> custom_hmac(my_hash_input) = sha256(sha256(key + leak) + my_hash_input) + sha256(key + my_hash_input)
```
- Lưu ý trong cả hai chức năng thì `key` được cố định và đây cũng chính là cách để mình kiếm tiền.
- Đầu tiên mình sẽ `Buy hint` từ server với `input = leak = b"Improving on the security of SHA is easy"`, việc chúng ta nhận được `custom_hmac` khá hên xui.
- Khi có được `custom_hmac(leak)`, mình sẽ cắt lấy 32 bytes cuối để nhận được `sha256(key + leak)`. Sau đó mình sẽ đem phần này chạy qua `sha256` để khôi phục lại `sha256(sha256(key + leak) + my_hash_input)` do `my_hash_input` đã biết, chính xác đây chính là phần đầu 32 bytes của `custom_hmac(my_hash_input)` giúp mình phân biệt với `impostor_hmac`.
- Code:
```python=
from pwn import *
from hashlib import sha256
from tqdm import tqdm
HOST = '94.237.49.212'
PORT = 30279
leak = b"Improving on the security of SHA is easy"
def keyed_hash(key, inp):
return sha256(key + inp).digest()
r = remote(HOST, PORT)
lst = []
for _ in tqdm(range(10)):
r.sendlineafter(b'Option: ', b'2')
r.sendlineafter(b' :: ', bytes.hex(leak).encode())
r.recvuntil(b' :: ')
get = r.recvuntil(b'\n', drop=True).decode()
if get in lst:
exploit = bytes.fromhex(get)[32:]
break
else:
lst.append(get)
for _ in tqdm(range(100)):
r.sendlineafter(b'Option: ', b'3')
r.recvuntil(b'I used input ')
my_hash_input = r.recvuntil(b'\n', drop=True).decode()
my_hash_input = bytes.fromhex(my_hash_input)
r.recvuntil(b'I got output ')
my_hash_output = r.recvuntil(b'\n', drop=True).decode()
my_hash_output = bytes.fromhex(my_hash_output)
check = keyed_hash(exploit, my_hash_input)
answer = b'0' if check == my_hash_output[:32] else b'1'
r.sendlineafter(b' :: ', answer)
r.sendlineafter(b'Option: ', b'1')
r.recvuntil(b' :: ')
flag = r.recvuntil(b'\n', drop=True)
print(flag)
```
### Flag
> ~~`HTB{#rule_of_thumb___do_not_roll_your_own_hash_based_message_authentication_codes___#_ab272f24245e73ec6c19b44331f587fb}`~~
# II. Blockchain