Shellcode

backtoshell

void main(void) {
  code *UNRECOVERED_JUMPTABLE;
  
  UNRECOVERED_JUMPTABLE = (code *)mmap((void *)0x0,0x1000,7,0x22,-1,0);
  read(0,UNRECOVERED_JUMPTABLE,0x200);
                    /* WARNING: Could not recover jumptable at 0x00401144. Too many branches */
                    /* WARNING: Treating indirect jump as call */
  (*UNRECOVERED_JUMPTABLE)(0,0,0,0,0,0);
  return;
}

UNRECOVERED_JUMPTABLE = the compiler is guessing the presence of a jump table, but its not. Its just a buffer.

mmap: mmap = linux func that allocates memory regions

If the mmap address is zero, the address will be randomized. The other parameters says where to find the size of the data that will be allocated, and its permissions. The fd is used to load a file into memory. in mmap 7 means rwx.

read : read = linux syscall that reads bytes from file descriptors (for e.g. 0 is the fd of stdin). Example:

read(0, UNRECOVERED_JUMPTABLE, 0x200) reads from zero into the jump table for 0x200 bytes. Note that this info is obtainable trough man.

behaviour

(*memory)(0,0,0,0,0,0) means: jump to memory. It means that the first six registers contain zeros when jumping. So basically this binary is creating a page in memory, reading user input into it, and then it jumps into it. We want to put some code in that page which when executed will helps us to spawn a shell.

Putting together the shellcode

First of all we need a syscall:

syscall = way that programs use to interact with the kernel, in order for e.g. to r/w a file, send packets, use hw, etc. To execute syscalls, some registers get set up and the syscall is ran. The kernel knows which syscalls is going to be executed by the rax register content (for e.g. read has number 0x00).

To open a shell, we need the execve syscall, which has to be executed with /bin/sh as first parameter. In order to do that we will put 0x3b in the rax register, and a pointer to /bin/sh\x00 into rdi.

Note: Since the code of the binary sets rsp to zero, when the program jumps to our shellcode and we push things on the stack, the execution fails. We first need to move the stack in the middle of the memory page allocated with memmap, and then we can execute our shellcode.

Note (zeros): that zeros in the exploits need to be avoided is the input is read with a scanf function.

Note (char array as sycall argument): We need to put a pointer to a zero to terminate the array of arguments of the execv function, which means that we need to put a pointer to a zero in the rsi register (since the function writes those registers in this order: rax, rdi, rsi).

Note (rax register): The rax register is usually used for function return values.

After flagging we can get the source code of the binary from the server:

syscall, syscalr

Here we need an auto modifying shellcode, since the only obstacle to executing our exploit is that we cannot give \x0f or \x05 in input to the read otherwise the program would exit:

The strategy here is to make the shell code increment the content of the instruction pointer. This means that when the code of the challenge is checked after the read, the incriminated bytes are not found, but when the shellcode is acually executed the final code will contain the opcodes for a syscall. Here's the assembly:

Here we make the code increment by 257 the last two bytes of itself:

And then when we get the assembled code:

We can manually swap the last two bytes:

This means that when the code will be run it will first pass the check, and then correct itself to execute syscall as last assembly instruction.

multistage

Hint: the challenge name means that we'll probably need a two step exploit for some reason. In this case the problem is the following:

Which means that we need an exploit no longer than 20 bytes. The following is a valid (but still too long) shellcode:

Length: (26 bytes of shellcode +8 of /bin/sh\x00) > 20.

Solution: execute another read, which reads more byte into the buffer global variable, in which we will put the execve call to spawn a shell:

Note: mov takes up a lot more bytes than xor.

We can also put 20 nop at the beginning of our shellcode instead of putting rax+20 in the rdx register, and try to shorten even more our shellcode:

gimme3bytes

Ok so we have a giant memory page allocated to which we put user controlled input into, and then we jump to it and execute it. This would be the simplest ctf ever, if it was not for the fact that we can only read three bytes into it.

This really sucks.

The first thing that comes to mind while tackling this challenge is that we could try to execute another read, but still, how the f*ck would we do that with only 3 bytes of code? To execute a read, since we've got a 64 bit binary and the syscall calling convenction wants function parameters to be set up into registers, we'll need the following:

  • rax = 0x0

  • rdi = 0x0

  • rsi = <READ_CONTENT>

  • rdx = <READ_LENGTH>

And then we need to execute the syscall with the relevant opcodes, which alone take up two bytes (0f 05). If we run the binary in gdb and we check the content of registers while the program jumps to our shellcode, we'll see the following configuration:

Where 0x7ffff7ff7000 is the address of the input taken by the read. This means that we actually have all we need to correctly perform a read. In fact all the registers are correctly set to spawn another read at the time the program jumps to our input.

Note that rdx actually contains the address to the page in which our input goes, but since it will be interpreted as an integer representing how much we are going to read, it is more than fine! In fact gdb reports that the address is 0x7ffff7ff7000, which amounts to 140737354100736. I think its enough.

At first I tried to pass \x90\x0f\x05, which is nop syscall. Technically it should work, but from man read we see that:

On Linux, read() (and similar system calls) will transfer at most 0x7ffff000 (2,147,479,552) bytes, returning the number of bytes actually transferred. (This is true on both 32-bit and 64-bit systems.)

:(

Still, we can change rdx content with one byte!

Then, after rdx has been properly set up, we can use two syscalls: the first, by using only three bytes, will be a read that will read the proper shellcode to which we'll jump right after the first call. Final exploit:

Note: we can send both shellcodes with one call of the send function.

leakers, gonnaleak, aslr

Note: I have put all those challenges in one chapter because they are really similar.

leakers code:

Note: The source code of the challenge is found in the server directory after obtaining the shell. We are given only the binary. This is true for every challenge.

To leak values from the stack we can exploit the fact that we have a read that puts uset input on the stack, but the size check performed is actually bigger than the buffer itself. Since later on the buffer content is displayed via a printf, which stops reading when it encounters a terminator, we have our exploit: the read does not put a terminator when it stops (for e.g. the scanf does), which means that we can overwrite null bytes that follows the buffer in the stack to leak stack content up to our canary.

To leak stack canary we can give in input to the read which fills the local buffer the following content:

To completely fill it and to overwrite the null bytes that makes up the padding of the canary. In fact the canary is located right after the local buffer in the stack:

Then the canary gets leaked by the printf:

And we can extract it with python:

Gonnaleak is similar, but one more step is required: we do not have a global buffer, which means that we cannot jump to a fixed address. The obvious solution is to leak some sort of address from the stack which belongs to the frame of main, which we can use to jump to our local shellcode by doing some math on relative addressing. Full code below.

Regarding ASLR, we have to go one step further: we have PIE enabled, which means no more hardcoded global buffers. We also got NX, so no local buffer overflows. To solve this challenge we have to leak the address of the global buffer, which is executable, put there the shellcode and overwrite seip with its address. Source code of the challenge:

Last updated