首页
社区
课程
招聘
[原创]X86内核笔记_6_APC相关
发表于: 2021-12-23 16:38 22416

[原创]X86内核笔记_6_APC相关

2021-12-23 16:38
22416

APC(异步过程调用)。具体概念百度很多文章都说了,通俗的来讲就是个官方的Hook回调,只是hook点是事先定义好的。

Windows维护了两种APC队列(内核APC、用户APC),队列中是一个个APC回调函数,在特定情况下Windows会遍历APC对列去执行每个APC回调函数。

在用户层我们调用TerminateThread一类的函数可以结束某个线程或进程,看上去好像是我们的进程把目标进程给干掉了。实际上一个线程不会被其他线程直接杀死,线程在运行时是独自占有CPU的。线程真正的死亡是“自杀”。结束线程的本质是:线程A向线程B插入一个APC,APC的功能是杀死B线程。当B线程调用一些系统API时,API内部会执行APC函数,从而让B线程杀死自己。

初步有个概念,不需要背,逆向函数时自然就明白每个成员的作用了。

在进程线程章节中我们看到线程结构体KTHREAD有一些APC相关的成员,这里说明一下作用:

APC管理结构

APC成员结构

查看WRK上面KeInitializeApc的函数声明:

知道了参数的大概意思,可以去分析KeInitializeApc了。

image-20211215141328694

通过对KeInitializeApc的分析,暂时得知在初始化用户APC时貌似也要传入一个KernelRoutine。

当为内核APC时,NormalRoutine是否有值决定了这个APC是普通内核APC还是特殊内核APC。

特殊内核APC无法携带参数,即使初始化APC时的NormalContext参数有值也会被强制清0。

去WRK看一下函数声明和注释:

分析KeInsertQueueApc

image-20211215143448713

首先根据之前传入的Enviroment来判断要取哪个_KPAC_STATE成员

image-20211215145917734

选择完_KPAC_STATE后,判断ApcMode与NormalRoutine决定插入到哪个链表中。经分析得知,用户APC回调存在NormalRoutine中,但KernelRoutine会存一个名为PsExitSpecialApc的特殊APC回调(用于释放当前APC内存空间)。

image-20211215150054279

如果当前APC插入到了备用APC队列(SavedApcState)中就返回。如果插入的是ApcState队列中就继续判断这个APC是自身插入还是其他线程插入的。

image-20211215170701877

如果是插入到其他线程的APC并且是个用户APC。

image-20211215170731441

如果这个APC是内核APC并且是插入到其他线程的

image-20211215170748275

APC的插入位置与传入的Enviroment函数相关。如果是插入到了备用APC队列中则执行返回。若是普通APC队列中则继续进行多个判断。

当这个APC是自身线程插入给自身的,并且是个特殊内核APC,则会立马触发软中断执行。

如果这个APC是当前线程插入给其他线程的,且是个用户APC。当APC所属线程处于等待时,会尝试唤醒线程来执行APC。如果不是等待状态,则UserOrNormalKernel默认为0,插入后不执行APC。

如果这个APC是当前线程插入给其他线程的,且是个内核APC。当APC所属线程处于运行时,会直接触发软中断执行APC或通知其他核触发软中断执行。当APC所属线程处于等待时,会尝试唤醒线程来执行APC。其他状态则不会立马执行APC。

通过对比发现WRK上的插入逻辑还是有一些区别的,但是大体逻辑相差不大,说明APC插入逻辑是一直在优化细节的

APC派发函数在很多函数中都会被调用。

image-20211217140026542

这里以KiServiceExit为例子,也就是系统调用返回时,会判断用户APC队列是否需要被执行。如果用户APC队列没有值则不派发APC。

image-20211217140249207

其中派发APC的函数名为KiDeliverApc,在WRK中看一下函数声明:

KiDeliverApc函数中首先会检测是否有内核APC,如果有则先处理内核APC。

image-20211217140806113

image-20211217140825722

如果内核APC链表没有值或者处理完毕了,就开始处理用户APC链表。处理用户APC时也会先执行KernelRoutine回调。

image-20211217142646976

随后执行KiInitializeUserApc函数来派发用户APC,而不是像内核APC一样直接执行。

image-20211217150254068

用户APC的执行不像内核APC一样直接调用回调即可。用户APC需要在3环执行,因此在派发时需要将执行环境降到3环。执行APC结束后再返回0环。此时就会产生如下问题:

系统调用进0环时构造了一个TramFrame结构体用于保存3环进来时的环境,系统调用完成返回3环时通过TrapFrame中保存的寄存器信息来继续执行3环代码。因此通过更改TrapFrame中的值可以让当前代码在返回3环时改变执行位置。

由于执行完用APC后还要回到0环继续执行后续代码,但TrapFrame已经被更改。因此需要备份更改前的TrapFrame。KiInitializeUserApc中使用CONTEXT结构备份TrapFrame。

image-20211220162159862

函数内部直接取出栈顶的NormalRoutine并调用,将NormalContext、SystemParam1、SystemParam2作为参数传递。NormalRoutine为用户APC派发函数总入口,由Windows填充,函数为kernel32!BaseDispatherApc。

APC执行完成后通过ZwContinue函数回到0环继续处理剩余用户APC,并根据堆栈中的Context结构来填充TrapFrame结构。

image-20211220162755338

进程挂靠本质为将某个线程的CR3切换为目标进程的CR3,使该线程的内存映射走目标进程的GDT。函数KeStackAttachProcess就是用于进程挂靠的。

首先判断DPC是否处理完成,如果还有DPC没处理直接蓝屏。

image-20211223153519537

如果当前线程已经是在目标进程的环境下了,直接返回。

image-20211223153546756

否则直接调用KiAttachProcess开始挂靠,第一次挂靠和多重挂靠处理方式不同。

KiAttachProcess有4个参数:当前线程、目标进程、原IRQL等级、原ApcState要保存在哪。

image-20211223153723787

将当前线程的ApcState队列备份至第四个参数中并清空ApcState。

image-20211223153844548

如果是第一次挂靠,直接交换ApcStatePointer成员并设置挂靠状态。如果是多重挂靠则没必要设置了。

image-20211223154016997

然后调用KiSwapProcess进行CR3的切换。切换前还会判断进程空间是否被交换到磁盘上了,如果被交换到磁盘上会先恢复进程空间再切换CR3。

image-20211223154133642

判断是否为挂靠状态,不是则直接返回。

image-20211223161417909

等待内核APC处理完成。

image-20211223161440661

如果非挂靠、APC没处理完成或正在处理,直接蓝屏。

image-20211223161507237

前置判断都通过后恢复CR3。(多重挂靠下,参数ApcState的Process存的是上一层进程环境的Kprocess)

image-20211223162720770

想啥呢,这种东西我会放出来吗

QQ图片20211223163451

 
 
struct _KTHREAD
{
    UCHAR Alerted[2];                                                       //0x3a
    ULONG Alertable:1;                                              //0x3c
    struct _KAPC_STATE ApcState;                                        //0x40
    volatile ULONG ApcQueueable:1;                                  //0xb8
    UCHAR ApcStateIndex;                                                    //0x134
    struct _KAPC_STATE* ApcStatePointer[2];                                 //0x168
    struct _KAPC_STATE SavedApcState;                                   //0x170
}
struct _KTHREAD
{
    UCHAR Alerted[2];                                                       //0x3a
    ULONG Alertable:1;                                              //0x3c
    struct _KAPC_STATE ApcState;                                        //0x40
    volatile ULONG ApcQueueable:1;                                  //0xb8
    UCHAR ApcStateIndex;                                                    //0x134
    struct _KAPC_STATE* ApcStatePointer[2];                                 //0x168
    struct _KAPC_STATE SavedApcState;                                   //0x170
}
//0x18 bytes (sizeof)
struct _KAPC_STATE
{
    struct _LIST_ENTRY ApcListHead[2];                                      //0x0
    struct _KPROCESS* Process;                                              //0x10
    UCHAR KernelApcInProgress;                                              //0x14
    UCHAR KernelApcPending;                                                 //0x15
    UCHAR UserApcPending;                                                   //0x16
};
//0x18 bytes (sizeof)
struct _KAPC_STATE
{
    struct _LIST_ENTRY ApcListHead[2];                                      //0x0
    struct _KPROCESS* Process;                                              //0x10
    UCHAR KernelApcInProgress;                                              //0x14
    UCHAR KernelApcPending;                                                 //0x15
    UCHAR UserApcPending;                                                   //0x16
};
//0x30 bytes (sizeof)
struct _KAPC
{
    UCHAR Type;                                                             //0x0
    UCHAR SpareByte0;                                                       //0x1
    UCHAR Size;                                                             //0x2
    UCHAR SpareByte1;                                                       //0x3
    ULONG SpareLong0;                                                       //0x4
    struct _KTHREAD* Thread;                                                //0x8
    struct _LIST_ENTRY ApcListEntry;                                        //0xc
    VOID (*KernelRoutine)(struct _KAPC* arg1, VOID (**arg2)(VOID* arg1, VOID* arg2, VOID* arg3), VOID** arg3, VOID** arg4, VOID** arg5); //0x14
    VOID (*RundownRoutine)(struct _KAPC* arg1);                             //0x18
    VOID (*NormalRoutine)(VOID* arg1, VOID* arg2, VOID* arg3);              //0x1c
    VOID* NormalContext;                                                    //0x20
    VOID* SystemArgument1;                                                  //0x24
    VOID* SystemArgument2;                                                  //0x28
    CHAR ApcStateIndex;                                                     //0x2c
    CHAR ApcMode;                                                           //0x2d
    UCHAR Inserted;                                                         //0x2e
};
//0x30 bytes (sizeof)
struct _KAPC
{
    UCHAR Type;                                                             //0x0
    UCHAR SpareByte0;                                                       //0x1
    UCHAR Size;                                                             //0x2
    UCHAR SpareByte1;                                                       //0x3
    ULONG SpareLong0;                                                       //0x4
    struct _KTHREAD* Thread;                                                //0x8
    struct _LIST_ENTRY ApcListEntry;                                        //0xc
    VOID (*KernelRoutine)(struct _KAPC* arg1, VOID (**arg2)(VOID* arg1, VOID* arg2, VOID* arg3), VOID** arg3, VOID** arg4, VOID** arg5); //0x14
    VOID (*RundownRoutine)(struct _KAPC* arg1);                             //0x18
    VOID (*NormalRoutine)(VOID* arg1, VOID* arg2, VOID* arg3);              //0x1c
    VOID* NormalContext;                                                    //0x20
    VOID* SystemArgument1;                                                  //0x24
    VOID* SystemArgument2;                                                  //0x28
    CHAR ApcStateIndex;                                                     //0x2c
    CHAR ApcMode;                                                           //0x2d
    UCHAR Inserted;                                                         //0x2e
};
NTKERNELAPI
VOID
KeInitializeApc (
    __out PRKAPC Apc,   
    __in PRKTHREAD Thread,   
    __in KAPC_ENVIRONMENT Environment,    //KAPC的ApcStateIndex
    __in PKKERNEL_ROUTINE KernelRoutine,    //APC回调函数地址
    __in_opt PKRUNDOWN_ROUTINE RundownRoutine,    //线程终止时会调用的APC
    __in_opt PKNORMAL_ROUTINE NormalRoutine,   
    __in_opt KPROCESSOR_MODE ProcessorMode,        //APC模式
    __in_opt PVOID NormalContext        //参数
);
 
typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,
    AttachedApcEnvironment,
    CurrentApcEnvironment,
    InsertApcEnvironment
} KAPC_ENVIRONMENT;
NTKERNELAPI
VOID
KeInitializeApc (
    __out PRKAPC Apc,   
    __in PRKTHREAD Thread,   
    __in KAPC_ENVIRONMENT Environment,    //KAPC的ApcStateIndex
    __in PKKERNEL_ROUTINE KernelRoutine,    //APC回调函数地址
    __in_opt PKRUNDOWN_ROUTINE RundownRoutine,    //线程终止时会调用的APC
    __in_opt PKNORMAL_ROUTINE NormalRoutine,   
    __in_opt KPROCESSOR_MODE ProcessorMode,        //APC模式
    __in_opt PVOID NormalContext        //参数
);
 
typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,
    AttachedApcEnvironment,
    CurrentApcEnvironment,
    InsertApcEnvironment
} KAPC_ENVIRONMENT;
 
 
 
NTKERNELAPI
BOOLEAN
KeInsertQueueApc (
    __inout PRKAPC Apc,
    __in_opt PVOID SystemArgument1,
    __in_opt PVOID SystemArgument2,
    __in KPRIORITY Increment    //优先级,3环添加用户APC时默认为0.
);
/*++
 
Routine Description:
 
    This function inserts an APC object into the APC queue specifed by the
    thread and processor mode fields of the APC object. If the APC object
    is already in an APC queue or APC queuing is disabled, then no operation
    is performed. Otherwise the APC object is inserted in the specified queue
    and appropriate scheduling decisions are made.
 
Arguments:
 
    Apc - Supplies a pointer to a control object of type APC.
 
    SystemArgument1, SystemArgument2 - Supply a set of two arguments that
        contain untyped data provided by the executive.
 
    Increment - Supplies the priority increment that is to be applied if
        queuing the APC causes a thread wait to be satisfied.
 
Return Value:
 
    If the APC object is already in an APC queue or APC queuing is disabled,
    then a value of FALSE is returned. Otherwise a value of TRUE is returned.
 
--*/
NTKERNELAPI
BOOLEAN
KeInsertQueueApc (
    __inout PRKAPC Apc,
    __in_opt PVOID SystemArgument1,
    __in_opt PVOID SystemArgument2,
    __in KPRIORITY Increment    //优先级,3环添加用户APC时默认为0.
);
/*++
 
Routine Description:
 
    This function inserts an APC object into the APC queue specifed by the
    thread and processor mode fields of the APC object. If the APC object
    is already in an APC queue or APC queuing is disabled, then no operation

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-12-23 17:23 被SSH山水画编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (7)
雪    币: 871
活跃值: (9841)
能力值: ( LV13,RANK:385 )
在线值:
发帖
回帖
粉丝
2
我就要APC读写鹅厂dxf内存 你给不给吧
2021-12-23 17:33
0
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
3
TkBinary 我就要APC读写鹅厂dxf内存 你给不给吧[em_57]
T哥,刚刚外面人多,咱屋里说
2021-12-23 18:35
0
雪    币: 112
活跃值: (293)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
NormalContext:APC回调函数参数1。当前为用APC时,此处存放实际的用户APC回调函数。
好像不是呀
2021-12-24 10:25
0
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
5

是的,你逆一下kernelBase的插入用户APC函数,就会发现NormalRoutine存的是RtlDispatchApc

2021-12-24 11:55
0
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
6
我是小牛牛 NormalContext:APC回调函数参数1。当前为用APC时,此处存放实际的用户APC回调函数。 好像不是呀
跟一下就知道了,这东西还是要自己逆,只看概念容易忘
2021-12-24 11:56
0
雪    币: 31
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
关注,后面的笔记内容还上吗,像调试、窗口、内存那些
2023-6-29 17:32
0
游客
登录 | 注册 方可回帖
返回
//