Posts LINE CTF
Post
Cancel

LINE CTF

babycrypto1

Source code

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env python
from base64 import b64decode
from base64 import b64encode
import socket
import multiprocessing

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
import hashlib
import sys

class AESCipher:
    def __init__(self, key):
        self.key = key

    def encrypt(self, data):
        iv = get_random_bytes(AES.block_size)
        self.cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return b64encode(iv + self.cipher.encrypt(pad(data, 
            AES.block_size)))

    def encrypt_iv(self, data, iv):
        self.cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return b64encode(iv + self.cipher.encrypt(pad(data, 
            AES.block_size)))

    def decrypt(self, data):
        raw = b64decode(data)
        self.cipher = AES.new(self.key, AES.MODE_CBC, raw[:AES.block_size])
        return unpad(self.cipher.decrypt(raw[AES.block_size:]), AES.block_size)

flag = open("flag", "rb").read().strip()

COMMAND = [b'test',b'show']

def run_server(client, aes_key, token):
    client.send(b'test Command: ' + AESCipher(aes_key).encrypt(token+COMMAND[0]) + b'\n')
    client.send(b'**Cipher oracle**\n')
    client.send(b'IV...: ')
    iv = b64decode(client.recv(1024).decode().strip())
    client.send(b'Message...: ')
    msg = b64decode(client.recv(1024).decode().strip())
    client.send(b'Ciphertext:' + AESCipher(aes_key).encrypt_iv(msg,iv) + b'\n\n')
    while(True):
        client.send(b'Enter your command: ')
        tt = client.recv(1024).strip()
        tt2 = AESCipher(aes_key).decrypt(tt)
        client.send(tt2 + b'\n')
        if tt2 == token+COMMAND[1]:
            client.send(b'The flag is: ' + flag)
            client.close()
            break

if __name__ == '__main__':
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('0.0.0.0', 16001))
    server.listen(1)

    while True:
        client, address = server.accept()

        aes_key = get_random_bytes(AES.block_size)
        token = b64encode(get_random_bytes(AES.block_size*10))[:AES.block_size*10]

        process = multiprocessing.Process(target=run_server, args=(client, aes_key, token))
        process.daemon = True
        process.start()

Solution

Reading the source code, we can see that the idea is to replace the last block of encryption with show instead of test. In addition, the server allows us to encrypt a string with a given IV.

AES_decyption

When you look at the following diagram of AES_CBC decryption, you see that the last block is basically decrypted and then XORed with the previous block. So if we encrypt the string show with the previous block as the IV we would get the correct final block. Then we just need to replace that in the string that we were given at the start from the server and we can go ahead and send that to the serevr.

Solution Code

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 base64 import b64decode
from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad

from pwn import *

import hashlib
import sys

r = remote("35.200.115.41", 16001, level='debug')

line = str(r.recvline().replace(b'\n', b'')).split(": ")

r.recv()

testcommand = line[1]

iv = b64encode(b64decode(testcommand)[:16])

token = b64encode(b64decode(testcommand)[16:AES.block_size*10+16])
tokenlast = b64decode(token)[-16:]
r.sendline(b64encode(tokenlast))

r.sendline(b64encode(b'show'))
line = str(r.recvline().replace(b'\n', b'')).split(":")

encryptedshow = line[2]

payload = b64encode(b64decode(iv)+b64decode(token)+b64decode(encryptedshow)[16:])
r.recvuntil(b'Enter your command: ')
r.sendline(testcommand)
r.recvuntil(b'Enter your command: ')
r.sendline(payload)
r.recv()
r.recv()

Rabbit Holes

  • One thing that I kept doing was XORing show with the IV and the lastblock that have it work and send the IV at the start of the string sent by the server as the encryption IV but this didn’t work because show was XORed with the lastblock before encryption
  • I also tried padding show to make it have the right length but that didn’t work because the data was additionally padded

babycrypto2

Source Code

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env python
from base64 import b64decode
from base64 import b64encode
import socket
import multiprocessing

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
import hashlib
import sys

class AESCipher:
    def __init__(self, key):
        self.key = key

    def encrypt(self, data):
        iv = get_random_bytes(AES.block_size)
        self.cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return b64encode(iv + self.cipher.encrypt(pad(data, 
            AES.block_size)))

    def encrypt_iv(self, data, iv):
        self.cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return b64encode(iv + self.cipher.encrypt(pad(data, 
            AES.block_size)))

    def decrypt(self, data):
        raw = b64decode(data)
        self.cipher = AES.new(self.key, AES.MODE_CBC, raw[:AES.block_size])
        return unpad(self.cipher.decrypt(raw[AES.block_size:]), AES.block_size)

flag = open("flag", "rb").read().strip()

AES_KEY = get_random_bytes(AES.block_size)
TOKEN = b64encode(get_random_bytes(AES.block_size*10-1))
COMMAND = [b'test',b'show']
PREFIX = b'Command: '

def run_server(client):
    client.send(b'test Command: ' + AESCipher(AES_KEY).encrypt(PREFIX+COMMAND[0]+TOKEN) + b'\n')
    while(True):
        client.send(b'Enter your command: ')
        tt = client.recv(1024).strip()
        tt2 = AESCipher(AES_KEY).decrypt(tt)
        client.send(tt2 + b'\n')
        if tt2 == PREFIX+COMMAND[1]+TOKEN:
            client.send(b'The flag is: ' + flag)
            client.close()
            break

if __name__ == '__main__':
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('0.0.0.0', 16002))
    server.listen(1)

    while True:
        client, address = server.accept()

        process = multiprocessing.Process(target=run_server, args=(client, ))
        process.daemon = True
        process.start()

Solution

This challenge in my opinion was easier than the previous one. In this case the goal is to get the string to look like Command: show+token instead of Command: test+token.

AES_decyption

Again going back to our diagram, in this case we actually only need to modify the IV so that when it is XORed with the decrypted ciphertext it gives Command: show+token. Since we know that:

\[IV \oplus cipher = test\]

Thus we just need to do the following:

\[\Rightarrow IV \oplus cipher \oplus test \oplus show = test \oplus test \oplus show = show\]

Solution Code

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
from base64 import b64decode
from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad

from pwn import *

import hashlib
import sys

r = remote("35.200.39.68", 16002, level='debug')

line = str(r.recvline().replace(b'\n', b'')).split(": ")

testcommand = line[1]

iv = b64decode(testcommand)[:16]
payload = b64decode(testcommand)[16:]

iv = iv[:9] + xor(xor(iv[9:13], b'test'), b'show') + iv[13:]

payload = b64encode(iv+payload)
r.recvuntil(b'Enter your command: ')
r.sendline(testcommand)
r.recvuntil(b'Enter your command: ')
r.sendline(payload)
r.recv()
r.recv()

babycrypto3

We are given a pub.pem file. Load it into pycryptodome and we see that the bit length of n is very small so it can be factorized. Using msieve we get the two factors p and q of n. Later on the CTF, that was doable with RSACTFTool since a kindered soul decided to publish the factorization on factordb. We can then decrypt with the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.PublicKey import RSA
from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Cipher import AES, PKCS1_OAEP
from base64 import b64decode
from base64 import b64encode

with open('pvt.pem', 'r') as f:
    key = RSA.import_key(f.read())

with open('ciphertext.txt', 'rb') as f:
    cipher = bytes_to_long(f.read())

assert key.d == pow(key.e, -1, (key.p-1)*(key.q-1))

print(long_to_bytes(pow(cipher, key.d, key.n)))

We get the following string

1
\x02`g\xff\x85\x1e\xcd\xcba\xe5\x0b\x83\xa5\x15\xe3\x00Q0xPU0lORyBUSEUgRElTVEFOQ0UuCg==\n

We can see that there is some base64 at the end. When we decrypt that, we get something readble. Place that in the flag format and BINGO.

1
2
3
4
# decrypt the b64 at the end and put it in the flag format afterwards
m = "Q0xPU0lORyBUSEUgRElTVEFOQ0UuCg=="
print(b64decode(m))
# LINECTF{CLOSING THE DISTANCE.}
This post is licensed under CC BY 4.0 by the author.