内核态调用Nt*函数

本文介绍了NtReadFile和ZwReadFile在用户态和内核态的区别,并探讨了在内核态直接调用Nt*函数的原因,包括如何绕过监控以及解决调用中的问题,如修改PreviousMode来避免ACCESS_VIOLATION错误。通过分析内核代码,展示了如何在内核态正确调用Nt*函数。

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

2007-11-18 22:27
我先简单介绍一下Nt系列函数与Zw系列函数的区别 以ReadFile为例   ntdll.dll导出了ZwReadFile和NtReadFile 以下是反汇编代码
.text:7C92E27C ; __stdcall NtReadFile(x, x, x, x, x, x, x, x, x)
.text:7C92E27C                 public _NtReadFile@36
.text:7C92E27C _NtReadFile@36 proc near               ; CODE XREF: RtlGetSetBootStatusData(x,x,x,x,x,x)+5F p
.text:7C92E27C                                         ; RtlGetSetBootStatusData(x,x,x,x,x,x):loc_7C95170D p ...
.text:7C92E27C                 mov     eax, 0B7h       ; NtReadFile
.text:7C92E281                 mov     edx, 7FFE0300h
.text:7C92E286                 call    dword ptr [edx]
.text:7C92E288                 retn    24h
.text:7C92E288 _NtReadFile@36 endp
.text:7C92E288
.text:7C92E288 ; ---------------------------------------------------------------------------
.text:7C92E28B                 db 6 dup(90h)
.text:7C92E291 ; Exported entry 274. NtReadFileScatter
.text:7C92E291 ; Exported entry 1083. ZwReadFileScatter
.text:7C92E291
我们可以看到 ZwReadFile和NtReadFile函数指向同一段代码....
也就是说在用户态 不管你调用ZwReadFile还是NtReadFile都是一样的 因为他们是同一个函数的两个不同名称而已.... 而且他们最终都会调用到ntoskrnl中的NtReadFile中去

而ntoskrnl.exe导出的ZwReadFile和NtReadFile却是不同的
.text:00406508 ; NTSTATUS __stdcall ZwReadFile(HANDLE FileHandle,HANDLE Event,PIO_APC_ROUTINE ApcRoutine,PVOID ApcContext,PIO_STATUS_BLOCK IoStatusBlock,PVOID Buffer,ULONG Length,PLARGE_INTEGER ByteOffset,PULONG Key)
.text:00406508                 mov     eax, 0B7h
.text:0040650D                 lea     edx, [esp+FileHandle]
.text:00406511                 pushf
.text:00406512                 push    8
.text:00406514                 call    _KiSystemService
.text:00406519                 retn    24h
.text:00406519 _ZwReadFile@36 endp

PAGE:004990D8 ; NTSTATUS __stdcall NtReadFile(HANDLE FileHandle,HANDLE Event,PIO_APC_ROUTINE ApcRoutine,PVOID ApcContext,PIO_STATUS_BLOCK IoStatusBlock,PVOID Buffer,ULONG Length,PLARGE_INTEGER ByteOffset,PULONG Key)
PAGE:004990D8                 push    68h
PAGE:004990DA                 push    offset stru_418748
PAGE:004990DF                 call    __SEH_prolog
PAGE:004990E4                 xor     esi, esi
PAGE:004990E6                 mov     [ebp+var_20], esi
PAGE:004990E9                 mov     [ebp+var_34], esi
PAGE:004990EC                 mov     [ebp+var_70], esi
PAGE:004990EF                 mov     [ebp+var_6C], esi
PAGE:004990F2                 mov     eax, large fs:124h
PAGE:004990F8                 mov     [ebp+var_58], eax
PAGE:004990FB                 mov     al, [eax+140h]
PAGE:00499101                 mov     [ebp+WaitMode], al
PAGE:00499104                 push    esi             ; HandleInformation
PAGE:00499105                 lea     eax, [ebp+FileObject]
PAGE:00499108                 push    eax             ; Object
PAGE:00499109                 push    dword ptr [ebp+WaitMode] ; AccessMode
PAGE:0049910C                 push    _IoFileObjectType ; ObjectType
PAGE:00499112                 push    1               ; DesiredAccess
PAGE:00499114                 push    [ebp+Handle]    ; Handle
PAGE:00499117                 call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
PAGE:0049911C                 cmp     eax, esi
PAGE:0049911E                 jl      loc_49928C
...
...
...

Ntoskrnl导出的NtReadFile是真正的执行函数 而ZwReadFile仍然是一个stub函数
内核态调用ZwReadFile 会将Previous Mode设置为Kernel Mode 然后再调用到NtReadFile中 而在内核态直接调用NtReadFile 不会改变Previous Mode     . 而在NtReadFile中 会检测当前调用来自用户态还是内核态 如果是来自内核态 不会检测参数 而如果是来自用户态 就会做一系列的参数检测 而我们知道 内核组件可能运行在任意进程的上下文中 当它调用NtReadFile时 因为Previous Mode很可能是User Mode ....    而我们的参数请求的内核态的地址 这时通常就会产STATUS_ACCESS_VIOLATION
错误 ..  
所以内核态一般用Zw*系列的函数
关于Nt*与Zw*更为详细的介绍请参考 [URL="http://www.osronline.com/article.cfm?id=257"]Nt vs. Zw - Clearing Confusion On The Native API [/URL]
那我们为什么还要在内核态直接调用Nt*函数呢?
大家都知道很多杀毒软件hook了ssdt 对系统进行监控....   现在比如我们已经进入了Ring0(这个不在本文介绍的范围内) 然后想干一些猥琐的事情 那么怎样绕过监控呢 ? 一种方法先恢复被ssdt
然后再调用我们要用到的Zw*函数 ,不过这种方法不太好 不够隐藏 而且有很多监控程序 会定时检测ssdt表的
那么我们可不可以直接调用 Nt函数呢? 如果可以的话 那么监控对我们就失去作用了 .
直接调用Nt*函数要解决几个问题 第一个就是Nt*函数在内存中的地址 只有有了地址我们才能调用啊 而ssdt表已经被hook了 所以我们不能直接从ssdt中获得 而要从磁盘文件ntoskrnl.exe中得到它
第二个问题是系统会在Nt*函数中检查当前线程结构的PreviousMode 来确定调用是来自用户态还是内核态 如果是来自用户态 函数会检测参数地址是否小于_MmUserProbeAddress.如果不是 将会产生一个错误..   所以我们的目标就是把PreviousMode改为Kernel Mode
下面看NtReadFile的代码
PAGE:004990D8 ; NTSTATUS __stdcall NtReadFile(HANDLE FileHandle,HANDLE Event,PIO_APC_ROUTINE ApcRoutine,PVOID ApcContext,PIO_STATUS_BLOCK IoStatusBlock,PVOID Buffer,ULONG Length,PLARGE_INTEGER ByteOffset,PULONG Key)
PAGE:004990D8                 push    68h
PAGE:004990DA                 push    offset stru_418748
PAGE:004990DF                 call    __SEH_prolog
PAGE:004990E4                 xor     esi, esi
PAGE:004990E6                 mov     [ebp+var_20], esi
PAGE:004990E9                 mov     [ebp+var_34], esi
PAGE:004990EC                 mov     [ebp+var_70], esi
PAGE:004990EF                 mov     [ebp+var_6C], esi
PAGE:004990F2                 mov     eax, large fs:124h           ;内核态fs指向KPCR
/*                                                                                              ;fs:120为KPRCB
lkd> dt _kprcb
nt!_KPRCB
   +0x000 MinorVersion     : Uint2B
   +0x002 MajorVersion     : Uint2B
   +0x004 CurrentThread    : Ptr32 _KTHREAD                       ;fs:124
   +0x008 NextThread  
*/                                                                                         

PAGE:004990F8                 mov     [ebp+var_58], eax
PAGE:004990FB                 mov     al, [eax+140h]             
/*
lkd> dt _kthread
nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
........
+0x140 PreviousMode     : Char                                                ;al =PreviousMode
......................................
*/
PAGE:00499101                 mov     [ebp+WaitMode], al         
PAGE:00499104                 push    esi             ; HandleInformation
PAGE:00499105                 lea     eax, [ebp+FileObject]
PAGE:00499108                 push    eax             ; Object
PAGE:00499109                 push    dword ptr [ebp+WaitMode] ; AccessMode
PAGE:0049910C                 push    _IoFileObjectType ; ObjectType
PAGE:00499112                 push    1               ; DesiredAccess
PAGE:00499114                 push    [ebp+Handle]    ; Handle
PAGE:00499117                 call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
PAGE:0049911C                 cmp     eax, esi
PAGE:0049911E                 jl      loc_49928C
PAGE:00499124                 mov     ebx, [ebp+FileObject]
PAGE:00499127                 push    ebx             ; FileObject
PAGE:00499128                 call    _IoGetRelatedDeviceObject@4 ; IoGetRelatedDeviceObject(x)
PAGE:0049912D                 mov     [ebp+var_28], eax
PAGE:00499130                 cmp     [ebp+WaitMode], 0                 ;如果调用来处内核态
                                                                                                      ;则不作后面的检查     
PAGE:00499134                 jz      loc_4A9553
PAGE:0049913A                 mov     [ebp-4], esi
PAGE:0049913D                 mov     edi, [ebp+IoStatusBlock]
PAGE:00499140                 mov     eax, _MmUserProbeAddress
PAGE:00499145                 cmp     edi, eax
PAGE:00499147                 jnb     loc_50EBDC
PAGE:0049914D
PAGE:0049914D loc_49914D:                             ; CODE XREF: NtReadFile(x,x,x,x,x,x,x,x,x)+75B06 j
PAGE:0049914D                 mov     eax, [edi]
PAGE:0049914F                 mov     [edi], eax
PAGE:00499151                 mov     eax, [edi+4]
PAGE:00499154                 mov     [edi+4], eax
..............
我们只要把kthread中的PreviousMode值设为0就可以了
而PreviousMode在kthread中的偏移在各个系统版本中是不一样的 我们可以硬编码之
也可以在NtAdjustPrivilegesToken函数中得到这个偏移
PAGE:004B7BB2 loc_4B7BB2:                             ; CODE XREF: NtAdjustPrivilegesToken(x,x,x,x,x,x)+23 j
PAGE:004B7BB2                 mov     eax, large fs:124h
PAGE:004B7BB8                 mov     al, [eax+140h]                      ;这个140h就是偏移量
PAGE:004B7BBE                 mov     [ebp+AccessMode], al
PAGE:004B7BC1                 test    al, al
PAGE:004B7BC3                 jz      loc_4B8F67
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值