2020年4月

Windows内核笔记 - R3和R0通信

大量的知识真的劝退,这边把重点重新写出来,后期在看什么重要来高亮吧

DLL的封装

  • kernel32.dll和user32.dll这种的底层函数就被包含在ntdll.dll中
  • ntdll.dll中的函数成对出现,分别以“Nt”和“Zw”开头,这种函数叫做Native API

前排提醒

ntdll.dll中的Nt和Zw与ntoskrnl.exe中的Nt和Zw不一样

  • ntdll.dll中的Nt*函数为ntdll!Nt
  • ntoskrnl.exe中的Nt*函数为nt!Nt
  • Zw*同理

系统调用

  • 通过中断指令'int 0x2e'
    _KiIntSystemCall@0:
    lea edx, [esp+8]
    int 0x2e
    ret
  • 通过指令'sysenter'
    _KiFastSystemCall@0:
    mov edx,csp
    sysenter
    _KiFastSystemCallRet@0:
    ret
  • 不管是中断式系统调用(通过中断指令'int 0x2e')还是快速系统调用(通过指令'sysenter'),在调用函数时都是通过将函数所对应的服务号(SSDT数组中的索引值)放入EAX中来传递的(详见后面的SSDT)
  • 'int 0x2e'用于xp以下版本
  • 'sysenter'用于xp及以上版本
  • 通过系统调用,能让R3层进入R0层
  • 'KUSER_SHARED_SYSCALL'指向KiIntSystemCall()或KiFastSystemCall()两个函数其中之一,实际开发中,直接
    mov ecx, KUSER_SHARED_SYSCALL
    CALL [ecx]

SSDT

  • SSDT是系统服务描述符表,这个表在内核ntoskrnl.exe中
  • 这个表的作用是将函数对应的服务号和nt!Nt*的API一一对应
  • SSDT查表操作在系统调用中发生,由于_KiFastSystemCall/_KiFastSystemCall是内核的总入口(就它们俩中的一个),在调用nt!Nt函数时需要将函数所对应的服务号放入EAX中来传递然后_KiFastSystemCall/_KiFastSystemCall中的'int 0x2e'/'sysenter'里面就会进行一个查表操作,把EAX传递过去的服务号当作索引来找到nt!Nt函数,然后调用这个nt!Nt*函数

从用户模式调用Native API

585678_67hi0eqg6aho6la (1).jpg

  • 通过ntdll!NT和ntdll!Zw调用是没区别的,参数都会经过严格审核
  • 图中的中介函数是ntdll!Nt,内核函数实现是nt!Nt

从内核模式调用Native API

585678_fltl4j7rhfeddup.jpg

  • 如果是快速调用的话,_KiSystemService换成_KiFastCallEntry
  • 图中的Zw是nt!Zw,Nt是nt!Nt

Previous Mode(先前模式)

  • 从用户态调用Native API则Previous Mode为用户态,Native API对传入的参数进行严格的审查
  • 从内核态调用Native API则Previous Mode为内核态,Native API不会对传入的参数进行严格的审查

Nt和Zw的区别

  • 调用nt!Nt*的API会直接调用对应的代码,Previous Mode很可能为用户态
  • 调用nt!Zw的API会通过KiSystemService/KiFastCallEntry最终跳转到对应函数代码处(nt!Nt处),Previous Mode会被设为内核态

直接调用nt!Nt*会位于用户态的原因

  • 由于内核组件可能在各任意线程的上下文中

The NtXxxx version of the native system service is the name of the function itself. Thus, when a Kernel Mode component calls the NtXxxx version of the system service, whatever is presently set into previous mode is unchanged. Thus, it is quite possible that the Kernel component could be running on an arbitrary User stack, with the requestor mode set to User. The system service will not know any better, attempt to validate the request parameters, possibly using the credentials of the arbitrary User Mode thread, and thus possibly fail the request. Another problem here is that one step in the validation process for a User Mode request is that all passed in buffers have either ProbeForRead or ProbeForWrite executed on them, depending on the buffer’s usage. These routines raise exceptions if executed on Kernel Mode addresses. Therefore, if you pass in Kernel Mode buffers with your request mode set to User, your calls into the native API return STATUS_ACCESS_VIOLATION.

  • 开发内核模式驱动的时候,可以通过nt!Zw*来避免额外的参数检查
  • 如果想要避免杀毒软件在SSDT中上Hook可以直接调用nt!Nt。要自己调用nt!Nt的话必须先将Previous Mode设为内核模式(KernelMode)。(详见:https://bbs.pediy.com/thread-55142.htm

参考

  1. 段钢-加密与解密
  2. https://bbs.pediy.com/thread-177772.htm
  3. http://www.osronline.com/article.cfm%5earticle=257.htm