这节课给大家讲一下进程结构体,之前有过win32基础的同学肯定都知道进程和线程的概念,进程也好线程也好,在操作系统中都是一个结构体,在操作系统中想要创建进程或者线程的时候,本质上就是分配一块内存、填充一个结构体,为这个结构体里面填上具体的值,这样一来进程和线程就被创建出来,所以如果我们想要了解进程、线程的秘密,最主要的就是一定要对进程或者线程这个结构体非常的了解,这样我们就能够找到很多我们之前不知道的细节。我们这节课主要学习的是进程的结构体
每个WINDOWS的程序,也就是每个WINDOWS中运行中的程序,我们通常称为进程。每一个进程在零环都有一个对应的结构体。这个结构体叫eprocess。大家可以看一下,如果想看这个结构体呢?可以用这个指令。
dt _eprocess。
这个结构体非常大。每一个进程啊,这里再强调一遍,每一个进程在零环都有这样的一个结构体。
那么大家有些人有些基础的人呢?不要把这个结构体跟peb搞混了。peb呢,也是一个结构体,也是来描述进程相关信息的一个结构体,但是peb它主要是给三环程序用的,它不是在零环的,这个结构体PEB在三环,而eprocess在零环,这是它俩最大的差异。好了,这个eprocess的结构体如下,这些结构体里边的成员了,我们不可能把里边所有的成员都背下来都知道它是做什么的,这是不现实的,因为这些结构体的成员WINDOWS本身并没有公布。那么,我们想了解它的每个成员做什么事情,其实是需要自己去逆向分析的。那么,这么多成员我们不需要都记下来,我们只要把一些最关键的对程序执行最关键的一些成员了解到了就足够了。我们看一下这个eprocess的结构体:
这个结构体里边的第一个成员,大家注意观察它的第一个成员也是一个结构体。这个结构体呢,叫keeper size,我们想看keeper size呢,一样的用dt:
dt _KPROCESS 然后呢,按一下回车。这样的话呢,我们就能看到了1 process里边的第一个成员k process,所有的相关信息。
那么,这个keep process里边的第一个成员,大家看到了它这个名字呢?叫Header,然后它的类型呢?是dispatcher_Header。这个东西是什么呢?大家之前写win32程序的时候肯定都用到过,类似于什么wait for single object等等这类的函数。那么,这类函数呢?我们通常说可以通过这种函数等待一个什么事件呀?等待一个互斥体呀,等等。那么,这种函数除了等待事件或者等待互斥体以外,它还可以等待什么进程啊?线程啊?那原因是什么呢?
原因只有一个,就是只要你的这个对象,零环的这个对象开头是以这个结构体开头的,也就是这个dispatch hander,这个结构体开头的。我们都可以称它为可等待对象?可等待对象有一个特点呢,就是可以使用这种wait for single object这种函数来使用。把这个这种对象当成这种wind for single object,这个函数的参数。那么,关于这种所谓的可等待对象呃的更多的细节呢?我们后边会有专题来详细的讲解,现在呢?
我们只是简单了解一下就可以了。你需要记住的就是不管呃内核对象的哪一个呃哪一个对象,比如说是进程啊还是线程啊,只要它这个零环的结构体。是以这个成员开头的,那它就是可以称为什么可等待对象就okay了?然后呢?我们再看一下keeper size,0x18的这个成员keeper size,0x幺八刚才介绍的hander。现在介绍的是这个DirectoryTableBase,这个叫什么呢?叫页目录表基址,这个成员可以认为是整个进程结构体里边最重要的成员。那么一个进程,我们之前学页表、页目录的时候,大家都有印象的,一个进程,它都有一个自己的页目录表,这个页目录表里边呢,记录了当前进程的线性地址,所映射的所有的物理页,对吧?那么也就是说这张表,只要找到了这张表,就相当于找到了整个进程所有的物理页面。那你如果能控制这个这个这个表的话,那就意味着你控制了整个进程的所有的物理页。那这个值呢,就是最终添到cr 3里边的值。
这是这个成员,所以呢,大家要注意这个成员是最重要的,所谓进程切换切换的是什么,我们说比如说操作系统,现在只跑了一个进程,跑了一个进程,那么这个时候想换成另外一个进程的时候。做什么事情呢?那就是把第二个进程k process里边的direct table base拿出来添到cr 3里,OK了,这样的话呢,所谓的进程切换就切换完了。所以简单点说,进程切换就是什么切换cr 3。以上就是这个DirectoryTableBase成员。
我们再再往下看。key process的呃里边呢,还有三八到3c,大家可以看一下38到3c这两个成员:
一个叫kernel time,还有个叫user time,这两个成员统计的是你当前的这个进程在零环运行的。时间和在三环运行的时间各是多少?这个kernel time呢,就是你当前这个进程在零环的运行的时间,这个user time呢,就是在三环运行的时间,这些都是些统计信息啊,我们了解一下就可以了。然后呢,我们再看。
在5c的这个位置,在5c的这个位置有这样的一个成员:
这个成员呢?有四个字节。那么,这个成员呢?他做什么的呢?这个成员规定了进程里边所有的线程能在哪个CPU上跑?怎么理解呢?它一共一共只有四个字节啊(Uint4B),四个字节是四八三十二位,如果这个里边存储的值是1的话,大家可以算一下,如果它里边存储的值是一。那么,把它转成二进制的话,那就是前边有31个零,后边只有一个一,那这意味着什么呢?这意味着当前的这个进程里边所有的线程。只能在零号CPU上跑,这值第零位。最后一个是第零位就是第零个CPU,往左边每查1位,就是一号CPU这个呢?再查1位,就是二号CPU,对吧?以此类推。那么你比如说当前这个位置Affinity里边存储的值,如果是三的话,如果是三那就意味着这个进程里边所有的线程。可以在零号和一号CPU上跑,为什么呢?因为三转换成二进制的话,前边有30个零对吧?最后两位是一,那这个代表零号CPU这个代表一号3 pu呃CPU很好理解,那同样的,如果这个值是四是四的话,转化成二进制,那么最后三位就是100。那这就意味着,当前这个进程,所有的线程只能在二号CPU上跑,不能在零号CPU上跑,也不能在一号CPU上跑。只能在二号CPU上跑,对吧?那么,如果这个值是五,那大家拆一下就变成了幺零,那就是说零号CPU可以二号CPU可以,其他的CPU不可以?那么,正是因为它这个里边这个值啊,一共只有四个字节,四八三十二位,那这就意味着32位的操作系统支持的CPU的核呢,最多只能有32个,你不能超过33个。对吧,不能超过32个,最多32个,那同样的道理,如果你这个WINDOWS操作系统是64位的话,那它的这个成员呢?就是八字节八八六十四位,那这就意味着在64位的操作系统里边。最多可以支持多少个核呢?就是64个核。诶,这就是这个成员他的作用。然后呢?我们继续看。
在k process的六二的这个位置啊,六二的这个位置有一个基本优先级,基本优先级是什么意思呢?当我们在这个进程里边创建线程的时候。它都会参考这个基本优先级基础,优先级或者叫最低优先级,该进程中所有的线程最起始的创建的那个优先级都是这个。这是这个成员。那我们刚才给大家介绍的是什么呢?给大家介绍的是eprocess里边最重要的一个成员,也就是一个子结构体叫k process。那么大家看到了,我刚才也强调了这个进程结构体里边这个成员呀,如果将子结构体都都都这么多了,这这上百了对吧?一两百那这些成员不需要全记住我这节课给大家介绍的这些主要的成员呢?大家有一个印象,因为我们后边要自己去逆向分析WINDOWS的。一些非常非常关键的函数,你逆向它这些函数的时候,如果没有这些结构体,你是看不懂的。那么,如果大家能够把我这节课讲的这些主要的结构体理解怎么回事了?等你在逆向分析的时候呢,对你逆向代码是非常有帮助的。所以呢,我这节课介绍的结构体都是要求大家能够掌握的,看到的时候能想起来、知道它大概是什么意思?
那我们再看一下。刚才介绍的是eprocess里边的第一个成员,子结构体叫 process,那这个eprocess结构体还有一些其他的成员。也是挺有用的,比如说eprocess的七零到七八这个位置七零是什么?
我们看一下1 process啊。1 process的七零在这呢,create time七八呢,叫exist time,create time指的就是你当前这个进程什么时候创建的?那exist time呢,就是你当前这个进程是什么时候退出的?所以呢,以后呢,我们所学灵环的时候,当我一提到进程的时候,你先把那些别的概念全抛掉。你首先要掌握的就是进程,就是一个结构体,在零环对应一个结构体,这个结构体就是e process,你掌握了这个结构体,你就掌握了进程。因为所有的代码是为数据服务的,我们分析分析的时候,分析内核的时候,最关键的是什么?你一定要有一个点要围绕着一个点去分析,这个点是什么?这个点就是数据结构。所以呢,只要数据结构确定了,分析代码就变得非常容易了。那好了,这两个给大家介绍完了。
那再有一个0x八四的这个位置,0x八四大家找一下八四在这:
叫unit process ID。这个就是PID啊,我们平时在任务管理器里看进程的时候,看进程都有一个对应的那个PID的那个值那个值呢就在这个里边存储着。相当于进程的一个唯一的编号。看八八的这个位置,八八的这个位置呢?叫活动进程链表,什么意思呢?它是个双向链表,这个双向链表所有的活动的进程都连在了一起,构成了一个链表。在操作系统里边,我再强调一遍,在操作系统里边,所有正在活动中的这个进程都被操作系统通过一个双向链表给链起来了。双向链表,大家应该知道有两个成员,一个成员四个字节就是两个地址对吧?一个是一直往后找的。大家看这个图:
双向链表,双向链表,比如说存了两个成员,第一个成员呢,是指向下一个。那它的下一个这个第二个成员呢?是指向前一个这样的话呢?形成了一个双向链表,那么有了双向链表就意味着你从任何一个结点,既可以从它向后边变力。也可以同它通过它的第二个成员向前面便利,这双向链表的好处嘛,向前找还是向后找都非常的方便。那么,这些刚才我说了呃操作系统呢?把所有的这些活动中的进程都串到一个链表里了。那头是在哪里呢?总得有个开始的位置啊,对不对啊?那么有一个全局变量叫PS active process head。我给大家演示一下:
看到了不,这个全局变量,这个全局变量呢?它也有八个字节,863b78b8这是第一个成员,85d528b0这是第二个成员,那么它的第一个成员指向的是谁呢?就是指向了当前这个操作系统里边的第一个process,第一个进程。那好了,我们可以沿着这个往下走一下。比如说现在863b78b8这个成员指向的就是操作系统里边的第一个进程,那么需要注意的,我们做练习的时候需要注意的,它指向的并不是这个eprocess的头,如果你要是直接这样dt写、,直接看看它指向的第一个成员是谁,你要这样写就错了。
比如说你可能习惯这样写,我已经知道这个这个进程结构体了,我就可以dt。对不对?然后呢?1 pro 1 process,最后呢?把地址给它,你摁一下回车,实际上这个时候你看到的是错的。dt啊e process,然后呢,我们拿过来看这个值,
你看这个值这个偏移的这些值全是错的,为什么说是错的呢?因为这个头这个头它指向的。是1 process这个结构体的0x88的这个位置,它并没有指向头。所以你要好好体会一下啊,微软设计这个双向链表穿的时候,它并没有穿到这个结构体的头。而是指向了什么?指向了这个结构体腰部的一个位置,你们可以想象一下每一个1 process都是一个人。那然后呢?用一个链子把这一群人全穿起来,这个链子拴在什么位置了?不是拴在头上,而是拴在腰上,对吧?那你现在如果想看第一个成员是谁,那你在这块得。得到这个地址以后呢,你要减。比如说我刚才演示的这个地址。现在全局变量的第一个成员不就是指向下一个结构体吗?那它要减去什么?减去0x八八回车,这个时候我们看到的这些成员。一,process。剪的时候中间别有空直接剪上八八,按下回车,
大家看,现在我们看到的这个成员才是正确的。这块好好体会,做实验的时候要注意。那这是这个链表,大家说呢,大家可能会想这里面有什么用呢?这个链表呢,跟进程隐藏有关系,比如说你把任务管理器打开的时候,你在这里边是不是能看到所有的进程?对不对?
所有活动这些进程哪里来的?这些进程实际上就是WINDOWS提供了一些内核函数,这个内核函数查询了一个结构体。查询通过这个结构体来判断有多少个正在运行中的进程,那它查的是哪呢?实际上查的就是这个链表,那么如果你们在这里边把某一个进程给它断掉。断掉以后呢,你再到任务管理器里边,你看一看它有没有了,你就知道这个呃成员,他的作用是啥了?那这个呢?就是呃活动中的进程链表ActiveProcessLinks,它是个双向链表,那好了,我们继续往下看。
eprocess的90和9c这个位置呢?存储的是和物理液相关的一些统计信息。
什么是物理液呢?比如说每个进程都有4 GB的虚拟的线性地址,对吧?这个线性地址呢?实际上并不是每个线性地址都指向了实际存在的物理液。每一个进程里边呢,使用了哪些物理液?不是说你有4 GB,你的物理页也是有4 GB,而是说你真正用的时候操作系统呢,才会给你挂上这个物理页,那这两个成员QuotaUsage和QuotaPeak呢,主要就是和我当前这个进程一共用了哪些物理页,和它的统计信息有关系,了解一下就可以。
然后呢,我们再看一下a8、AC,还有b0,上边呢,是跟物理液统相关的统计信息,那么这三个成员CommitCharge、PeakVirtualSize以及VirtualSize呢是跟虚拟内存相关的统计信息,像这种成员,我们现在要做的事情就是知道它是什么,有个印象,等我们以后呢,真正去分析内核的时候。再见到这种成员了,回头再来看。你不要试图去一下,背下来背记不下来的。
那么大家再看这个11c的这个位置,11c的这个位置呢叫VadRoot,这个位置其实是非常重要的。等我们讲到内存那块相关的知识点的时候,大家就知道了这块这个成员标识了什么,你零到低两g,也就是用户空间用一个4 GB程序分为低两g和高两g,对吧?那么低两g也就是用户空间有哪些内存还没有分配,那同样的哪些内存分配了哪些内存没分配?其实呢,都是由这个成员所决定的,这个成员呢,指向了一棵平衡二叉树,这个二叉树里边就记录了我d两g的地址,哪些是分配的,哪些是没分配的。对吧,当我在低两级申请内存的时候,首先就会查询这棵树的,也就是查询VadRoot,如果你要申请的这个地址没在这棵树里,那就说明没有分配。那这个线性地址呢?就可以给你分,否则的话,如果你要申请的那个线性地址已经在这棵树里了,那就不能给你分了,对吧?这个成员也是有用的呃,跟什么有关系呢?以后呃,大家就知道了,他跟这个。模块隐藏有关系,比如说你通过传统的方式把某些模块隐藏起来了,其实是没有意义的。如果这个人知道这个VadRoot成员的话,他通过内存遍历可以找到被你所谓被你隐藏掉的那些模块。好了,这是第六个成员,我们再往后看。
bc和c0这两个成员debug pot和exception pot这两个成员是和调试相关的,比如说大家如果对调试比较感兴趣的话,那么你这块这两个成员就一定要重点了解。debug port里边呃,大家可能听说过一个所谓的反调试的一种技术,叫debug port清零。对吧,什么叫debugport的清零呢?其实就是把当前的1process结构体里边的这个成员DebugPort给它清成零,为什么要清成零呢?因为正常情况下一个进程一旦处于调试状态,这里边会存储一个值。对吧,存储一个值,这个值呢,是也是一个结构体,这个结构体呢,就是被调试进程跟调试器之间的桥梁。所以呢,有了这个DebugPort的值,里边有了这个结构体的值呢,被调试进程跟调试器之间就可以通信了,对吧?就可以交互数据了,那么如果当前的这个程序为了避免别人调试它的话呢?就可以疯狂的把这个里边的值给抹掉,抹掉的话,你调试器跟跟这个当前呃正在被调试的这个这个进程呢桥梁。可以理解成,桥梁就断了,对吧?就达到了一个所谓的反调试的目的。这是debug port清零,其实这种技巧呢,都是都是雕虫小技啊,如果你对调试非常熟的话,谁说我建立桥梁的时候,我一定要用这个debug port呢?我完全可以再建第二座桥,第三座桥,第四座桥嘛,对不对?还是那句话,说这个东西只能是只能是欺负那些对这些内核不了解的人。如果很了解的话,它是没有办法的。那么,我们继续看啊,1 process里边的0c四的一个地方呢,叫object table。这个叫什么呢?叫句柄表,句柄表也跟反调反调式的时候也会用到聚丙表这个东西什么用呢?你当前的一个进程可能还会用到很多很多其他的。内核对象。对吧,你用到了别的内核对象,那么假设我用了十个,另外的这个其它的内核对象那OK了,你当前的这个进程的聚丙表里边。句柄表里边就会存储另外那十个内核对象的。地址,所以呢,你可以理解成这个这个句柄表,它是存储在零环的啊,零环里每个进程都有一张表,这张表里存储了,我还用了哪些其他的?内核对象,我为什么说这个东西跟跟这个反调式呃也有关系呢?比如说你有个a进程,有个a进程,它想知道自己有没有被别人调试的话,它就可以遍历系统内所有其它的进程。查什么呢?查其它进程的句柄表。如果我在别的进程的句柄表里边发现我自己当前进程的进程结构体的地址,也就是发现了我当前自己进程的1 process的值。那说明什么问题,说明那个进程打开了,我那它为什么打开我呢?那只有一种可能,就它一定是在调试我。对吧,所以呢,这也是一种反调试的一种手段,遍历所有其他进程的剧本表,只要找到我进程结构体的地址了okay,自己进程立马退掉或者蓝屏。对吧,这是句柄表,我句柄表和调调试呢,我们有专门的专题句柄表这块呢,后边也有专门的专题。所以我这节课讲的东西呢,可能拓展的比较多,你有很多东西没听过,没关系,不用担心哈,这节课的内容其实很简单,主要是混个脸熟,见到这些结构体知道是是啥。就可以了,
174的这个位置呢,就是一个image file name,image file name就是你当前这个进程进程的名字。镜像名字叫什么?对吧,大家看我刚才给大家看过了,我看过了一个呃。看这了吧:
如果你这个是对的,就是如果你看这个进程结构体,你看到的是对的话,那你看它的幺七四的位置就可以看到当前进程的名字是什么了?我刚才给大家演示的这个链表,双向链表,我们从这是不是找到了第一个进程?第一个进程这个值减八八,得到了这个进程,然后你看一下。第一个进程是谁呢?是system。对吧,那么我如果想我想看第二个进程,可不可以看?可以呀,你找到它八八的位置。对吧,找到八八的位置:
这不是一个双向链表吗?找到八八的位置,然后你继续dt拿过来e process,把这个值。一样的,这个值也要什么减八八?
啊,这就得到了第二个进程,第二个进程,你看一下幺七四就知道第二个进程是什么了:
smss.exe对吧?这就是第二个进程。这样的话呢,你就可以通过,只要有这个全局变量就可以一个一个一个的变列下去,就得到了当前这个操作系统内核里边到底运行了多少个?进程对吧?你把这个进程链给断掉了,有一种术语叫所谓的断链,你们如果看过其他的一些相关的内核的文章的话,可能听说过断链的概念,断链是什么?断链就是我刚才说的这个。把这个链表给砍掉,达到一个所谓的进程隐藏的目的,对吧?很多文章写的东西。实际上也是浮云。根本达不到真正的隐藏,只能欺负小白。我们以后再说,那这是这个这个幺七四这个成员。
然后呢,我们再看一下0x1a0也就是ActiveThreads,这里边指的是活动线程。每一个进程呢,都可以包含多个线程,那你这个进程里边到底有多少个线程呢?我看这个成员就可以了,eprocess的0x1a0的这个位置。它里边存储的就是当前进程活动中的线程的数量。
好了,那我们再看一下幺b0的位置:
0x1b0的位置是什么呢?找一找0x1b0。0x1b0这个位置呢叫peb:
我在讲1 process之前,我跟大家强调了哈,三环也有一个成员,也有一个结构体,这个结构体呢,也有一些数据描述了当前的这个进程的一些信息,但是呢,这个结构体呢,它不在零环存储,它存在哪呢?它存在三环。那这个结构体是什么样的呢?我想看这个结构体我怎么看呢?
我们可以使用dt指令来看:
这就是这个结构体,这个结构体所有的成员都在这了。这个成结构体呢,叫peb啊peb,这个结构体那么大家呃,可以可以查一查相关的文章。或者看如果你们手里边有潘明老师讲那本WINDOWS内核原理与实现的的话,你可以看一下第三章,在第三章里边呢,详细的介绍了PB这个结构体。呃,这些成员都有什么作用?那在这里边呢,我给大家介绍几个比较重要的成员,你比如说第二个成员叫BeginDebugged:
这个成员跟调试相关,为什么说它跟调试相关呢?如果你当前的这个进程一旦被调试器附加了,或者是被调试器打开的话。这个成员会被置于1。被置于1那这样的话呢,你如果这你自己想写程序,想写程序判断自己有没有被调试的话,你就可以编写代码找自己的这个成员的。找自己当前进程,得到自己当前进程的PEB,得到当前进程的PEB,以后呢,看一下它的begindebugged是不是一,如果是一,那你就知道了自己正在被调试呢?对吧,那其实这种这种东西呢,像所有人都知道的东西就达不到调试的目的了。如果你们用的这个od调试器的话,是带插件的,那它插件里边有一个有一个有一个插件专门用来不停的去给它改零,对吧?即使是当前的这个进程被被附加了或者被调试器打开了,那么这个插件也会把这个值给改成零。那这样的话呢,你如果写反调试程序,仅仅是通过这个成员去判断它有没有被调试的话,实际上已经没有意义了。但我们要了解,对吧?这是三环啊,三环的一个结构体。然后呢?
我们继续往下看。在这个结构体里边的0c的这个位置,有一个PEBLDRDATA,大家可以把这个打开。看一下dt:
这个结构体,这是一个子结构体,在这子结构体里边呢,有三个链表。从0c幺四幺c开始,三个双向链表,这三个双向链表干什么用的呢?这三个双向链表里边记录了你当前的这个进程有多少个模块?对吧,你比如说第一个,第一个模块也就是InLoadOrderModuleList是你这些模块加载的时候,那个顺序,第二个呢是你这些模块在内存中。的顺序。第三个呢,是你这些模块初始化的顺序,这些模块有的顺序是一样的,有的不一样,对吧?三个双向链表里边存储的都是你当前这个进程包含了哪些模块?刚才我跟大家提了哈,如果你们把eprocess结构体里边零环的那个链表给给给断掉的话,就是所谓的断链。可以达到进程隐藏的目的,那么如果你们把peb里边这三个链表给它断链的话,也就是InLoadOrderModuleList、InMe...,就可以达到所谓的模块隐藏的目的。对吧,这个呢,就是呃零环里边的PB这个结构体比较重要的两个成员。其他的成员呢?大家可以自己嗯了解一下。好了,那我们这节课的内容呢?相对比较简单,大家就是可以熟悉熟悉这个结构体,然后有一个印象。好吧好,我们这节课的内容就这些。