Last week, I took some time to check the mitigation for the EoP vulnerability that I described in the last post. In this short blog post I’ll describe how Malwarebytes v3.7.1.2839 implemented a failed mititgation and how I exploited it again with the same attack vector.

As in the last post, I’m assuming Windows 7/10 x86 but x64 is also affected. The final PoC is here.

Recap

Let’s recap the issue. The Anti-Exploit driver of Malwarebytes v3.6.1.2711 doesn’t check the input buffer properly and uses a DWORD at the offset 0x290 to call _wcslwr.

Let x be the value of the DWORD and p be the address of the input buffer. Then the argument to _wcslwr is: p + 2 * x + 0x29A.

If we assume x = 0x40000000, the argument almost always points to an unmapped region in the userspace. In such cases, _wcslwr causes a recoverable exception and creates a crash file under C:\, which can be redirected to an arbitrary location.

My previous PoC dropped a bind shell DLL on C:\Windows\system32\msfte.dll to achieve EoP.

Patch Analysis

Let’s see how Malwarebytes patched the driver. They just added an extra layer of defence, i.e. sub_402144.

alt

sub_402144 is such a small function. A decompiled version of it is shown below.

alt

sub_402144 imposes a few more constraints on the input buffer. Let x, p be as defined above, y be a DWORD at the offset 0x294 of the buffer and L be the size of the buffer. Then the following three conditions must be satisfied.

  1. L = 2 * z + 0x29E
  2. WORD at (p + 2 * x + 0x298) = 0
  3. WORD at (p + 2 * z + 0x29A) = 0

where z = x + y. If we assume x = 0x40000000, y = 0x40000311 and L = 0x8c0, the first condition is satisfied. The last one is trivial as p + 2 * z + 0x29A points to the tail of the input buffer considering the first one.

As for the second condition, note that p + 2 * x + 0x298 points to a WORD in the userspace and p + 2 * x + 0x29A is the argument to _wcslwr. If we fill unmapped regions of the userspace with readonly copies of “\x00\x00\x41\x00” with something like heap spraying, then the second condition is satisfied.

Below is an example of the code that performs readonly heap spraying.

def heap_spray():
    VirtualAlloc = windll.kernel32.VirtualAlloc
    VirtualProtect = windll.kernel32.VirtualProtect

    buff = "\x00\x00\x41\x00" * 0x4000
    PAGE_READONLY = 0x2
    PAGE_READWRITE = 0x4
    MEM_COMMIT = 0x00001000
    MEM_RESERVE = 0x00002000
    oldProtect = c_int(0)

    for i in xrange(0x7800):
        addr = VirtualAlloc(0, 0x10000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
        if not addr:
            break
        memmove(addr, buff, 0x10000)
        result = VirtualProtect(addr, 0x10000, PAGE_READONLY, byref(oldProtect))
        #if not result:
        #    print "failed to change memory protection"
        #print "0x%08x" % addr

With these settings, sub_402144 returns 1 without causing any exception or BSOD in most cases. Eventually _wcslwr is called, which causes an exception as it tries to convert a readonly upper case string \x41\x00 (“A”) to \x61\x00 (“a”).

This is the same situation in which I exploited the driver in the last post.

To summarize, the payload I used in my PoC looks like below before it’s obfuscated. The dotted parts are filled with nulls.

alt

Exploitation

alt

References

[1] https://github.com/GradiusX/HEVD-Python-Solutions/blob/master/Win7%20x86/HEVD_InsecureKernelResourceAccess.py

[2] http://blog.airesoft.co.uk/2012/01/chroot-ing-in-windows-as-easy-as-a-b-c/