Skip to content

Instantly share code, notes, and snippets.

@dhrumilp15
Last active March 7, 2022 06:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dhrumilp15/1b86991cc2a5544b5c3b5572f35b109c to your computer and use it in GitHub Desktop.
Save dhrumilp15/1b86991cc2a5544b5c3b5572f35b109c to your computer and use it in GitHub Desktop.

TurnKey

What a problem! From mom and pop's notes, we immediately notice the TurnKey Protocol:

We can observe from the protocol that we only need to send three messages per vault:

INITIALIZE CONNECTION
SEND FLAG
[Challenge String]

But, we receive many more messages:

Oh hai!
Connection successfully initialized. Please reply with request...
Flag requested. Reply with challenge string [Challenge String]
Success! Your request took [time] ms!
The flag encryption rotates every on all 3 servers every [time] ms.

AES-256 (CBC PKCS5)
Partial key (1/3): 0x[Partial Key]
IV: 0x[iv]
Ciphertext: 0x[ciphertext]

We don't need all the messages we receive - the only parts we need to know are the:

  • Challenge string
  • Partial keys
  • IV
  • Ciphertext.

But it's not super clear where to start - the first step in the protocol is to connect, but where do we even connect??

Where do I connect??

I spent a good hour looking at the challenge webpage and its source to see if there were any hidden clues for the port to access the vaults. Unsurprisingly, there were none. I was about to give up on the challenge, but my teammate ABigPickle suggested that I check for open ports using nmap (he's wicked smart...).

Sure enough, after a little bit of testing, it was clear that port 5555 guarded mom and pop's flag vault. Now the challenge begins...

Let's Test that Connection

I followed along with the conversation manually, sending messages in my terminal:

Vault: Oh hai!
Me: INITIALIZE CONNECTION
Vault: Connection successfully initialized. Please reply with request...
Me: SEND FLAG
Vault: Flag requested. Reply with challenge string [challenge string here...]
Me: [challenge string]
Vault: Sorry, too slow! Your request took [numbers and text here to tell me that I'm too slow]

Clearly, we'll need to make a computer send these requests.

Let's make contact...

I opted to use pwntools for connecting to the vaults. It's very useful for a myriad of CTF challenges (that's the main purpose of the library) and their remote tools have yet to fail me.

We can automate the TurnKey protocol using a script.

An important part of this challenge is recognizing that we can send commands as a single string joined by the \n character, like INITIALIZE CONNECTION\nSEND FLAG. With this in mind, we can write a script to connect to the first vault:

from pwn import *

def vault(num):
    return f"vault{num}.momandpopsflags.ca"

port = 5555

p_keys = [] # we'll store the keys in here

r = remote(vault(1), port)

# Send a message to the vault 
r.sendline(b'INITIALIZE CONNECTION\nSEND FLAG')

# Receive messages until we get to the Challenge String
r.recvuntil(b'ng ')
challenge_string = r.recvline()

# Send the Challenge String
r.send(challenge_string)

# Receive and store the Partial Key, IV and Ciphertext
r.recvuntil(b'Partial key')
p_key = r.recvline().decode().strip()
iv = r.recvuntil(b'IV: ').decode().strip()
ciphertext = r.recvline().decode()

# Close the connection
r.close()

p_keys.append(p_key)
print(p_keys, iv, ciphertext)

Observe that:

  • We use the \n character to send both messages at the same time
  • We use r.recvuntil(b'ng ') to receive text up to the IV (Initialization Vector for AES)

We also know from the challenge description that the IV and Ciphertext for each vault should be the same. So, if the IV and ciphertext from the first and third vaults don't match, we have to restart the protocol.

Extending Our Approach

Our code above gives us the IV, Ciphertext and the first third of the AES key. We can extend the code above for the other two vaults.

# For Vault 2
r2 = remote(vault(2), port)

# Send the first two commands at the same time
r2.sendline(b'INITIALIZE CONNECTION\nSEND FLAG')
r2.recvuntil(b'ng ')
ch = r2.recvline()

# Send the Challenge String
r2.send(ch)

# Receive the Partial Key
r2.recvuntil(b'Partial key ')
p_key = r2.recvline().decode().strip()
r2.close()
p_keys.append(p_key)

# For Vault 3
r3 = remote(vault(3), port)

# Send the first two commands at the same time
r3.sendline(b'INITIALIZE CONNECTION\nSEND FLAG')
r3.recvuntil(b'ng ')

# Send the Challenge String
ch_string = r3.recvline()
r3.send(ch_string)

# Receive the Partial Key
r3.recvuntil(b'Partial key ')
p_key = r3.recvline().decode().strip()
print(f'iv: {r3.recvline().decode().strip()}')
r3.close()

# Join the Partial keys

p_keys.append(p_key)
print(p_keys)
p_keys = [x[9:] for x in p_keys]
print(p_keys)
key = '0x' + ''.join(p_keys)

iv = int(iv, 16)
print(key)
print(iv)
print(ciphertext)

My requests to vault 2 would occasionally time out, but sending the command as a single string significantly increased the chance that the vault would reply with the key.

Armed with the key, IV and Ciphertext, I hopped onto CyberChef to decrypt the precious flag...

Flag: magpie{tH15_b3tT3R_f**k1n9_w0Rk_p0p...}

Notes

The official solution suggests that we run our code physically closer to the actual servers, but we can get around this by sending our commands at once :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment