Following on from a previous example, where we overflowed a string buffer, let’s try doing something more practical. As mentioned in the previous post, there is no guarantee that variables will be stored in a particular order in the stack. The only thing that is guaranteed to be in the stack is the stack pointer ($sp) and frame pointer ($ebp).

If we had something like this:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int check(char *password) {
    char buffer[5];
    strcpy(buffer, password);
    int auth = 0;

    if (strcmp(buffer, "abcd") == 0) {
        auth = 1;
    }
 
    return auth;
}
 
int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: ./overflow <password>\n");
        exit(1);
    }
 
    if (check(argv[1])) {
        printf("Correct password.\n");
    } else {
        printf("Incorrect password.\n");
    }
 
    return 0;
}

You’ll notice that this is basically the exact same code as in the previous example except we have moved the declaration of ‘int auth = 0’ below our declaration of the buffer. We have covered how this doesn’t allow us to override the value of ‘auth’ anymore, but is there something else we can override? Let’s visualise the stack – I would imagine it would look something like the below, but it’s not quite accurate. The main thing is knowing that we can override the return address as that’s usually the first or second thing that’s saved in the stack.

So let’s try smashing the stack – namely, we want to override the return address to instead point to a memory location that we want.

Hmmm…. Somehow we have arrived at address 0x49484848. If you haven’t already got ascii installed on your system, please do so with ‘sudo apt-get install ascii’ as it makes life easier 🙂

Use ‘ascii’ to show an ASCII table in terminal

So we have an address of 0x49484848… Looking at the ASCII table, 0x49484848 corresponds to ‘IHHH’; remember that ‘0x’ denotes that we are using hexadecimal. Now what’s interesting is in our input we typed in ‘…HHHI…’ which is backwards of the address we just found. This is due to endianness – just remember you may need to type in your addresses ‘backwards’ into your program. So let’s find an address which we want to redirect our function return address to.

So when we first disassemble our program, we have memory addresses starting with ‘0x0000…’ – this occurs because it is both a small program, and because as we haven’t run the program yet, the operating system has not read any pages/reserved memory for our program yet. Addresses containing the byte ‘0x00’ are also not easy to jump to as attempting to input ‘0x00’ is the same as attempting to input a NULL byte, which programs will usually ignore/treat it as end of string. Thankfully, upon running the program we get ‘nice’ addresses. So let’s try to jump to the very first address we see – ‘0x56555601’.

Jumping to ‘0x56555644’; How do we input non printable characters?

By referring to the ASCII table, we know that ‘0x56’ is ‘V’ and ‘0x55’ is ‘U’ – but ‘0x01’ is a special character – so how do we input that into the program? Unfortunately we can’t type it using a keyboard, but we can use a bash shell to call python and let it print it out for us.

Success!

So if you’re unsure what all of this means – ‘$()’ tells gdb to open up a bash shell. The ‘-c’ flag in python basically tells it to treat the following as python code instead of a file containing python code. When you tell python to print something starting with ‘\x’ it interprets the next two characters as hexadecimal instead. A breakpoint has been set to allow you to view the contents of ‘password’ which as you can see, has ‘0x01’ equivalent to ‘\001’. Note that if you try to manually input ‘0x01’ by typing in ‘\001’, the program will ignore the ‘\’ and if you try escaping the backslash with another backslash (‘\\001’), the program will read in ‘\\001’.

So as you can tell, we have successfully redirected our function input to the first address of our main() function – causing our program to restart after our function call to ‘check’. Because we have smashed the stack rather crudely, our program has basically forgotten where all the program inputs (argv[]) are located in memory, leading it to believe no inputs has been passed and hence exiting with that message.

Now is a good time to mention that if you haven’t already, start learning python. You may like to check our pwntools, one of the many tools that makes binary exploitation easier.