首页
社区
课程
招聘
[原创]小Win,点一份APC(Apc机制详解)(一)
发表于: 2017-4-28 22:11 18735

[原创]小Win,点一份APC(Apc机制详解)(一)

2017-4-28 22:11
18735

翻开小Win的菜单,APC赫然在目...

做工讲究,味道不错,是小Win的热门菜,我们点一来尝尝!

吃了可以做很多事情...

细节来自于ReactOS源码分析。

如果对这个发神经的文风有任何不适,请谅解,因为我确实神经了

点APC的正确姿势是使用QueueUserApc,不走寻常路的也可以使用NtQueueApcThread

也就是QueueUserApc内部是NtQueueApcThread做的,两者区别不大,当然,使用后者可以字节加点调料(不使用IntCallUserApc、换成自己的函数,函数参数也可以有三个了,而PARCFUNC只有一个参数)。

小Win默认是通过统一的接口IntCallUserApc来调用的顾客指定的Apc函数。

NtQueueApcThread经过系统调用进入到ring0,一般人是看不到了...,我也是一般人来着,下面努力变成二班的...。

进了NtQueueApcThread,先通过KeInitializeApc初始化一个Apc对象

APC对象结构定义如下:

根据KeInitializeApc传入参数,Apc被赋值如下:

其中关于ApcStateIndex有4中值,如下:

Apc->KernelRoutine总是有值的,被赋值为PspQueueApcSpecialApc,用于Apc结束时候释放Apc对象内存

通过KeInsertQueueApc插入队列,在队列中等待被上菜...

先不管Apc是怎么得到执行的,来看看KAPC_STATE

其中ApcListHead保存了线程的两个Apc链表,分别对应UserMode和KernelMode。

Thread->ApcState表示当前需要执行的ApcState,可能是挂靠进程的

Thread->SavedApcState表示挂靠后保存的当前线程的ApcState,

KTHREAD的ApcStatePointer[2]字段保存了两个ApcState的指针

具体看下面的代码

来一个结构图

img

Apc已经点了,什么时候才能端上来呢?我们接着看...

线程wait、线程切换到应用层、线程被挂起等,一旦线程有空隙了,windows就会把apc队列顺便执行一遍

搜索NormalRoutineKernelRoutine字段,找到KiDeliverApc,这个函数是具体分发Apc的函数

那在哪里调用的KiDeliverApc的呢,找到多处

根据《windows内核情景分析》介绍, 执行用户APC的时机在从内核返回用户空间的途中(可能是系统调用、中断、异常处理之后需要返回用户空间)

也就是肯定会经过_KiServiceExit,那就跟着来看看吧。

// This macro creates an epilogue for leaving any system trap. // It is used for exiting system calls, exceptions, interrupts and generic // traps.

继续看一下调用KiDeliverApc内部究竟是怎么处理的

根据注释应该很清楚deliver的逻辑了,还是在看张图 img

CHECK_FOR_APC_DELIVER用户态Apc的delvier有个重点,Thread->ApcState.UserApcPending必须是TRUE,那什么时候才会是TRUE,我蛮来看看

两种情况都需要Alertable = TRUE,这个字段表示线程是唤醒的,也就是说只有可唤醒的线程,才能拿投递他的用态APC,否则不会

SleepEx, WaitForSingleObject,WaitForMultipleObjects都可以设置线程为Alertable

接着继续看看KiInitializeUserApc是怎么切换到用户空间执行的用户态函数

执行流程根据注释应该很清楚了,这里要解释一下TrapFrame。

CPU进入内核之后,内核堆栈就会有个TrapFrame,保存的是用户空间的线程(因进入内核原因不同,可能是自陷、中断、异常框架,都是一样的结构)。CPU返回用户空间时会使用这个TrapFrame,才能正确返回原来的断点,并回复寄存器的状态 这里为了让Apc返回到用户空间执行,就会修改这个TrapFrame,原来的TrapFrame就需要保存,这里保存在了用户空间堆栈中(CONTEXT) 执行完Apc函数之后,执行一个NtContinue,将这个CONTEXT作为参数,这样保存的TrapFrame就会还原到原来的状态,然后CPU又能正常回之前的用户空间了。

KiDeliverApc完了之后,回到_KiServiceExit,会使用被修改过的TrapFrame回到用户空间,执行指定的KiUserApcDispatcher(ntdll提供)

KiUserApcDispatcher其实挺简单的,通过esp弹出APc函数,然后调用,就进入了IntCallUserApc,

执行完成后,调用_ZwContinue(Context, 1),回到内核回复之前修改TrapFrame,也会重新检查是否有Apc需要投递,有则继续投递, 重复上面的步骤,直到没有了则可以回到之前被中断的用户态的断点处。

下面将_KiServiceExit到IntCallUserApc的流程总结一下: img

到这里,终于执行到了用户的Apc函数。

到这,APC流程基本弄清楚了。

下一篇将结合APC机制分析一下最近比较新的AtomBombing注入技术的详细实现和各个细节。

参考


如果大家觉得还不错,欢迎关注我的博客:http://anhkgg.github.io/win-apc-analyze1/


DWORD WINAPI QueueUserApc(PARCFUNC pfnApc, HANDLE hThread, ULONG_PTR dwData);
{
    NtQueueApcThread(hThread, IntCallUserApc, pfnApc, dwData, NULL);    
}

NTSTATUS NTAPI NtQueueApcThread(IN HANDLE ThreadHandle, 
                                IN PKNORMAL_ROUTINUE ApcRoutine,                                IN PVOID NormalContext, //pfnApc
                                IN PVOID SystemArgument1, //dwData
                                IN PVOID SystemArgument2
                                );
static void CALLBACK 
IntCallUserApc(PVOID Function, PVOID dwData, PVOID Arg3){
    ((PAPCFUNC)Function)(dwData);
}

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (9)
雪    币: 775
活跃值: (2292)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
谢谢楼主,学习了1
2017-4-28 22:13
0
雪    币: 2347
活跃值: (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
讲的很清楚吖  谢谢楼主  学习了
2017-4-28 22:45
0
雪    币: 221
活跃值: (2326)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
谢谢楼主    学习了 
2017-4-29 09:45
0
雪    币: 44
活跃值: (32)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
非常不错,正在学APC,跟到了ZwContinue
2017-4-30 13:24
0
雪    币: 10072
活跃值: (3008)
能力值: ( LV15,RANK:515 )
在线值:
发帖
回帖
粉丝
6
MOVESP 非常不错,正在学APC,跟到了ZwContinue
求指教
2017-5-1 18:34
0
雪    币: 44
活跃值: (32)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
Angelxf 求指教
不敢当,我这个菜鸟还希望兄台多多指点,现在我有个问题想向你请教一下:
哪里能体现出一个线程的IRQL,KfLowerIrql是如何操作一个线程的IRQL,按理说线程调度的时候应该是运行在DISPATCH级别,可是KPCR.irql的值怎么是0,希望大哥能解释一下
2017-5-3 18:47
0
雪    币: 247
活跃值: (109)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
MOVESP 不敢当,我这个菜鸟还希望兄台多多指点,现在我有个问题想向你请教一下: 哪里能体现出一个线程的IRQL,KfLowerIrql是如何操作一个线程的IRQL,按理说线程调度的时候应该是运行在DISPAT ...
irql就是一个枚举类型,0就是dispatch级别,1就是APC,2就是DPC
2020-7-13 10:12
0
雪    币: 4942
活跃值: (4663)
能力值: ( LV10,RANK:171 )
在线值:
发帖
回帖
粉丝
9
感谢分享,了解了APC的调用时机和其原理。
2021-10-3 18:39
0
雪    币: 6172
活跃值: (4947)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
10
mark
2023-3-29 11:57
0
游客
登录 | 注册 方可回帖
返回
//