⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 漫谈兼容内核之十:windows的进程创建和映像装入.txt

📁 漫谈系统内核内幕 收集得很辛苦 呵呵 大家快下在吧
💻 TXT
📖 第 1 页 / 共 2 页
字号:
漫谈兼容内核之十:Windows的进程创建和映像装入

[align=center]毛德操[/align]

    关于Windows的进程创建和映像装入的过程,“Microsoft Windows Internals 4e”一书的第六章中有颇为详细的说明。本文就以此为依据,夹译、夹叙、夹议地作一介绍。书中说,创建进程的过程分成六个阶段,发生于操作系统的三个部分中,那就是:Windows客户端即某个应用进程的包括Kernel32.dll在内的动态连接库,Windows的“执行体”、即内核(确切地说是内核的上层),以及Windows子系统的服务进程Csrss中。这六个阶段是:
    1. 打开目标映像文件。
    2. 创建Windows的“执行体进程对象”,也就是内核中的“进程控制块”数据结构。
    3. 创建该进程的初始(第一个)线程,包括其堆栈、上下文、以及“执行体线程对象”,即内核中的“线程控制块”数据结构。
    4. 将新建进程通知Windows子系统。
    5. 启动初始线程地运行(除非因为参数中的CREATE_SUSPENDED标志位为1而一创建便被挂起)。
    6. 在新进程和线程的上下文中完成用户空间的初始化,包括装入所需的DLL,然后开始目标程序的运行。
下面分阶段叙述。

第一阶段:打开目标映像文件
    在Win32位API中,创建进程是由CreateProcess()完成的。这实际上是个宏定义,根据不同的情况定义成CreateProcessA()或CreateProcessW()之一,这两个函数都在kernel32.dll中(可以用工具depends观察)。两个函数的区别仅在于字符串的表达,前者采用ASCII字符,而后者采用“宽字符”、即Unicode。实际上Windows的内部都采用宽字符,所以前者只是把字符串转换成宽字符格式,然后调用后者。
    可以在Windows上运行的可执行软件有好几类,处理的方法自然就不一样:
    ● Windows的32位.exe映像,直接运行。
    ● Windows的16位.exe映像,启动ntvdm.exe,以原有命令行作为参数。
    ● DOS的.exe、.com、或.pif映像,启动ntvdm.exe,以原有命令行作为参数。
    ● DOS的.bat或.cmd批命令文件(脚本),启动cmd.exe,以原有命令行作为参数。
    ● POSIX可执行映像,启动posix.exe,以原有命令行作为参数。
    ● OS/2可执行映像,启动os2.exe,以原有命令行作为参数。
    这里面最重要的当然是32位的.exe映像,而最后两类现在已经很少见了。从对于除32位.exe以外的各种映像的处理,读者不妨对比一下Wine对.exe映像的处理,看看这里有着什么样的相似性。
    当然,我们在这里只关心32位.exe映像。对于这一类映像,CreateProcess()首先打开映像文件,再为其(分配)创建一个“Section”、即内存区间。创建内存区间的目的当然是要把映像文件影射到这个区间,不过此时还不忙着映射,还要看看。看什么呢?首先是看已经打开的目标文件是否一个合格的.exe映像(万一是DLL映像?)。还要看的事就有点出乎读者意外了,看的是在“注册表”中的这个路径:
    HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options

    用depends可以看到,ntdll.dll中有个函数LdrQueryImageFileExecutionOption s(),就是专门干这个事的。
    如果上述路径下有以目标映像文件的文件名和扩展名为“键”的表项,例如“image.exe”,而表项中又有名为“Debugger”的值,那么这个值(一个字符串)就替换了原来的目标文件名,变成新的目标映像名,并重新执行上述的第一阶段操作。这样做的目的当然是为调试程序提供方便,但是我们不妨设想:如果黑客或某个木马程序设法在注册表中加上了一个表项?这时候用户以为是启动了程序A,而实际启动的却是B!。

第二阶段:创建内核中的进程对象
    我们知道,Linux上的每个进程(线程)都有一个“进程控制块”、即task_struct数据结构,与具体进程/线程有关的绝大部分信息都集中存储在这个数据结构中。而Windows则有所不同。首先,Windows的进程和线程各有不同的“对象”、即数据结构,从概念上把线程和进程分离开来。线程是具体的(执行)上下文,是CPU调度的单位和目标,而进程只是若干共享地址空间和特性(如调度优先级)的线程的集合。于是,进程有进程的数据结构,线程有线程的数据结构。这就好像是对一组task_struct数据结构“提取公因子”所形成的结果,这个举措是很好理解的。进一步,Windows又把本可集中存储的的进程数据结构也拆分成好几个对象,有的在内核中,有的则在用户空间。
    内核中与进程有关的对象有:
    ● EPROCESS。即struct _EPROCESS,在“Internals”书中也称为“Process Block”。它代表着Windows的一个进程,‘E’表示“Executive”,微软把Windows内核中的上层称为“Executive”、以区别于下层的设备驱动和内存管理等成分、一般翻译成“执行体”。“Executive”也有“管理”、“运行”的意思(所以CEO就是“总裁”)。
    ● KPROCESS。这是EPROCESS内部的一个成分,其名称就叫“Pcb”。
    ● W32PROCESS。下面将要讲到,在用户空间有个“Windows子系统”的服务进程csrss。这个服务进程为系统中的每个Windows应用进程都维持着一个数据结构,其中包含了一些与窗口和图形界面有关的信息。而对于窗口和图形界面的操作原来也是由csrss在“客户”进程的请求下实现的。但是,为了提高效率,后来把这部分功能移到了内核中。与此相应,有关数据结构的一部分也需要移到内核中,就成了W32PROCESS。
    既然KPROCESS是EPROCESS一部分,实际上内核中与进程有关的对象实际上只有两种,就是EPROCESS和W32PROCESS。不过这里没有包括“打开对象表”,那也是每个进程都有的(Linux内核中的“打开文件表”也在进程控制块的外面)。
    用户空间与进程有关的对象有:
    ● 如上所述,把W32PROCESS数据结构移入内核以后,csrss仍需要为每个Windows进程保持一些别的信息,所以csrss内部仍有按进程的相应数据结构。
    ● PEB(Process Environment Block)、即“进程环境块”。PEB中记录着进程的运行参数、映像装入地址等等信息。PEB在用户空间中的位置是固定的,总是在0x7ffdf000。在Windows中,用户空间和系统空间的分界线是2GB、即0x80000000,所以PEB在靠近用户空间顶端的地方。

    “Internals”书中并未给出有关数据结构的定义,但是通过Debug手段给出了EPROCESS的内部结构:
[code]   +0x000  Pcb               : _KPROCESS
   +0x06c  ProcessLock       : _EX_PUSH_LOCK
   +0x070  CreateTime        : _LARGE_INTEGER
   +0x078  ExitTime          : _LARGE_INTEGER
   +0x080  RundownProtect    : _EX_RUNDOWN_REF
   +0x084  UniqueProcessId    : Ptr32Void
   +0x088  ActiveProcessLinks   : _LIST_ENTRY
   +0x090  QuotaUsage        : [3]  Uint4B
   +0x09c  QuotaPeak         : [3]  Uint4B
   +0x0a8  CommitCharge      : Uint4B
   +0x0ac  PeakVirtualSize    : Uint4B
   +0x0b0  VirtualSize        : Uint4B
   +0x0b4  SessionProcessLinks  : _LIST_ENTRY
   +0x0bc  DebugPort         : Ptr32Void
   +0x0c0  ExceptionPort      : Ptr32Void
   +0x0c4  ObjectTable        : Ptr32_HANDLE_TABLE
   +0x0c8  Token             : _EX_FAST_REF
   +0x0cc  WorkingSetLock    : _FAST_MUTEX
   +0x0ec  WorkingSetPage    : Uint4B
   +0x0f0  AddressCreationLock  : _FAST_MUTEX
   +0x110  HyperSpaceLock    : Uint4B
   +0x114  ForkInProgress     : Ptr32_ETHREAD
   +0x118  HardwareTrigger    : Uint4B[/code]

    可见,EPROCESS的第一个成分是Pcb,其类型是_KPROCESS、即KPROCESS,这是一个大小为0x6c的数据结构。书中也给出了它的内部结构。
    “Undocumented Windows 2000 Secrets”一书也以Debug手段给出了这个数据结构的内部结构,但是列出的结构与此有很大的不同,也许是因为版本的关系。从所列的内容看,似乎“Secrets”一书倒是正确的,因为那里所列的EPROCESS结构中有关于虚存的成分Vm,是一个大小为0x50的数据结构,而这里没有,但是虚存(地址空间)显然是进程的主要资源,所以EPROCESS数据结构中理应有它的位置。由此看来,“Secrets”一书所述更接近于桌面和服务器系统的现实,而“Internals”书中所列可能更接近于不带MMU的嵌入式系统。而且,“Secrets”一书还在附录C中给出了通过逆向工程手段得到的EPROCESS和PEB等数据结构的定义(代码),这当然是很有价值的。
    那么,如果确有不同版本的EPROCESS,这会有什么影响呢?首先,用户空间的应用程序不能直接访问内核中的EPROCESS数据结构,所以具体的EPROCESS数据结构属于内核的内部实现,只要内核中的各种成分、各个环节都配套成龙,“自圆其说”,就没有什么问题,这跟Linux内核中一些条件编译和裁剪的效果是类似的。可是,另一方面,对于可以动态装入的.sys模块,如果在模块中需要访问这些数据结构,那就可能有问题了,因为.sys模块都是以二进制映像的形式提供的,不像在Linux中那样可以由源代码重新编译。怎么办呢?我们可以到Windows的DDK中去找找答案。
    在DDK的.h文件中,有函数IoGetCurrentProcess()的申明:

[code]NTKERNELAPI
PEPROCESS
IoGetCurrentProcess(
    VOID
);[/code]

    这个函数是内核为.sys模块提供的支撑函数,相当于由Linux内核导出的函数。其返回值类型是PEPROCESS,就是指向EPROCESS数据结构的指针。显然,这跟Linux内核中的current相似,调用的目的是获取当前进程的EPROCESS数据结构(指针)。但是,DDK的.h文件中却并未给出EPROCESS数据结构的定义,所以调用这个函数所得到的仅仅是个指针,实际上与void*并无区别。这意味着在.sys模块中是不允许直接访问其内部成分的。那么,.sys模块如何使用这个指针呢?下面就是一个例子,还是在DDK中:

[code]NTKERNELAPI
VOID
MmProbeAndLockProcessPages (
    IN OUT PMDL MemoryDescriptorList,
    IN PEPROCESS Process,
    IN KPROCESSOR_MODE AccessMode,
    IN LOCK_OPERATION Operation

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -