|
.-= {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-}=-. Principles of Buffer Overflow explained by Jus .-= {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-} {-=+=-}=-. This article is an attempt to quickly and simply explain everyone's favourite manner of exploiting daemons - The Buffer Overflow. - Huh? - The remote buffer overflow is a very commonly found and exploited bug in badly coded daemons - by overflowing the stack one can cause the software to execute a shell equal to its current UID - thus if the daemon is run as root, like many are, a root shell will be spawned, giving full remote access. A buffer is a block of computer memory that holds many instances of the same data type - an array. Arrays can be static and dynamic, static being allocated at load time and dynamic being allocated dynamically at run time. We will be looking at dynamic buffers, or stack-based buffers, and overflowing, filling up over the top, or breaking their boundaries. A stack has the property of a queue of objects being placed one on top of the other, and the last object placed on the stack will be the first one to be removed. This is called LIFO - or last in first out. An element can be added to the stack (PUSH) and removed (POP). A stack is made up of stack frames, which are pushed when calling a function in code and popped when returning it. The stack pointer (SP) always points to the top of the stack, the bottom of it is static. PUSH and POP operations manipulate the size of the stack dynamically at run time, and its growth will either be down the memory addresses, or up them. This means that one could address variables in the stack by giving their offsets from SP, but as POP's and PUSH's occur these offsets change around. Another type of pointer points to a fixed location within a frame (FP). This can be used for referencing variables because their distances from the FP will not change. - The Overflow - A buffer overflow is what happens when more data is forced into the stack than it can handle. We use this to change the flow of execution of a program - hopefully by executing code of our choice, normally just to spawn a shell. We can change the return address of a function by overwriting the entire contents of the buffer, by overfilling it and pushing data out - this then means that we can change the flow of the program. By filling the buffer up with shellcode, designed to spawn a shell on the remote machine, and overwriting the return address so that it points back into the buffer, we can make the program run the shellcode. This is just a simplified version of what actually happens during a buffer overflow - there is more to it, but the basics are essential to understand if you want to win an argument one day. -jus (jus@security.za.net) [ Epilogue by Wyzewun: Time for a practical example. I did this some time ago on my Dad's Windoze box to explain it to myself: I had downloaded a file on Win32 buffer overflows but I really didn't feel like reading, so I figured it out myself instead. It took me +-20 mins to do the whole thing, but at least I was keeping a log of me trying to get it right so I can just paste it more or less unchanged here - save, of course, for the explanations. Next time I'll get human and actually READ UP on whatever I'm trying to do before I try DO it so I don't waste so much damn time. :/ Anyway, here's the notes... #include <iostream.h> #include <string.h> int main() { char buffer[40]; char buffer2[20]; // This doesn't need to be smaller though cout << "Gimmee a variable\n"; cin >> buffer; strcpy(buffer2, buffer); return 666; } Because strcpy() has no bounds checking, there is an obvious buffer overflow vulnerability here... c:\>overflow Gimmee a variable 12345678901234567890 It executed fine. Now lets try... c:\>overflow Gimmee a variable 123456789012345678901 At this point Windoze cuts in with the following... OVERFLOW caused an invalid page fault in module OVERFLOW.EXE at 015f:00402127. Registers: EAX=0000029a CS=015f EIP=00402127 EFLGS=00000206 EBX=00530000 SS=0167 ESP=0063fe0c EBP=00630031 ECX=0063fdd4 DS=0167 ESI=81596754 FS=1157 EDX=00400031 ES=0167 EDI=00000000 GS=0000 Bytes at CS:EIP: 89 45 e4 50 e8 12 15 00 00 8b 45 ec 8b 08 8b 09 Stack dump: 00000000 81596754 00530000 c0000005 0063ff68 0063fe0c 0063fc3c 0063ff68 00403d18 00407190 00000000 0063ff78 bff8b537 00000000 81596754 00530000 Is this a buffer overflow bug or is this something else we are mistaking for one? Well, let's check, we feed it a good 30 "a" characters and we look at the values of the registers when it dies.... Registers: EAX=0000029a CS=015f EIP=61616161 EFLGS=00000202 EBX=00530000 SS=0167 ESP=0063fe00 EBP=61616161 ECX=0063fddc DS=0167 ESI=81596628 FS=117f EDX=00006161 ES=0167 EDI=00000000 GS=0000 Aaah, see that? EIP is 61616161 - 61 being the hex value of the "a" character, so it's overflowing allright. Now let's exploit it. :) First off, we add the following line into the example C++ proggy above... cout << &buffer2 << "\n"; And when executing the program, the output we get is as follows... 0x0063FDE4 Gimmee a variable Right, so buffer2's address is 0x0063FDE4 - and just in case that's a bit off for some reason - we'll pad it a bit. Padding? Right. Executing the NOP function (0x90) which most CPU's have - just something to do nothing. That way, hopefully, when we overwrite the return address we can land somewhere in the middle of the NOPs, and then just execute along until we get to our shellcode. Errr, I'm not being clear, what I mean is the buffer will look like: [NOPNOPNOPNOP] [SHELLCODE] [NOPNOPNOPNOP] [RET] Shellcode? Right. We can execute pretty much anything we want, and as much as I would like to have interesting shellcode, I don't have the tools to make some on this PC, and I *really* don't feel like going online to rip somebody else's. And so, my choice in shellcode - int 20h - program termination. :) Right!!! So our shellcode is 2 characters, and we can feed the program 24 characters before we start overwriting the return address, so lets have 11 NOP characters on either side of our shellcode just to make it pretty and even looking. Let's try this out... c:\>overflow Gimmee a variable Í cıä c:\> Heeey, I gave it too many characters and it didn't crash. It worked. :) That string in hex would be 9090909090909090909090CD20909090909090909090909063FDE4, the CD20 in the middle being interrupt 20h, and the 63FDE4 being the address of the buffer we're overflowing, which we are setting as the return address, namely 0x0063FDE4. Hopefully you're beginning to see the idea here. If you would like to play around with my example file some more, I included the binary in the general-junk directory of this issue. Have fun! ]