1.5 Windows内核函数的命名
Windows的内核函数在命名上有个很好的特色,就是函数名都按其所在的层次或模块加上了特定的前缀。了解了这些前缀,看到一个函数名就可以大致知道这个函数所属的层次和模块,主要的前缀有:
前缀 | 说明 |
---|---|
Io | I/O管理 : 包含许多驱动程序可以使用的服务函数。 |
Ex | 管理层-Executive:提供堆管理和同步服务。 |
Ke | 核心层-Kernel:代表的是内核模式的API接口。所有多线程和多处理器的低级同步活动都发生在内核中。 |
Zw | Zw 和同名的 Nt 函数具有相同的功能,中间是从 Zw 到 Nt 函数的简单跳转。本系列内核函数用于文件和注册表方面的操作,比如文件操作、注册表操作、访问进程、事件操作、令牌操作、进程操作和端口操作等。 Zw - 没有具体的缩写含义,只是为了避免和其它前缀的重复。它的功能和与之相对应的Nt函数是一致的(可以说是Nt功能的镜像)。 不同点在于: 相应的Nt函数,是对系统服务的直接;而Zw需要经过一系列系统准备动作,比如:系统服务码入寄存器保存,系统KiSystemService加载,然后才执行具体的服务功能调用。 看着负担加重了,但好处是,在执行时,系统参数的系列校验不必再进行了(拜所谓的previous access mode之赐),所以反而轻快了;而Nt系列函数虽然调用时简洁,但每一次执行都要参数校验,因此反而累赘了。这也正是内核态程序(比如驱动程序)多用Zw系统的原因(因为需要和previous mode打交道)。 |
Rtl | 运行时程序库-Runtime Library:本系列内核函数用于运行时库,以Rtl为前缀的函数可以完成多种操作,例如字符串、线程、资源、临界区、安全对象的初始化和使用,内存、进程异常和数据类型的处理,还用于完成定时器、堆、IPv4和IPv6方面的操作,以及压缩和解压缩等。 包含工具例程,例如列表和串管理例程,内核模式驱动程序可以用这些例程来替代常规的ANSI标准例程,大部分例程可以从其名字上直接看出它的功能。 |
Hal | Hardware Abstraction Layer:硬件抽象层。 |
Ob | 对象管理-Object:集中控制Windows NT中的各种数据对象,WDM驱动程序仅需要对象管理器维护对象的参考计数,以防止对象被意外删除。 |
Mm | 内存管理-Memory Manager:控制页表,页表定义了虚拟内存到物理内存之间的映射。 |
Ps | 进程(线程)管理-Process:创建并管理内核模式线程,普通的WDM驱动程序应使用一个独立的线程来循检无中断生成能力的设备00000000000000000000000000000000000000000000000 |
Se | 安全管理-Security:使文件系统驱动程序执行安全检测。I/O请求到达WDM驱动程序前已经做完了安全检测 |
Fs | 文件系统-File System |
Cc | 文件缓存管理-Cache |
Cm | 系统配置管理-Configuration Manager |
Pp | “即插即用”管理-PnP(Plug and Play) |
Ndis | 与NDIS网络驱动开发相关的函数。 |
Wdf | 开发WDF驱动相关的函数都是以 Wdf 开头。 |
Ki | 本系列内核函数用于系统内核,这些函数只能从内核的内部进行调用,常用的有:KiUserCallbackDispatcher、KiRaiseUserExceptionDispatcher、KiUserApcDispatcher、KiUserExceptionDispatcher等。 |
Csr | 此系列函数用于客户机和服务器运行时,如果您想拦截客户机/服务器方面的操作,那么就需要对Csr系列内核函数做进一步的了解。常见的有:CsrClientCallServer、CsrCaptureMessageBuffer、CsrConnectClientToServer和CrsNewThread等。 |
Ldr | 本系列内核函数用于加载程序管理器,如果你打算拦截加载程序的话,那么请进一步考察这组以Ldr为前缀的函数,常用的有:LdrInitializeThunk、LdrLockLoaderLock、LdrUnlockLoaderLock、LdrGetDllHandle、LdrGetProcedureAddress等。 |
Dbg | 本系列内核函数用于调试管理,如果打算拦截调试操作的话,那么请进一步考察这组以Dbg为前缀的函数,常用的函数包括:、DbgBreakPoint、DbgUserBreakPoint、DbgPrint和DbgUiConnectToDbg等。 |
Etw | 本系列内核函数用于追踪窗口事件,如果你打算拦截追踪之类的操作的话,那么请进一步考察这组以Etw为前缀的函数。常用的函数包括:EtwTraceEvent、EtwEnableTrace、EtwGetTraceEnableLevel和EtwGetTraceEnableFlags等 |
Pfx | 本系列内核函数用于ANSI字符串操作,如果你打算拦截ASNI串表方面的操作的话,就需要进一步了解这些函数。常用的包括:PfxInitialize、PfxRemovePrefix、PfxInsertPrefix、PfxFindPrefix等。 |
Nt | Native,对应于win32 API的内核函数。 Nt - Windows New Technology的缩写,代表的是Windows 系统服务功能API接口。 大部分以Nt开头的函数,都映射到了用户态(User Mode)API接口。比如你编写的用户模式程序,用到了CreateFile这个函数,由于它需要访问系统内部的数据结构,必须要进入内核模式,这时的程序就要转入内核模式,相对应的内核模式功能服务接口,正是ntdll.dll中的NtCreateFile,它最终完成来自用户态程序的函数功能请求。 |
不过并非所有的函数名都带有这样的前缀,对前缀的使用也并非十分严格。例如,核心层函数名的前缀本应是Ke,但是实际上有不少核心层函数名的前缀是Ki,这些函数大都是与中断有关的比较底层的函数。<br />有时候还在函数名的前缀后面加上小写字母f,表示这个函数是快速调用函数。<br />例如:<br /> NTKERNELAPI LONG_PTR FASTCALL ObfDereferenceObject(IN PVOID Object);<br /> 这里的类型说明FASTCALL向编译工具表明这是个快速调用函数,函数名前缀后面加上f则使人一看见函数名就知道这是个快速调用函数。一般的函数调用是通过堆栈传递参数的,而快速调用函数则通过ECX等寄存器传递参数,因为避免了几个堆栈操作而使效率有所提高。当然,这里的说明FASTCALL是必需的,否则编译工具不知道应该通过寄存器传递参数,而在函数名中加上f则只是为了增加程序的可读性。<br /> 上面所说的是C语言程序中的函数名,如果是在汇编语言中,则需要在函数名前面再加上前缀"_",并加上表示参数个数的后缀。例如,函数名RtlZeroMemory()出现在汇编程序中就是_RtlZero Memory@8。为什么是@8呢?这是因为这个函数有两个参数,一共是8个字节。之所以如此,是因为GCC在编译C语言程序而生成汇编代码时就会作这样的转换。<br /> 如果是快速调用函数,则又与普通的函数名有所不同。例如KfLowerIrql()是个快速调用函数,这个函数名出现在汇编程序中时则变成@KfLowerIrql@4。这里的前缀@表示这是快速调用函数,后缀的@4则表示这个函数带有4个字节的参数,即一个参数。当然,这是通过寄存器(因为只有一个参数,所以是ECX)传递的参数,而不是通过堆栈传递的参数。<br /> 按理说,KfLowerIrql()是核心层的函数,又是快速调用,因而其函数名的前缀应该是Kef,但是实际用的却是Kf,所以这些前缀的使用并不严格。读者也许怀疑,是否ReactOS的人不守规矩?其实不然,因为在别的资料中也看到过这个函数名。
1.对于ring3 api主要由 kernel32.dll,user32.dll等dll导出函数,
2.对于内核函数,由ntoskrnl.exe导出。可以直接用IDA加载,在export栏中察看(可使用搜索栏搜索函数)
注意:
文档化:在export显示的函数,说明函数已导出,在WDK文档中,可查询到的函数(联网+本地),为文档化,可直接调用。
未文档化:若导出了,未文档化,则要声明此内核函数。