原生x64程序的异常分发
-
x64平台上原生x64程序的异常分发流程与x86平台上一致
不同的是SEH相关数据结构改变了
主要设计思路如下
-
在编译阶段,确定所有异常处理
Handler
的地址将其放入一个自读的表中,当异常发生,异常分发函数会根据异常分发的地址在这个表中查找相应的handler进行处理这是一种基于table的异常处理机制
-
编译器做了以下的工作
-
提取所有函数的起始地址,结束地址,函数的序言操作(包括栈操作和寄存器操作)异常处理信息等生成两个表,其中一个称做函数信息表,包含当前程序中所有函数在内存中的位置信息(除了叶函数,即那些既不调用其他函数,也不包含异常处理的函数)。这些信息被放在一个单独的区段
.pdata
中,该区段的位置可以从PE头部的数据目录IMAGE_EXVEPTION_DIRECTORY
中找到typedef struct _RUNTIME_FUNTION{ ULONG BeginAddress; ULONG EndAddress; ULONG UnwindData; }RUNTIME_FUNCTION,*PRUNTIME_FUNCTION;
在该表中,每一条函数的信息被称为一个
FunctionEntry
所有的FuntionEntry
都是按照RVA的大小升序排列的,便于使用二分法快速查找-
当异常发生时,
ntdll!RtlDispatchException
函数根据异常发生时的Rip调用函数ntdll!RtlLookupFuncitonTable
,查找异常Rip位于哪个模块的ExceptionTable
中,然后调用RtlLookupFuncitonEntry
查找Rip
所在的FuncitonEntry
来取它的UnWindInfo,从UnWindInfo中取得ExceptionHandler并执行 -
X64上该函数通常是
_C_specifc_handler
。_C_specifc_handler
函数的功能等同于 x86 平台上的_exceplhandler3/_excepi_handler4
函数,它会继续根据异常 Rip 和 ScopeTahle 定位异常到底发生在哪个 TRy
块中,然后转去执行相应的 FiterFunc,并根据返回值确定是执行 HandlerFunc、返回异常点继续执
行还是查找下一个异常处理函数。在査找下一个异常处理节点时,需要调用 ntd!RtlVirtualUnwind
函数进行模拟展开,该函数是推动异常遍历的重要函数,其原型如下。该函数的主要功能是根据传入的 ControlPc(也就是异常发生时的 Rip)和 ContextRecord 等参数
虚拟(模拟)展开该函数,并返回该函数的一些信息,例如HandlerData(SCOPE_TABLE)、
EstablisherFrame(rsp 或栈帧 )。这样,ContextRecord 就被恢复成父函数在调用 ControlPe 所在函数之
后的状态了。
根据 SEH 异常处理的嵌套关系可以知道,当子函数不处理异常时,会继续寻找其父函数的异常处理函数。所以,当 RtlVirtualUnwind 返回后,RtDispatchException 就可以根据“ContextRecord->Rip语句找到父函数对应RUNTIMEFUNCTION,进而找到它的UNWINDINFO,从而推动整个遍历过程了。对于叶函数,RtlLookupFunetionEnty 将返回“NULL”,RtlDispatchException 就能知道这是
一个叶函数,并找到该叶函数的父函数,从父函数开始继续遍历。因为叶函数对整个异常处理过程
没有影响,所以在这里将完全无视叶函数。
关于顶层异常处理,x64平台与 x86 平台相同,UEF 函数都是 kemel32!UnhandledExceptionFilter,
功能也完全相同,因此就不重复介绍了。
-
-
wow64
WOW64是在x64平台上运行32位程序的机制,在内部实现使用了操作系统提供的一些特殊模块,在32位应用程序和64位系统内核之间做了一个代理,也就是截获32位程序的系统调用,然后翻译成64位调用,再把内核返回结果翻译为32位返回
- Wow64.d:管理进程和线程的创建,勾住异常分发和 Nloskrml.exe 导出的基本系统调用,实现文件重定向及注册表重定向。
- Wow64Cpu.dl:为每个正在 WOW64 内部运行的线程管理其 32 位 CPU 环境,针对从 32 位到 64 位或者从 64 位到 32 位的 CPU 模式切换,提供与处理器体系结构相关的支持。
- Wow64Win.dll:截取 Win32k.sys 导出的 GUI系统调用。
- 64 位 ntdll.dll:负责执行真正的系统调用。
WOW64 通过 64 位 ntdll.dl 的 KiUserExceptionDispatcher 勾住异常分发过程。在 64 位内核要给一个 WOW64 进程分发一个异常时,异常信息会先到达 64 位 ntdll.dll 的 KiUserExceplionDispalcher
函数处。
在该函数中,WOW64 会捕获原生的异常及用户模式下的环境记录(ConlextRecord),
然后将其转换为 32 位的异常和环境记录,再把它交给 32 位 ntdll.dl 的 KiUserExceptionDispatcher,接下来的过程与原生 32 位程序中的异常分发过程一致
veh和SEh的一些应用
-
利用SEH对用户输入进行验证
如验证字符串的API IsBadStringPtrA 就使用SEH机制
-
驱动层的输入合法性验证
如
ProbeForRead
和ProbeForWrite
-
加密与解密
由于SEH可方便地操作线程环境CONTEXT,而且不像普通函数调用能直接看到调用关系,在加密和解密中肯呢个会使用SEH实现一些隐蔽的调用和特殊的功能,典型的应用就是主动制造异常,然后在异常处理程序中修改CONTEXT达到反调试的效果,或者跳跃性的改变程序的流程,达到反跟踪的目的
-
利用VEH可以实现API Hook
可以利用VEH的全局特性和优先SEH执行的特性,在进程内的指定代码处下断点,然后进行一些干预操作,可以只更改1字节就能实现API HOOK