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了。
通过对KeInitializeApc的分析,暂时得知在初始化用户APC时貌似也要传入一个KernelRoutine。
当为内核APC时,NormalRoutine是否有值决定了这个APC是普通内核APC还是特殊内核APC。
特殊内核APC无法携带参数,即使初始化APC时的NormalContext参数有值也会被强制清0。
去WRK看一下函数声明和注释:
分析KeInsertQueueApc
首先根据之前传入的Enviroment来判断要取哪个_KPAC_STATE成员
选择完_KPAC_STATE后,判断ApcMode与NormalRoutine决定插入到哪个链表中。经分析得知,用户APC回调存在NormalRoutine中,但KernelRoutine会存一个名为PsExitSpecialApc的特殊APC回调(用于释放当前APC内存空间)。
如果当前APC插入到了备用APC队列(SavedApcState)中就返回。如果插入的是ApcState队列中就继续判断这个APC是自身插入还是其他线程插入的。
如果是插入到其他线程的APC并且是个用户APC。
如果这个APC是内核APC并且是插入到其他线程的
APC的插入位置与传入的Enviroment函数相关。如果是插入到了备用APC队列中则执行返回。若是普通APC队列中则继续进行多个判断。
当这个APC是自身线程插入给自身的,并且是个特殊内核APC,则会立马触发软中断执行。
如果这个APC是当前线程插入给其他线程的,且是个用户APC。当APC所属线程处于等待时,会尝试唤醒线程来执行APC。如果不是等待状态,则UserOrNormalKernel默认为0,插入后不执行APC。
如果这个APC是当前线程插入给其他线程的,且是个内核APC。当APC所属线程处于运行时,会直接触发软中断执行APC或通知其他核触发软中断执行。当APC所属线程处于等待时,会尝试唤醒线程来执行APC。其他状态则不会立马执行APC。
通过对比发现WRK上的插入逻辑还是有一些区别的,但是大体逻辑相差不大,说明APC插入逻辑是一直在优化细节的
APC派发函数在很多函数中都会被调用。
这里以KiServiceExit为例子,也就是系统调用返回时,会判断用户APC队列是否需要被执行。如果用户APC队列没有值则不派发APC。
其中派发APC的函数名为KiDeliverApc,在WRK中看一下函数声明:
KiDeliverApc函数中首先会检测是否有内核APC,如果有则先处理内核APC。
如果内核APC链表没有值或者处理完毕了,就开始处理用户APC链表。处理用户APC时也会先执行KernelRoutine回调。
随后执行KiInitializeUserApc函数来派发用户APC,而不是像内核APC一样直接执行。
用户APC的执行不像内核APC一样直接调用回调即可。用户APC需要在3环执行,因此在派发时需要将执行环境降到3环。执行APC结束后再返回0环。此时就会产生如下问题:
系统调用进0环时构造了一个TramFrame结构体用于保存3环进来时的环境,系统调用完成返回3环时通过TrapFrame中保存的寄存器信息来继续执行3环代码。因此通过更改TrapFrame中的值可以让当前代码在返回3环时改变执行位置。
由于执行完用APC后还要回到0环继续执行后续代码,但TrapFrame已经被更改。因此需要备份更改前的TrapFrame。KiInitializeUserApc中使用CONTEXT结构备份TrapFrame。
函数内部直接取出栈顶的NormalRoutine并调用,将NormalContext、SystemParam1、SystemParam2作为参数传递。NormalRoutine为用户APC派发函数总入口,由Windows填充,函数为kernel32!BaseDispatherApc。
APC执行完成后通过ZwContinue函数回到0环继续处理剩余用户APC,并根据堆栈中的Context结构来填充TrapFrame结构。
进程挂靠本质为将某个线程的CR3切换为目标进程的CR3,使该线程的内存映射走目标进程的GDT。函数KeStackAttachProcess就是用于进程挂靠的。
首先判断DPC是否处理完成,如果还有DPC没处理直接蓝屏。
如果当前线程已经是在目标进程的环境下了,直接返回。
否则直接调用KiAttachProcess开始挂靠,第一次挂靠和多重挂靠处理方式不同。
KiAttachProcess有4个参数:当前线程、目标进程、原IRQL等级、原ApcState要保存在哪。
将当前线程的ApcState队列备份至第四个参数中并清空ApcState。
如果是第一次挂靠,直接交换ApcStatePointer成员并设置挂靠状态。如果是多重挂靠则没必要设置了。
然后调用KiSwapProcess进行CR3的切换。切换前还会判断进程空间是否被交换到磁盘上了,如果被交换到磁盘上会先恢复进程空间再切换CR3。
判断是否为挂靠状态,不是则直接返回。
等待内核APC处理完成。
如果非挂靠、APC没处理完成或正在处理,直接蓝屏。
前置判断都通过后恢复CR3。(多重挂靠下,参数ApcState的Process存的是上一层进程环境的Kprocess)
想啥呢,这种东西我会放出来吗
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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-12-23 17:23
被SSH山水画编辑
,原因: