• 进程与线程


    Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

    进程与线程

    1. _EPROCESS结构体
    2. _ETHREAD结构体
    3. _KPCR结构体
    4. 线程的等待/调度链表
    5. 线程切换
    6. 线程切换与TSS
    7. FS:[0]三环指向TEB的切换
    8. 进程挂靠

     

    1. _EPROCESS结构体

      +0x000 Header 可等待对象

      +0x018 DirectoryTable 页目录表基址(进程最重要的地址)

      +0x038 KernelTime 该进程在零环运行的时间

      +0x03c UserTime 该进程在三环用到的时间

      +0x05c Affinity 规定进程里的线程可以在哪个CPU跑(详细细节查询)

      +0x062 BasePriority 该进程中的所有线程中最起始的优先级

      +0x078 CreateTime 当前进程的创建时间

      +0x078 ExitTime 当前进程的退出时间

      +0x084 UniqueProcessId 进程编号,进程管理器的编号

      +0x088 ActiveProcessLink 当前系统活动进程的编号(进程管理器就是查找该链表)

      +0x090 QuotaUsage  / +0x09c QuotaPeak 物理页统计的相关信息

      +0x0a8 CommitCharge / +0x0ac PeakVirtualSize / +0x0b0 VirtualSize 虚拟内存相关的统计信息

      +0x11c VadRoot 标识0-2G哪些地址被占用了

      +0x0bc DebugPort / +0x0c0ExceptionPort 调试相关

      +0x0c4 ObjectTable 句柄表

      +0x174 ImageFileName 进程镜像文件名(最多16个字节)

      +0x1a0 ActiveThreads 活动线程的数量

      +0x1b0 PEB 在三环描述了相关信息

     

    2. _ETHREAD结构体

      +0x000 Header 可等待对象

      +0x018 InitialStack / +0x01c StackLimit / +0x028 KernelStack 线程切换相关

      +0x020 TEB 在三环描述了该线程相关信息(fs:[0]在三环指向TEB)

      +0x02c DebugActive 如果为-1,则不能使用调试寄存器 Dr0~Dr7

      +0x034 ApcState / +0x0e8 ApcQueueLock / +0x138 ApcStatePointer / +0x14c SavedApcState Apc相关

      +0x02d State 线程状态:就绪、运行还是等待

      + 0x06c BasePriority 当前线程优先级

      +0x070 WaitBlock 等待哪个对象

      +0x0e0 ServiceTable 系统服务表机制

      +0x134 TrapFrame 进零环时保存的环境

      +0x140 PreviousMode  某些内核函数会判断程序是0环调用的还是3环调用的

      +0x1b0 ThreadListEntry 双向链表,一个进程的所有线程,都挂在这样一个链表中(图解)。

      +0x1ec Cid 当前线程和进程的编号

      +0x220 ThreadProcess 当前线程所属的进程

      +0x22c ThreadListEntry 双向链表,一个进程的所有线程,都挂在这样一个链表中(图解)。

          

    3. _KPCR结构体

      +0x000 ExceptionList 当前线程内核异常链表(SEH)

      +0x004 StackBase / +0x008 当前内核线程栈的基址和大小

      +0x018 self 指向自己(NT_TIB),方便查找

      +0x01c SelfPcr 指向自己(KPCR),方便寻址

      +0x020 Prcb 指向扩展结构体,PCRB

      +0x038 IDT IDT表基址

      +0x03c GDT GDT表基址

      +0x040 TSS 指向TSS

      +0x051 Number CPU编号:0,1,2,....

      +0x120 PrcbData 拓展结构体

        +0x004 CurrentThread 当前线程

        +0x008 NextThread 即将切换的下一个线程

        +0x00c IdleThread 空闲线程

     

    4. 线程的等待/调度链表

      KiWaitListHead - 等待链表

        比如:线程调用了Sleep()或者WaitForSingleObject()等函数,就挂到这个链表中。

        

      KiDispatcherReadyListHead - 调度链表

        其存在32个链表,按不同调度级别来进行划分。

        

      操作系统所有线程:当前KCPR正在跑的+等待链表+32个调度链表。

    5. 线程切换

      1)线程主动切换

        线程切换依次调用 KiSwapThread-> KiSwapContext -> SwapContext,因此我们看其如何调用KiSwapThread调用。

        

         该类函数有被其他很多函数调用,因此得出结论:内核函数绝大部分会引起线程切换。

      2)线程被动切换

        一个线程并不是必须自身调用API来实现线程切换,其他可以引起中断。

        两类:①异常/中断;②时钟中断。

        其中时钟中断走0x20号中断,系统每过20ms触发一次时钟中断来(可能)切换线程。

        

       3)时钟中断的时间片管理

        时钟中断可能触发线程切换,但并不一定触发线程切换。

        在两类条件下,时钟中断会触发线程切换:

        ① 当前的线程CPU时间片到期

        ② 有备用线程(KPCR.PrcbData.NextThread)

        这里之后会补充上······

     

    6. 线程切换与TSS

      1)如何实现线程切换

        如下图,其KiSwapContext先保存几个最基本的寄存器,之后调用SwapContext,当从该函数出来之后,就会切换新的线程。

        对于线程切换,以下要点是必须要理解的:

        ① 线程切换本质就是切换寄存器,堆栈地址。 

        ② 虽然我们有虚拟内存的概念,但线程切换发生在内核中,其无论线程A还是B都公用一个函数,因此在0环,将_KPCR中指向的线程改变,其就达到线程切换的目的

        

      2)内核栈的描述:

        这里有一点需要注意,Trap_Frame是保存线程三环进零环时的寄存器环境,KernelStack是当前内核栈的栈顶。

        千万不要以为用TrapFrame来保存原线程寄存器环境,其直接使用push压入栈顶,其_ETHREAD+0x134 TrapFrame的位置直接指向_TRAP_FRAME结构。

        如果你理解了这部分,整个堆栈结构就很好理解了。

      

      3)线程替换的具体细节 - SwapContext函数分析

        首先,需要明确以下细节:

        ① TSS是一块固定内存,当初Intel把它作为任务切换,但实际上这么大块内存只有ESP0、SS0,用作从三环到零环时栈切换的具体位置。

        ② ESP0与新线程的ESP不是一个概念,新线程的ESP被保存到_KTRHEAD.KernelStack中,新线程的EBP同理。

        ③ 而ESP0只是作为该线程从三环到零环的起始位置,当线程切换时,其就计算得出该具体位置,计算方式是_KTHREAD.Initstack+0x210+0x10.

          因此,当我们从三环进零环时,esp指向的就是Trap_Frame的初始值,因此你看SystemService直接压栈来存储TrapFrame结构。

        ④ N个线程仅使用一个TSS和一个KPCR结构体,因此在SwapContext中你会发现其找到该部分的地址,然后手动替换掉元素,并不会改变其位置。

        如果你理解上面这些汇编代码,你再看下面的汇编代码,就会掌握线程替换的实质

        

    7. FS:[0]三环指向TEB的切换

      FS在三环时指向TEB,但在零环指向KPCR(地址固定是0FFDFF000h)。

      FS是段描述符,其形式如下:

      

      现在有两种方式:FS指向的段选择子改变;只改正段选择子的BaseAddress。

      而Windows采用后面一种方式,TEB表对应的IDT[7],KPCR表对应着IDT[6],

      因此三环进零环,仅修改段选择子的指向即可,只要保护模式熟练,这很容易理解。

      

    8. 进程挂靠

      我们在保护模式中学习过,当前进程的线性地址存储在Cr3中,进程为线程提供空间上的支持。

      一个线程中存在两个位置指向_EPROCESS,分别是+0x220 _EPROCESS,+0x44 _KAPC_STATE._EPROCESS。

      那线程切换时判断是否切换CR3依照什么呢?结论可能有点让你吃惊,通过 _KAPC_STATE 来切换CR3。

      

       1)养父母与亲生父母

        +0x220位置是作为线程的亲生父母,该进程创建了线程。

        +0x44位置作为线程的养父母,其实对该线程负责。

        因此线程切换,参照的是养父母,而不是亲生父母。

      2)进程挂靠

        将当前进程的CR3切换为其他进程,就叫做进程挂靠。

        比如,Windows 提供 API 来读取其他进程的内存地址,这一定使用内存挂靠。

      3)NtReadVirtualMemory实现路径

        NtReadVirtualMemory->AttchProcess(修改养父母)->KiSwapProcess(修改Cr3)

        进程挂靠的实现函数是Nt!SwapProcess,其代码过程如下:

        

         另外,在进程挂靠时,SwapProcess的上层函数 ,Nt!KiAttachProcess也修改了Apc_State函数

      4)进程挂靠为何要修改养父母的值

        因为存在进程挂靠的缘故,其才会需要修改养父母的值,因为进程挂靠的过程中可能会产生进程切换,一旦发生进程切换,切换回来,根据的是养父母的值,

        如果不修改养父母的值,则切换回来,就不是挂靠进程的值,而是自己了,如果此时再读,就会变成自己读自己,这样会发生错误。

        

  • 相关阅读:
    飞机大战4-我的子弹
    飞机大战3-我的飞机
    飞机大战1-分析设计
    继承
    常见题
    42个例子算法
    心跳
    tomcat
    service
    URI URL
  • 原文地址:https://www.cnblogs.com/onetrainee/p/12602302.html
Copyright © 2020-2023  润新知