Shellcode
backtoshell
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 therax
register content (for e.g. read has number0x00
).
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