初识APC
零、
(1)、APC是一个线程提供给另一个线程一个函数让它去调用
(2)、同步与异步概念
同步与异步
当您同步执行某项任务时,您将等待它完成,然后再转到另一项任务。当您异步执行某项任
务时,您可以在它完成之前转移到另一个任务。
(3)、海哥提的几个小问题
1、谁插入的APC:
别的线程
2、插入到哪里
两个双向链表(在_KAPC_STATE结构体里)
3、谁执行的APC
自己这个线程
4、什么时候执行的
执行KiInsertQueueApc()函数的时候 //以我目前的知识得到的理解是这样的//
一、分析TerminateThread/SuspendThread是如何实现的(从三环开始分析) //我只分析了TerminateThread函数//
(1)、
调用号为0x102h
(2)、KeInitializeApc()函数是NtTerminateThread里面的函数,初始化APC
第一个参数是ExAllocatePoolWithTag函数的返回值,即开辟的一块空间的初始地址
第二个参数是上上个函数传进来的ThreadHandle,是线程结构体(一开始我还以为是句柄号,后来我见这个参数在ObReferenceObjectByHandle()函数中做了两个参数,我才意识到,这个句柄号可能在这个函数里面被转化成线程结构体了)
第三个参数填的0,不知道是什么
第四个参数,PsExitSpecialApc函数地址(应该是判断是否为特殊APC)
第五个参数,ExFreeCallBack函数地址
第六个参数,PspExitNormalApc函数地址(应该是判断是否为普通APC)
第七个参数,填的0,不知道是什么
第八个参数,返回码
结合部分伪代码:
再结合_KAPC结构体
可以发现这个KeInitializeApc函数就是在构造_KAPC结构体
(3)、分析 KeInsertQueueApc()函数,该函数执行插入APC的操作
(4)、KeInsertQueueApc()函数里面有一个KiInsertQueueApc函数执行真正的插入APC函数
char __fastcall KiInsertQueueApc(int a1, int a2)
第一个参数是_KAPC结构体
第二个参数写死为2
这个函数的逆向分析在下面,与第三个任务的一部分重了
二、自己编写代码向某个线程中插入一个用户APC
DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC, //APC函数地址
[in] HANDLE hThread, //线程//
[in] ULONG_PTR dwData //APC函数的参数
);
实验结果如下:
三、自己分析APC的插入过程
我逆向了一手QueueUserAPC函数
(1)、
QueueUserAPC函数功能是将用户模式异步过程调用(APC) 对象添加到指定线程的 APC 队列。
一、 DWORD __stdcall QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData)
第一个参数是插入的APC函数的指针
第二个参数是线程句柄
第三个参数是传给APC函数的参数
QueueUserAPC调用了NtQueueUserAPC函数
先找到ZwQueueUserAPC
不知道为啥,这里都写的KiSystemService
(2)、 进入ntoskrnl.exe,找NtQueueApcThread函数(这里省略了)
NTSTATUS __stdcall NtQueueApcThread(HANDLE ThreadHandle, PKNORMAL_ROUTINE ApcRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
第一个参数为线程结构体句柄
第二个参数为BaseDispatchAPC(3环函数)用户APC总入口
第三个参数是APC函数的函数地址
第四个参数是APCC函数的参数
第五个参数未知,好像也是要传递的参数 参考 https://repnz.github.io/posts/apc/user-apc/ ,作者还说,在x64下有隐藏属性
(3)、 插入APC的函数在KeInsertQueueApc种
char __stdcall KeInsertQueueApc(_DWORD *a1, int a2, int a3, int a4)
第一个参数是构造的_KAPC结构体地址,
第二个参数是上一个函数传递的参数(上一个函数的倒数第二个参数)
第三个也是上一个函数传递的参数(上一个函数的倒数第一个参数)
第四个是0
(4)、 真正执行插入APC的是KiInsertQueueApc函数
char __fastcall KiInsertQueueApc(int a1, int a2)
第一个参数是构造的_KAPC结构体地址,
第二个参数是上一个函数的第四个参数,即0
这里我只是对部分必要代码进行了分析:
其中APC的插入在这里执行:
过程就像下图所示
(5)、插入APC的核心代码就是那个链表的插入...
参考:
APC与DPC
屏蔽级别:
https://zh.wikipedia.org/wiki/IRQL
APC的API介绍
https://zh.wikipedia.org/wiki/%E5%BC%82%E6%AD%A5%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8
SleepEx函数
挂起当前线程,直到满足指定条件。发生以下情况之一时,将恢复执行:
- 调用 I/O 完成回调函数。
- 异步过程调用 (APC) 排队等待线程。
- 超时间隔已过。
- DWORD SleepEx(
- [in] DWORD dwMilliseconds,
- [in] BOOL bAlertable
- );
链接:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex
https://blog.csdn.net/b2292486308/article/details/72832203
PS:
在THREAD偏移0x248的位置对应了线程的九种状态
+0x248 CrossThreadFlags : Uint4B
+0x248 Terminated : Pos 0, 1 Bit
+0x248 DeadThread : Pos 1, 1 Bit
+0x248 HideFromDebugger : Pos 2, 1 Bit
+0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
+0x248 SystemThread : Pos 4, 1 Bit
+0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
+0x248 BreakOnTermination : Pos 6, 1 Bit
+0x248 SkipCreationMsg : Pos 7, 1 Bit
+0x248 SkipTerminationMsg : Pos 8, 1 Bit
在逆向中遇到了一些函数或结构体:
1、
CONTAINING_RECORD这个宏我们之前使用过,就是通过结构体的对象的成员地址与成员名来获取对象的基址
2、
KeAcquireInStackQueuedSpinLockRaiseToSynch这个函数来自HAL
HAL,硬件抽象层,不知道是干什么的
那么KeReleaseInStackQueuedSpinLock应该就是释放排队自旋锁
__EOF__

本文链接:https://www.cnblogs.com/lordtianqiyi/articles/15747190.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现