windows内核基础与异常处理
阅读原文时间:2023年07月09日阅读:1

前两日碰到了用异常处理来做加密的re题目 所以系统学习一下windows内核相关

windows内核基础

内核层:R0 零环 核心态工作区域 大多数驱动程序

应用层:R3 用户态工作区域 只能使用win32 api与系统交互

调用流程

当用户调用一个有关I/O的API时

该API封装在一个用户层的DLL文件中(如kernel32.dll或user32.dll)

此dll函数的更底层函数被包含在ntdll.dll中

即用户调用的系统API在ntdll.dll均有对应

(ntdll.dll中的函数均为成对出现 且为Nt与Zw开头 他们本质一样)

当调用到ntdll.dll中的函数时,检查完参数后,会通过int 2Eh或SysEnter进入R0内核层

调用内核ntoskrnl.exe中的SSDT(系统服务处理函数)他们与ntdll.dll的函数一一对应

调用区别

_从用户态调用 Nt 与Zw_ 函数时**

完全一致 均为设置系统服务表与栈中参数后由SYSENTER跳入内核态

再由KiSystemService跳入对应系统例程

代码会严格检验传入参数

_从内核态调用 Nt 与Zw_ 函数时**

Nt* API:直接调用对应函数代码

Zw* API:由KiSystemService跳入对应函数代码

用户模式Nt*api会将Previous Mode改为用户态

内核模式Nt*api会将Previous Mode改为内核态

Zw*api只有用户态 但一调用就会将Previous Mode改为内核态

而Zw*不会严格检查传入参数 进而提高效率

总结

Zw和Nt在表面功能一模一样!

windows异常处理

也可使用RaiseException()函数来主动抛出一个异常

window正常启动后运行在保护模式下,当中断或异常发生时,CPU会通过IDT(中断描述符表 Interrupt Descriptor Table)来寻找相对应的处理函数

IDT

IDT存在于物理内存中 共有256项 32位每项8字节 64位中每项64字节

每个cpu中都有一份IDT拷贝 下面主要考虑32位

异常处理准备工作

会根据异常号来寻找异常处理函数 如中断号03(即int 3)对应断点异常 由nt!KiTrap03 函数处理

该函数封装异常信息(包括异常产生原因与异常时线程状态(如寄存器值与其他特殊变量))

然后下发给内核的nt!KiDispatchException来处理

该函数会根据是否存在内核调试器用户态调试器,以及调试器对异常的干预结果来进行不同的处理

内核态的异常处理机制(内核态下出现异常

1.交由内核态调试器

当在被内核态调试器调试时,将异常处理控制权移交,表明FirstChance,

​ 内核调试器根据设置来判断处理,若无法决定则发生中断将控制权移交给用户

​ 若正确处理则继续执行程序

若无内核态调试器则跳过该步

2.交由nt!RtlDispatchException函数处理(由SEH处理

若无内核调试器或内核调试器选择不处理该异常,将会调用内核的nt!RtlDispatchException函数,根据SEH来处理

3.交由内核态调试器

若nt!RtlDispatchException函数未能处理该异常,系统将异常处理控制权移交内核调试器(SecondChance)

4.蓝屏!

若不存在内核态调试器或在第二次机会时仍不处理,则系统调用KeBugCheckEx的BSOD 引发蓝屏

在以上任何一步异常被处理 整个异常处理流程就会被终结

用户态的异常处理机制

完全可以交由内核调试器来处理 但一般内核调试器对用户异常不关心

所以将会分发给用户调试器

1.交由用户态调试器

当在被用户态调试器调试时,将异常处理控制权移交,表明FirstChance,

若无用户态调试器则跳过该步

2.由SEH与VEH来处理

若无用户态调试器或用户态调试器未处理该异常,

将在栈上放置EXCEPTION_RECORD和CONTEXT两个结构

然后调用ntdll.dll的nt!RtlDispatchException函数

SEH:在有调试器时进入下一步,否则调用API函数SetUnhandledExceptionFilter的顶级异常处理,即显示以下对话框

若无调试器附加或调试器无法处理异常则ExitProcess函数来终结程序

3.再次分发给用户态调试器

若nt!RtlDispatchException函数未能处理该异常,系统将异常处理控制权移交用户调试器(SecondChance)

若无调试器则直接结束进程

4.错误弹窗

若第二次机会调试器仍不处理则

TIB

TIB(线程信息块)位于TEB(线程环境块)头部

而TIB的首项指向异常处理链表

32位下FS寄存器指向TEB 即指向TIB 即指向异常处理链表

64位下是gs寄存器指向TEB

而TIB[0]对应的

因为TEB是线程环境块,所属于当前线程,所以SEH机制仅限于当前线程

SEH

若想新增或删除 则直接在链表头部编写一个新的_EXCEPTION_REGISTRATION_RECORD

可以说SEH是基于栈帧的异常处理机制

因此SEH是从0开始往后面找的异常处理

SEH中异常处理函数的返回值

VEH与SEH大抵类似 同样是链表 但调用顺序为调试器>VEH>SEH

VEH对整个进程都有效 而SEH对单个线程有效

总结 大概吧

对我目前碰到的re题来说 与异常相关的只有主动抛出异常(如idiv rax)在循环加密过程中rax可能为0 从而进入另外的加密函数

若想动态跟踪该类函数 可以调试OD x64dbg 或IDA 的debug调试 将异常交由被调试者处理

(别没事瞎F5 伪代码还真不显示SEH 汇编

只总结了一点点 实际上加密与解密中关于内核和异常处理还有很多更深的内容 (但我还是选择先看17章(