nc doubles.2018.ctf.kaspersky.com 10001

doubles

Let’s start with looking at file information and protections.

The file is an 64-bit executable that is dynamically linked and its stack is not executable.

Let’s disassemble its main function.

It simply calls a function named f. Let’s decompile it.

It first allocates 1000 bytes from heap with read, write, and execute permissions. The pointer of this allocated memory is stored at v0. Then, it reads a value between 0 and 6 which is named as n. Next, it reads n doubles from stdin. Each of these doubles are stored in the allocated memory adjacently. Finally, it puts 0x909090909090C031 into v0[14] and puts the average of these floats into v0[15]. Then, simply jump to the v0[14].

0x909090909090C031 is simply xor eax, eax and 6 nops.

The idea is to place a shellcode at the beginning of the v0 using doubles. Since we can enter at most 6 doubles, we are allowed to place a shellcode up to 48 bytes which is more than enough!

We are also able to modify the 8 bytes at v0[15] by altering the average value. We don’t know the address of v0, but since we know the difference between v0 and v0[15] , we can put a relative jump to the start of it.

Here is the assembly code that jumps to v0[15]:

Notice that all registers, even esp, are cleared.

I decided to place the “/bin/sh” string at v0 and place the shellcode at v0 + 8.

Let’s create our shellcode.

Since all registers are already cleared, it is enough to put the address of “/bin/sh” into rdi and set al to 59 before our syscall.

Let’s compile it and get our bytes.

Let’s talk about the relative jump we need to get by the average value. We know that our instruction will be placed at v0[15] which is 112 bytes further from v0[1] which is where our shellcode is located at. We can achieve this jump with the following two bytes: EB 8E. Therefore, we’d like to get a double number whose first two bytes are EB and 8E, respectively, after getting divided by n. In our case n is 4 (“/bin/sh” as double, shellcode as 2 doubles, and a final double to modify the average).

Floating point numbers are stored in the following form:

-1S × M × 2E

S is the sign bit, M is mantissa, and E is exponent field. In 64 bit floating point number mantissa is the last 52 bits. We need a number whose last 4 hexadecimal digits are 8EEB (due to little endian). Since these are the last digits of the number, these digits are belong to mantissa. The good thing is that we don’t need to worry about the division anymore since we are dividing the number by 4, it will only decrease E by 2 and the mantissa will stay the same.

Let’s find a good number for our purpose. We shouldn’t simply go for 0x8EEB, since it is a value which is really really close to 0, the program can store it as 0.

1337.0 is 0x4094e40000000000 in the hexadecimal form. If we alter its last 4 digits and make it 0x4094e40000008eeb, we get 1337.000000008319.

Now, we have everything we need to create our exploit.

Let’s run it and find our flag.

Here is the flag KLCTF{h4ck1ng_w1th_d0ubl3s_1s_n0t_7ha7_t0ugh}.