Score:2

CTF - DES Challenge

bv flag

I am trying to solve a CTF challenge based on DES. I attached the code of the challenge to the question. So far I have noticed that the otp used for the encryption is the same for the entire session, and my idea would be the following:

  1. Get the OTP by using choice 1
  2. Use the obtained OTP to decrypt the ciphertext given by choice 2 (since I pass the key)

I tried to implement this process and haven't managed to get the flag. Given that the hint of the challenge is the following:

Not all keys are strong alike.

Do you think that I am on the right track or do you have any proposal on how I could approach the problem? Thank you in advice.

Code of the challenge:

#!/usr/bin/env python3

import signal
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad
from Crypto.Util.number import bytes_to_long
import os

TIMEOUT = 300

assert("FLAG" in os.environ)
flag = os.environ["FLAG"]
assert(flag.startswith("CCIT{"))
assert(flag.endswith("}"))

otp = os.urandom(8)

def xor(a, b):
    return bytes([a[i % len(a)] ^ b[i % len(b)] for i in range(max(len(a), len(b)))])

def encrypt_des(text, key):
    try:
        key = bytes.fromhex(key)
        text = xor(bytes.fromhex(text), otp)
        cipher = DES.new(key, DES.MODE_ECB)
        ct = xor(cipher.encrypt(pad(text, 8)), otp)
        return ct.hex()
    except Exception as e:
        return f"Something went wrong: {e}"


def handle():
    while True:
        print("1. Encrypt text")
        print("2. Encrypt flag")
        print("0. Exit")
        choice = int(input("> "))
        if choice == 1:
            text = input("What do you want to encrypt (in hex)? ").strip()
            key = input("With what key (in hex)? ").strip()
            print(encrypt_des(text, key))
        elif choice == 2:
            key = input("What key do you want to use (in hex)? ").strip()
            print(encrypt_des(flag.encode().hex(), key))
        else:
            break


if __name__ == "__main__":
    signal.alarm(TIMEOUT)
    handle()

My current approach:

from pwn import *
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad
import warnings
warnings.filterwarnings("ignore")
r = remote('desoracle.challs.cyberchallenge.it', 9035)
r.recvuntil('>')
# First we want to get the OTP
r.sendline('1')
myText = b'This is my cool text'.hex()
r.recvuntil('(in hex)?')
r.sendline(myText)
myKey = "FEFEFEFEFEFEFEFE" #this is one of the four weak keys
print(myKey)
r.recvuntil('(in hex)?')
r.sendline(myKey)
output = r.recvline().strip().decode()
outputInBytes = bytes.fromhex(output)
otp = xor(bytes.fromhex(myText), outputInBytes)
print(otp)
r.recvuntil('>')
r.sendline('2')
myKey = "FEFEFEFEFEFEFEFE" #this is one of the four weak keys
r.recvuntil('(in hex)?')
r.sendline(myKey)
output = r.recvline().strip().decode()
paddedFlagAfterXor = bytes.fromhex(output)
flag = xor(paddedFlagAfterXor, otp)
print(flag.decode())
r.interactive()
```
fgrieu avatar
ng flag
The hint could be related to [weak keys in DES](https://en.wikipedia.org/wiki/Weak_key#Weak_keys_in_DES). Or not.
Shark44 avatar
bv flag
I thought about that as well, I have read some articles about them and as I understood with 4 specific keys I could obtain the same ciphertext as the plaintext. I have tried passing one of the keys suggested as weak to the program and proceed to obtain the encrypted flag. I obtained some bytes, but I do not think they are equal to the flag, they seem to be just random bytes..
Shark44 avatar
bv flag
I edited the question and added my current approach which makes use of the weak keys to obtain the plaintext by the ciphertext, but the problem is that when I xor it with the otp I get in phase 1 I get random bytes, and I was expecting to get the flag, is there any error in the idea or is the problem in the implementation itself according to you?
J_H avatar
nr flag
J_H
You repeatedly `assert(foo)`. Please don't do that. Better to simply `assert foo`. Then a maintenance engineer won't be tricked into "refactoring" as `assert(foo, "diagnostic msg")`, which means something very very different from `assert foo, "diagnostic msg"`.
Score:2
nr flag
J_H

You seem to have gotten pretty far. Here are my musings.

spoiler: some keys can be weak

$ nc desoracle.challs.cyberchallenge.it 9035
1. Encrypt text
2. Encrypt flag
0. Exit
> 2
What key do you want to use (in hex)? FEFEFEFEFEFEFEFE
4bbe3dbd2af198110b1dba3b8be427a53119b02e2c9872687846c3058a225f77
1. Encrypt text
2. Encrypt flag
0. Exit
> 1
What do you want to encrypt (in hex)? 4bbe3dbd2af198110b1dba3b8be427a53119b02e2c9872687846c3058a225f77
With what key (in hex)? FEFEFEFEFEFEFEFE
434349547b35306d335f6b3379355f63346e5f62335f7733346b7df1bd443636b5c08a964c2193be
1. Encrypt text
2. Encrypt flag
0. Exit
> 0
>>> enc = '434349547b35306d335f6b3379355f63346e5f62335f7733346b7d447622cbd9c695f42387476e51'
>>> 
>>> vals = [int(enc[i:i+2], 16)  for i in range(0, len(enc), 2)]
>>> 
>>> ''.join(map(chr, vals))

'CCIT{50m3_k3y5_c4n_b3_w34k}Dv"ËÙÆ\x95ô#\x87GnQ'

Repeating it with another weak key, 0101010101010101, leads to this:

>>> enc = '434349547b35306d335f6b3379355f63346e5f62335f7733346b7d447622cbd9c695f42387476e51'
>>> enc = '434349547b35306d335f6b3379355f63346e5f62335f7733346b7d3877318cc695d6d51f0502ef03'
>>> vals = [int(enc[i:i+2], 16)  for i in range(0, len(enc), 2)]
>>> ''.join(map(chr, vals))

'CCIT{50m3_k3y5_c4n_b3_w34k}8w1\x8cÆ\x95ÖÕ\x1f\x05\x02ï\x03'

Looks like a 27-byte Flag, as confirmed by the asserts.

EDIT

Whoops, no need for the list comprehension, as this suffices:

import codecs

print(codecs.decode(enc, 'hex'))
Shark44 avatar
bv flag
Ok so basically your approach is a bit different. You first get the encrypted flag, and then try to encrypt the encrypted flag (which is still the same since we are using a weak key). It works because the xor done in the first phase is re-made in the second phase re-transforming the text to the original flag. Is this correct? In any case chapeau and thank you :)
J_H avatar
nr flag
J_H
Yes, that's right. We exploit that the 2nd encrypt acts as decrypt for a weak key. Suppose the use of weak keys is prohibited. I wonder if this approach could be extended to use a related pair of semi-weak keys. If so, feel free to post an Answer.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.