Constructing the ExploitWe 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.
- 500 byte maximum exploit length
- We don't know what OS version we're running
- We don't know where any useful functions are located
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.
- Build a jumptable of the functions we intend to use, and
- Run our code with reference to our own jumptable.
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.