This program will execute any arbitrary code you give it! Well, almost any — it prohibits syscalls, and only gives you 16 bytes of space. These incredible security features were added at the last minute to ensure nobody can read any secrets hidden on the server. Prove them wrong and get the flag. Download the source and binary.

The program reads up to 16 bytes first. Then, it checks for system calls and jumps/call to the dynamic addresses. If the code does not contain any system calls and dynamic addresses, it gets mapped to a new allocated memory which is read-only and executable. Then, 0xC3 is appended to our input which is the RETN instruction. Finally, our input gets executed.

Let’s check the executable binary.

It is a 64-bit ELF executable file with stack canary and non-executable stack. Let’s disassemble the code_runner function.

The call rdx is executing the 16 bytes we sent as input.

If there were no limitations, we could easily enter a shellcode as input and would get an interactive shell. However, we have some obstacles here. First one is the length limitation. 16 bytes are not enough for us, so we need to find a way to execute more bytes. In order to overcome this, I decided to use return-oriented programming to call the code_runner again. You might think that there is a check with the boolean variable called executing. It gets set to true before our code gets executed, so you might say that it won’t execute our code next time, but you are wrong. That would be the case if it was a global variable. However, it is a local variable and gets initialized everytime we enter the code_runner. So, it will be always zero. We will also skip over the

part to not mess with the rbp such that we will be using the same local variables all the time. Now, we also need a way to save the results we find each time our code gets executed. We can make use of the stack, but also we may make use of even some registers!

If you carefully check the disassembled code, you will see that rbx, r12, and r11 are never modified from the start of the code_runner to call rdx. However, when I checked with debugger, I noticed that r11 gets modified. Probably, some of the calls modify it. Still, we have two registers to save our results.

Let’s move on to next obstacle which is not being able to make system calls. We can handle this issue with reading a libc offset from the stack and adding/substracting some value to/from that address to get the execve function’s address.

This brings out the next issue that we are unable to use both 0x00 and 0xFF in our inputs. However, we can do some mathematical tricks to overcome this issue. You will understand what I mean as we move forward.

Let’s first clarify what we really want to achieve. This is the code we’d love to execute.

Since, I will calculate offsets for libc, I connected to ssh and downloaded their file.

Let’s put a breakpoint on memcpy call to get the destination address for our code.

0x7ffff7ff6000 is where our code will be copied. Let’s put a breakpoint there and continue.

Now, we will check the stack to find a libc address.

We have __libc_start_main+240 at offset 128. We can use this address to calculate the address of execve.

Let’s check the address of code_runner, so that we can start writing our payloads.

As I mentioned above, we will skip the first a few lines of the function. We will jump right below the stack canary initialization. So, our target address is 0x4009ab

In our first payload, we will put the code_runner’s address to r12 for future calls. However, 0x004009ab include a 0x00 byte which we cannot use since it would cause fgets to stop reading the rest of our input. In order to solve this issue we will assign 0x4009ab11 to a register. Then, we will shift that register to right by 8 bits. Thus, that register will have the value 0x4009ab.

We will start each payload with pop rcx to get rid of the original return address from the stack to prevent stack from getting corrupted.

Now, we stored code_runner‘s address in r12 and also replaced the return address with it. Let’s move on to our second payload.

Remember that our libc address was at offset 128, but we popped an address first so its new offset became 120. Here we set rax to 15 and then shifted it to left by 3 bits. Finally, we had read the libc address to rbx using rax as the offset. Finally, we pushed the code_runner‘s address again.

Now, we need to calculate the offset for execve using the libc file that we got from their server.

The offset is 0x000abf40 but this also include 0x00 byte. Therefore, we need a trick. Instead of adding 0x000abf40 to rbx, we will first substract 0x11111111 from rbx and then we will add 0x111bd051 to rbx which does not contain any null bytes.

Here is our next payload.

We just substracted 0x11111111 from rbx.

Now, we added rbx to 0x111bd051 and rbx contains the address of execve. Since, I we will use rbx to store “/bin//sh” in the next payload, we saved the execve‘s address to the stack as well.

We set rbx to “/bin//sh”.

Here, we saved the execve‘s addres to rdx first, because we wouldn’t be able to pop it back if we pushed “/bin//sh” top of it. I noticed that rax is always 0 when our function is called. Therefore, I pushed rax and rbx to the stack respectively to create the “/bin//sh” string with null byte at the of it on the stack. Then, push rsp and pop rbx instructions assigned the “/bin//sh” string’s address to the rbx. Then, we again stored execve in the stack and called one more time code_runner.

The cdq instruction clears rdx which is the second parameter of execve. Then, we assign “/bin//sh” string’s pointer to rdi which is the first parameter of the execve and finally we clear rsi which is the third parameter of the execve. Remember that execve’s address is now at the top of the stack. Therefore, with the last return we will succeed to call execve(“/bin//sh”, NULL, NULL).

Let’s compile these payloads.

Now, we will create our payload strings using their bytes.

Here is the python script for the exploit.

Let’s run the script and get our flag.

Here is our flag actf{a_secure_code_invoker_is_oxymoronic}.