欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]Windows注入篇之内核APC注入[高清有码]

[原创]Windows注入篇之内核APC注入[高清有码]

0x01 进程启动过程简介

   最近正在研习毛德操那本《Windows内核情景分析》上卷,看的真是难受呀,菜鸟还是表示有压力...老大跟我说要先

过一遍然后再深入研究,慢慢啃吧。总之言归正传,在看的懂本文附源代码之前,先看看第五章 进程管理 这章吧。我这里简要概括下:

    ReactOS中描述的进程启动过程如下:

    1. CreateProcess打开文件创建出Section然后调用进入系统调用

    2. NtCreateProcess初始化进程资源(进程内存空间,句柄表,Peb,设备描述表等等),但是进程只是一个空壳需要线程作为其执行体,所以进程在初始化的时候必须为自己创建一个线程(即我们通常说的主线程)

    3. 主线程由KeInitThread函数进行初始化,并规定其受到调度时内核的启动点是PspUserThreadStartup

    4. PspUserThreadStartup为主线程的Ring3执行机会创造一些环境,进程需要加载动态链接库,这个函数是家喻户晓的ntdll!LdrInitializeThunk,ReactOS并不是将TrapFrame.EIP直接修改为ntdll!LdrInitializeThunk,而是采用UserApc的方式,在内核线程受到调度回到Ring3层时,此Apc得到执行机会。

    5. 一般的线程的Ring3层入口,ReactOS中描述是BaseProcessStartup(主线程)/BaseThreadStartup(非主线程),现在XP往后我们可能再也看不到这个函数了,现在我们更多看到的是ntdll!RtlUserThreadStart,这个函数同样也是修改TrapFrame.EIP的方式得到执行的,不幸的是,只要存在用户Apc那么BaseProcessStartup/BaseThreadStartup就不要想执行起来,由于每次内核返回Ring3的过程每次都会修改TrapFrame.EIP,修改为KiUserApcDispatch直到没有用户层Apc。

    6. APC描述:2E中断KiSystemService退出时KiSystemServiceExit会调用KiDeliverApc,这个函数检查当前线程体(KTHREAD)的两个Apc队列,KernelMode队列的与UserMode队列的。前者调用一个循环全给执行了,后者就有些麻烦,因为涉及到Ring0->Ring3->Ring0->Ring3...重复检查的过程(详细的LZ就不多bb了,大家可以看书学习...)


0x02 进程回调的限制

   1. Apc注入离不开系统回调,而Apc是线程相关的,那么就肯定要在回调中由线程Id去查找线程体(ETHREAD),我们看2000的进程回调与线程回调的调用位置,两者都是在PspCreateThread中得到执行的(进程在前线程在后):

    

2000在初始化线程的时候才会将EProcess.UniqueProcessId置为其句柄表的索引,后续线程创建时将不再通知进程回调。而这个时候PspCreateThread还未将当前线程体初始化完毕,特别的是EThread.GrantedAccess没被初始化,那么在进/线程中 不管是Ring3的OpenThread或者是Ring0的PsLookupThreadByThreadId都将Fail,这个算是微软的一个小bug,但是到Vista之后,也就是Win7~Win10都不存这个问题了。


    2. Win7及之后的系统版本动态库加载点ntdll!LdrInitializeThunk不再使用Apc的方式启动,而是直接将TrapFrame.EIP修改为ntdll!LdrInitializeThunk,这样UserMode Apc的执行机会就自然被延迟了,那么延迟到什么时候呢? (这是64位ntdll,不过跟32也没什么差别):

ntdll!LdrInitializeThunk会调入_LdrpInitialize,在加载完导入表的一系列模块依赖之后会调用ZwTestAlert,这个函数内核情景分析也有介绍,他会将当前线程的Apc队列的UserPending重新置位,让已存在队列里的UserMode Apc得到执行(就在此次系统调用返回用户层之后),Win7以及之后的UserApc不再像2000那样,UserApc执行时机有了明确的几个点,具体大家可以深入研究


0x03 XP~Win10 APC注入方案实现

   XP之前由于进程回调的限制,所以与Win7后续要分两种情况处理,看到某款安全软件的做法是采用A盾劫持KiFastCallEntry,在进程初始化加载动态链接库的过程会调用NtQuerySection询问系统模块,以这个点作为Apc注入时机,LZ只想说哪有那么麻烦,直接在模块回调通知exe加载时就可以了,而且又可以避免Apc插入到LdrInitializeThunk的UserApc之前,因为Image回调在通知exe加载的时候在这里:


b1c88c08 80644526 nt!PsCallImageNotifyRoutines+0x36 

b1c88d0c 805d0e6b nt!DbgkCreateThread+0xa2

b1c88d50 805470de nt!PspUserThreadStartup+0x9d

00000000 00000000 nt!KiThreadStartup+0x16


这里正好是待创建进程的主线程刚得到调度的时候,这里线程体什么的都已初始化完毕,可以无忧进行插入。

     Win7及之后由于没有那么多限制,可以直接在进程回调完成。


     LZ用Insert Kernel Apc再Insert User Apc的原因在于:

       1. 得到线程已经开始执行或者调度没多久的时机,这能服从先后的原则;

       2. 申请ShellCode时可以不用考虑EProcess的地址空间锁的问题,32一般不会遇到,64的LoadImage可是一调用Zw*VirtualMemory就GG;

       3. 避免XP或者XP之前的Apc插入过早,插到LdrInitializeThunk的UserApc之前,这样所有动态链接库都没有加载就直接LdrLoadDll 基本上必crash。


注:

    1. 测试用的动态库放入C:\下,导出AiQ3Helper001函数,自己附上调试信息可以看到结果撒

    2. 书写代码时基本上用的都是32/64兼容变量,没有做64位测试,如果有蓝屏现象可能也就几处小问题,大家可以自行修改哈

    3. 支持XP ~ Win10,win10(BuildNumber<=14393),不要在最新版本上测试,微软出了保护...


上传的附件 AiQ3Helper.zip
AiQHelper.zip

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功