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.
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 becauseshow
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
.
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:
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.}