欢迎来到 嗅灵易学

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

[原创]64位CreateProcess逆向:(六)新进程、新线程的管理

[原创]64位CreateProcess逆向:(六)新进程、新线程的管理

点击下面进入总目录:
64位Windows创建64位进程逆向分析(总目录)
  在这个系列的上两篇文章中,我们已经谈及了进程、线程的创建。按照教科书式的说法,Windows操作系统中,各个进程与进程之间的内存空间是独立的,而同一个进程的多个线程是共享同一个内存空间的。但是,在windows编程中又一定会涉及到“进程间通信”,这说明进程与进程之间是不可能完全独立的。直接点说,站在Windows操作系统(0环)的角度看,Windows是保存有所有进程的信息、并能够管理他们的关系,以此完成进程间通信、线程间切换的一系列问题。
  在这篇文章中,我们通过分析0环的PspInsertProcess函数与PspInsertThread函数,可以看到windows其实就是以链表这种数据结构管理进程、线程。
  新建进程的管理PspInsertProcess
  在0环中,Windows通过调用PspInsertProcess来管理新进程。他的函数原型如下:

NTSTATUS 

PspInsertProcess(  IN PEPROCESS   pCurentEprocess,

        IN PEPROCESS pParentEprocess,

        IN ACCESS_MASK DesiredAccess,

        IN ULONG nCreateFlags,

        IN ULONG JobMemberLevel,

        IN HANDLE DebugObjectHandle,

        IN ULONG unKnownFlag,

        IN ULONG enumType,

        OUT ACCESS_STATE_EXPAND* ptrAccessState

         );


  简单而言,它只有一个核心功能:将代表新进程的_EPROCESS结构体指针,插入到PsActiveProcess链表中。
  以下是PspInsertProcess的工作流程图:

  为了集中内容,我们仅关注与进程信息有关的结构体更新的各步骤,其它部分(如APC、进程权限等)我们留在ida文件中大家自行参阅(与前面提供的IDB文件一致):
将当前PID写入当前进程句柄表

更新全局pActiveProcessLinks,头插法插入当前EPROCESS到链表

拷贝调试端口到新的进程

创建一个访问状态结构体

将EPROCESS插入到当前进程的句柄表中和对象目录中

如果插入失败,则释放之前创建的访问结构体

以上涉及到了PID与句柄,我们也可以顺便提一下windows编程中常见新手问题“进程ID与进程句柄有什么区别?”其实,从以上的代码中就可以看出,PID是与进程对应且独一无二的(_EPROCESS.UniqueProcessId),即使PID写入到当前进程的句柄表中,仍然是放在一个特殊且独一无二的成员中(_HANDLETALBE_UniqueProcessId)。那么,我们在SDK编程中调用的OpenProcess返回的是PID这个“句柄”吗?答案是否定的。这是因为在由A进程打开B进程,其实涉及到两个进程的多对多关系,操作系统需要记录好A、B进程两者的PID(以及其它权限信息等),而为了更快的索引这些信息,会给这些结构体使用统一的“下标”,那就是句柄,也正是OpenProcess的返回值。
  以上提到的pActiveProcessLinks中记录有当前运行的进程信息,因此,通过遍历pActiveProcessLinks就可以列举出所有当前进程,这是一种方法,使得我们不调用进程快照函数就可以遍历当前进程。此外,通过修改pActiveProcessLinks的链表结构(断链),我们还可以隐藏进程。
  新线程的管理PspInsertThread
  PspInsertThread函数的原型如下:
NTSTATUS

PspInsertThread(PETHREAD ptrEthread,

        PEPROCESS ptrEprocess,

        PINITIAL_TEB initTeb,

        ULONG ptrByteProcessFlags,

        ACCESS_MASK DesiredAccess,

        LONG  nFlag,

        CreateProcessContext createProcessContext,

        ACCESS_STATE_EXPAND *AccessState,

        GROUP_AFFINITY* pGroupAffinity,

        HANDLE* pProcessHandle,

        PCLIENT_ID pClientID)


  它初始化EHTREAD结构,调用注册的线程回调函数,将线程插入到进程中的链表中。这个函数中的ptrEthread与ptrEprocess,在函数内部体现了进程与线程的一对多关系。
  函数的流程图如下:

函数内部详细步骤如下:
保存当前PEB,并且判断参数CreateProcessContext是否为空

判断参数pGroupAffinity是否为null, 如果不为空,再次判断作业对象是否存在


对EPROCESS中的ProcessInserted, ProcessDelete, ProcessSelfDelete标志位进行检测

如果满足以上条件,跳转到下面,做释放工作,最终返回STATUS_UNSUCCESSFUL 或者
STATUS_PROCESS_IS_TERMINATING(当EProcess.Flags & STATUS_SERIAL_MORE_WRITES 不为0)


调用KeStartThread来填充当前的ETHREAD对象,插入到PKProcess->ThreadListHead中

更新ETHREAD中的CrossThreadFlags标志位,并且更新ActiveThreads的数值

将当前的ETHREAD插入到EPROCESS的LISTENTRY中,同样是头插法

如果是suspend调用,则挂起线程

如果CrossThreadFlags 第0位置1,则调用KeForceResumeThread唤醒线程


创建访问状态对象,并写入函数参数

插入对象到句柄表和对象目录

判断线程插入的是否是当前函数调用进程,不是的话,再判断参数Flags的标志位,如果不满足,调用PspAdjustThreadSecurityDescriptor调整线程安全描述符



填充输出参数clientID

如果开启了线程回调,循环遍历PspCreateThreadNotifyRoutine,调用ExGetCallBackBlockRoutine 获得回调函数地址,并调用


创建线程对象句柄

调用KeReadyThread , 将线程置于运行状态

  在PspInsertThread中,我们会发现其对EPROCESS.ThreadListHead的操作,将新线程更新到LISTENTRY中去,这也等于告诉我们,可以通过遍历该链表(而不是调用API),来获取指定进程的所有线程。
  DEMO: ProcessPeek
  正如我们以上的分析,通过遍历0环中的进程链表、线程链表,我们就可以获取到当前所有进程、线程的信息。更进一步,如果我们将代表某进程的节点,从进程链表中删除掉,就可以达到隐藏特定进程的目的。事实上,不少病毒就是用这个技术隐藏自己的进程的。
  附件中的demo,就是通过对进程、线程链表操作来显示所有进程或隐藏指定进程的:

使用前请看解压包中的“说明.txt”
  不过,在实际对抗中,进程的信息不仅存放在pPorcessLink中,而是以冗余的形式存放在多处,真要完全隐藏又要稳定,需要对各细节进行大量测试。
  另外一方面,从对抗的角度,若已经知道某些程序会对进程链表或线程链表“断链”来隐藏自身,我们怎样遍历才能让它隐藏失败呢?我们想到的,可以通过句柄表(系统未导出,要定位可在论坛里找相关资料)来找到被隐藏的进程。不过这个思路一是需要大量实验,二与我们这篇文章话题较远。就不公布在这里了,当作抛砖引玉,供大家讨论。

上传的附件 PspInsertProcess.png
PspInsertThread.png
1.png
2.png
3.png
4.png
5.png
6.png
7.png
8.png
9.png
10.png
11.png
12.png
13.png
14.png
15.png
16.png
17.png
18.png
19.png
20.png
21.png
22.png
23.png
24.png
25.png
26.png
27.png
28.png
29.png
ProcessPeek.7z
插入进程、插入线程.doc
插入进程、插入线程.pdf

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

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

0 0 0 举报
复制成功