2020.04.20-2020.04.26

Windows内核基础

内核理论基础

Windows r0和r3通信

当一个应用程序调用一个有关io的api,事实上这个api被封装在应用层的某个dll库文件中,dll动态库中函数调用的更底层的函数包含在ntdll.dll文件中。
当应用层的某个api通过ntdll.dll里的native api执行时,会完成参数检查工作,再用一个中断指令从r3层进入r0层。

ntdll.dll中的函数时成对出现的,分别以“nt”和“zw”开头,如ntcreatefile、zwcreatefile
从用户模式调用nt* 和zw* api,连接ntdll.lib:
  二者都是通过设置系统服务表中的索引和在栈中设置参数,通过sysenter或syscall指令进入内核态,并最终跳转到kiservicetable对应的系统服务例程中。代码会严格检查从用户空间传入的参数
从内核模式调用nt* 和zw* api,连接ntdll.lib:
  nt* api将直接调用对应函数,zw* api通过kisystemservice跳转到对应的函数代码
调用nt* api时不会改变previous mode(分为用户态和内核态)的状态;调用zw* api时会将previous mode 改为内核态。使用zw* api可以避免额外的参数列表检查,提高效率

内核主要由各种驱动(在磁盘上时.sys文件)组成,有的是系统自带的,有的是软件厂商提供的。驱动加载后会生成对应的设备对象,并可以选择向r3提供一个可供访问和打开的符号链接
应用层程序可以根据符号链接调用CreateFile()函数打开,在获得句柄后,程序就可以调用应用层函数与内核驱动进行通信
  符号链接(软链接):a symbolic link is a file that links to another file or directory using its path.然后大概是路径可以自动变的快捷方式…吧
内核驱动执行DriverEntry()函数后就可以接受r3层的通信请求了。内核驱动中专门有一组函数用于响应应用层的调用请求

内核函数

前缀:

调用内核函数要注意它的中断请求级别(IRQL,Interrupt Request Level)要求。内核在不同情况下会运行在不同的IRQL级别上,此时必须调用符合该级别的内核函数

passive_level:最低级别,对所有中断都可以做出响应,用户模式代码都运行在该中断级别上,可以访问分页内存
apc_level:只有apc级别的中断可以被屏蔽,可以访问分页内存,分页调度管理就运行在该级别上
dispatch_level:该级别和更低的中断被屏蔽,不能访问分页内存,只能处理不可分页的内存,因此在这个级别上能访问的api大大减少。线程调度运行在该级别上。线程调度用于执行多任务,由时钟中断来保证,因此该级别的中断即是调度中断。这个级别对一些严重中断不进行处理,所以要慎重使用(emmm…)
DIRQL(Device IRQL):IRQL的最高级别,在该级别上的中断都会被忽略,通常用于判断设备优先级

  • 分页内存:
    在保护模式中,内存访问使用分段机制,即"段基址:段内偏移地址";操作系统将一些进程不常用的内存放到硬盘中,腾出内存空间,在一定程度上解决了内存不足的问题,但如果内存特别小,就会无法容纳任何一个进程的段。这个问题的原因是在只有分段的情况下,cpu认为线性地址等于物理地址,所以物理地址也必须要连续,但实际上可用的物理地址不连续。为了解决这个问题,需要让物理地址和线性地址重新建立映射,让前者不连续,后者连续,由此引出内存分页机制
    分页机制建立在分段机制的基础上,因此分页在分段后进行。在内存分页机制下,由“段基址:段内偏移地址”得到的线性地址不是物理地址而是虚拟地址,对应的物理地址要在页表中查找。
    分页机制提供连续线性地址到不连续物理地址的映射,以及用大小相等的页代替大小不相等的段

内核驱动模块

内核驱动扩展名为.sys

  • 驱动的加载和执行
    创建一个服务(注册表),在services键下(…\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\)建立一个与驱动名称相关的服务键,服务键规定了驱动的一些属性,如加载的先后等
    对象管理器生成驱动对象,并传递给DriverEntry()函数,执行DriverEntry()函数。(DriverEntry()是驱动执行的入口函数)
    创建控制设备对象;创建控制设备符号链接;如果是过滤驱动,则创建过滤设备对象并绑定
    注册特定的分发派遣函数
    其他初始化动作,如hook、过滤(如文件过滤、防火墙过滤)等的注册和 初始化

 *过滤驱动就是挂载在其他驱动上,对某设备的irp(I/O Request Package)进行拦截过滤作用,可以对设备进行功能扩展,或是数据加密等的驱动程序。

内核的数据结构

内核对象

内核对象是一种Windows内核中的数据结构管理机制。应用层的进程、线程、文件、等对象或打开的句柄在内核中都有对应的内核对象
一个内核对象可以分为对象头和对象体。对象头中至少有一个OBJECT_HEADER和对象额外信息。对象体紧接着对象头中的OBJECT_HEADER。一个指针总是指向对象体而不是对象头,将对象体指针减去偏移值,获得OBJECT_HEADER的结构,从而访问其他对象结构辅助信息

内核对象分为:

  1. Dispatcher对象
    在对象体开始位置放置了一个共享的公共数据结构DISPATCHER_HEADER,包含了这个结构的内核对象名字都以K开头(但K开头的不一定都是这个对象),这些内核对象都是可以等待的((waitable):①当到达某一个时间后,才继续线程的执行。/②当到达某一个时间后,调用某一个函数,而且间隔多少时间后,再次调用。)
  2. io对象
    在开始位置不放置DISPATCHER_HEADER,但通常放置一个与type和size有关的整型成员。
  3. 其他对象
    包括进程对象(EPROCESS)和线程对象(ETHREAD)等
    EPROCESS用于管理进程的各种信息,每一个进程都对应一个EPROCESS结构,用于记录进程执行期间的各种数据。
    第一个成员是进程的对象KPROCESS,所有进程的EPROCESS内核结构都被放入一个双向链表,R3在枚举系统进程的时候,通过遍历这个链表获得了进程的列表

ETHREAD结构是线程的内核管理对象,每一个线程都有一个对应的ETHREAD结构。结构的第一个成员是线程对象KTHREAD,所有的ETHREAD结构也被放在一个双向链表里

SSDT

“system services descriptor table”, 在内核中的实际名称是"KeServiceDscriptorTable"
用于处理应用层通过Kernel32.dll下发的各个api操作请求。当kernel32.dll的api通过ntdll.dll时,会先完成对参数的检查,在调用一个中断,从而实现r3层进入r0层。

SSDT表中最重要的两个成员为ServiceTableBase(表的基地址)和NumberOfServices(系统中SSDT服务函数的个数)
由ssdt表的基地址和ssdt函数的索引号可以求出对应的服务函数的地址(32位和64位计算公式不同)

Shadow SSDT

原理和SSDT类似,对应的表名为KeServiceDscriptorTableShadow,是内核未导出(不能在自己的模块中导入和直接引用)的一张表,包含Ntoskrnel.exe和win32.sys服务函数。
hook该表中的某些函数可以实现截屏保护、模拟按键、防止窗口被关闭等