---
# System prepended metadata

title: GOOGLE CTF 2024

---

# GOOGLE CTF 2024

## Desfunctional

Mình chỉ giải được bài này trong lúc giải đang hoạt động, còn mấy bài khác khoai quá. Bài này mình thấy khá là hay liên quan tới tính chất của DES.

```python=
import signal
from Crypto.Util.number import*
import os
import random
import sys
from Crypto.Cipher import DES3


class Desfunctional:
    def __init__(self):
        self.key = os.urandom(24)
        self.iv = os.urandom(8)
        self.flipped_bits = set(range(0, 192, 8))
        self.challenge = os.urandom(64)
        self.counter = 128

    def get_flag(self, plain):
        if plain == self.challenge:
            with open("flag.txt", "rb") as f:
                FLAG = f.read()
            return FLAG
        raise Exception("Not quite right")

    def get_challenge(self):
        cipher = DES3.new(self.key, mode=DES3.MODE_CBC, iv=self.iv)
        return cipher.encrypt(self.challenge)

    def corruption(self):
        if len(self.flipped_bits) == 192:
            self.flipped_bits = set(range(0, 192, 8))
        remaining = list(set(range(192)) - self.flipped_bits)
        num_flips = random.randint(1, len(remaining))
        self.flipped_bits = self.flipped_bits.union(
            random.choices(remaining, k=num_flips))
        mask = int.to_bytes(sum(2**i for i in self.flipped_bits), 24, byteorder="big")
        return bytes(i ^ j for i, j in zip(self.key, mask))

    def decrypt(self, text: bytes):
        self.counter -= 1
        if self.counter < 0:
            raise Exception("Out of balance")
        key = self.corruption()
        if len(text) % 8 != 0:
            return b''
        cipher = DES3.new(key, mode=DES3.MODE_CBC, iv=self.iv)
        return cipher.decrypt(text)

count = 0
if __name__ == "__main__":
    chall = Desfunctional()
    PROMPT = ("Choose an API option\n"
              "1. Get challenge\n"
              "2. Decrypt\n"
              "3. Get the flag\n")
    while True:
        try:
            option = int(input(PROMPT))
            if option == 1:
                print(chall.get_challenge().hex())
            elif option == 2:
                ct = bytes.fromhex(input("(hex) ct: "))
                print(chall.decrypt(ct).hex())
            elif option == 3:
                pt = bytes.fromhex(input("(hex) pt: "))
                print(chall.get_flag(pt))
                sys.exit(0)
        except Exception as e:
            print(e)
            sys.exit(1)
```

Mình có tìm được một tính chất của DES đó là: $P_2 = \overline{P_1}$, $k_2 = \overline{k_1}$. Từ đó ta suy ra được rằng $E_{k_1}(P_1) = \overline{E_{k_2}(P_2)}$. Bạn có thể đọc tài liệu này: https://crypto.stackexchange.com/questions/19064/des-with-the-bitwise-complement-of-a-key/19069#19069

Và tính chất bù 2 này của DES sẽ không ảnh hưởng tới 3DES https://crypto.stackexchange.com/questions/75984/2des-and-3des-against-a-des-build-with-complimentary

Thế nên, ta có thể tìm lại được plaintext nhờ sử dụng phép bù 2 của ciphertext và decrypt lại.

Mình có thể minh chứng bằng cách chạy code sau:

```python=
from Crypto.Util.number import*
from Crypto.Cipher import DES3
from pwn import*

def twos_complement(data):
    return bytes(~b & 0xFF for b in data)

key = b'trananhnhatviet123456789'
flag = b'helloworldabcdefhelloworldabcdefhelloworldabcdefhelloworldabcdef'
iv = b'a'*8
cipher1 = DES3.new(key,mode=DES3.MODE_CBC,iv=iv)
c = (cipher1.encrypt(flag))
print(c.hex())

bu_key = twos_complement(key)
iv = twos_complement(iv)
cipher2 = DES3.new(bu_key,mode=DES3.MODE_CBC,iv=iv)
c = (twos_complement(c))
print(cipher2.decrypt(c))
```

Thế nhưng, iv trong challenge này không hề được bù hai, thế nên là iv giữ nguyên, trong trường hợp này, ta có đoạn code như sau:

```python=
from Crypto.Util.number import*
from Crypto.Cipher import DES3
from pwn import*

def twos_complement(data):
    return bytes(~b & 0xFF for b in data)

key = os.urandom(24)
flag = b'helloworldabcdefhelloworldabcdefhelloworldabcdefhelloworldabcdef'
iv = b'a'*8
cipher1 = DES3.new(key, mode=DES3.MODE_CBC, iv=iv)
c = cipher1.encrypt(flag)
print(c.hex())

key = twos_complement(key)
cipher2 = DES3.new(key,mode=DES3.MODE_CBC,iv=iv)
c = (twos_complement(c))
print(twos_complement(cipher2.decrypt(c)))
```

Ta thấy chỉ thu được một block đầu tiên của flag, vì tính chất của CBC là sẽ lấy ciphertext trước đó làm iv tiếp theo, thế nên sau khi bù 2 của ciphertext, các block tiếp theo sẽ bị sai về iv. Thế nên, ta phải để nguyên ciphertext của block trước đó thì mới có thể lấy được plaintext tiếp theo.

```python=
from Crypto.Util.number import*
from Crypto.Cipher import DES3
from pwn import*

def twos_complement(data):
    return bytes(~b & 0xFF for b in data)

key = os.urandom(24)
flag = b'helloworldabcdefhelloworldabcdefhelloworldabcdefhelloworldabcdef'
iv = b'a'*8
cipher1 = DES3.new(key, mode=DES3.MODE_CBC, iv=iv)
c = cipher1.encrypt(flag)
print(c.hex())

key = twos_complement(key)
cipher2 = DES3.new(key,mode=DES3.MODE_CBC,iv=iv)
c = twos_complement(c) + c[:8] + (twos_complement(c[8:])) + c[:16] + (twos_complement(c[16:])) + c[:24] + (twos_complement(c[24:])) + c[:32] + (twos_complement(c[32:])) + c[:40] + (twos_complement(c[40:])) + c[:48] + (twos_complement(c[48:])) + c[:56] + (twos_complement(c[56:]))
out = (twos_complement(cipher2.decrypt(c)))
print(out[:64+8][:8])
print(out[64+8:64*2+16][:8])
print(out[64*2+16:64*3+24][:8])
print(out[64*3+24:64*4+32][:8])
print(out[64*4+32:64*5+40][:8])
print(out[64*5+40:64*6+48][:8])
```

Thế nhưng, bài này key không biến đổi theo bù hai, nhưng mà có xác suất trở thành bù hai của key, thế nên, ta cứ spam để lấy lại challenge nha.

Vì lúc đó đang trong giải nên mình chưa clean code được, code hơi xấu =)))

```python=
from Crypto.Util.number import*
from Crypto.Cipher import DES3
from pwn import*

def twos_complement(data):
    return bytes(~b & 0xFF for b in data)


from pwn import*

for i in range(100):
    # io = process(["python3","new.py"])
    io = remote("desfunctional.2024.ctfcompetition.com",1337)
    io.recvuntil(b'flag\n')
    io.sendline(b'1')
    c = io.recvuntil(b'\n',drop=True).decode()
    a = bytes.fromhex(c)
    payload = twos_complement(a) + a[:8] + (twos_complement(a[8:])) +a[:16] + (twos_complement(a[16:])) + a[:24] + (twos_complement(a[24:])) + a[:32] + (twos_complement(a[32:])) + a[:40] + (twos_complement(a[40:])) + a[:48] + (twos_complement(a[48:])) + a[:56] + (twos_complement(a[56:]))
    for _ in range(7):
        io.recvuntil(b'flag\n')
        io.sendline(b'2')
        io.recvuntil(b"(hex) ct: ")
        io.sendline(payload.hex().encode())
        data = io.recvuntil(b'\n',drop=True).decode()
        data = bytes.fromhex(data)
    data = twos_complement(data)
    x = 8
    chall = data[0*x:1*x]+data[9*x:10*x]+data[18*x:19*x]+data[27*x:28*x]+data[36*x:37*x]+data[45*x:46*x]+data[54*x:55*x] + data[63*x:64*x]
    chall = chall.hex()
    io.recvuntil(b'flag\n')
    io.sendline(b'3')    
    io.recvuntil(b"(hex) pt: ")
    io.sendline(chall.encode())
    io.interactive()

```

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

**Flag: CTF{y0u_m4y_NOT_g3t_th3_k3y_but_y0u_m4y_NOT_g3t_th3_c1ph3rt3xt_as_w3ll}**

Giải này khó quá, chắc mình sẽ bổ sung thêm bài **Blinders** sau.