Constructing the Exploit

We are now in control of the machine. We need to do something useful now, but we are limited on how long we can make our code. You'll notice that after about 763 characters, that we end up crashing in a different place. This is also an overflow, a different one. So Microsoft really has two bugs to fix, but hey, we're only exploiting one right now. If we have time, we'll get back to the other.

The first 256 characters get blown away so our code only has about 500 bytes of room in which to fit. Here's what we have to deal with:

This kinda sucks, but let's look at this from a non-exploit point of view. If I was a little executable, compiled for Windows, I would run on both Win95 and WinNT. If I want to call ExitProcess, how do I know where the function is? It's in two different locations in Kernel32.DLL between the two OS's. (and in two different places between OSR1 and OSR2 of W95, and various service pack releases of WinNT, for that matter). I can't just jump to a random address.

I have to be told the location of these functions. There is a function in the Win32 API called "GetProcAddress". It returns the memory address of a function, given it's name and it's module handle. So what's the address of GetProcAddress? We don't know! We would have to call it to find out! So how does it work? Import tables.

Import tables are structures in the PE-Executable format that specify that the operating system should tell us the location of certain functions and fill in a table with the values. Use DUMPBIN to get the import table. Both DLLs and EXEs have import tables. We know that MSCONF.DLL is in memory, and that since we're only dealing with one version of MSCONF.DLL, if GetProcAddress was in it's import table, then the address for GetProcAddress was written to a fixed location in MSCONF.DLL's table space by the operating system when it was loaded.

So we dump it:


Microsoft (R) COFF Binary File Dumper Version 5.10.7303

Copyright (C) Microsoft Corp 1992-1997. All rights reserved.





Dump of file msconf.dll



File Type: DLL



  Section contains the following imports:



    KERNEL32.dll



                 23F   Sleep

                 183   IsBadReadPtr

                 17E   InterlockedIncrement

                 .

                 .

                 .

                  1E   CompareStringA

                  98   FreeLibrary

                 116   GetProcAddress

                 190   LoadLibraryA

                  4C   DeleteCriticalSection

                  51   DisableThreadLibraryCalls

                 .

                 .

                 .

And there we are! GetProcAddress, and LoadLibraryA! LoadLibrary can be used to get module handles of DLLs that are loaded, and to load DLLs that aren't loaded. It basically returns the DLL base address. This is important because the base address of the KERNEL32.DLL differs between NT and 95.

So we pop into our debugger and search through memory until we find the address of the functions. They appear at 0x6A60107C (LoadLibraryA), and 0x6A601078 (GetProcAddress). We just need to call these locations using an indirection (call dword ptr [0x6A60107C]) and we'll go to the right places.

In order to be efficient, we are going to build our exploit in two parts:

This reduces the amount of code required to call a function when necessary, and minimizes stack usage to save registers. This is important, because if we PUSH or POP too much, we might blow away our code or cause other stack problems. In order to build this jumptable though, we'll need to know ahead of time what Win32 functions we'll be calling. So lets figure out what we want to do. 500 bytes is far too small for a really useful Windows program, so instead, we'll make our little egg code download another program off of the internet, a larger, well constructed executable, and execute it. This will enable us to write this little tedious chunk once, and have it execute a piece of higher level code.

To download a URL, we'll need InternetOpenA, InternetCloseHandle, InternetOpenUrlA, and InternetReadFile from WININET.DLL. We'll also need _lcreat, _lwrite, and _lclose from KERNEL32.DLL to write the file to disk once downloaded. We'll need GlobalAlloc from KERNEL32.DLL to allocate memory for what we're downloading. We'll also want WinExec and ExitProcess (also in KERNEL32.DLL) to execute what we've downloaded, and kill the RUNDLL32 process that we so thoroughly corrupted (before it can make a sound).

Note that in a regular Win32 program, you would never call _lcreat, or any of the other obsolete functions. However, they exist in Win95 and NT, and they have far simpler calling syntax than CreateFile and friends. So we'll use 'em.

Show me the code!

What's an EIP again?