很久不写博客了,笔记大多记在电脑上在,以后整理好了再搬运上来吧。

今天记一下“进程内存管理器”这个小程序上遇到的一个问题——内核模式调用Nt*函数。

使用的是内核中的NtQueryVirtualMemory函数,先看一下WinDbg:

  kd> u NtQueryVirtualMemory
  ntdll!NtQueryVirtualMemory:
  00000000`76e414e0 4c8bd1 mov r10,rcx
  00000000`76e414e3 b820000000 mov eax,20h
  00000000`76e414e8 0f05 syscall
  00000000`76e414ea c3 ret
  00000000`76e414eb 0f1f440000 nop dword ptr [rax+rax]
  ntdll!ZwOpenThreadToken:
  00000000`76e414f0 4c8bd1 mov r10,rcx
  00000000`76e414f3 b821000000 mov eax,21h
  00000000`76e414f8 0f05 syscall
  kd> u nt!NtQueryVirtualMemory

  kd> u nt!ZwQueryVirtualMemory l20
  nt!ZwQueryVirtualMemory:
  fffff800`03eb75c0 488bc4 mov rax,rsp
  fffff800`03eb75c3 fa cli
  fffff800`03eb75c4 4883ec10 sub rsp,10h
  fffff800`03eb75c8 50 push rax
  fffff800`03eb75c9 9c pushfq
  fffff800`03eb75ca 6a10 push 10h
  fffff800`03eb75cc 488d053d2e0000 lea rax,[nt!KiServiceLinkage (fffff800`03eba410)]
  fffff800`03eb75d3 50 push rax
  fffff800`03eb75d4 b820000000 mov eax,20h
  fffff800`03eb75d9 e962650000 jmp nt!KiServiceInternal (fffff800`03ebdb40)
  fffff800`03eb75de 6690 xchg ax,ax

  nt!NtQueryVirtualMemory:
  fffff800`041979f0 48895c2410 mov qword ptr [rsp+10h],rbx
  fffff800`041979f5 4889742420 mov qword ptr [rsp+20h],rsi
  fffff800`041979fa 48894c2408 mov qword ptr [rsp+8],rcx
  fffff800`041979ff 57 push rdi
  fffff800`04197a00 4154 push r12
  fffff800`04197a02 4155 push r13
  fffff800`04197a04 4156 push r14
  fffff800`04197a06 4157 push r15

ntdll中的NtQueryVirtualMemory与ZwQueryVirtualMemory是同一个函数,也就是一个简单的包装函数,使用了syscall切入内核,调用了nt!ZwQueryVirtualMemory函数,0x20(NtQueryVirtualMemory的服务号)存入eax,然后在SSDT中查找相应的系统服务,然后调用NtQueryVirtualMemory。这个就不用多说了,写过内核的,人尽皆知。

  总而言之,Ntoskrnl导出的NtQueryVirtualMemory才是真正的执行函数。而直接使用内核中NtQueryVirtualMemory的好处在于,这样做的好处在于可以避免SSDTHook和InlineHook引起的错误调用,而且杀毒软件一般都会Hook掉SSDT的,这样直接调用也可以绕过监控。

下面先来具体看看NtQueryVirtualMemory函数(本文如未特殊说明,则所谈NtQueryVirtualMemory函数皆为ntoskrnl.exe中的NtQueryVirtualMemory函数):

 

 NTSTATUS NTAPI NtQueryVirtualMemory(  

          IN HANDLE                  ProcessHandle,                 //目标进程句柄  

          IN PVOID                  BaseAddress,               //目标内存地址  

          IN MEMORY_INFORMATION_CLASS  MemoryInformationClass,       //查询内存信息的类别  

          OUT PVOID                  Buffer,                      //用于存储获取到的内存信息的结构地址  

          IN ULONG                  Length,                      //Buffer的最大长度  

          OUT PULONG                  ResultLength OPTIONAL);        //存储该函数处理返回的信息的长度的ULONG的地址   

  )

  第三个参数类型MEMORY_INFORMATION_CLASS是一个枚举类型其定义如下:

  

  1. //MEMORY_INFORMATION_CLASS定义
  2. typedef enum _MEMORY_INFORMATION_CLASS
  3. {
  4. MemoryBasicInformation,          //内存基本信息
  5. MemoryWorkingSetInformation,       //工作集信息
  6. MemoryMappedFilenameInformation    //内存映射文件名信息
  7. } MEMORY_INFORMATION_CLASS;

  0x00:使用MemoryBasicInformation时,Buffer应当指向的结构为MEMORY_BASIC_INFORMATION,其定义如下:

typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress;
PVOID AllocationBase;
DWORD AllocationProtect;
SIZE_T RegionSize;
DWORD State;
DWORD Protect;
DWORD Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

  0x01:使用MemoryWorkingSetInformation时,Buffer应当指向的结构为MEMORY_WORKING_SET_INFORMATION,其定义如下:

typedef struct _MEMORY_WORKING_SET_INFORMATION {
ULONG SizeOfWorkingSet;
DWORD WsEntries[ANYSIZE_ARRAY];
} MEMORY_WORKING_SET_INFORMATION, *PMEMORY_WORKING_SET_INFORMATION;

  0x02:当使用MemoryMappedFilenameInformation  时,Buffer应当指向结构为MEMORY_MAPPED_FILE_NAME_INFORMATION,其定义如下:

#define _MAX_OBJECT_NAME 1024/sizeof(WCHAR)
typedef struct _MEMORY_MAPPED_FILE_NAME_INFORMATION {
UNICODE_STRING Name;
WCHAR Buffer[_MAX_OBJECT_NAME];
} MEMORY_MAPPED_FILE_NAME_INFORMATION, *PMEMORY_MAPPED_FILE_NAME_INFORMATION;

  在程序中我对的NtQueryVirtualMemory函数的使用方法是,定义一个NtQueryVirtualMemory函数结构的指针,在通过SSDT表来找到内核层的NtQueryVirtualMemory的地址,用定义好的指针指向这个地址,从而调用这个函数。这样做的原因在于Ring0层也无法直接调用内核中的NtQueryVirtualMemory了,这一点看雪中的这篇文章有过讨论,并给出了上述的解决方案:

  http://bbs.pediy.com/thread-65392.htm

  

typedef
NTSTATUS
(*pfnNtQueryVirtualMemory)(HANDLE ProcessHandle, PVOID BaseAddress,
MEMORY_INFORMATION_CLASS MemoryInformationClass,
PVOID MemoryInformation,
SIZE_T MemoryInformationLength,
PSIZE_T ReturnLength);

  这也就引出了我想要解决的问题——直接调用内核中Nt*系列函数之前,是需要将Previous Mode切换为KernelMode的!

  其原因可以参考这篇外文讲的很详细:

  http://www.osronline.com/article.cfm?article=257

 (文中摘录:) 

Previous Mode

Time to step back and figure out what all of this means. An important fact to know is that Kernel Mode components by default trust all other Kernel Mode components. Because system services are always processed in Kernel Mode, Windows keeps track of whether the request originated from User Mode or Kernel Mode to determine if the caller is to be implicitly trusted. The system uses the previous mode indicator to determine the mode from which a system service call came. When a call comes from User Mode, previous mode is set to User. When a system service processing routine needs to determine whether or not to implicitly trust its caller, it checks the value of previous mode. If previous mode is set to User, the system service processing routine knows the call came from User Mode and thus any parameters passed in to the function need to be validated before they can be used.

This is why the previous mode being set is really the most important part about what we have talked about so far. No matter what a User Mode application does, the system treats its system service request as a User request, coming from User Mode, and goes out of its way to validate the request. All buffers are subject to validation, all access checks are performed, and absolutely no part of the request is implicitly trusted. However, a Kernel Mode request is not as scrutinized and it is assumed that the passed in parameters are valid.

If a Kernel component calls the ZwXxx version of a native API, all is well. The previous mode is set to Kernel and the credentials of the Kernel are used. The system service processing routine that is called assumes that any parameters that are passed are valid, because the request came from a Kernel Mode component (and Kernel Mode components implicitly trust each other).

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.

The moral of this bedtime story is that if you are in User Mode, use whatever variant you think makes your code look pretty. In Kernel Mode, use the ZwXxx routines and get your previous mode set properly, to Kernel Mode.

  

  简言之,调用NtQueryVirtualMemory中时,会检测当前调用来自用户态还是内核态 ,如果是来自内核态 ,不会检测参数, 而如果是来自用户态 ,就会做一系列的参数检测,  而内核组件可能运行在任意进程的上下文中 , 当它调用NtQueryVirtualMemory时 ,因为Previous Mode很可能是User Mode , 而我们的参数请求的内核态的地址 ,这时通常就会产STATUS_ACCESS_VIOLATION 。

  关于内核切换调用Nt系列函数的问题,看雪的这篇文章也有讨论,我们可以参考了解:

  http://bbs.pediy.com/thread-55142.htm

  

  

  

调用Nt函数内核模式切换问题的更多相关文章

  1. C++如何在r3静态调用NT函数

    原文最早发表于百度空间2010-02-22. 1.把ntapi.h.ntdll.lib放在一个目录,然后设置工具——选项——项目和解决方案——VC++目录——包含文件,把刚刚的目录设置在改包 含文件中 ...

  2. Zw函数与Nt函数的分别与联系

    Ring3中的NATIVE API,和Ring0的系统调用,都有同名的Zw和Nt系列函数,一度让初学者感到迷糊.N久前的我,也是相当的迷糊.现在就以ZwOpenProcess和NtOpenProces ...

  3. 50.Linux-分析ifconfig到内核的调用过程,实现内核启机自动设MAC地址(原)

    内核版本: Linux version 3.10.14 1.由于每次开发板开机的网卡eth0的物理地址都是随机的. 然后在网上找到可以通过命令行实现设置mac物理地址: ifconfig eth0 d ...

  4. (转)platform_driver_register,什么时候调用PROBE函数 注册后如何找到驱动匹配的设备

     platform_driver_register,什么时候调用PROBE函数 注册后如何找到驱动匹配的设备 2011-10-24 19:47:07 分类: LINUX   kernel_init中d ...

  5. electron/nodejs实现调用golang函数

    https://www.jianshu.com/p/a3be0d206d4c 思路 golang 支持编译成c shared library, 也就是系统中常见的.so(windows下是dll)后缀 ...

  6. LoadRunner如何调用外部函数

    LoadRunner如何调用外部函数 使用 VuGen 时,可以调用在外部 DLL 中定义的函数.通过从脚本调用外部函数,可以降低脚本的内存使用量以及总体运行时间.要调用外部函数,需要加载定义了该函数 ...

  7. C# 互操作性入门系列(二):使用平台调用调用Win32 函数

    好文章搬用工模式启动ing ..... { 文章中已经包含了原文链接 就不再次粘贴了 言明 改文章是一个系列,但只收录了2篇,原因是 够用了 } --------------------------- ...

  8. [转]C# 互操作性入门系列(二):使用平台调用调用Win32 函数

    传送门 C#互操作系列文章: C# 互操作性入门系列(一):C#中互操作性介绍 C# 互操作性入门系列(二):使用平台调用调用Win32 函数 C# 互操作性入门系列(三):平台调用中的数据封送处理 ...

  9. ng-repeat循环出来的部分调用同一个函数并且实现每个模块之间不能相互干扰

    使用场景:用ng-repeat几个部分,每个部分调用同一个函数,但是每个模块之间的功能不能相互干扰 问题:在用repeat实现.content块repeat的时候打算这样做:新建一个空的数组(nmbe ...

随机推荐

  1. xshell5 Linux 上传下载文件

    1,先登录身份验证和文件传输ZMODEM 选择自动激活. 2,rpm -qa | grep lrzsz 利用此命令查看是否安装了lrzsz . 如果没有任何反应则是没有安装 若没有安装 yum ins ...

  2. HDU 3586 Information Disturbing(二分+树形dp)

    http://acm.split.hdu.edu.cn/showproblem.php?pid=3586 题意: 给定一个带权无向树,要切断所有叶子节点和1号节点(总根)的联系,每次切断边的费用不能超 ...

  3. shell 基本操作小结

    1.echo和if else fi命令 #!/bin/bash echo hello;echo there filename=demo.sh if [ -e "$filename" ...

  4. python 元组列表合并

    #create a tuple l = [(,), (,), (,)] print(list(zip(*l)))

  5. ubuntu 14.04 (desktop amd 64) 下载

    http://cdimage.ubuntu.com/ubuntukylin/releases/14.04/release/

  6. ubuntu14.04(server amd64)免密码sudo

    vi /etc/sudoers.d/nopasswd4sudo 加入以下内容 用户名 ALL=(ALL) NOPASSWD : ALL

  7. Python day21模块介绍4(logging模块,configparser模块)

    1.日志等级从上往下依次降低 logging.basicConfig(#日志报错打印的基础配置 level=logging.DEBUG, filename="logger.log" ...

  8. Codeforces 101487E - Enter The Dragon

    101487E - Enter The Dragon 思路:做的时候两个地方理解错了,第一个事我以为龙吸了水,水就干了,其实龙是在下雨之前吸的,下雨时湖水又满了,所以湖水永远不会干:第二个是以为只要找 ...

  9. python - HTMLTestRunner 测试报告模板设置

    python - HTMLTestRunner 测试报告模板设置 优化模板下载地址: http://download.csdn.net/download/chinayyj2010/10039097   ...

  10. (转)关于C# 中的Attribute 特性

    摘要:纠结地说,这应该算是一篇关于Attribute 的笔记,其中的一些思路和代码借鉴了他人的文笔(见本文底部链接).但是,由于此文对Attribute 的讲解实在是叫好(自夸一下 ^_^),所以公之 ...