问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
内核APC&用户APC详解
内核APC&用户APC详解
0x01 内核APC ========== 线程切换 ---- ```c++ SwapContext 判断是否有内核APC KiSwapThread KiDeliverApc 执行内核APC函数 ``` 定位到`SwapContext`函数,然后查看`KernelApcPending`的值是否为空,不为空则跳转,这里只是进行判断,我们往上跟 ![image-20220326143554171.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-80fe45d3fc433b14016b5c11604233dcc721bf34.png) 然后回到`KiSwapContext` ![image-20220326143632315.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-40e78593f9bfa85d75e9ed56e156a65bbbeec72a.png) 再往上走得到`KiSwapThread` ![image-20220326143650339.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-c59cf19de39b0409437bc8cc8556d853665e8862.png) 这里判断后进行跳转 ![image-20220326143724811.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-0cdc3896a7e9cccd66729f47242695cd115407e3.png) 然后调用`KiDeliverApc` ![image-20220326143809782.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-68198be5d156e4088fd3feb75f6cff2b8f3e4839.png) 系统调用、中断或者异常 ----------- 当要执行用户APC之前,先要执行内核APC,这里找到`KiServiceExit`,有一个比较检验`UserApcPending`的值是否有APC请求 ![image-20220326144414076.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-537676692057089405b2468360b5caab09b02c16.png) 然后调用`KiDeliverApc` ![image-20220326144445731.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-009083a54479bad82ae00df5e41025b76c7acb69.png) 内核层APC执行 -------- ### KiDeliverApc 继续往里面跟,判断内核APC的链表是否为空,若不为空则跳转 ![image-20220326150225481.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-a7b420236faa3a3f487c52b209df0a2ea690fc93.png) ### NormalRoutine 跳转后判断`NormalRoutine`里面存储的是内核APC的地址还是APC的的总入口,然后再跳转 ![image-20220326150241543.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-f3f68875b84aa003464152c2fc3296e4db61d2c2.png) 如果为空向下执行则会调用`KernelRoutine`对APC进行销毁 ![image-20220327101813853.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-9305a3da9ac1b0c9eaf0e6f015d662bf8b0d96a1.png) 跳转过后执行真正的内核APC函数`NormalRoutine` ![image-20220326152845772.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-05505745b761a3564f08c3503bc46a5e81495ef6.png) 内核APC执行流程 --------- ```c++ KiDeliverApc函数执行流程 1) 判断第一个链表是否为空 2) 判断KTHREAD.ApcState.KernelApcInProgress是否为1 3) 判断是否禁用内核APC(KTHREAD.KernelApcDisable是否为1) 4) 将当前KAPC结构体从链表中摘除 5) 执行KAPC.KernelRoutine指定的函数 释放KAPC结构体占用的空间 6) 将KTHREAD.ApcState.KernelApcInProgress设置为1 标识正在执行内核APC 7) 执行真正的内核APC函数(KAPC.NormalRoutine) 8) 执行完毕 将KernelApcInProgress改为0 9) 循环 ``` 0x02 用户APC ========== 当产生系统调用、中断或者异常,线程在返回用户空间前都会调用`KiServiceExit`函数,在`KiServiceExit`会判断是否有要执行的用户APC,如果有则调用`KiDeliverApc`函数(第一个参数为1)进行处理。 处理用户APC要比内核APC复杂的多,因为,用户APC函数要在用户空间执行的,这里涉及到大量换栈的操作: 当线程从用户层进入内核层时,要保留原来的运行环境,比如各种寄存器,栈的位置等等 (`_Trap_Frame`),然后切换成内核的堆栈,如果正常返回,恢复堆栈环境即可。 但如果有用户APC要执行的话,就意味着线程要提前返回到用户空间去执行,而且返回的位置不是线程进入内核时的位置,而是返回到其他的位置,每处理一个用户APC都会涉及到: > 内核-->用户空间-->再回到内核空间 KiDeliverApc ------------ ```c++ 1) 判断用户APC链表是否为空 2) 判断第一个参数是为1 3) 判断ApcState.UserApcPending是否为1 4) 将ApcState.UserApcPending设置为0 5) 链表操作 将当前APC从用户队列中拆除 6) 调用函数(KAPC.KernelRoutine)释放KAPC结构体内存空间 7) 调用KiInitializeUserApc函数 ``` 线程进0环时,原来的运行环境(寄存器栈顶等)保存到`_Trap_Frame`结构体中,如果要提前返回3环去处理用户APC,就必须要修改`_Trap_Frame`结构体: 比如:进0环时的位置存储在EIP中,现在要提前返回,而且返回的并不是原来的位置,那就意味着必须要修改EIP为新的返回位置。还有堆栈ESP,也要修改为处理APC需要的堆栈。那原来的值怎么办呢?处理完APC后该如何返回原来的位置呢? `KiInitializeUserApc`要做的第一件事就是备份: 将原来`_Trap_Frame`的值备份到一个新的结构体中(`CONTEXT`),这个功能由其子函数`KeContextFromKframes`来完成,代码如下 首先判断参数是否为1,当参数为1的时候处理用户APC ![image-20220326194053904.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-3d0ef98b09b53c12cd41c64216932775b3e962f9.png) 然后进行一系列的操作 ![image-20220326194200087.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-708cd0fc1951ba0731babf3de14e079b66e4ce78.png) KiInitializeUserApc ------------------- 接着转到`KiInitializeUserApc`函数 ![image-20220326194207494.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-4b3e4e83adad01511b5d66021c37dd56078bc1db.png) 将`CONTEXT`和`TrapFrame`传入`KeContextFromKframes` ![image-20220326204520108.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-d490322ee0fbdcba88c557116d5e288317690695.png) 这里接着往下看,这里得到C4 ![image-20220326204559833.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-120cc87cc0d9895945677dcb6094fe2bc69dd555.png) C4对应的`Esp`存储的是3环原来的栈顶 ![image-20220326204624443.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-515164740a9f271704b9446ca6a7e9984bd1880b.png) 然后以4字节对齐将3环堆栈减去0x2DC个字节,这里是因为要将`CONTEXT`结构和KAPC的4个参数传给3环 ![image-20220326204758118.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-357585e619a2885c701cb1f997fd4005051b2ec8.png) 原本三环的ESP如图所示 ![image-20220326233115238.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-1d99551a9b7744732973963c0c72e1a2a8f392ce.png) `CONTEXT`结构体的大小为0x2CC,KAPC的4个参数的大小为0x10,所以减去0x2DC ![image-20220326233221648.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-10df0d430b35aec19ec758beb5fdb8a160768fd3.png) 这一部分代码主要是将`CONTEXT`结构复制到3环的堆栈 ![image-20220326233633857.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-58eb1bbbe737693e17471a308f996fe61ebf9cdc.png) ![image-20220326233612120.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-eb4b9b5554451d39da6f2086f0d38917699b3ce8.png) 当windows把`CONTEXT`结构复制到堆栈之后,准备用户层执行环境,首先修改SS、DS、ES、FS、GS和EFLAGS寄存器 ![image-20220326233926270.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-4459218c4185cfe44d774b575ff0a68fc29bc85f.png) 然后修改esp到3环堆栈 ![image-20220326234026401.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-d13d75e5f6ccade60c2aa1bdb75238cfa3f02b59.png) ![image-20220326234034331.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-1de2539fae2f73e774f994a2ae49b1871e133560.png) KiUserApcDispatcher ------------------- 然后修改eip,这里永远返回一个固定的位置,但是这个位置在每次系统启动的时候都不相同,存放在3环的`ntdll`里的`KiUserApcDispatcher`参数里面 ![image-20220326234048961.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-ee0ecdddf7abff596b0ce65525922ab4aecad6f4.png) 然后到ntdll里面定位到`KiUserApcDispatcher`,首先得到指向`CONTEXT`结构的指针,然后`pop eax`得到`NormalRoutine`结构,这里当APC是内核APC的时候存储的是真正的APC地址,当APC是用户APC的时候存储的是指向用户APC的总入口 ![image-20220326234409930.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-2ad36be4b2f34f233e85f986997ff979522af8e4.png) 当我们调用`QueueUserAPC`,并没有指定`NormalRoutine`结构,只指定了`NormalContext`和`SystemArgument1`,那么这个参数在`QueueUserAPC`内部指定,在`kernel32.dll`的`BaseDispatchAPC`,用来调用真正的用户APC函数 ![image-20220326234736069.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-fe8bf8df49107e88cc488028eeac9767f65503bd.png) 再继续往下跟,调用了`ZwContinue` ```c++ 1) 返回内核,如果还有用户APC,重复上面的执行过程。 2) 如果没有需要执行的用户APC,会将CONTEXT赋值给Trap_Frame结构体。就像从来没有修改过一样。ZwContinue后面的代码不会执行,线程从哪里进0环仍然会从哪里回去。 ``` ![image-20220326235039964.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-90efad09f90f145f366113b960fb632bed9c5002.png) 使用`0x20`的调用号利用调用门回到0环 ![image-20220327105651413.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-86f212936f390f63946f4d2e6aec761038bda4a8.png) 用户APC执行流程 --------- 总结: 1.内核APC在线程切换时执行,不需要换栈,比较简单,一个循环执行完毕。 2.用户APC在系统调用、中断或异常返回3环前会进行判断,如果有要执行的用户APC,再执行。 3.用户APC执行前会先执行内核APC。 ![1827556-20191103170141163-71115727.png](https://shs3.b.qianxin.com/attack_forum/2022/06/attach-0f38324d67833e82d3577f1aff04b37de81c962e.png)
发表于 2022-06-13 10:05:48
阅读 ( 5321 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
szbuffer
30 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!