问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
初探windows异常处理
windows系统里,为了保证系统内核的强壮和稳定,为了保证用户程序的强壮和稳定,提供了异常处理机制,来帮助程序员和系统使用人员处理异常。如果想要更加深入的掌握操作系统,异常处理的知识是必不可少的,不仅如此,软件调试也与异常处理息息相关。
0x00 前言 ======= windows系统里,为了保证系统内核的强壮和稳定,为了保证用户程序的强壮和稳定,提供了异常处理机制,来帮助程序员和系统使用人员处理异常。如果想要更加深入的掌握操作系统,异常处理的知识是必不可少的,不仅如此,软件调试也与异常处理息息相关。 0x01 异常执行流程 =========== 异常产生后,首先是要记录异常信息(异常的类型、异常发生的位置等),然后要寻找异常的处理函数,我们称为异常的分发,最后找到异常处理函数并调用,我们称为异常处理。 **异常的分类** - CPU产生的异常 - 软件模拟产生的异常 0x02 CPU异常 ========== ```c++ CPU指令检测到异常(例:除0) 查IDT表,执行中断处理函数 CommonDispatchException KiDispatchException ``` ![image-20220327153026523.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-624844b516fbd28fb78d031c46c0cc29db467a13.png) 找到IDT表的0号中断 ![image-20220327153034674.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-720c24f3c9f8034ac8a79c686c2f3588ea4734f0.png) 首先保存现场 ![image-20220327153138693.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-bf2c5b7cf17b7f0b1712d6cc43d6e8a96844759a.png) 然后向下走,但是并没有直接异常处理的代码,这里有一个跳转跟进去。为什么操作系统没有直接将异常处理写进去,这是因为操作系统希望我们自己首先能够将异常给处理掉 ![image-20220327153256604.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-95e12ce1282972f00f45ea9fc4a691eef7876ff6.png) 跟进去后发现调用了`CommonDispatchException`函数 ![image-20220327153417571.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-72bbfa694345ac019f562f049c0514d0cfb2ca59.png) `CommonDispatchException`主要是把一些异常的信息存储到了自己的结构体`_EXCEPTION_RECORD`里面,结构如下 ```c++ type struct _EXCEPTION_RECORD { DWORD ExceptionCode; //异常代码 DWORD ExceptionFlags; //异常状态 struct _EXCEPTION_RECORD* ExceptionRecord; //下一个异常 PVOID ExceptionAddress; //异常发生地址 DWORD NumberParameters; //附加参数个数 ULONG_PTR ExceptionInformation [EXCEPTION_MAXIMUM_PARAMETERS]; //附加参数指针 } ``` ![image-20220327153537518.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-dd8a48c1913438f27c622f6a79417aae4b801a29.png) 然后通过`KiDispatchException`去找到异常处理函数 ![image-20220327154532542.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-784d36faee1a8b45b54a7f47b1d37619b6447a98.png) 在前面的跳转中,带过去了两个寄存器`eax`、`ebx`,`eax`我们可以发现它的值为`0c000094`,这个值是操作系统定义的 ![image-20220327153718760.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-aa10701e26647845fc7aa2298ac5865d2b501a89.png) ![image-20220327153832583.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-102263070d1b2e3231ee8c4ad0a6819315f03dba.png) 然后再是ebx,ebp指向的是`_Trap_Frame`结构体的栈顶,+68指向的就是eip ![image-20220327153931139.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-a6bc4a5ca2bc5b642fa7a0e0d1e6a7724aa55b0b.png) 这两个值就对应了结构里面的`ExceptionCode`和`ExceptionAddress` ![image-20220327154129883.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-581fba8284681e2560a180fe3b905091db641462.png) 再看`ExceptionFlags`,CPU导致的异常这个值为0,软件调试导致的异常这个值为1 ![image-20220327154213174.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-d4fc5ad0118c1b6ef4b574b064480e94585ce200.png) CPU异常执行的流程: > 1、CPU指令检测到异常 > 2、查IDT表,执行中断处理函数 > 3、调用CommonDispatchException(构建EXCEPTION\_RECORD) > 4、KiDispatchException(分发异常:目的是找到异常的处理函数) 0x03 模拟异常记录 =========== 调用过程 ```c++ CxxThrowException RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR *lpArguments) NTDLL.DLL!RtlRaiseException() NT!NtRaiseException NT!KiRaiseException ``` 首先手动抛出异常 ![image-20220327155751293.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-2472df449ce129f7e4a17e5a07be3c584ea4def3.png) 然后去反汇编发现调用了`CxxThrowException` ![image-20220327155847189.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-f2f4d92be501fb3a8414d5ffc302ae8d6ad61b5c.png) CxxThrowException ----------------- ```c++ __CxxThrowException@8: 00401290 push ebp 00401291 mov ebp,esp 00401293 sub esp,20h 00401296 push esi 00401297 push edi 00401298 mov ecx,8 0040129D mov esi,offset string "The value of ESP was not properl"...+0E0h (00423118) 004012A2 lea edi,[ebp-20h] 004012A5 rep movs dword ptr [edi],dword ptr [esi] 004012A7 mov eax,dword ptr [ebp+8] 004012AA mov dword ptr [ebp-8],eax 004012AD mov ecx,dword ptr [ebp+0Ch] 004012B0 mov dword ptr [ebp-4],ecx 004012B3 lea edx,[ebp-0Ch] 004012B6 push edx 004012B7 mov eax,dword ptr [ebp-10h] 004012BA push eax 004012BB mov ecx,dword ptr [ebp-1Ch] 004012BE push ecx 004012BF mov edx,dword ptr [ebp-20h] 004012C2 push edx 004012C3 call dword ptr [__imp__RaiseException@16 (0042b15c)] ``` 该代码所做的事情如下: > ① 先从内存中拷贝一段0x20字节的固定结构体到堆栈中; > ② 将ExceptionList也拷贝到堆栈中(该结构体内部) > ③ 传入有关参数调用RaiseException函数。 注意,ThrowCode虽然从用户代码传入进来,但分析其函数并没有用到,而是直接调用一段固定的异常码。而&ThrowCode以及异常链被作为其参数存储,这样通过分析就可以轻易找到其ThrowCode值,其作为参考之后来处理SEH。 RaiseException -------------- 跟进去调用了`Kernel32.dll`的`RaiseException` ![image-20220327161000483.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-681a6d15fcaf502d3821c2244e1d16b0b09f1ce1.png) ![image-20220327161910401.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-b01012b6132af09db91f4d93dcf64a520aa01e3e.png) 这里跟CPU异常不同的是,CPU异常会将错误代码跟着寄存器一起传入,但是软件异常并没有,这里看一下 ![image-20220327161459530.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-debd5fa5b0ad6e618390aad76fc5028aa29341ff.png) 这里的edx为`E06D7363`就是软件调试的错误代码,这里注意,随着语言和版本的不同,这里的EDX即错误代码并不固定,取决于编译环境 ![image-20220327161638930.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-3df80d2069759bef037262dd89f9ee0902e3a08c.png) 第二个差异就是CPU异常存储的是发生异常的地址,软件异常则是存储`RaiseException`函数的地址 ![image-20220327162048454.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-d82874e9d054ab84df71943a89f38e023cb53e74.png) 0x04 内核层异常处理流程 ============== 前面我们分析过,存在两种异常,CPU异常与用户模拟异常,其异常触发时收集的线路是不同的,但是其最终走经过`KiDispatchException`函数。 当走到`KiDispatchException`,CPU异常与用户模拟异常唯一的区别是CPU异常最高位置1(`nt!KiRaiseException`异常派发时的上一行代码),其余记录的都是一样的。 而`KiDispatchException`的处理是按照其先前模式来处理的,也就是内核异常与用户异常两种,而不是按照CPU异常与用户模拟异常来进行处理。 ```c++ 1) _KeContextFromKframes 将Trap_frame备份到context 为返回3环做准备 2) 判断先前模式 0是内核调用 1是用户层调用 3) 是否是第一次机会 4) 是否有内核调试器 5) 如果没有或者内核调试器不处理 6) 调用RtlDispatchException 7) 如果返回FALSE 也就是0 8) 再次判断是否有内核调试器 有就调用 没有直接蓝屏 ``` KiDispatchException ------------------- 首先定位到`KiDispatchException`函数 ![image-20220327205416986.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-38989e1f559c7227ee688e8ccbbd01970c0f34b8.png) 首先备份`Trap_Frame`结构,如果是用户层的异常则需要返回3环堆栈 ![image-20220327205703543.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-de9b4a365068ebc2c1668c672fb6b20c7e56b5b9.png) 首先通过判断先前模式的值来识别是内核异常还是用户层异常,这里有一个是否第一次调用该函数的判断,这是因为这个函数会被调用很多次,如果不是第一次调用则直接跳转 ![image-20220327210422006.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-c56fad43d5c3836d7d47ab96abd03066df922b29.png) 这个函数的最后一个参数就是表示这个函数是第几次被调用 ![image-20220327210439563.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-08595985f132f2efb3b15256174c722338450c14.png) 然后继续判断有没有内核调试器的存在(如windbg) ![image-20220327210634934.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-741ad1e9e2ef5184cf50990b39e3bcbf1f919131.png) 如果有内核调试器的存在就走下面的`KiDebugRoutine`函数 ![image-20220327210735087.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-ab181a49a03131a461a074f27775d054847f8053.png) 如果内核调试器没有处理返回失败的话就跳转 ![image-20220327212341854.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-cd984d0e8235baba4db2c2f7d4869d04558aa42f.png) `RtlDispatchException`调用异常处理函数 ![image-20220327212316093.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-d60010a8002aefe6065020bb8fff1d2ea0f2c242.png) 跟进到`RtlDispatchException` ![image-20220327212548924.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-8225cf2b1cf946e295bcd3b89bce1bf61f21ed8a.png) 又调用了`RtlGetRegistrationHead` ![image-20220327212602769.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-7441d8ae929308166ec9d1e8c31b5e0f9c81a760.png) 跟进去发现取的是`fs:[0]` ![image-20220327212624842.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-996e1010b6310017518568bd37b3d491ab590a53.png) \_EXCEPTION\_REGISTRATION\_RECORD --------------------------------- 我们知道0环的`fs:[0]`指向KPCR,KPCR的第一个结构是`_NT_TIB`,`_NT_TIB`的第一个成员是`ExceptionList`,是一个`_EXCEPTION_REGISTRATION_RECORD`类型的结构体 `_EXCEPTION_REGISTRATION_RECORD`结构如下 ```c++ typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; PEXCEPTION_ROUTINE Handler; } EXCEPTION_REGISTRATION_RECORD; ``` `_EXCEPTION_REGISTRATION_RECORD`里面有两个成员,`*Next`是一个指针指向下一个`_EXCEPTION_REGISTRATION_RECORD`结构,而第二个成员`Handler`指向的就是一个异常处理函数 `RtlDispatchException`的作用如下: > 遍历异常链表,调用异常处理函数,如果异常被正确处理了,该函数返回1 > > 如果当前异常处理函数不能处理该异常,那么调用下一个,以此类推。 > > 如果到最后也没有处理这个异常,返回0。 ![image-20220327213102327.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-7e165cb919b6da9d6c2a7a90dfbb5cdb3e398ab9.png) 调用异常处理函数得到返回值后跳转到地址 ![image-20220327213504310.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-91ca5a1d9974f9a5a4462cfeddfc32c5ad3b9aa1.png) 然后判断返回值是否为1,1的话就是处理成功,跳转 ![image-20220327213527703.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-b485bb723a5901192097b9422ae37c7cc9c7b804.png) 异常被处理成功则把`Context`结构放回`Trap_Frame`里面 ![image-20220327213735559.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-a625b163cfbc8d75f0c323e988c8027dac353c9d.png) 如果没有被处理成功则继续往下走进行有无内核调试器的判断,如果有内核调试器则调用`KiDebugRoutine` ![image-20220327213835219.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-add67d1cd9e574952227ffb32f2e043a3c9b6517.png) 如果没有内核调试器或者有内核调试器但是没有处理异常,则跳转到下面的地方 ![image-20220327214035176.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-e629f261f21f7e7fbc142fc1f261e1d5fe36387c.png) 操作系统蓝屏 ![image-20220327214109874.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-1e575608ebdcb53fcf118f912e2fdc3b78c8a3ad.png) 0x05 用户层异常处理流程 ============== 定位到`KiDispatchException`,进入用户异常的函数 ![image-20220328095342284.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-c57f94ec9b735f46d5cde2900626b44c9eb0419d.png) 进入函数首先判断是不是第一次调用,然后继续往下走,如果有内核调试器则直接跳转,没有的话继续往下走 ![image-20220328100329260.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-2bafc94bab3b9249042f0db2a094bc8b833b6c02.png) 然后进行异常的处理,调用`DbgkForwardException`,这个函数的作用是调用3环的调试器 ,再进行判断有无3环的调试器接收异常,如果没有则返回3环处理 ![image-20220328100451340.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-e6138c7a8058351bc7b2f41cc2b4acff6bc72f42.png) 然后进行结构体的修改,这里同用户APC执行的修改过程 ![image-20220328101211189.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-db7761fc155370d86528681dd5a044dfef253f66.png) 然后修改`EIP`的值为`KeUserExceptionDispatcher`函数的地址,这时候`EIP`的值已经是函数地址,这时候再回到原函数 ![image-20220328101716892.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-c58a6f05bbf4ca6ca4e8615de99a9e982f0f4d12.png) ![image-20220328101848233.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-099c533ebaee32226c78cdce19b10fe13971d466.png) 注意这里并没有直接进行返回3环的操作,而是`KiDispatchException`这个函数执行结束过后回到原函数,用不同的方法回到3环 ```c++ CPU异常:CPU检测到异常 -> 查IDT执行处理函数 -> CommonDispatchException <-> KiDispatchException 通过IRETD返回3环 模拟异常:CxxThrowException -> RaiseException -> RtlRaiseException -> NT!NtRaiseException -> NT!KiRaiseException <-> KiDispatchException 通过系统调用返回3环 ``` 无论通过那种方式,但线程再次回到3环时,将执行`KiUserExceptionDispatcher`函数 KiUserExceptionDispatcher ------------------------- 返回三环后,可以看到其调用一个`RtlDispatchException`。注意,在处理内核异常时,也有一个同名的`RtlDispatchException`,那是内核模块,这是三环模块。 `RtlDispatchException`可以认为是异常的核心,区别是如果在内核模块,则处理零环,如果在ntdll模块,则处理三环。 ![1827556-20200402110019370-957825440.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-ac6a1781270785fb3ed418e1654c32ce18d9c6e9.png)
发表于 2022-04-14 09:57:00
阅读 ( 5693 )
分类:
漏洞分析
2 推荐
收藏
1 条评论
向往阳光的月光
2022-07-19 21:45
idb文件能给下不~
请先
登录
后评论
请先
登录
后评论
szbuffer
30 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!