---
# System prepended metadata

title: 'Cyber Apocalypse 2024: Hacker Royale'

---


# Cyber Apocalypse 2024: Hacker Royale

{%hackmd @themes/orangeheart %}


## Dynastic
![image](https://hackmd.io/_uploads/Byt1RFTT6.png)

source.py
```python=
from secret import FLAG
from random import randint

def to_identity_map(a):
    return ord(a) - 0x41

def from_identity_map(a):
    return chr(a % 26 + 0x41)

def encrypt(m):
    c = ''
    for i in range(len(m)):
        ch = m[i]
        if not ch.isalpha():
            ech = ch
        else:
            chi = to_identity_map(ch)
            ech = from_identity_map(chi + i)
        c += ech
    return c

with open('output.txt', 'w') as f:
    f.write('Make sure you wrap the decrypted text with the HTB flag format :-]\n')
    f.write(encrypt(FLAG))
```

**output.txt**
```
Make sure you wrap the decrypted text with the HTB flag format :-]
DJF_CTA_SWYH_NPDKK_MBZ_QPHTIGPMZY_KRZSQE?!_ZL_CN_PGLIMCU_YU_KJODME_RYGZXL
```

Đọc source code ta có:
`ech = chr((ord(m[i]) - 0x41)+i) % 26 + 0x41)`
--> `ord(ech) - 0x41 = ord(m[i]) - 0x41 + i - k*26`
--> `ord(m[i]) = ord(ech) - i + k*26`

**script:**
```python=
from random import randint

c = 'DJF_CTA_SWYH_NPDKK_MBZ_QPHTIGPMZY_KRZSQE?!_ZL_CN_PGLIMCU_YU_KJODME_RYGZXL'
p = ''
for i in range(len(c)):
    if not c[i].isalpha():
            p=p+c[i]
    else:
        for k in range(5):
            tmp = chr(ord(c[i]) + k*26 - i)
            if tmp.isalpha():
                p = p + tmp
                break

print(p)

```
Flag: `HTB{DID_YOU_KNOW_ABOUT_THE_TRITHEMIUS_CIPHER?!_IT_IS_SIMILAR_TO_CAESAR_CIPHER}`

Một số challange tiếp theo khá đơn giản nên mình sẽ lướt qua luôn nhé !!!

**Blunt:** Tìm share_secret trong Diffie-Hellman Elliptic Curves. Mình chỉ cần sử dụng logarit rời rạc để tính được khóa bí mật của Bob hoặc Alice đều được.

**Makeshift:** Mã hóa bằng cách hoán vị từng 3 kí tự một. Để giải mã ta chỉ cần làm ngược lại khá là đơn giản :))))).

## Arranged
Đây là sourcecode
**main.sage**
```python3=
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.number import long_to_bytes
from hashlib import sha256

from secret import FLAG, p, b, priv_a, priv_b

F = GF(p)
E = EllipticCurve(F, [726, b])
G = E(926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367, 4856802955780604241403155772782614224057462426619061437325274365157616489963087648882578621484232159439344263863246191729458550632500259702851115715803253)

A = G * priv_a
B = G * priv_b

print(A)
print(B)

C = priv_a * B

assert C == priv_b * A

# now use it as shared secret
secret = C[0]

hash = sha256()
hash.update(long_to_bytes(secret))

key = hash.digest()[16:32]
iv = b'u\x8fo\x9aK\xc5\x17\xa7>[\x18\xa3\xc5\x11\x9en'
cipher = AES.new(key, AES.MODE_CBC, iv)

encrypted = cipher.encrypt(pad(FLAG, 16))
print(encrypted)

```

**output.txt**
```python3=
(6174416269259286934151093673164493189253884617479643341333149124572806980379124586263533252636111274525178176274923169261099721987218035121599399265706997 : 2456156841357590320251214761807569562271603953403894230401577941817844043774935363309919542532110972731996540328492565967313383895865130190496346350907696 : 1)
(4226762176873291628054959228555764767094892520498623417484902164747532571129516149589498324130156426781285021938363575037142149243496535991590582169062734 : 425803237362195796450773819823046131597391930883675502922975433050925120921590881749610863732987162129269250945941632435026800264517318677407220354869865 : 1)
b'V\x1b\xc6&\x04Z\xb0c\xec\x1a\tn\xd9\xa6(\xc1\xe1\xc5I\xf5\x1c\xd3\xa7\xdd\xa0\x84j\x9bob\x9d"\xd8\xf7\x98?^\x9dA{\xde\x08\x8f\x84i\xbf\x1f\xab'
```

Target ở bài này là mình cần phải đi tìm được các giá trị còn lại của đường cong. Mình đã có 3 điểm trên đường cong và `a`.

Để tìm được `b` thì chỉ cần thay một điểm vào và giải phương trình thì ta tìm được ngay.

Ta có:
$y^2 = x^3 + a*x + b \pmod p$, tức là đường cong này được xác định trên trường $F_p$
suy ra: $y^2 - x^3 - a*x - b = k*p$, với 3 điểm có sẵn mình lần lượt thay thế vào thì mình có 3 cái bội của `p`. Tiếp đến sử dụng GCD để tìm ước chung của 3 cái bội đó thì ta tìm được p.

Sau khi đã có các dữ kiện cần thiết mình chỉ cần đi tìm share_secret và nhận flag thôi.

**script:**
```python3=
from sage.all import *


x1 = 926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367
y1 = 4856802955780604241403155772782614224057462426619061437325274365157616489963087648882578621484232159439344263863246191729458550632500259702851115715803253
x2 = 6174416269259286934151093673164493189253884617479643341333149124572806980379124586263533252636111274525178176274923169261099721987218035121599399265706997
y2 = 2456156841357590320251214761807569562271603953403894230401577941817844043774935363309919542532110972731996540328492565967313383895865130190496346350907696
x3 = 4226762176873291628054959228555764767094892520498623417484902164747532571129516149589498324130156426781285021938363575037142149243496535991590582169062734
y3 = 425803237362195796450773819823046131597391930883675502922975433050925120921590881749610863732987162129269250945941632435026800264517318677407220354869865

a = 726
#b = var('b')
#solve(y2**2 - x2**3 - a*x2 - b, b)
b = -235389841633113518256718847719878733250125469551464659906955192105157068458892240496251208387305226684383977028692545364635616542911806071089784509634298138989466830088876060296948154657939206289908022191050803100386883123914692738193940269023567868365152264359631600117242932969716150281155438233466191095095609214364837462241270538753838231525244229345683421423856137208707912136351137768367596194932966862290676506665434291342481388359433898092926158324340379
K1 = (y1**2 - x1**3 - a*x1 - b)
K2 = (y2**2 - x2**3 - a*x2 - b)
K3 = (y3**2 - x3**3 - a*x3 - b)

p = gcd(gcd(K1, K2), K3)
F = GF(p) 
E = EllipticCurve(F, [726, b])
G = E([x1, y1])
A = E([x2, y2])
B = E([x3, y3])
a = G.discrete_log(A)
S = a*B
S
```

```python3=
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.number import long_to_bytes
from hashlib import sha256

secret = 926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367
ciphertext = b'''V\x1b\xc6&\x04Z\xb0c\xec\x1a\tn\xd9\xa6(\xc1\xe1\xc5I\xf5\x1c\xd3\xa7\xdd\xa0\x84j\x9bob\x9d"\xd8\xf7\x98?^\x9dA{\xde\x08\x8f\x84i\xbf\x1f\xab'''

hash = sha256()
hash.update(long_to_bytes(secret))

key = hash.digest()[16:32]
iv = b'u\x8fo\x9aK\xc5\x17\xa7>[\x18\xa3\xc5\x11\x9en'
cipher = AES.new(key, AES.MODE_CBC, iv)

flag = cipher.decrypt(ciphertext)
print(flag)

# b'HTB{0rD3r_mUsT_b3_prEs3RveD_!!@!}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
```

## Iced Tea
**source.py**
```python3=
import os
from Crypto.Util.Padding import pad
from Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b
from enum import Enum
from pwn import xor

class Mode(Enum):
    ECB = 0x01
    CBC = 0x02

class Cipher:
    def __init__(self, key, iv=None):
        self.BLOCK_SIZE = 64
        self.KEY = [b2l(key[i:i+self.BLOCK_SIZE//16]) for i in range(0, len(key), self.BLOCK_SIZE//16)]
        self.DELTA = 0x9e3779b9
        self.IV = iv
        if self.IV:
            self.mode = Mode.CBC
        else:
            self.mode = Mode.ECB
    
    def _xor(self, a, b):
        return b''.join(bytes([_a ^ _b]) for _a, _b in zip(a, b))

    def encrypt(self, msg):
        msg = pad(msg, self.BLOCK_SIZE//8)
        blocks = [msg[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg), self.BLOCK_SIZE//8)]
        
        ct = b''
        if self.mode == Mode.ECB:
            for pt in blocks:
                ct += self.encrypt_block(pt)
        elif self.mode == Mode.CBC:
            X = self.IV
            for pt in blocks:
                enc_block = self.encrypt_block(self._xor(X, pt))
                ct += enc_block
                X = enc_block
        return ct

    def encrypt_block(self, msg):
        m0 = b2l(msg[:4])
        m1 = b2l(msg[4:])
        K = self.KEY
        msk = (1 << (self.BLOCK_SIZE//2)) - 1

        s = 0
        for i in range(32):
            s += self.DELTA
            m0 += ((m1 << 4) + K[0]) ^ (m1 + s) ^ ((m1 >> 5) + K[1])
            m0 &= msk
            m1 += ((m0 << 4) + K[2]) ^ (m0 + s) ^ ((m0 >> 5) + K[3])
            m1 &= msk
        
        m = ((m0 << (self.BLOCK_SIZE//2)) + m1) & ((1 << self.BLOCK_SIZE) - 1) # m = m0 || m1

        return l2b(m)
    def decrypt_block(self, msg):
        m = b2l(msg)
        m0 = (m >> (self.BLOCK_SIZE//2)) & ((1 << (self.BLOCK_SIZE//2)) - 1)
        m1 = m & ((1 << (self.BLOCK_SIZE//2)) - 1)
        K = self.KEY
        msk = (1 << (self.BLOCK_SIZE//2)) - 1

        s = 0xC6EF3720
        for i in range(32):
            m1 -= ((m0 << 4) + K[2]) ^ (m0 + s) ^ ((m0 >> 5) + K[3])
            m1 &= msk
            m0 -= ((m1 << 4) + K[0]) ^ (m1 + s) ^ ((m1 >> 5) + K[1])
            m0 &= msk
            s -= self.DELTA

        return l2b(m0) + l2b(m1)

    def decrypt(self, msg):
        msg = pad(msg, self.BLOCK_SIZE//8)
        blocks = [msg[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg), self.BLOCK_SIZE//8)]
        
        ct = b''
        if self.mode == Mode.ECB:
            for pt in blocks:
                ct += self.decrypt_block(pt)
        elif self.mode == Mode.CBC:
            X = self.IV
            for pt in blocks:
                enc_block = self.decrypt_block(self._xor(pt, X))
                ct += enc_block
                X = pt
        return ct

if __name__ == '__main__':
    key = bytes.fromhex("850c1413787c389e0b34437a6828a1b2")
    cipher = Cipher(key)
    Ciphertext = "b36c62d96d9daaa90634242e1e6c76556d020de35f7a3b248ed71351cc3f3da97d4d8fd0ebc5c06a655eb57f2b250dcb2b39c8b2000297f635ce4a44110ec66596c50624d6ab582b2fd92228a21ad9eece4729e589aba644393f57736a0b870308ff00d778214f238056b8cf5721a843"
    c = bytes.fromhex(Ciphertext)
    ct = cipher.decrypt(c)

    print(c)


```

**output.txt**
```
Key : 850c1413787c389e0b34437a6828a1b2
Ciphertext : b36c62d96d9daaa90634242e1e6c76556d020de35f7a3b248ed71351cc3f3da97d4d8fd0ebc5c06a655eb57f2b250dcb2b39c8b2000297f635ce4a44110ec66596c50624d6ab582b2fd92228a21ad9eece4729e589aba644393f57736a0b870308ff00d778214f238056b8cf5721a843
```

Mình mò trên mạng thì biết được đây là [Tiny Encryption Algorithm](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm). Một loại block cipher đơn giản các bước mã hóa cũng đơn giản không kém.

Mình có luôn hàm decrypt.
```python=
import os
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b
from enum import Enum

class Mode(Enum):
    ECB = 0x01
    CBC = 0x02

class Cipher:
    def __init__(self, key, iv=None):
        self.BLOCK_SIZE = 64
        self.KEY = [b2l(key[i:i+self.BLOCK_SIZE//16]) for i in range(0, len(key), self.BLOCK_SIZE//16)]
        self.DELTA = 0x9e3779b9
        self.IV = iv
        if self.IV:
            self.mode = Mode.CBC
        else:
            self.mode = Mode.ECB
    
    def _xor(self, a, b):
        return b''.join(bytes([_a ^ _b]) for _a, _b in zip(a, b))

    def encrypt(self, msg):
        msg = pad(msg, self.BLOCK_SIZE//8)
        blocks = [msg[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg), self.BLOCK_SIZE//8)]
        
        ct = b''
        if self.mode == Mode.ECB:
            for pt in blocks:
                ct += self.encrypt_block(pt)
        elif self.mode == Mode.CBC:
            X = self.IV
            for pt in blocks:
                enc_block = self.encrypt_block(self._xor(X, pt))
                ct += enc_block
                X = enc_block
        return ct

    def encrypt_block(self, msg):
        m0 = b2l(msg[:4])
        m1 = b2l(msg[4:])
        K = self.KEY
        msk = (1 << (self.BLOCK_SIZE//2)) - 1

        s = 0
        for i in range(32):
            s += self.DELTA
            m0 += ((m1 << 4) + K[0]) ^ (m1 + s) ^ ((m1 >> 5) + K[1])
            m0 &= msk
            m1 += ((m0 << 4) + K[2]) ^ (m0 + s) ^ ((m0 >> 5) + K[3])
            m1 &= msk
        
        m = ((m0 << (self.BLOCK_SIZE//2)) + m1) & ((1 << self.BLOCK_SIZE) - 1) # m = m0 || m1

        return l2b(m)

    def decrypt_block(self, ct):
        c0 = b2l(ct[:4])
        c1 = b2l(ct[4:])
        K = self.KEY
        msk = (1 << (self.BLOCK_SIZE//2)) - 1

        s = 0xC6EF3720
        for i in range(32):
            c1 -= ((c0 << 4) + K[2]) ^ (c0 + s) ^ ((c0 >> 5) + K[3])
            c1 &= msk
            c0 -= ((c1 << 4) + K[0]) ^ (c1 + s) ^ ((c1 >> 5) + K[1]) 
            c0 &= msk
            s -= self.DELTA

        c = ((c0 << (self.BLOCK_SIZE//2)) + c1) & ((1 << self.BLOCK_SIZE) - 1)

        return l2b(c)

    def decrypt(self, ct):
        blocks = [ct[i:i+self.BLOCK_SIZE//8] for i in range(0, len(ct), self.BLOCK_SIZE//8)]

        msg = b''
        if self.mode == Mode.ECB:
            for ct in blocks:
                msg += self.decrypt_block(ct)
        elif self.mode == Mode.CBC:
            X = self.IV
            for ct in blocks:
                msg += self._xor(self.decrypt_block(ct), X)
                X = ct        
        return msg
```

chỉ cần chạy đoạn code trên và lấy flag thôi!!!

## Tsayaki

source.py
```python=
from tea import Cipher as TEA
from secret import IV, FLAG
import os

ROUNDS = 10

def show_menu():
    print("""
============================================================================================
|| I made this decryption oracle in which I let users choose their own decryption keys.   ||
|| I think that it's secure as the tea cipher doesn't produce collisions (?) ... Right?   ||
|| If you manage to prove me wrong 10 times, you get a special gift.                      ||
============================================================================================
""")

def run():
    show_menu()

    server_message = os.urandom(20)
    print(f'Here is my special message: {server_message.hex()}')
    
    used_keys = []
    ciphertexts = []
    for i in range(ROUNDS):
        print(f'Round {i+1}/10')
        try:
            ct = bytes.fromhex(input('Enter your target ciphertext (in hex) : '))
            assert ct not in ciphertexts

            for j in range(4):
                key = bytes.fromhex(input(f'[{i+1}/{j+1}] Enter your encryption key (in hex) : '))
                assert len(key) == 16 and key not in used_keys
                used_keys.append(key)
                cipher = TEA(key, IV)
                enc = cipher.encrypt(server_message)
                if enc != ct:
                    print(f'Hmm ... close enough, but {enc.hex()} does not look like {ct.hex()} at all! Bye...')
                    exit()
        except:
            print('Nope.')
            exit()
            
        ciphertexts.append(ct)

    print(f'Wait, really? {FLAG}')


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

Challenge này sử dụng TEA đã được nói đến ở chall trước.

Việc chúng ta cần làm là gửi đến `target ciphertext`. Sau đó gửi 4 key liên tiếp sao cho chúng mã hóa `server_mesage` thành `target ciphertext`. Vượt qua được 10 rounds thì lụm flag.

Đọc [link](https://www.tayloredge.com/reference/Mathematics/VRAndem.pdf) này mình đã tìm ra cách xử lí. Ở phần 3.5 **Equivalent Key**

Đây là trường hợp các khóa tương đương của mã hóa TEA. Từ một khóa ban đầu ta có thể tạo thêm được 3 khóa tương đương. Nghĩa là nếu cùng sử dụng 4 khóa này thì sẽ cho ra cũng một ciphertext.

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


Đầu tiên mình cần tìm được IV để lấy `target ciphertext`.
Giả sử ta gửi đến server một `key` và `target ciphertext` lại sai (lúc này đã biết được ciphertext đâu). Thì server sẽ trả về cho ta ciphertext sau khi mã hóa `plaintext` của server.

Cùng nhìn lại hàm decrypt:
```python
def decrypt(self, ct):
        blocks = [ct[i:i+self.BLOCK_SIZE//8] for i in range(0, len(ct), self.BLOCK_SIZE//8)]

        msg = b''
        if self.mode == Mode.ECB:
            for ct in blocks:
                msg += self.decrypt_block(ct)
        elif self.mode == Mode.CBC:
            X = self.IV
            for ct in blocks:
                msg += self._xor(self.decrypt_block(ct), X)
                X = ct        
        return msg
```

Ta chỉ cần lấy `msg` xor lại với `ct` thì có IV.

```python=
c = bytes.fromhex("8c0acbde4a1af63579313db874ab79a991fbaad4a459e4a5")
key = bytes.fromhex("9d6e095e19614e4f0bade6546f99d105")
msg = bytes.fromhex("0bc8f872e85a549c9815e69e1be5774631837362")
cipher = Cipher(key)
from pwn import xor
block1 = cipher.decrypt(c[:8])
iv = xor(block1, msg[:8])
print(iv)
# iv = b'\r\xdd\xd2w<\xf4\xb9\x08'
```

Tiếp đến mình viết hàm để tạo thành các key tương đương:
```python=
CONST = bytes.fromhex("80000000")

def gen_key():
    keys = []
    key = os.urandom(16)
    key1, key2, key3, key4 = [key[i:i+4] for i in range(0, 16, 4)]
    keys = [
        b''.join([key1, key2, key3, key4]),
        b''.join([key1, key2, xor(key3, CONST), xor(key4, CONST)]),
        b''.join([xor(key1, CONST), xor(key2, CONST), key3 , key4 ]),
        b''.join([xor(key1, CONST), xor(key2, CONST), xor(key3, CONST), xor(key4, CONST)])
    ]
    return keys
```

**script**
```python=
import os
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b
from enum import Enum

class Mode(Enum):
    ECB = 0x01
    CBC = 0x02

class Cipher:
    def __init__(self, key, iv=None):
        self.BLOCK_SIZE = 64
        self.KEY = [b2l(key[i:i+self.BLOCK_SIZE//16]) for i in range(0, len(key), self.BLOCK_SIZE//16)]
        self.DELTA = 0x9e3779b9
        self.IV = iv
        if self.IV:
            self.mode = Mode.CBC
        else:
            self.mode = Mode.ECB
    
    def _xor(self, a, b):
        return b''.join(bytes([_a ^ _b]) for _a, _b in zip(a, b))

    def encrypt(self, msg):
        msg = pad(msg, self.BLOCK_SIZE//8)
        blocks = [msg[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg), self.BLOCK_SIZE//8)]
        
        ct = b''
        if self.mode == Mode.ECB:
            for pt in blocks:
                ct += self.encrypt_block(pt)
        elif self.mode == Mode.CBC:
            X = self.IV
            for pt in blocks:
                enc_block = self.encrypt_block(self._xor(X, pt))
                ct += enc_block
                X = enc_block
        return ct

    def encrypt_block(self, msg):
        m0 = b2l(msg[:4])
        m1 = b2l(msg[4:])
        K = self.KEY
        msk = (1 << (self.BLOCK_SIZE//2)) - 1

        s = 0
        for i in range(32):
            s += self.DELTA
            m0 += ((m1 << 4) + K[0]) ^ (m1 + s) ^ ((m1 >> 5) + K[1])
            m0 &= msk
            m1 += ((m0 << 4) + K[2]) ^ (m0 + s) ^ ((m0 >> 5) + K[3])
            m1 &= msk
        
        m = ((m0 << (self.BLOCK_SIZE//2)) + m1) & ((1 << self.BLOCK_SIZE) - 1) # m = m0 || m1

        return l2b(m)

    def decrypt_block(self, ct):
        c0 = b2l(ct[:4])
        c1 = b2l(ct[4:])
        K = self.KEY
        msk = (1 << (self.BLOCK_SIZE//2)) - 1

        s = 0xC6EF3720
        for i in range(32):
            c1 -= ((c0 << 4) + K[2]) ^ (c0 + s) ^ ((c0 >> 5) + K[3])
            c1 &= msk
            c0 -= ((c1 << 4) + K[0]) ^ (c1 + s) ^ ((c1 >> 5) + K[1]) 
            c0 &= msk
            s -= self.DELTA

        c = ((c0 << (self.BLOCK_SIZE//2)) + c1) & ((1 << self.BLOCK_SIZE) - 1)

        return l2b(c)

    def decrypt(self, ct):
        blocks = [ct[i:i+self.BLOCK_SIZE//8] for i in range(0, len(ct), self.BLOCK_SIZE//8)]

        msg = b''
        if self.mode == Mode.ECB:
            for ct in blocks:
                msg += self.decrypt_block(ct)
        elif self.mode == Mode.CBC:
            X = self.IV
            for ct in blocks:
                msg += self._xor(self.decrypt_block(ct), X)
                X = ct        
        return msg
    

from pwn import remote, xor
import os

CONST = bytes.fromhex("80000000")
IV = b'\r\xdd\xd2w<\xf4\xb9\x08'

def gen_key():
    keys = []
    key = os.urandom(16)
    key1, key2, key3, key4 = [key[i:i+4] for i in range(0, 16, 4)]
    keys = [
        b''.join([key1, key2, key3, key4]),
        b''.join([key1, key2, xor(key3, CONST), xor(key4, CONST)]),
        b''.join([xor(key1, CONST), xor(key2, CONST), key3 , key4 ]),
        b''.join([xor(key1, CONST), xor(key2, CONST), xor(key3, CONST), xor(key4, CONST)])
    ]
    return keys
    
io = remote('83.136.254.223', 32915)

io.recvuntil(b'Here is my special message:')
server_message = io.recvuntil(b'\n', drop = True).decode()

def payload(key):
    cipher = Cipher(key, IV)
    enc = cipher.encrypt(bytes.fromhex(server_message))
    return enc.hex()

for i in range(10):
    keys = gen_key()
    io.recvuntil(b'Enter your target ciphertext (in hex) : ')
    io.sendline(payload(keys[0]).encode())
    for key in keys:
        
        io.recvuntil(b'Enter your encryption key (in hex) : ')
        io.sendline(key.hex().encode())
    
io.recvlines()
        
```

## Partial Tenacity
source.py
```python=
from secret import FLAG
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

class RSACipher:
    def __init__(self, bits):
        self.key = RSA.generate(bits)
        self.cipher = PKCS1_OAEP.new(self.key)
    
    def encrypt(self, m):
        return self.cipher.encrypt(m)

    def decrypt(self, c):
        return self.cipher.decrypt(c)

cipher = RSACipher(1024)

enc_flag = cipher.encrypt(FLAG)

with open('output.txt', 'w') as f:
    f.write(f'n = {cipher.key.n}\n')
    f.write(f'ct = {enc_flag.hex()}\n')
    f.write(f'p = {str(cipher.key.p)[::2]}\n')
    f.write(f'q = {str(cipher.key.q)[1::2]}')

```

output.txt

```
n = 118641897764566817417551054135914458085151243893181692085585606712347004549784923154978949512746946759125187896834583143236980760760749398862405478042140850200893707709475167551056980474794729592748211827841494511437980466936302569013868048998752111754493558258605042130232239629213049847684412075111663446003
ct = 7f33a035c6390508cee1d0277f4712bf01a01a46677233f16387fae072d07bdee4f535b0bd66efa4f2475dc8515696cbc4bc2280c20c93726212695d770b0a8295e2bacbd6b59487b329cc36a5516567b948fed368bf02c50a39e6549312dc6badfef84d4e30494e9ef0a47bd97305639c875b16306fcd91146d3d126c1ea476
p = 151441473357136152985216980397525591305875094288738820699069271674022167902643
q = 15624342005774166525024608067426557093567392652723175301615422384508274269305
```

Đọc đoạn code trên mình nhận thấy p và q đã bị khuyết các số. Nó kiểu như này:
```python=
leak_p = "1?5?1?4?4?1?4?7?3?3?5?7?1?3?6?1?5?2?9?8?5?2?1?6?9?8?0?3?9?7?5?2?5?5?9?1?3?0?5?8?7?5?0?9?4?2?8?8?7?3?8?8?2?0?6?9?9?0?6?9?2?7?1?6?7?4?0?2?2?1?6?7?9?0?2?6?4?3"
leak_q = "?1?5?6?2?4?3?4?2?0?0?5?7?7?4?1?6?6?5?2?5?0?2?4?6?0?8?0?6?7?4?2?6?5?5?7?0?9?3?5?6?7?3?9?2?6?5?2?7?2?3?1?7?5?3?0?1?6?1?5?4?2?2?3?8?4?5?0?8?2?7?4?2?6?9?3?0?5?"
```

Cách mà mình hay sử dụng để giải quyết các bài kiểu như thế này đó chính là dựa vào:
 - Cho 2 số a, b với a*b = n, ta có: 
Số chữ số cuối của a * số chữ số cuối của b = số chữ số cuối của n.

**script:**
```python=
from Crypto.Util.number import long_to_bytes
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP


# p = "151441473357136152985216980397525591305875094288738820699069271674022167902643"
# leak_p = ''
# for i in p:
#     leak_p += i + '?'
    
# q = "15624342005774166525024608067426557093567392652723175301615422384508274269305"
# leak_q = ''
# for i in q:
#     leak_q += '?' + i 
# print(leak_p)
# print(leak_q)

leak_p = "1?5?1?4?4?1?4?7?3?3?5?7?1?3?6?1?5?2?9?8?5?2?1?6?9?8?0?3?9?7?5?2?5?5?9?1?3?0?5?8?7?5?0?9?4?2?8?8?7?3?8?8?2?0?6?9?9?0?6?9?2?7?1?6?7?4?0?2?2?1?6?7?9?0?2?6?4?3"
leak_q = "?1?5?6?2?4?3?4?2?0?0?5?7?7?4?1?6?6?5?2?5?0?2?4?6?0?8?0?6?7?4?2?6?5?5?7?0?9?3?5?6?7?3?9?2?6?5?2?7?2?3?1?7?5?3?0?1?6?1?5?4?2?2?3?8?4?5?0?8?2?7?4?2?6?9?3?0?5?"
n = 118641897764566817417551054135914458085151243893181692085585606712347004549784923154978949512746946759125187896834583143236980760760749398862405478042140850200893707709475167551056980474794729592748211827841494511437980466936302569013868048998752111754493558258605042130232239629213049847684412075111663446003
ct = "7f33a035c6390508cee1d0277f4712bf01a01a46677233f16387fae072d07bdee4f535b0bd66efa4f2475dc8515696cbc4bc2280c20c93726212695d770b0a8295e2bacbd6b59487b329cc36a5516567b948fed368bf02c50a39e6549312dc6badfef84d4e30494e9ef0a47bd97305639c875b16306fcd91146d3d126c1ea476"
e = 65537
def guess(a, b, i, p, q):
    conditon = 1
    if b == '?':
        a, b = b, a
        p, q = q, p
        conditon = 0
    for _ in range(10):
        if str(int(str(_) + str(p)) * int(str(b) + str(q)))[-i:] == str(n)[-i:]:
            a = str(_)
            break
        
    if conditon:
        return a + p, b + q
    else:
        return b + q, a + p
    
p = ''
q = ''
index = 1
for i, j in zip(leak_p[::-1], leak_q[::-1]):
    p, q = guess(i, j, index, p, q)
    index += 1
    
p = int(p)
q = int(q)
assert p * q == n

phi = (p-1)*(q-1)
d = pow(e, -1, phi)

private_key = RSA.construct([n, e, d])

cipher = PKCS1_OAEP.new(private_key)
m = cipher.decrypt(bytes.fromhex(ct))
print(m)
```

