APC注入复现全过程:从零开始,手把手带你走一遍,坑我都替你踩了!
大家好呀,今天这篇文章,我想跟你们聊聊APC注入复现全过程,说实话,这个东西我前前后后折腾了好几天,踩了不知道多少个坑,网上很多文章写得那叫一个“高冷”,好像默认你啥都会似的,我这人比较笨,所以就想着,干脆自己写一篇接地气的,把我这APC注入复现全过程老老实实记录下来,也方便以后自己回头看。

先说个大概,APC注入到底是个啥?
嗯……怎么说呢,APC,全称是 Asynchronous Procedure Call,异步过程调用,听起来很唬人对吧?我一开始也是一脸懵,简单点理解就是:Windows 系统里,每个线程都有一个 APC 队列,你往里面塞个函数,等线程处于“可警告等待状态”的时候,它就会去执行你塞进去的那个函数。
那 APC注入 呢,就是利用这个机制,把我们的恶意代码(复现的时候用的是无害的弹窗啦)塞到目标进程的线程 APC 队列里,等它一执行,我们的代码就跑起来了,是不是有点意思?
我当时第一次听到这个思路的时候,心里就想:哇,还能这样玩?Windows 你这设计也太“贴心”了吧……
复现环境,咱得先搭起来
别急,动手之前先把家伙什准备好,我这次用的是:
- Windows 10 虚拟机(一定要用虚拟机啊朋友们,别拿自己主力机瞎搞,我上次差点把电脑搞蓝屏,吓死)
- Visual Studio 2019(2017、2022 都行,看你自己顺手)
- 一个目标进程,我选的是
notepad.exe,记事本,人畜无害,弹个窗出来也不吓人
哦对了,权限方面,如果你要注入的进程跟你自己权限一样,那普通用户就行,但如果你想注入系统进程,那就得管理员权限,甚至得搞点更高级的手段,咱们复现嘛,先从简单的来。
核心步骤,我一步一步说
找到目标进程的 PID
这个简单,我用的是 CreateToolhelp32Snapshot 那一套,遍历进程列表,找到 notepad.exe 的 PID,代码我就不全贴了,网上一搜一大把,但我要提醒一句:别忘了关闭句柄!我一开始就是忘了 CloseHandle,结果跑几次之后系统资源就被占满了,风扇呼呼转,我还以为电脑要炸了……
打开目标进程
拿到 PID 之后,用 OpenProcess 打开它,权限要给够:PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,再加上 PROCESS_QUERY_INFORMATION,少一个都可能失败,别问我怎么知道的,我试了好几遍才凑齐。
在目标进程里分配内存
用 VirtualAllocEx 在目标进程空间里申请一块内存,用来放我们的 shellcode 或者 DLL 路径,这里注意,内存属性给 MEM_COMMIT | MEM_RESERVE,保护属性给 PAGE_EXECUTE_READWRITE,为啥要给执行权限?因为后面要往里面写东西然后让它跑起来嘛。
把我们的“货”写进去
用 WriteProcessMemory 把 shellcode(或者 DLL 路径字符串)写进去,这一步一般不会出啥大问题,但如果你写的是 DLL 路径,记得路径要是目标进程能访问到的绝对路径,别整相对路径,不然它找不到,你就等着干瞪眼吧。
找到目标线程
这一步挺关键的,APC 是往线程队列里塞的,所以你得找到目标进程里的一个线程,用 CreateToolhelp32Snapshot 再遍历一次线程,找到属于那个进程的线程 ID,我一般会选主线程,但其实随便哪个都行,看运气。
OpenThread,权限给 THREAD_SET_CONTEXT,这个是必须的。
把 APC 排进去!
重头戏来了!用 QueueUserAPC,把我们的函数地址(其实就是 LoadLibraryA 的地址,如果你要加载 DLL 的话)和参数(DLL 路径的远程内存地址)传进去。
QueueUserAPC((PAPCFUNC)pRemoteLoadLibrary, hThread, (ULONG_PTR)pRemoteDllPath);
这时候 APC 就已经在队列里了,但它不会立刻执行,得等线程进入“可警告等待状态”,啥叫可警告等待?SleepEx、WaitForSingleObjectEx、MsgWaitForMultipleObjectsEx 这些函数,带 Ex 的等待函数,bAlertable 参数为 TRUE 的时候。
记事本主线程平时就在那等消息,所以一般很快就能触发,但如果你发现半天没反应,别慌,你可以手动让它“醒一醒”——比如给它发个消息,或者用 SuspendThread + ResumeThread 刺激一下,我试过,有时候确实得等几秒,泡杯茶回来就看到弹窗了,哈哈。
弹窗出现,成功!
当 APC 被执行的时候,LoadLibraryA 就会加载我们的 DLL,DLL 的 DllMain 里我写了个 MessageBox,弹出来一个“Hello, APC Injection!”。
那一刻,说实话,挺有成就感的,虽然只是个弹窗,但那种“我控制了别人”的感觉(虽然是自己的记事本),还是挺爽的。
我踩过的那些坑,你们别踩了
- 坑1:权限不够。
OpenProcess失败,返回 NULL,一定要检查权限,该给的都给上。 - 坑2:线程找不到,有些进程线程太多,或者你遍历的时候没匹配对 PID,结果
OpenThread失败,耐心点,多试几次。 - 坑3:APC 不执行,这个最气人,后来我才知道,目标线程必须处于可警告等待状态,记事本一般没问题,但如果你注入的是个忙得要死的进程,那就得等,或者想办法让它进入等待状态。
- 坑4:32位 vs 64位,如果你编译的是 32 位程序,去注入 64 位进程,那肯定失败,反过来也一样,一定要匹配架构!我在这上面浪费了一个下午,简直想砸键盘。
最后说两句
好了,以上就是我这次 APC注入复现全过程 的完整记录了,写这篇文章不是为了教你去干坏事啊,纯粹是技术学习、安全研究,你要是拿去搞破坏,那跟我没关系,后果自负哦。
其实复现这个过程,最重要的不是代码本身,而是理解 Windows 的线程调度和 APC 机制,搞懂了原理,你再看其他注入方式,比如远程线程注入、劫持注入,都会觉得豁然开朗。
如果你也在复现的过程中遇到了什么奇葩问题,欢迎在评论区跟我吐槽,我看到了会尽量回复,毕竟,踩坑的路上,有人陪着总是好的嘛。
好啦,今天就聊到这里,我要去把我的虚拟机快照恢复一下了,下次见!👋