【2021.04.26】API函数的调用过程(Ring3进Ring0):上

本文深入探讨Windows系统调用过程,特别是通过kernel32.dll的ReadProcessMemory调用ntdll.dll的NtReadVirtualMemory。讲解了_KUSER_SHARED_DATA结构在用户层和内核层的角色,以及如何通过0x7FFE0300位置确定系统调用入口。文章还介绍了CPU的sysenter/sysexit指令支持情况对系统调用的影响,以及不同支持情况下的系统调用实现方式——ntdll.dll!KiFastSystemCall()和ntdll.dll!KiIntSystemCall()。最后,讨论了进入内核模式时寄存器的变化和调用过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内容回顾

kernel32.dll(ReadProcessMemory)->ntdll.dll(NtReadVirtualMemory)

mov  eax, 0BA //提供一个内核函数编号

mov edx, 7FFE0300 //然后提供一个函数的地址,这个函数地址存储在7FFE0300这个位置。

本文将重点介绍该函数到底是什么,要了解该函数到底是什么还需要弄清楚7FFE0000这块内存有什么特殊含义。

_KUSER_SHARED_DATA

  • 在user层和kernel层分别定义了一个 _KUSER_SHARED_DATA 结构区域,用于user层和kernel层共享某些数据。
  • 它们使用固定的地址值映射,_KUSER_SHARED_DATA 结构区域在user和kernel层,地址如下:
  1. user层地址:0x7FFE0000,任何一个3环程序都可以访问。
  2. kernel层地址:0xFFDF0000

虽然指向的是同一个物理页,但在user层是只读的,在kernel层是可读写的。

0x7FFE0000(3环地址):

0xFFDF0000(0环地址):

可以看到储存的内容是完全一致的,别的地方暂时忽略,先来关注内容回顾图中代码 mov edx, 7FFE0300:

查看 _KUSER_SHARED_DATA结构体(0x7FFE0000),然后找到0x300的位置。

0x300的位置名字叫做SystemCall,现在回过头来看一下内容回顾的图:

当调用某个API的时候,进入ntdll的时候,会调用(mov edx, 7FFE0300、call dword ptr [edx])这个位置,这个位置存储的就是一个函数地址。

现在要研究的就是,这个位置存储的到底是什么函数?

0x7FFE0300 到底存储的是什么?

实验:是否支持快速调用

当通过 eax = 1,来执行cpuid指令时,处理器的特征信息被放在ecx和edx寄存器中。

其中edx包含了一个SEP位(下标为11的位置),该位指明了当前处理器是否支持sysenter/sysexit指令。

SEP=1:支持sysenter/sysexit指令。

SEP=0:不支持sysenter/sysexit指令。

支持sysenter/sysexit指令:ntdll.dll!KiFastSystemCall(),不是内核函数,它仍然是ntdll中的函数。

不支持sysenter/sysexit指令:ntdll.dll!KiIntSystemCall()。

CPU如何知道是否支持这种指令?

cpuid指令可以用来查询很多CPU相关信息,但是在查询CPU相关信息的时候需要传递参数,通过EAX寄存器来传参。

比如当前想知道CPU是否支持sysenter/sysexit指令:

在EAX中写入一个1:

然后写上cpuid指令:

当cpuid指令执行完成后,它会将结果存储在ecx和edx寄存器中,所以现在把ecx和edx都置零:

这样,在执行完成后看起来更加直观。

执行完成后:

执行完毕以后,在edx下标为11的位置,也就是所谓的SEP位。

SEP位如果置1,就证明当前的CPU是支持sysenter/sysexit指令的。

如果为0,那就是不支持。

当操作系统启动的时候,操作系统首先就会通过cpuid指令来查询当前CPU是否支持sysenter/sysexit指令。

当支持的时候,它会将 ntdll.dll!KiFastSystemCall() 函数的地址写入到 _KUSER_SHARED_DATA 结构体的0x300的偏移处。

如果不支持的时候,它会将另一个函数的地址,也就是 ntdll.dll!KiIntSystemCall() 的函数地址写入到 _KUSER_SHARED_DATA 结构体的0x300的偏移处。

所以看到类似这种情况,不能武断的论证到底是什么函数,而要先经过测试,通过测试才能论证它存储的到底是哪个函数。

进0环需要更改哪些寄存器?

  1. CS的权限由3变为0,意味着需要新的CS。
  2. SS与CS的权限永远一致,需要新的SS。
  3. 权限发生切换的时候,堆栈也一定会切换,所以需要新的ESP。
  4. 进入0环后,代码从哪里开始执行的位置,需要EIP。

ntdll.dll!KiFastSystemCall() 与 ntdll.dll!KiIntSystemCall() 的区别

相同点:为了提供4个值(CS、SS、ESP、EIP)怎么找,只不过这两种函数提供了不同的找法。

不支持sysenter/sysexit指令(中断门进入0环):

当不支持sysenter/sysexit指令的时候,将系统调用号(内核函数编号)、参数压入寄存器(eax、edx),然后通过中断门(int 0x2E)进入0环。

所有的API,进入0环的时候,统一的中断门中断号是0x2E。在IDT表中0x2E的位置。

支持sysenter/sysexit指令(快速调用进入0环):

将当前栈顶的值放入edx,edx用来找参数在哪里。

eax存储的仍然是系统调用号(内核函数编号)。

然后就没有使用中断门了,而是使用sysenter。

为什么叫快速调用?

中断门进0环,需要的CS、EIP在IDT表中,需要查内存(SS与ESP由TSS提供)。

而CPU如果支持sysenter指令时,操作系统会提前将CS、SS、ESP、EIP的值存储在MSR寄存器中。

sysenter指令执行时,CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,所以叫快速调用,本质是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值