WMF 0-day exploit & developing Snort sigs

Two days ago, the presence of a Windows Metafile / graphics processing engine vulnerability made itself known in the form of an exploit, first identified on the Full-Disclosure mailing list. If you're not already familiar with this, I recommend you read up on it now. This has the potential to cause a lot of problems, even though its current implementation is "only" the distribution of spyware. For starters, you can check out the ISC's announcement and Microsoft's advisory.

As part of my job is protecting a large company from junk like this, I immediately began searching for work-arounds. Having tested (and failed) all of the work-arounds mentioned on public websites, I turned to my IDS. "At least," I figured, "I can see when my systems are getting compromised." Unfortunately, all of the signatures I'd found on the WMF exploit seemed either a bit arbitrary, very specific to this vulnerability, or generally not well-formed. I therefore took it upon myself to develop one of our own. Here, I share the process I used and the resulting signature. It was a good exercise on proper signature building that I hadn't been through in awhile. If you develop IDS signatures for your organization, this may be worth a read. Oh, and one more thing: this is your fair warning that my [pre] tags may make some of the literal text a bit difficult to read. I tried to work around this, but there are still formatting problems - my bad.

So, my approach was to identify any WMF files that contained what looked like an overflow or exploit, rather than write the signature to the exploit I currently had in-hand. My first task was to get a handle on what WMF files looked like: their file structure, etc. I found a great sourceforge article describing this here.

From that article:

The standard Windows metafile header is 18 bytes in length and is
structured as follows:

typedef struct _WindowsMetaHeader
WORD FileType; /* Type of metafile (0=memory, 1=disk) */
WORD HeaderSize; /* Size of header in WORDS (always 9) */
WORD Version; /* Version of Microsoft Windows used */
DWORD FileSize; /* Total size of the metafile in WORDs */
WORD NumOfObjects; /* Number of objects in the file */
DWORD MaxRecordSize; /* The size of largest record in WORDs */
WORD NumOfParams; /* Not Used (always 0) */

FileType contains a value which indicates the location of the metafile
data. A value of 0 indicates that the metafile is stored in memory,
while a 1 indicates that it is stored on disk.

HeaderSize contains the size of the metafile header in 16-bit WORDs.
This value is always 9.

Version stores the version number of Microsoft Windows that created the
metafile. This value is always read in hexadecimal format. For example,
in a metafile created by Windows 3.0 and 3.1, this item would have the
value 0x0300.

FileSize specifies the total size of the metafile in 16-bit WORDs.

NumOfObjects specifies the number of objects that are in the metafile.

MaxRecordSize specifies the size of the largest record in the metafile
in WORDs.

NumOfParams is not used and is set to a value of 0.

The first 18 bytes of the captured exploit file (dont' try this at home, kids!) are:

[clopperm@orion wmf_vuln]$ hexdump -n 18 xpl.wmf
0000000 0001 0009 0300 1f52 0000 0006 003d 0000
0000010 0000

Remember, we're looking at this little-endian. This means we see...

WMFHEAD our_file = {
WORD FileType=1;/* won't change */
WORD HeaderSize=9; /* always 9 - won't change */
WORD Version=0x0300; /* doesn't matter, but is 0300 = windows 3.0/3.1 */
DWORD FileSize=8018; /* 8018 words=16036 bytes, checks out, but may change */
WORD NumOfObjects=6; /* 6 objects in file, may change */
DWORD MaxRecordSize=61;/* 61 word max size. Not sure how this impacts sig either */
WORD NumOfParams=0; /* Always = 0. */

So, we can say the typical WMF header looks like this:

0001 0009 ???? ???? ???? 0006 ???? ???? 0000

Translating this into a snort signature (remember, on the network, we need to translate to big-endian!), we get something like this:

alert tcp any any -> any any (msg: "INFORMATIONAL - Windows Metafile (WMF) detected"; content: "|01 00 09 00|"; content: "|06 00|"; distance:6; within:2; content: "|00 00|"; distance:4; within:2; sid:2000002; rev:1; )

Testing this signature on our exploit, it fires properly. MS Office comes with a large number of WMF files. I tested this signature's effectiveness by copying all of the files in the clear between two servers and validating that this identification signature fired on them, as well.

The WMF files in MS Office appear to be Aldus placeable metafiles, which means they have an additional header at the beginning of the file (see the sourceforge article). Since we didn't specify a depth, the signature fired anyway. This is good. While it would be nice to be able to specify an initial search depth to match the first pattern, HTML & such appearing before the file make this difficult to guess.

We know the Intel NOP opcode is 0x90. Looking for this repeated will identify a NOP slide. The exploit we have in-hand shows the NOP slide beginning at address 0x426 to address 0x15b0, but without more detailed knowledge of the vulnerability (and to make this more generic, covering ALL WMF overflows), we'll need to search the rest of the packet. We'll require 16 NOP's (an arbitrary number) to trigger. Our new signature looks like this:

alert tcp any any -> any any (msg: "SHELLCODE - Windows Metafile (WMF) with NOP Slide"; content: "|01 00 09 00|"; content: "|06 00|"; distance:6; within:2; content: "|00 00|"; distance:4; within:2; content: "|90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90|"; distance:0; sid:2000002; rev:1; )

Testing this against our exploit, we have a winner! This should detect this and any other WMF files that include a NOP slide in them. Confirming we didn't just create a noisy signature, this didn't fire on traffic containing legitimate WMF file copies, the same one we used previously to validate the part of the rule that detects the WMF file format.

The "any" keywords for ports & IP's are dangerous. This is the one thing we'll modify specifically for this exploit, so we don't over-burden the ids:

alert tcp $EXTERNAL_NET $HTTP_PORTS -> $HOME_NET any (msg: "SHELLCODE - Windows Metafile (WMF) with NOP Slide"; flow: established,from_server; content: "|01 00 09 00|"; content: "|06 00|"; distance:6; within:2; content: "|00 00|"; distance:4; within:2; content: "|90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90|"; distance:0; classtype:attempted-user; reference:url,www.frsirt.com/english/advisories/2005/3086; sid:2000002; rev:1; )

This will catch any HTTP downloads of WMF files with NOP slides in them. You'll note that I also added the classtype and URL references. Of course, this was tested against the exploit flying over the wire, as well as legitimate WMF file copies, and passed with flying colors. Now,
all we need to worry about is if the attack vector changes. We don't have to worry about the specifics of the exploit!

Hope this is informative to everyone. It was yesterday's afternoon project for me :-)


metagrapher said...

you fucking rock. I just want you to know that. that is all. ;)

metagrapher said...

PS. I wish I had started at this company a day ago. And I now also regret not reading Full Disclosure as regularly as I should. Not that I could have prevented the ignorance of an entire staff at a company I wasn't "technically" working for yet. :P But yeah.
thanks for working on the IDS sig for us tho. for that, sir, you rock. and you rock hard.

Michael Cloppert said...

Thanks Steve, glad I could help!!