原生x64程序的异常分发

  • x64平台上原生x64程序的异常分发流程与x86平台上一致

    不同的是SEH相关数据结构改变了

主要设计思路如下

  • 在编译阶段,确定所有异常处理Handler的地址将其放入一个自读的表中,当异常发生,异常分发函数会根据异常分发的地址在这个表中查找相应的handler进行处理

    这是一种基于table的异常处理机制

  • 编译器做了以下的工作

    1. 提取所有函数的起始地址,结束地址,函数的序言操作(包括栈操作和寄存器操作)异常处理信息等生成两个表,其中一个称做函数信息表,包含当前程序中所有函数在内存中的位置信息(除了叶函数,即那些既不调用其他函数,也不包含异常处理的函数)。这些信息被放在一个单独的区段.pdata中,该区段的位置可以从PE头部的数据目录IMAGE_EXVEPTION_DIRECTORY中找到

      typedef struct _RUNTIME_FUNTION{
          ULONG BeginAddress;
          ULONG EndAddress;
          ULONG UnwindData;
      }RUNTIME_FUNCTION,*PRUNTIME_FUNCTION;

      在该表中,每一条函数的信息被称为一个FunctionEntry所有的FuntionEntry都是按照RVA的大小升序排列的,便于使用二分法快速查找

      1. 当异常发生时,ntdll!RtlDispatchException函数根据异常发生时的Rip调用函数ntdll!RtlLookupFuncitonTable,查找异常Rip位于哪个模块的ExceptionTable中,然后调用RtlLookupFuncitonEntry查找Rip所在的FuncitonEntry来取它的UnWindInfo,从UnWindInfo中取得ExceptionHandler并执行

      2. X64上该函数通常是_C_specifc_handler_C_specifc_handler 函数的功能等同于 x86 平台上的_exceplhandler3/_excepi_handler4 函数,它会继续根据异常 Rip 和 ScopeTahle 定位异常到底发生在哪个 TRy
        块中,然后转去执行相应的 FiterFunc,并根据返回值确定是执行 HandlerFunc、返回异常点继续执
        行还是查找下一个异常处理函数。在査找下一个异常处理节点时,需要调用 ntd!RtlVirtualUnwind
        函数进行模拟展开,该函数是推动异常遍历的重要函数,其原型如下。

        image-20240722151728918

        该函数的主要功能是根据传入的 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机制

  • 驱动层的输入合法性验证

    ProbeForReadProbeForWrite

  • 加密与解密

    由于SEH可方便地操作线程环境CONTEXT,而且不像普通函数调用能直接看到调用关系,在加密和解密中肯呢个会使用SEH实现一些隐蔽的调用和特殊的功能,典型的应用就是主动制造异常,然后在异常处理程序中修改CONTEXT达到反调试的效果,或者跳跃性的改变程序的流程,达到反跟踪的目的

  • 利用VEH可以实现API Hook

    可以利用VEH的全局特性和优先SEH执行的特性,在进程内的指定代码处下断点,然后进行一些干预操作,可以只更改1字节就能实现API HOOK