Photo by Thomas Jensen on Unsplash

Stage One: General Overview

First of all, what are we trying to achieve here? Our goal is to write shellcode for the Linux x64 architecture that will connect back to a remote location over TCP/IPv4 and provide a shell only after the remote client provides a valid password.

In order to write a regular reverse shell, we need to chain several syscalls. The exact order is the following (we’ll take care of the authentication later):

1- We create a new socket to manage the new connection with the socket syscall

2- We connect to the target address issuing the connect syscall

3- We duplicate each standard stream into the new connection stream using the dup2 syscall, so the target machine can read and write messages to and from the source machine

4- We open up a shell by using the execve syscall

Each of these syscalls has a signature we need to address. Certain registers must contain specific values. For example, the rax register is used to identify the syscall that is executed so it should always contain the syscall number. A whole document containing a full syscall table can be found here

No, we are not writing HTML! — Photo by Markus Spiske on Unsplash

Stage Two: Writing a Syscall

Let’s see an example of how to write a syscall

A Simple Syscall: Socket (0x29)

48c7c029000000 mov rax,0x29 ; this is the socket syscall number

48c7c702000000 mov rdi,0x02 ; 0x02 correponds with IPv4

4831f6 xor rsi,rsi

48ffc6 inc rsi ; 0x01 correponds with TCP

31d2 xor edx,edx ; 0 corresponds with protocol sub-family

0f05 syscall ; executes the syscall

Now, this code has some issues. First of all, it’s remarkably long (48 bytes to be precise). Second, it contains a lot of null bytes. Let’s try to fix that!

A More Realistic Approach: Socket (0x29)

The following implementation is 12 bytes long (a quarter of the last example) and contains no null bytes:

6a29 push 0x29

58 pop rax ; sets rax to 0x29 without nullbytes

6a02 push 0x02

5f pop rdi ; same technique for rdi

6a01 push 0x01

5e pop rsi ; same for rsi

99 cdq ; setting rdx to 0 using just one byte

0f05 syscall

In order to put together the reverse shell, we need to write every syscall like this last example.

Photo by Bartosz Kwitkowski on Unsplash

Stage Three: Authentication

In order to add authentication, we need to read from the client file descriptor and compare the input against a password before executing the shell. The code should look roughly like this:

; 6 - Handle incoming connection ; 6.1 - Save client fd and close parent fd

mov r9, rax ; store the client socket fd into r9

; this is not mandatory, may be commented out to save some space

push syscalls.close

pop rax ; close parent

syscall ; 6.2 - Read password from the client fd

read_pass:

xor rax, rax ; read syscall == 0x00

mov rdi, r9 ; from client fd

push 4

pop rdx ; rdx = input size

sub rsp, rdx

mov rsi, rsp ; rsi => buffer

syscall ; 6.3 - Check password

mov rax, config.password

mov rdi, rsi

scasq

jne read_pass

Basically, we read from the client file descriptor, then compare the input against a given password and repeat the process until it succeeds.

Photo by Christian Wiediger on Unsplash

Stage Three: Writing a Reverse Shell

Armed with all our knowledge we are now prepared to chain every syscall and put together our reverse TCP shell. The following is an example implementation with added comments aimed to clarify each part of the process:

Photo by Mathew Schwartz on Unsplash

Stage Four: Testing!

We can check the shellcode is working by assembling and linking this file, then extracting the shellcode and running it. I have some custom scripts that make this process a little bit easier by automating the assembly and linking process, the shellcode extraction and the generation of test skeletons to run our shellcode into. You may want to check those scripts and/or use them yourself (and report bugs/improvements of course!).

In order to test this we need to get something like netcat listening on port 4444, then fire our shellcode and it should connect back to our server. Here’s a graphic example:

We can also confirm/debug the syscalls being made by using strace. In the following recording, you can go ahead and find the socket & connect combo, then the repeated read syscalls, and finally, the dup2 * 3 and execve after the right password is provided.