[Crypto] TUCTF 2018 - AESential Lesson


AESential Lesson
465

Thought I’d give you an essential lesson to how you shouldn’t get input for AES in ECB mode.

nc 18.218.238.95 12345

Learning by the description we can already know the cryptography used here is AES ECB mode, we are provided a file with the encryption process:

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
#!/usr/bin/env python2

from Crypto.Cipher import AES

from select import select

import sys

INTRO = """
Lol. You think you can steal my flag?
I\'ll even encrypt your input for you,
but you can\'t get my secrets!

"""

flag = "REDACTED" # TODO Redact this

key = "REDACTED" # TODO Redact this


if __name__ == '__main__':

padc = 'REDACTED' #TODO Redact this

assert (len(flag) == 32) and (len(key) == 32)

cipher = AES.new(key, AES.MODE_ECB)

sys.stdout.write(INTRO)
sys.stdout.flush()

while True:
try:
sys.stdout.write('Enter your text here: ')
sys.stdout.flush()

rlist, _, _ = select([sys.stdin], [], [])

inp = ''
if rlist:
inp = sys.stdin.readline().rstrip('\n')

plaintext = inp + flag
l = len(plaintext)

padl = (l // 32 + 1)*32 if l % 32 != 0 else 0

plaintext = plaintext.ljust(padl, padc)
#print plaintext

sys.stdout.write('Here\'s your encrypted text:\n{}\n\n'.format((cipher.encrypt(plaintext)).encode('hex')))
except KeyboardInterrupt:
exit(0)

By looking at the script we can already see, the flag has 32 bytes of size, the key as well, the encryption method processes as follows, since it’s ECB we know the plaintext will be split in blocks of 16 bytes and for each block will be applied the AES encryption function with the key provided in the file:

Before the encryption we can see the program asks for an input to be encrypted, the input is concatenated with the flag and then it’s applied some padding to fill the last blocks, for example imagine the padding character is 1, the sent input is ‘A’ and the respective flag is ‘TUCTF{MY_B34UT1FULL_FL4G_L0L_XD}’ the padding will be applied to the plaintext as follows:

1
2
Block1                Block2                Block3                Block4
ATUCTF{MY_B34UT1 FULL_FL4G_L0L_XD }111111111111111 1111111111111111

After this as described before the encryption is applied to each block with a key, now we can perform an attack without needing the key, we can bruteforce the flag byte by byte, imagine we sent an input of 15 ‘A’s the first block of the plaintext and ciphertext will be as follows (The key in this examples is a random key chosen by me):

1
2
3
4
5
6
7
8
9
10
Plaintext
Block1 Block2 Block3 Block4
AAAAAAAAAAAAAAAT UCTF{MY_B34UT1FU L_FL4G_L0L_XD}1 1111111111111111

Ciphertext
Block1 Block2 Block3
82b094debf0605ef9d46ad671ac3605d 08663b2c2a83bd539e14e2ea671035c4 b2140d0e9125ca1de2cd1ea85d21ae7e

Block4
e845fa5520b78d20bdd0ff93339df9fe

Now we know that input + 1st_char_of_the_flag its corresponding ciphertext is the 1st block, now we just need to send the inputs for every character possible until we match the 1st ciphertext we got with the input of 14 “A”s:

1
2
3
4
5
6
Plain Block1     Cipher Block1
AAAAAAAAAAAAAAAA 207ca0ee7f5bdb8897caa7b1f8ff2157
AAAAAAAAAAAAAAAB 9884f8e45e5ea527ca6c9b090bef5e64
AAAAAAAAAAAAAAAC e422f4ff06175cfe7e00b3002bd8b464
.... .......
AAAAAAAAAAAAAAAT 82b094debf0605ef9d46ad671ac3605d

As we can see from above we matched the 82b094debf0605ef9d46ad671ac3605d, we now know the first character of the flag is T, well we already knew that! but this was just a confirmation :), using this method we can get the first 16 bytes of the flag, but how can we get the last 16 ? For this we need to find the padding character we can do this by sending an A to the server, imagine the padding character is _:

1
2
3
4
5
6
7
8
9
Plaintext
Block1 Block2 Block3 Block4
ATUCTF{MY_B34UT1 FULL_FL4G_L0L_XD }_______________ ________________

Ciphertext
Block1 Block2 Block3
7a80f91bcf406446befb10e0720b8a7d ece296fd74e495dc5b2890596777f3eb 2a5831079c0a0591601f25278f4623f3
Block4
e845fa5520b78d20bdd0ff93339df9fe

Now we want to extract the block 3 or the block 4 cipher, block 3 also works because we actually know that the last byte of the flag is }, so making an example using block 3 2a5831079c0a0591601f25278f4623f3

1
2
3
4
5
6
Plain Block1     Cipher Block1
}000000000000000 5f87ae73237e86dd625e820ba93056e0
}111111111111111 d9044a33bc1e523222289c56a6505c79
}222222222222222 774066300cda4278f32729b032a3bfe7
.... .......
}_______________ 2a5831079c0a0591601f25278f4623f3

Now that we have the padding character, we need to work something similar on how we got the 1st part of the flag, but now instead of comparing with the 1st block of the cipher we want to compare it with the 3rd block by sending an A we get the 3rd block cipher:

1
2
3
4
5
6
7
8
9
Plaintext
Block1 Block2 Block3 Block4
ATUCTF{MY_B34UT1 FULL_FL4G_L0L_XD }_______________ ________________

Ciphertext
Block1 Block2 Block3
7a80f91bcf406446befb10e0720b8a7d ece296fd74e495dc5b2890596777f3eb 2a5831079c0a0591601f25278f4623f3
Block4
e845fa5520b78d20bdd0ff93339df9fe

Now we test all characters until we match this block3:

1
2
3
4
5
6
Plain Block1     Cipher Block1
0_______________ 545506ce0fa14673dba3d92a09a28774
1_______________ a26f177d5ef506e40e79411f4a1ef0bf
2_______________ a4a7aee9adba7af7d709662da5c19aee
.... .......
}_______________ 2a5831079c0a0591601f25278f4623f3

With this we get the last character of the flag, now repeat this for the rest of the characters, I’ll give one more example how to get the next character, sending “AA” as input:

1
2
3
4
5
6
7
8
9
Plaintext
Block1 Block2 Block3 Block4
AATUCTF{MY_B34UT 1FULL_FL4G_L0L_X D}______________ ________________

Ciphertext
Block1 Block2 Block3
6ca95659b3828138ff0408db597a6614 45551fa5b4b751017e717e2e4193cc8d 3c872cee5f361ca44e66a554602ee1c3
Block4
e845fa5520b78d20bdd0ff93339df9fe

Now brute force it like this:

1
2
3
4
5
6
Plain Block1     Cipher Block1
0}______________ 6ef0fae728efd2738f1a5f179f6980a8
1}______________ 2c851b1a58399ffdc6b1c4ed23287fa7
2}______________ 664fafa0563cc1b84931daf6cf1cbc68
.... .......
D}______________ 3c872cee5f361ca44e66a554602ee1c3

Repeat this and you’ll get every character of the flag, the python script I implemented for 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *
import re
import string
import time
r = remote('18.218.238.95', 12345)
#r = process('./redacted.py')
print r.recvuntil('Enter your text here: ')

flag = ''

if flag == '':
r.sendline('A'*15)
encrypted_first = re.findall(r'[a-f0-9]{32,}', r.recvuntil('Enter your text here: '))[0]
for i in range(16):
for c in string.printable:
r.sendline('A'*(15-i) + flag+c)
encrypted = re.findall(r'[a-f0-9]{32,}', r.recvuntil('Enter your text here: '))[0]

if encrypted[:32] == encrypted_first[:32]:
flag += c
r.sendline('A'*(15-i-1))
encrypted_first = re.findall(r'[a-f0-9]{32,}', r.recvuntil('Enter your text here: '))[0]
break
padding_char = ''
if padding_char == '':
r.sendline('A'*1)
encrypted_first = re.findall(r'[a-f0-9]{32,}', r.recvuntil('Enter your text here: '))[0]
time.sleep(0.1)
for x in string.printable:
p = '}'+ x*15
r.sendline(p)
encrypted = re.findall(r'[a-f0-9]{32,}', r.recvuntil('Enter your text here: '))[0]
if encrypted[:32] == encrypted_first[64:64+32]:
padding_char = x
break

r.sendline('}'*1)
encrypted_first = re.findall(r'[a-f0-9]{32,}', r.recvuntil('Enter your text here: '))[0]

flag_part2 = ''

for i in range(16,0,-1):
for c in string.printable:
p = c+flag_part2 + (i-1)*padding_char
r.sendline(p)
encrypted = re.findall(r'[a-f0-9]{32,}', r.recvuntil('Enter your text here: '))[0]

if encrypted[:32] == encrypted_first[64:64+32]:
flag_part2 = c + flag_part2
r.sendline('A'*(17-i+1))
encrypted_first = re.findall(r'[a-f0-9]{32,}', r.recvuntil('Enter your text here: '))[0]
print flag+flag_part2
break

Running it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
python reeas.py 
[+] Starting local process './redacted.py': pid 11451
Enter your text here:
TUCTF{A3S_3CB_1S}
TUCTF{A3S_3CB_1S!}
TUCTF{A3S_3CB_1S!!}
TUCTF{A3S_3CB_1S!!!}
TUCTF{A3S_3CB_1S!!!!}
TUCTF{A3S_3CB_1S3!!!!}
TUCTF{A3S_3CB_1SL3!!!!}
TUCTF{A3S_3CB_1SBL3!!!!}
TUCTF{A3S_3CB_1S4BL3!!!!}
TUCTF{A3S_3CB_1SR4BL3!!!!}
TUCTF{A3S_3CB_1S3R4BL3!!!!}
TUCTF{A3S_3CB_1SN3R4BL3!!!!}
TUCTF{A3S_3CB_1SLN3R4BL3!!!!}
TUCTF{A3S_3CB_1SULN3R4BL3!!!!}
TUCTF{A3S_3CB_1SVULN3R4BL3!!!!}
TUCTF{A3S_3CB_1S_VULN3R4BL3!!!!}