Hi Folks,

I just skimmed the example exploits for the hack.lu ctf and discovered, that I actually came up with an exploit which runs “smoother” than the example solution. After I presented a overly complicated exploit last time, I want to show you now, how Callgate can be solved completely deterministically, without the need of interaction with the challenge itself.

In a nutshell, the given file is a x86_32 binary with an obvious buffer overflow but hardened by allowing syscalls only from a privileged context. So, the challenge is to bypass this done hardening measurement. Indeed, other classical techniques like stack canaries or even a non-executable stack are not used in this challenge.

For a better readability, I will provide the structure of this writeup in advance: First of all, the specific hardening technique is elaborated closer. Afterwards, a general exploitation strategy is provided and followed by some implementation details. Concludingly, I added some remarks to the exploit itself.

The syscall restriction technique is implemented directly inside the challenge in three distinct positions.

First of all, upon execution of the binary the prctl syscall is issued in order to add a custom BerkeleyPacketFilter  via PR_SET_SECCOMP/SECCOMP_MODE_FILTER. I spent some time to understand and reverse the structure of this PacketFilter since I initially thought the vulnerability would lie in a sloppy filter. However, the filter is working quite correctly and as a result, only syscalls from 3 distinct positions in the binary are allowed, whereby on two places additional restrictions for the register contents are applied.

  1. 0x0700001a at invoke_syscall, no restrictions
  2. 0x08048126 at enter_gate, registers must be aligned for syscall mprotect(0x0700001a,0x1000,PROT_READ|PROT_EXEC)
  3. 0x08048143 at leave_priviliged_mode, registers must be aligned for syscall mprotect(0x0700001a,0x1000,PROT_NONE)

It is obvious to see, that the occurence of 1) is only be meant to be executed from privileged context. The privileged context hereby is entered by setting the page containing the invoke_syscall function readable and executable and left by revoking all access permissions to this page.

This leads us to the second part of the hardening, the enter_gate and leave_privileged_mode functions. The first function is called whenever a syscall is needed from the binary and marks the invoke_syscall page executable, thus entering the privileged mode. Afterwards it calls the handle_secure_call function and  transits to leave_privileged_mode upon its completion.

The handle_secure_call function is the third part of the hardening. It takes as first argument a number n which determines which syscall should be executed. The additional arguments are dependent on n, but in the case of read_wrapper for instance these are the actual arguments passed to the read syscall. However, it has to be noted that n is not related to the original x86_32 syscall conventions, instead we have:

n = 0: exit
n = 1: read_wrapper
n = 2: jailed_open
n = 3: close_wrapper
n = 4: print_wrapper

The jailed_open basically calls open with read_only permissions as long the filename begins with an ‘#’ and is afterwards alphanumeric. If this requirement is not met, the binary exits.

Now, with all this knowledge we can actually start to talk about the exploit. In general, the binary is asking us for a filename and a password whereby both is overflowable. In order to retrieve the flag, we need to enter the privileged mode without leaving it. This can be done by corrupting the stackframe of secure_handle_call since this function returns to leave_privileged_mode. We can do this if we control the arguments for read_wrapper since we then can easily read into the stackframe of secure_handle_call. This can for instance be done directly by overflowing the password buffer - however, this would leave us without the knowledge of the actual address we want to write to, which results into the need of bruteforcing as shown in the example solution.

My exploit, however, works different. The binary basically has a push esp; jl 0x80383ab; dec cl; ret gadget, which basically passes the control flow  to the stack as long as SF <> OF - which is the case when we overflow password!

From there, I wrote a small shellcode to set up the fake stackframe for read and passing the control flow back to enter_gate, as shown below:

mov    ebp,esp
mov    eax,0xdeadbeef ;will be overwritten in runtime
mov    [esp],eax
mov    eax,0x1        ;n = 1
mov    [ebp+0x4],eax
xor    eax,eax        ;eax = 0
mov    [ebp+0x8],eax
mov    eax,ebp
sub    eax,0x24       ;savedeip of secure_handle_stackframe
mov    [ebp+0xc],eax
mov    eax,0x100      ;this will be enough for our ROPchain
mov    [ebp+0x10],eax
mov    eax,0x8048110  ;go to callgate
jmp    eax

The resulting read overflows the stackframe of secure_handle_call and we can just do traditional ROPing to read the string “flag\x00” to a deterministic address, open the flagfile, read its content to a deterministic address and write it to stdout. The only thing we have to take care off, is using the syscall in the invoke_syscall code chunk.

The full exploit can be found here: http://pastebin.com/kxMKA4jg

[[email protected] hacklu14]$ python2.7 -u callsploit.py |  nc wildwildweb.fluxfingers.net 1413 
Please enter a filename: Attempting to open file...
Please enter password: bad password

I didn’t bother to set up a python socket connection, since we only write to the challenge, which can easily be done by piping into netcat. Additionally, I want to mention that ASLR was disabled on the remote machine, and thus we just can use a random address inside the known stack region. However, the exploit is easily modifiable to leak addresses first, but this would have required interaction between challenge and exploit. Also, another usage of the ret2stack gadget would have been neat and bypasses the need of ROPing, but unfortunately, the registers are set now so that the jump would be taken and I was too lazy to find a gadget to fix this.

I hope you enjoyed this writeup,