Windows内核(10)内存管理
1. 线性地址的管理1.1. 进程空间的地址划分 分区 x86 32位Windows 空指针赋值区 0x00000000-0x0000FFFF 用户模式区 0x00001000-0x7FFEFFFF 64KB禁入区(空区域) 0x7FFEFFFF-0x7FFFFFFF 内核 0x80000000-0xFFFFFFFF 1.2. 内核的线性地址管理Windows使用链表来记录内核的线性地址分配情况,每个节点标识了未分配的地址空间。详见《Windows内核原理与实现》。 1.3. 用户空间的线性地址管理由于每个进程的用户空间不同,链表效率低下,所以Windows使用搜索二叉树来记录用户空间线性地址分配情况,每个节点标识一块已经被占用的线性地址。EPROCESS.VadRoot指向根节点,节点为_MMVAD结构体,结构体包含内存块的起始结束线性地址、类别和属性等信息。通过Windbg指令!vad可以查看一个进程的线性地址分配的所有情况。内存块分为两种,私有内存和映射内存。 1.3.1. 私有内存:PrivateMemory通过VirtualAlloc函数分配的...
Windows内核(9)消息机制
1. 消息机制窗口可以接收由鼠标、键盘、进程(Windows下的SendMessage、PostMessage等函数)等发出的消息并做出响应,这些消息需要存储在消息队列里。 1.1. Linux的解决方案消息队列位于三环各个进程内,存在一个专用进程监听各种消息,并发送到对应的进程中。这种解决方案较为简单,但是需要频繁的进程间通信,效率较低。 1.2. Windows的解决方案Windows将消息队列放入了零环,并设计了很复杂的消息机制。以下讨论内容为Windows方案。 2. GUI线程和消息队列线程刚创建的时候是普通线程,当线程第一次调用win32k.sys模块的时候(代表使用到了图形界面),会调用函数PsConvertToGuiThread将线程转换为GUI线程。只有GUI线程才有消息队列(只有窗口才能接收消息),一个GUI线程对应一个消息队列,消息队列位于零环,通过KTHREAD.Win32Thread.MessageQueue可以找到。 2.1. 转换成GUI线程:PsConvertToGuiThread函数功能如下: 拓展内核栈到64KB,普通内核栈大小为12KB 创...
Windows内核(8)驱动
1. 驱动开发环境配置Visual Studio(内置SDK) + WDK 2. 驱动的开发流程编写代码 -> 编译生成.sys文件 -> 部署 -> 启动 -> 停止 -> 卸载 3. 编写代码驱动程序的入口函数为DriverEntry,可以设置卸载函数为DriverUnload。 4. 调试驱动程序 需要使用0环驱动器,VirtualKD + WinDbg 利用PDB文件获取函数和地址之间的关系、结构体等信息 在代码中插入INT 3来插入断点,配合PDB、SYS可以进行源码调试 5. 内核编程基础5.1. APIWDK文档包含了公开的内核API,对于未公开函数,可以通过特征码搜索或者解析内核PDB文件来获取函数地址,然后定义函数指针指向并调用。 5.2. 基本数据类型使用WDK自己定义好的类型,以避免跨平台错误。 1234ULONG(unsigned long) PULONG(unsigned long *)UCHAR(unsigned char) PUCHAR(unsigned char *)UINT(unsi...
Windows内核(7)异常处理
1. 概述1.1. 异常与调试的关联异常与调试是紧密相连的,异常是调试的基础。软件调试、反调试、调试器都与异常息息相关。 1.2. 参考资料微软工程师Matt Pietrek所发表的《A Crash Course on the Depths of Win32™ Structured Exception Handling》。 1.3. 异常处理机制当一个线程出现错误时,操作系统调用用户定义的一个回调函数,用以修复错误或者进行其它任何事情,并返回一个值来告诉操作系统下一步做什么。 1.4. 异常的类别 按照产生源分类 CPU异常:由CPU产生、发现的异常 模拟异常:由软件模拟产生,如throw、raise语句 按照产生位置分类 内核异常:在内核产生的异常 用户异常:在用户空间产生的异常 1.5. 异常的处理流程 记录异常信息(异常类型、异常发生的位置等) 进行异常分发:寻找异常的处理函数 异常处理:调用异常处理函数 2. 记录异常信息 throw关键词和CxxThrowException为C语言的模拟异常特征,其它编程语言提供的模拟异常机制的这两点(关键词和初始调用函数)...
Windows内核(6)内核对象句柄表
1. 进程句柄表当一个进程创建或者打开一个内核对象时,将会获得一个句柄,句柄的值除以4就是该内核对象在进程句柄表中的索引。进程句柄表位于0环,其中存储了进程引用的各个内核对象的指针。所有的内核对象均以_OBJECT_HEADER结构体(0x18大小)开头,而进程句柄表中存储的指针指向_OBJECT_HEADER结构体。 1.1. 如何找到进程句柄表进程句柄表位于_EPROCESS.ObjectTable,其中_EPROCESS.ObjectTable.TableCode即为句柄表的内存地址。同一个内核对象可以被不同的进程引用,其句柄值可能一样也可能不一样。 2. 句柄表的作用隐藏内核对象指针,保护内核对象。 3. 句柄表的结构 句柄表中,每个内核对象占据8个字节,所以一个物理页可以容纳512个表项。如果TableCode低2位为0,则句柄表为1级结构;如果TableCode低2位为1,则句柄表为2级结构;如果TableCode低2位为2,则句柄表为3级结构。 4. 全局句柄表系统另外维护有一个全局句柄表PsdCidTable,存储了所有的进程和线程(无论是否被打开)的指针(全局句...
Windows内核(5)并发与同步
1. 并发与同步 并发是指多个线程在同时执行 单核(本质上是分时执行,不是真正意义上的同时执行) 多核(在某一时刻,会有多个线程同时执行) 同步是指保证在并发执行的环境中,各个线程可以有序的执行(即保证并发代码执行安全)。如果线程中只对局部变量进行操作,由于每个线程拥有自己的堆栈,那么便不会产生同步问题。只有当线程对全局变量进行操作时,才会出现同步问题。 2. 临界区2.1. 并发环境下的代码安全 dwVal++;,这种C代码是不安全的 INC DWORD PTR DS:[0x12345678],这种汇编代码在单核情况下安全,在多核情况下不安全 2.2. 保护代码安全:LOCK前缀LOCK INC DWORD PTR DS:[0x12345678],这种汇编代码是安全的,LOCK前缀会锁住内存,保证同一时刻只能有一个核对该内存进行操作。Windows提供的一些原子操作相关的API就是利用了这个前缀,如InterlockedIncrement、InterlockedDecrement、InterlockedExchange、InterlockedCompareExchang...
Windows内核(4)APC
1. APC的作用:改变线程行为线程在运行的时候是不受其它线程控制的,不会被结束、挂起、恢复。如果想要改变线程行为,可以通过给线程提供一个函数,让线程自己去调用的形式来实现。这个函数就是APC(Asyncroneus Procedure Call,异步过程调用) 2. _KTHREAD结构体中APC相关成员 ApcState:线程的Apc队列 SavedApcState:备用APC队列。对于A进程中的线程,其APC函数所使用的内存空间是A进程的内存空间,但是当线程挂靠到B进程之后,CR3切换导致内存空间切换,APC函数中使用的地址将会出现错误。所以在发生进程挂靠之后,会将ApcState中的内容存储到SavedApcState(备用APC队列)中。这时线程的所属进程为B进程,这时候再往线程中插入APC,插入的是ApcState(ApcState.Process也是指向B进程)。等线程回到A进程之后,会再将SavedApcState中内容恢复到ApcState中。 2.1. APC寻址Windows使用ApcStatePointer和ApcStateIndex联合进行APC寻址,...
Windows内核(3)进程与线程
1. 相关结构体1.1. 进程相关结构体:_EPROCESS每个Windows进程在0环都有一个对应的结构体_EPROCESS,这个结构体包含了进程所有重要的信息。 1.1.1. 进程链表示意图 1.2. 线程相关结构体:_ETHREAD每个Windows线程在0环都有一个对应的结构体_ETHREAD,这个结构体包含了线程所有重要的信息。 1.2.1. 连接一个进程所有线程的双向链表示意图 即使线程断链,线程仍然可以得到执行,因为调度线程不依靠这个链表。 1.3. CPU相关结构体:_KPCR每个CPU在0环都有一个对应的结构体_KPCR,这个结构体包含了CPU本身要用的一些重要数据如GDT、IDT、线程相关信息。 2. 线程状态与等待、调度链表2.1. 线程状态与对应链表 等待:存储在等待链表中(共有1个等待链表),线程调用了Sleep或者WaitForSingleObject等函数时,会进入等待状态 就绪:存储在就绪链表中(共有32个等待链表,分别对应线程优先级0-31,0最低,31最高,默认优先级一般是8,改变优先级就是脱链再挂链的过程) 运行:存储在_KPCR中 这些链...
Windows内核(2)API的调用过程
1. API的定义Application Programming Interface,应用程序接口,简称API,主要是存放在系统目录C:\Windows\System32下的所有dll,目前大概存在上千个。在Windows上编程,必须要使用WindowsAPI,无法绕开。比如说C函数malloc底层调用的是WindowsAPI中的HeapAlloc。 Kernel32:内存管理、进程线程 User32:用户界面相关 GDI32:画图和显示文本相关 Ntdll:内核入口(大部分函数都需要进入0环实现功能,3环仅仅是一个接口;只有少部分函数可以完全在3环实现功能) 2. API的调用过程-3环部分2.1. kernel32.dll!ReadProcessMemory分析一下kernel32中的ReadProcessMemory函数 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748.text:7C8021D0 ; BOOL __stdcall ReadPro...
Windows内核(1)保护模式
1. 保护模式简介1.1. x86CPU的三种运行模式 实模式 保护模式 保护操作系统的数据结构如GDT、IDT 保护操作系统的寄存器 保障多任务的并发进行 现代操作系统大多采用保护模式 虚拟8086模式 CPU刚启动的时候处于实模式,如果CPU开启了保护模式功能,CPU会在之后切换到保护模式 1.2. x86CPU的等级环x86CPU存在4个等级环0-3 0环为特权环 0、1、2环为系统环 3环为用户环 1.3. 保护模式的核心机制段机制和页机制是保护模式的基础,也是实现现代操作系统的基础。只有深刻掌握和理解CPU的段页机制,才能掌握操作系统底层的一些原理。注意,段机制和页机制是CPU这个硬件提供的功能,并不是操作系统提供的,操作系统只是利用了CPU的这个功能 1.4. 学习原则 x64是x86的拓展,x86是x64的基础 学完后可以不通过驱动来进入内核 学完后可以不通过操作系统的API来实现相应功能 1.5. 实验环境搭建 虚拟机内搭建Windows XP系统(配置成单核,否则后面的实验可能会出错),安装VC6和OllyDbg 宿主机内安装Windbg,配置Sym...