Tags: python3 crypto 3des 

Rating:

# Solution for dCTF 2021 Challenge "Just Take Your Time" (Crypto)
## Challenge Description

Let's go. In and out. 2 second adventure.
```
nc dctf-chall-just-take-your-time.westeurope.azurecontainer.io 9999
```
While this may not be pwn, its tools may still be quite handy.

### Provided Python Code
```
#!/usr/bin python3

from flag import flag
from Crypto.Cipher import DES3
from time import time
from random import randint
from secrets import token_hex
from pytimedinput import timedInput

guess = 3
TIMEOUT = 1

a = randint(1000000000000000, 9999999999999999)
b = randint(1000000000000000, 9999999999999999)

print("Show me you are worthy and solve for x! You have one second.")
print("{} * {} = ".format(a, b))

answ, _ = timedInput("> ", timeOut = 1, forcedTimeout = True)

try:
assert(a*b == int(answ))
except:
print("You are not worthy!")
exit(1)

key = str(int(time())).zfill(16).encode("utf-8")
secret = token_hex(16)
cipher = DES3.new(key, DES3.MODE_CFB, b"00000000")
encrypted = cipher.encrypt(secret.encode("utf-8"))
print("You have proven yourself to be capable of taking on the final task. Decrypt this and the flag shall be yours!")
print(encrypted.hex())

start_time = time()
while(time() - start_time < TIMEOUT and guess > 0):
delta = time() - start_time
answ, _ = timedInput("> ", timeOut = TIMEOUT + 1 - delta, forcedTimeout = True)

try:
assert(secret == answ)
break
except:
if answ != "":
guess -= 1
if (guess != 1):
print("You are wrong. {} guesses remain.".format(guess))
else:
print("You are wrong. {} guess remains.".format(guess))

if (secret != answ):
print("You have been unsuccessful in your quest for the flag.")
else:
print("Congratulations! Here is your flag.")
print(flag)
```
## Solution Overview
To solve this challenge, we need 3 steps:

1. Write code for network service interaction: read from remote, and send data as a response.
2. Add code to calculate multiplication result from the intercepted numbers (solve task 1)
3. Add code to decrypt the ciphertext with 3DES (CFB mode, IV=b"00000000") and a timestamp-based key (solve task 2)

Due to a time-out condition, we need to automate the response by writing a script. Before starting to code the script, lets look at the tasks separately to fully understand each.

Language choice: Python3

This way we can re-use code fragments provided with the challenge.

### 1. Network Service Interaction - Connect to remote service and parse output

The manual network connection test:

```
fm@NUC7:~$ nc dctf-chall-just-take-your-time.westeurope.azurecontainer.io 9999
Show me you are worthy and solve for x! You have one second.
3431802513119313 * 8053949562118619 =
> You are not worthy!
```
Now we do the same under Python:

```
fm@NUC7:~$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
>>> import socket
>>> remotehost = "dctf-chall-just-take-your-time.westeurope.azurecontainer.io"
>>> remoteport = 9999
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> connect = s.connect((remotehost, remoteport))
>>> remoteout = str(s.recv(1024).decode("utf-8"))
>>> remotelines = remoteout.splitlines();
>>> print(remotelines[1])
7030601571755451 * 1909787585147146 =
```

### 2. Solve the first service challenge - Multiply two random 16-digit numbers

```
fm@NUC7:~$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from random import randint
>>> a = randint(1000000000000000, 9999999999999999)
>>> b = randint(1000000000000000, 9999999999999999)
>>> print("{} * {} = ".format(a, b))
3748747587965172 * 1830956562823976 =
```
To resolve, we simply multiply and return it:

```
>>> a*b
6863793998555381943399854563872
```

### 3. Decrypt the service ciphertext

Per challenge code, a 16-char key string is generated, containing the current timestamp prefixed with zeros.

```
fm@NUC7:~$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
>>> from time import time
>>> key = str(int(time())).zfill(16).encode("utf-8")
>>> print(key)
b'0000001621051737'
```
A random 16-char text string is generated, displayed in hexadecimal.

```
fm@NUC7:~$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
>>> from secrets import token_hex
>>> secret = token_hex(16)
>>> print(secret)
bb775e5e11abc3ea28967061fc1957f1
```
The random secret string gets Triple-DES encrypted in CFB mode, using the timestamp key. CFB mode turns the block cipher into a stream cipher. Each byte of plaintext is XOR-ed with a byte taken from a keystream: the result is the ciphertext. IV is set to b"00000000".

```
fm@NUC7:~$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
>>> from Crypto.Cipher import DES3
>>> cipher = DES3.new(key, DES3.MODE_CFB, b"00000000")
>>> encrypted = cipher.encrypt(secret.encode("utf-8"))
>>> print(encrypted)
b'b\xf1\x7f\x1d|%\xe3\xf4\x0f\xb0\xdaS\xed\x0fM\x17\x99;\x86\x8e\x04Ds\xb1`\xfbF\x12\xd6\xe6\xa8\xcb'
>>> print(encrypted.hex())
62f17f1d7c25e3f40fb0da53ed0f4d17993b868e044473b160fb4612d6e6a8cb
```
So, to solve it we need to decrypt the provided ciphertext and return the original 'secret' string, using a timestamp as the key. This will be the decryption part:

```
fm@NUC7:~$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
>>> from binascii import unhexlify
>>> from Crypto.Cipher import DES3
>>> ciphertext = "62f17f1d7c25e3f40fb0da53ed0f4d17993b868e044473b160fb4612d6e6a8cb"
>>> key = b'0000001621051737'
>>> cipher = DES3.new(key, DES3.MODE_CFB, b"00000000")
>>> cleartext = cipher.decrypt(unhexlify(ciphertext))
>>> print(cleartext.decode("utf-8"))
bb775e5e11abc3ea28967061fc1957f1
```

### Final Solve

```
fm@NUC7:~$ ./jtyt-solve.py
debug remote output:
Show me you are worthy and solve for x! You have one second.
2904958076352217 * 4432329690179776 =
>

debug number extract: 1=2904958076352217 2=4432329690179776
debug multiplication: 12875731930543460049557026163392
debug remote output:
You have proven yourself to be capable of taking on the final task. Decrypt this and the flag shall be yours!
f0199fa6054cd2a677967b4fb135a60cd832d370c830d7376613fc254eaf5ac2
>

debug cipher extract: enc=f0199fa6054cd2a677967b4fb135a60cd832d370c830d7376613fc254eaf5ac2
debug decryption key: key=0000001621068036
debug show cleartext: clr=c5a84a46f94e7065ac8fdd0410747466
debug remote output:
Congratulations! Here is your flag.
dctf{1t********************}
```

### Code Listing
```
fm@NUC7:~$ cat jtyt-solve.py
#!/usr/bin/python3
import socket
from time import time
from Crypto.Cipher import DES3
from binascii import unhexlify

remotehost = "dctf-chall-just-take-your-time.westeurope.azurecontainer.io"
remoteport = 9999

# Connect to the remote side and capture the output
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((remotehost, remoteport))
output1 = str(s.recv(1024).decode("utf-8"))
print("debug remote output:\n" + output1 + "\n")

# Parse and process the output to calculate result 1
remotelines = output1.splitlines();
num1 = remotelines[1].split()[0];
num2 = remotelines[1].split()[2];
print("debug number extract: 1=" + num1 + " 2=" + num2)
result1 = str(int(num1) * int(num2))
print("debug multiplication: " + result1)

# Send result to remote side, and check response
s.send(bytearray(result1 + "\n", 'utf-8'))
output2 = str(s.recv(1024).decode("utf-8"))
print("debug remote output:\n" + output2 + "\n")

# Parse and process the output to calculate result 2
remotelines = output2.splitlines();
enc = remotelines[1];
print("debug cipher extract: enc=" + enc)

# Generate the decryption key based on the timestamp
key = str(int(time())).zfill(16).encode("utf-8")
print("debug decryption key: key=" + str(key.decode("utf-8")))

# Decrypt the ciphertext
cipher = DES3.new(key, DES3.MODE_CFB, b"00000000")
cleartext = str(cipher.decrypt(unhexlify(enc)).decode("utf-8"))
print("debug show cleartext: clr=" + cleartext)

# Sent decryption result to remote, and check for flag
s.send(bytearray(cleartext + "\n", 'utf-8'))
output3 = str(s.recv(1024).decode("utf-8"))
print("debug remote output:\n" + output3 + "\n")
```