CryptoFlow
In this challenge we need to decypher a text encrypted with AES using a buffer overflow.
The challenge
They only give use the compiled file and we can connect in tcp to an instance to run the code.
First we connect to the remote instance and we get :
The key has been successfully loaded.
Here is an encrypted secret, I swear you can't decrypt it, Rijndael told me.
5ace67b21aea0aa955ca5a19cad275eee748a3226e54c19665fdc8a2500cea3f
Enter the cleartext (at most 224 character), I will give you the ciphertext back.
few things about it :
- Rijndael so they use AES.
- We can send a message and we get back the ciphertext, their is no limite of ciphertext
- “at most 224 character” of course I will not respect this!
Second we try localy and we get this :
Couldn't open the key file ... Please contact an admin.
Nothing to add for now
First Try
As I say I will not respect the only 224 char so let’s try to send more. Here is the out I get :
Have u seen my canary ?! His name is 'Don't overwrite me or, at least, do it well...'
There is a “canary” protection but I’m on the good way.
Reverse
Let’s launch Gihdra on the compiled file and watch the main()
function :
- We first see a function
LoadKey()
which open the fileaes_ecb_key.txt
and take the first 16 bytes (I can add this file locally to test). And we learn it is well AES using ECB. - We then see the function
SendEncryptedFlag()
which open the fileflag
and take 32 bytes then he do theKeyExpansion
, cypher the flag and print it. I quickly checked theKeyExpansion()
andCypher()
functions but it seems well an AES. - Then we arrive in the main loop which first remake the
KeyExpansion()
, clean a variablelast_message
, and callGetMessage()
. The message is then cypher and print to us.
Let’s see inside GetMessage()
: here is the code Gihdra give us :
int GetMessage(void)
{
long j;
undefined8 *char_in;
int i;
byte zero;
zero = 0;
puts("Enter the cleartext (at most 224 character), I will give you the ciphertext back.");
i = 0;
char_in = &last_message;
while( true ) {
fread(char_in,1,1,stdin);
if (*(char *)char_in == '\n') break;
i = i + 1;
char_in = (undefined8 *)((long)char_in + 1);
}
i = i + 1;
if (mycanary != mycanary_backup) {
puts("Have u seen my canary ?! His name is \'Don\'t overwrite me or, at least, do it well...\'" )
;
mycanary = mycanary_backup;
i = 0;
char_in = &last_message;
for (j = 28; j != 0; j = j + -1) {
*char_in = 0;
char_in = char_in + (ulong)zero * -2 + 1;
}
}
return i;
}
We now see mycanary
and mycanary_backup
.
Let’s look at the different values and adress:
last_message
is at 0x00104020mycanary
is at 0x00104100 (224 afterlast_message
)mycanary_backup
is at 0x00104230
So if I try a message of size 543 (=0x21f, eg the difference between adress of mycanary_backup
and last_message
) I will overwrite mycanary
and mycanary_backup
. And indeed if I try I no more have the message saying I overwrite something.
But the best in this story is that sbox
is beetween mycanary
and mycanary_backup
(at adress 0x00104120) so I can overwrite it.
Exploitation
Now we know how to overwrite sbox
let’s find how is it usefull.
If you remember well how AES work, you know sbox
is use during the key expension and during SubBytes
. You also know that last turn of AES is only : SubBytes
, MixRows
and AddRoundKey
. So if sbox
is fill only with 0, after SubBytes
the state would be only 0. The state will not change after MixRows
. And then after AddRoundKey
I will get an xor of 0 and round key 10
which mean I will get round key 10
. And luckily if I know round key 10
I can get back to my key!
Here is my code to get the final flag, I recode the reverse of the key but I’m sure tools already exist.
from pwn import *
from Crypto.Cipher import AES
SERVER = "instances.challenge-ecw.fr"
PORT = 41780
sbox = [
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
]
rcon = [
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d
]
def get_encrypted_flag_and_round_10_key():
con = remote(SERVER, PORT)
d = con.recvuntil(b'back.')
encrypted_flag = d.decode().split('\n')[2]
con.sendline(bytearray([0] * 543))
d = con.recvuntil(b'back.')
round_10_key = d.decode().split('\n')[2][:32]
return (encrypted_flag, round_10_key)
def my_xor(a,b):
r = []
for i in range(4):
r.append(a[i] ^ b[i])
return r
def Rcon(i):
return [rcon[i%256]] + [0,0,0]
def RotWord(l):
return l[1:] + l[:1]
def SubWordList(l):
r = []
for i in l:
r.append(sbox[i])
return r
def inverse_key_expansion(key, n):
b = bytes.fromhex(key)
k = [b[4*i:4*i+4] for i in range(4)]
k.reverse()
for i in range(n):
for _ in range(3):
k.append(my_xor(k[-4], k[-3]))
z = my_xor(k[-4], Rcon(n-i))
a = RotWord(k[-3])
a = SubWordList(a)
k.append(my_xor(a, z))
k.reverse()
return k[0] + k[1] + k[2] + k[3]
def decrypt_aes(cypher, key):
a = AES.new(bytearray(key), AES.MODE_ECB)
return a.decrypt(bytes.fromhex(cypher)).decode()
def main():
(encrypted_flag, round_10_key) = get_encrypted_flag_and_round_10_key()
key = inverse_key_expansion(round_10_key, 10)
print(decrypt_aes(encrypted_flag, key))
if __name__ == "__main__":
main()
Flag
And finaly you get the flag : ECW{Whyd0yOuMod1fyMySb0xDude??!}