Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stack-based buffer overflow - challenge in C using scanf with limited input

As part of a security CS course, my class has been given the task of exploiting a vulnerability to beat a password check using a stack/buffer overflow. The code with the vulnerability is as follows:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/md5.h>

int main(int argc, char **argv) {
    char correct_hash[16] = {
        0xd0, 0xf9, 0x19, 0x94, 0x4a, 0xf3, 0x10, 0x92,
        0x32, 0x98, 0x11, 0x8c, 0x33, 0x27, 0x91, 0xeb
    };
    char password[16];

    printf("Insert your password: ");
    scanf("%29s", password);

    MD5(password, strlen(password), password);

    if (memcmp(password, correct_hash, 16) == 0) {
        printf("Correct Password!\n");
    } else {
        printf("Wrong Password, sorry!\n");
    }
    return 0;
}

I understand the classic "stack-smashing" principle (I think), and there is a clear overflow vulnerability here, where the first 14 bytes of the correct_hash array can be overwritten, by inputting a password longer than 15 characters when prompted. However, I don't understand how to leverage this to allow the memcmp check to pass, completing the challenge. Some of the things I have discovered/attempted:

  • Setting password to be the equivalent of correct_hash doesn't work, as password gets hashed using MD5() (setting the two to be equal is impossible anyway, as scanf will insert precisely one unique ASCII NUL character into the 30 spaces available to it, meaning the two arrays can never be equivalent. NUL characters additionally (to my knowledge) cannot be inserted in the middle of a scanf string).

  • Overwriting the maximum number of bytes with scanf (which will always append a NUL character) means the last 3 bytes of correct_hash will always be 0x00 0x91 0xeb. Attempting to randomly generate a 16-character password that then hashes to something with these last 3 bytes/characters (reasonably computationally easy, given the use of MD5) doesn't work, however, due to the use of strlen(password) (which will give a value of 29 instead of something convenient like 16 thanks to only finishing the length count upon hitting a NUL character) in the call to MD5(). This means that instead of hashing the 16-character password to produce the expected output, the call to MD5() will hash 16 characters from password followed by 13 characters from correct_hash, producing a different final hashed value.

    • To get around this problem, I believe one would have to find a 29-character string (call it S) where the first 16 characters of S hash to a string R comprised of the last 13 characters of S, followed by 0x00 0x91 0xeb. I'm not sure how viable finding this through random MD5 hash computation is, but it don't fancy my chances.

Some notes (mentioned in the explanations above):

  • scanf is limited to a 29 character string, but will append an ASCII NUL character, allowing 30 characters total (16 from the password array, 14 from the correct_hash array) to be overwritten.

  • ASCII NUL characters cannot be input via scanf so the strlen(password) in the call to MD5() (if the maximum password length is used) will be 29.

So the question here is, how else could I go about doing this? Am I missing something extremely obvious? Is random generation a viable solution, or even the only solution?

The solution has to use a buffer overflow (otherwise I imagine I could do something like preload a memcmp that always returns 0), and has to be executable as a shell script (if that's of any relevance).

like image 634
Murray Avatar asked Feb 26 '16 18:02

Murray


1 Answers

Just to merge the comments into an answer here:

The crux of the matter is, that scanf will happily accept a zero-byte as part of a string and will not treat it as whitespace (thus, will not stop reading further bytes into the string).

Redirect a file or use echo -ne "\x00" | program or the like. Redirecting an input file is probably the preferred way here.

The final command I used was: echo -ne "\x49\x5a\x4e\x52\x48\x49\x41\x56\x5a\x43\x54\x52\x51\x4c\x4‌​3\x00\x81\xae\xf3\xd‌​f\xa2\x45\xb1\x57\x1‌​9\xb3\xa9\xb8\x7d\x0‌​0\x91\xeb" | ./vulnerable where the first set of 16 bytes (up to and including the first \x00) were a randomly generated string, which, when hashed produced the second set of 16 bytes, with the required \x00\x91\xeb ending. The last 3 bytes there weren't copied anyway, but I left them in to show the string and hash.

like image 64
Murray Avatar answered Oct 17 '22 22:10

Murray