首页
社区
课程
招聘
[旧帖] [原创]APC浅浅的说 0.00雪花
2011-5-19 16:23 4258

[旧帖] [原创]APC浅浅的说 0.00雪花

2011-5-19 16:23
4258
以下是我对APC的一些理解,肯定不全面也不深入,真的是浅浅的,但本着共同学习进步的想法发出来,欢迎各位发表意见多多指导多多拍砖。

APC,异步过程调用。
APC是线程相关的,每个线程都有自己独自的APC链表,因此可以让一段代码在指定的线程上下文中运行。系统中有两种APC,用户层和内核层APC(忽略用户层)。

初始化APC的函数定义如下:
VOID
KeInitializeApc (
    __out PRKAPC Apc,
    __in PRKTHREAD Thread, 
    __in KAPC_ENVIRONMENT Environment,          //APC环境
    __in PKKERNEL_ROUTINE KernelRoutine,         //内核模式APC函数
    __in_opt PKRUNDOWN_ROUTINE RundownRoutine,   //线程退出时且APC链表中非空,若此例程也非空则被调用
    __in_opt PKNORMAL_ROUTINE NormalRoutine,
    __in_opt KPROCESSOR_MODE ApcMode,
    __in_opt PVOID NormalContext
    )
参数详细说明下面介绍。

内核APC又分为两种:普通APC和特殊APC。这两种APC在线程中共用同一个APC链表,但特殊APC总是排在普通APC的前面,即在交付时优先交付特殊APC。APC对象结构中并没有标记自己是一个普通APC还是特殊APC的字段,这种区分是通过参数NormalRoutine来区分的。初始化的时候如果参数NormalRoutine为NULL,则说明这是一个特殊APC,此时忽略参数NormalContext且参数ApcMode默认为KernelMode;如果参数NormalRoutine不为NULL,则这是一个普通
APC,参数NormalContext作为参数NormalRoutine的参数。在交付普通APC时,即执行NormalRoutine,也执行KernelRoutine。

下面说一下参数Environment,这个参数的设定,是为了解决线程Attach到其它进程以及Dettach回来的情况。
在线程的内核对象中,有两个域ApcState和SavedApcState,以及一个包含两个元素的数组ApcStatePointer和保存ApcStatePointer下标的整数索引值ApcStateIndex。APC插入时,总是插入到ApcStatePointer[ApcStateIndex]代表的链表中(也即总是插入到ApcState代表的链表中,因为ApcStatePointer[ApcStateIndex]和ApcState总是指向同一块地址)。例如,当线程在自己的进程中运行时,新的APC被插入到ApcState所代表的链表中,ApcStateIndex为0,ApcStatePointer[0]指向ApcState指向的地址。当线程附加到其它进程中时,系统先将当前ApcStatePointer[ApcStateIndex]指向的内存中的内容(即ApcState指向的内容)保存到SavedApcState中,然后,将ApcStateIndex改为1,令ApcStatePointer[1]指向ApcState指向的内存,ApcStatePointer[0]指向ApcState指向的内存。然后重新初始化ApcStatePointer[ApcStateIndex]指向的内存。为什么这么麻烦呢?直接来两个字段,一个ApcState,一个SavedApcState不就完了吗?当然不行,因为我们可以指定APC被插入到哪个链表中(ApcState还是SavedApcState),而我们正是通过Environment指定的。
先看一下Environment的结构:
typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,
    AttachedApcEnvironment,
    CurrentApcEnvironment,
    InsertApcEnvironment
} KAPC_ENVIRONMENT;
下面是对这四个域的解释:
OriginalApcEnvironment:插入到目标线程ApcStatePointer[0]代表的链表中。这样可以保证这个APC只在目标线程在自己的进程中时运行,为什么呢?因为从上面啰嗦的分析中可以知道,当目标线程没有Attach时,ApcStatePointer[0]指向ApcState,即线程正在使用的APC链表;当目标线程Attach了,则ApcStatePointer[0]指向SavedApcState,它所代表的链表此时是作为备份的,其中的APC不会被交付,只有在Dettach后将其恢复到ApcState后才会被交付。所以喽。
AttachedApcEnvironment:插入到目标线程ApcStatePointer[1]代表的链表中。这个的使用发生在目标线程已经Attach,并且插入的APC也想在这个Attach的进程中运行。因为如果目标线程没有Attach,则ApcStatePointer[1]代表备份,所以APC不会被交付;当目标线程真的Attach的时候,原来的SavedApcState就会被覆盖掉,那这个APC还有什么用?所以不会使用。因此只有目标线程已经Attach了,APC被插入到ApcStatePointer[1]中,此j时ApcStatePointer[1]代表正在被使用的APC链表,因此就被交付啦。
CurrentApcEnvironment:APC被插入到初始化时目标线程的ApcStatePointer[ApcStateIndex]所代表的链表中。使用这个值时,当初始化后和插入之间这
段时间没有Attach或者Detach时,APC肯定会被执行(不能保证是在自己进程中还是Attach进程中)。如果在这之间发生了Attach,则APC等待目标线程Detach后被执行;如果这之间发生了Detach,则APC不会被执行。
InsertApcEnvironment:APC被插入到插入时(调用KeInsertQueueApc时)目标线程的ApcStatePointer[ApcStateIndex]所代表的链表中。使用这个环境值时,APC可以被保证肯定会马上执行(所谓的马上指不需要等待Dettach),因为目标线程无论是Attach还是没有,ApcStatePointer[ApcStateIndex]都代表当前使用的链表。只是不能保证APC是在目标线程自己的进程运行还是在Attach后的进程中运行。

知道了上面的参数后,使用起来就比较简单了,调用KeInitializeApc初始化一个APC后,调用KeInsertApc插入APC就可以了。
别外需要注意的是,APC的函数都是运行在IRQL为APC_LEVEL级别的。

应用:
APC被知道的最多的应用应该是对一些异步操作的支持,如ReadFile的异步方式等。以ReadFile为例,文件系统发现这是一个异步调用,就会标记为Pending,然后立刻返回。等读取操作完成的时候,在将结果拷贝到用户层的内存空间和调用用户层提供的完成函数时,就用到APC。

另外,挂钩APC相关函数可以达到阻止结束目标进程(ZwTerminateProcess)、阻止创建远程线程(CreateRemoteThread)、阻止对其调用GetContext SetContext等。(但直接读写进程空间或者Attach操作是阻止不了的)。

总结:
如果说其它进程或者线程间通信的方式(比如共享一个对象、一块映射内存、甚至套接字等等)是两个国家间的以合作的方式交流:我需要你的什么信息或者要送给你什么,要得到你的同意才行,那么APC就像是间谍:我安插在你选干部的队伍里(插入APC),等你请我去当你的干部。哪一天我当上了(交付),我就可以做我自己想做的事了,我同国外的沟通不用得到同意(目标进程里的代码可能完全不知道我的存在)。

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞5
打赏
分享
最新回复 (21)
雪    币: 111
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
ronging 2011-5-20 11:18
2
0
有难度啊。基本上不理解。有没有更简单的?

"线程附加到其它进程"是什么,一个线程所属的进程不是固定的嘛?
雪    币: 98
活跃值: (30)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
pDriObj 2011-5-20 14:18
3
0
可以临时换嘛,KeStackAttachProcess。像WriteProcessMemory这种函数就是利用这个功能读写远程进程的。
雪    币: 111
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
ronging 2011-5-21 09:45
4
0
还是有问题,
执行APC的线程是不是系统线程?还是安装APC的线程?执行APC的时机或者说是check点是什么时候?
雪    币: 98
活跃值: (30)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
pDriObj 2011-5-23 08:25
5
0
APC是在目标线程中执行的。执行时机一下子也说不清,我在看的时候对这个问题不太关心所以也没祥细了解。以下是《windows内核原理与实现》的原话:
APC对象被插入到线程的APC链表中以后,一旦APC_LEVEL软件中断发生,或者IRQL从更高降底到APC_LEVEL以下,则当前线程的APC被交付。
雪    币: 111
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
ronging 2011-5-23 10:27
6
0
谢谢LZ的回答,学到不少东西。

忽然有个想法,如果ReadFile设置了一个APC,然后while(1);进入循环,这样是不是APC就不会执行?因为目标线程不会进程内核。

另外,能稍微具体说下文中的这段嘛?

"另外,挂钩APC相关函数可以达到阻止结束目标进程(ZwTerminateProcess)、阻止创建远程线程(CreateRemoteThread)、阻止对其调用GetContext SetContext等。(但直接读写进程空间或者Attach操作是阻止不了的)。"
雪    币: 284
活跃值: (106)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
鹿剑 3 2011-5-23 11:12
7
0
呵呵,APC的牛叉之处在于在线程获得CUP时间片之后在执行线程代码前调用,所以无视了你的死循环了,APC使用很简单,执行机制是很复杂的,涉及线程调度等内容……
雪    币: 98
活跃值: (30)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
pDriObj 2011-5-23 19:43
8
0
如楼上所说,使用简单,机制复杂。咱们的代码都是受系统调度的,系统要安插点东西让你执行,你循环再怎么死也是没用的

ZwTerminateProcess、CreateRemoteThread这些函数在实现的时候都用到了APC,所以阻止APC的正常初化或插入就会使这些函数失败。有一个病毒hook KeInitializeApc,然后判断目标线程是否是自己要保护的线程,如果是就把APC对象的Insert域改为True。这样在调用KeInsertQueueApc时就会失败,从而保护了自己(据网上说有的杀软也是这么干的)。像WriteProcessMemory这样的函数是Attach到目标进程再读写进程内的数据,完成后再Detach回来,没有用到APC,所以阻止不了啦。
雪    币: 111
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
ronging 2011-5-25 12:05
9
0
谢谢LZ和楼上的帮忙回答问题。
雪    币: 7475
活跃值: (148)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
guobing 2011-5-26 21:24
10
0
呵呵,大湿的帖子不得不顶啊。
雪    币: 98
活跃值: (30)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
pDriObj 2011-5-27 18:00
11
0
八百年不露一次脸 好不容易露一次就让你发现了
雪    币: 60
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
twtgh 2011-8-17 16:14
12
0
不错
慢慢学习
雪    币: 43
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
注定幸福 2011-8-17 16:22
13
0
先回复再看~
雪    币: 220
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
radarhp 2012-3-5 21:08
14
0
额,正好在纳闷APC这东西滴时候,楼主很低调的出现了

感谢楼主的科普
雪    币: 260
活跃值: (64)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
beijingren 2012-3-5 21:54
15
0
非常好的基础知识讲座,有幸学习了!
雪    币: 6
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
whitehead 2012-3-5 23:05
16
0
有难度啊。基本上不理解。
雪    币: 2329
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lermonto 2012-3-6 11:32
17
0
不懂,但是,也见识一下~
雪    币: 1184
活跃值: (1410)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
truedada 2012-3-6 13:38
18
0
不会先来学习下
雪    币: 29
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
不衰的心 2012-3-10 22:07
19
0
不错,深奥中的浅显易懂
雪    币: 707
活跃值: (3990)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
kagayaki 2012-11-29 20:03
20
0
不懂,但是,也见识一下~
雪    币: 26
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
revcode 2012-11-30 08:40
21
0
了解APC~~~
雪    币: 77
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cqzj70 2014-4-17 15:33
22
0
mark
游客
登录 | 注册 方可回帖
返回