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.

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

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
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
oldProtect = c_int(0)

for i in xrange(0x7800):
break
#if not result:
#    print "failed to change memory protection"