zoukankan      html  css  js  c++  java
  • 在NT中直接访问物理内存

    我们知道,在NT/2K/XP中,操作系统利用虚拟内存管理技术来维护地址空间映像,每个进程分配一个4GB的虚拟地址空间。运行在用户态的应用程序,不能直接访问物理内存地址;而运行在核心态的驱动程序,能将虚拟地址空间映射为物理地址空间,从而访问物理内存地址。

    如果要在应用程序中以物理地址方式访问内存,自然而然的办法,是编写一个专用的驱动程序(如大家熟悉的WinIO),里面设置一定的IOCTL码,应用程序通过调用DeviceIoCtrol()来实现这样的功能。

    那么,有没有一种方法,省去编写专用驱动程序这一步,很方便地就能访问物理内存呢?答案是肯定的。实际上,微软早就给我们准备好了一套办法,只是他们秘而不宣罢了。系统内建一个叫做PhysicalMemory的内核对象,可以通过系统核心文件NTDLL.DLL中的有关API进行操纵,从而实现物理内存的直接访问。微软声称这些API是用于驱动程序开发的,在VC/.NET中未提供原型说明和库文件,然而事实证明在应用程序中调用它们是没有问题的。我们感兴趣的API主要包括:

    ZwOpenSection 或 NtOpenSection - 打开内核对象
    ZwMapViewOfSection 或 NtMapViewOfSection - 映射虚拟地址空间
    ZwUnmapViewOfSection 或 NtUnmapViewOfSection - 取消地址空间映射
    RtlInitUnicodeString - 用UNICODE串初始化UNICODE描述的结构
    以下的代码描述了如何利用NTDLL.DLL中的上述几个API,实现对物理内存的读取。需要指出的是,只有system拥有读写权限,administrator只有读权限,而user连读权限都没有。这一点,是不能与专用驱动程序方法向相比的。

    在VC/.NET中,由于没有相应的原型说明和库文件,我们用GetProcAddress()进行DLL显式调用。前面大段的代码,用于说明必需的类型和结构。读取物理内存的主要步骤为:打开内核对象 → 映射虚拟地址空间 → 读取(复制)内存 → 取消地址空间映射。

    typedef LONG    NTSTATUS;
     
    typedef struct _UNICODE_STRING
    {
        USHORT Length;
        USHORT MaximumLength;
        PWSTR Buffer;
    } UNICODE_STRING, *PUNICODE_STRING;
     
    typedef enum _SECTION_INHERIT
    {
        ViewShare = 1,
        ViewUnmap = 2
    } SECTION_INHERIT, *PSECTION_INHERIT;
     
    typedef struct _OBJECT_ATTRIBUTES
    {
        ULONG Length;
        HANDLE RootDirectory;
        PUNICODE_STRING ObjectName;
        ULONG Attributes;
        PVOID SecurityDescriptor;
        PVOID SecurityQualityOfService;
    } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
     
    #define InitializeObjectAttributes( p, n, a, r, s ) { \
        (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
        (p)->RootDirectory = r; \
        (p)->Attributes = a; \
        (p)->ObjectName = n; \
        (p)->SecurityDescriptor = s; \
        (p)->SecurityQualityOfService = NULL; \
    }
     
    // Interesting functions in NTDLL
    typedef NTSTATUS (WINAPI *ZwOpenSectionProc)
    (
        PHANDLE SectionHandle,
        DWORD DesiredAccess,
        POBJECT_ATTRIBUTES ObjectAttributes
    );
    typedef NTSTATUS (WINAPI *ZwMapViewOfSectionProc)
    (
        HANDLE SectionHandle,
        HANDLE ProcessHandle,
        PVOID *BaseAddress,
        ULONG ZeroBits,
        ULONG CommitSize,
        PLARGE_INTEGER SectionOffset,
        PULONG ViewSize,
        SECTION_INHERIT InheritDisposition,
        ULONG AllocationType,
        ULONG Protect
    );
    typedef NTSTATUS (WINAPI *ZwUnmapViewOfSectionProc)
    (
        HANDLE ProcessHandle,
        PVOID BaseAddress
    );
    typedef VOID (WINAPI *RtlInitUnicodeStringProc)
    (
        IN OUT PUNICODE_STRING DestinationString,
        IN PCWSTR SourceString
    );
     
    // Global variables
    static HMODULE hModule = NULL;
    static HANDLE hPhysicalMemory = NULL;
    static ZwOpenSectionProc ZwOpenSection;
    static ZwMapViewOfSectionProc ZwMapViewOfSection;
    static ZwUnmapViewOfSectionProc ZwUnmapViewOfSection;
    static RtlInitUnicodeStringProc RtlInitUnicodeString;
     
    // initialize
    BOOL InitPhysicalMemory()
    {
        if (!(hModule = LoadLibrary("ntdll.dll")))
        {
            return FALSE;
        }
     
        // 以下从NTDLL获取我们需要的几个函数指针
        if (!(ZwOpenSection = (ZwOpenSectionProc)GetProcAddress(hModule, "ZwOpenSection")))
        {
            return FALSE;
        }
     
        if (!(ZwMapViewOfSection = (ZwMapViewOfSectionProc)GetProcAddress(hModule, "ZwMapViewOfSection")))
        {
            return FALSE;
        }
     
        if (!(ZwUnmapViewOfSection = (ZwUnmapViewOfSectionProc)GetProcAddress(hModule, "ZwUnmapViewOfSection")))
        {
            return FALSE;
        }
     
        if (!(RtlInitUnicodeString = (RtlInitUnicodeStringProc)GetProcAddress(hModule, "RtlInitUnicodeString")))
        {
            return FALSE;
        }
     
        // 以下打开内核对象
        WCHAR PhysicalMemoryName[] = L"\\Device\\PhysicalMemory";
        UNICODE_STRING PhysicalMemoryString;
        OBJECT_ATTRIBUTES attributes;
        RtlInitUnicodeString(&PhysicalMemoryString, PhysicalMemoryName);
        InitializeObjectAttributes(&attributes, &PhysicalMemoryString, 0, NULL, NULL);
        NTSTATUS status = ZwOpenSection(&hPhysicalMemory, SECTION_MAP_READ, &attributes );
     
        return (status >= 0);
    }
     
    // terminate -- free handles
    void ExitPhysicalMemory()
    {
        if (hPhysicalMemory != NULL)
        {
            CloseHandle(hPhysicalMemory);
        }
     
        if (hModule != NULL)
        {
            FreeLibrary(hModule);
        }
    }
     
    BOOL ReadPhysicalMemory(PVOID buffer, DWORD address, DWORD length)
    {
        DWORD outlen;            // 输出长度,根据内存分页大小可能大于要求的长度
        PVOID vaddress;          // 映射的虚地址
        NTSTATUS status;         // NTDLL函数返回的状态
        LARGE_INTEGER base;      // 物理内存地址
     
        vaddress = 0;
        outlen = length;
        base.QuadPart = (ULONGLONG)(address);
     
        // 映射物理内存地址到当前进程的虚地址空间
        status = ZwMapViewOfSection(hPhysicalMemory,
            (HANDLE) -1,
            (PVOID *)&vaddress,
            0,
            length,
            &base,
            &outlen,
            ViewShare,
            0,
            PAGE_READONLY);
     
        if (status < 0)
        {
            return FALSE;
        }
     
        // 当前进程的虚地址空间中,复制数据到输出缓冲区
        memmove(buffer, vaddress, length);
     
        // 完成访问,取消地址映射
        status = ZwUnmapViewOfSection((HANDLE)-1, (PVOID)vaddress);
     
        return (status >= 0);
    }
     
    // 一个测试函数,从物理地址0xfe000开始,读取4096个字节
    // 对于Award BIOS,可以从这段数据找到序列号等信息
    BOOL test()
    {
        UCHAR buf[4096];
     
        if (!InitPhysicalMemory())
        {
            return FALSE;
        }
     
        if (!ReadPhysicalMemory(buf, 0xfe000, 4096))
        {
            // ... 成功读取了指定数据
            ExitPhysicalMemory();
            return FALSE;
        }
     
        ExitPhysicalMemory();
     
        return TRUE;
    }
    补充说明一点,由于Windows虚拟内存页面大小默认是4KB,NtMapViewOfSection()返回的虚拟空间基址是按照4KB对齐的,返回的长度也是4KB的整数倍。在上面的ReadPhysicalMemory()中,认为输入的物理地址也是4KB对齐的,如果不是,需要更加全面地考虑。

     

    [相关资源]
    本文Demo源码:Kernel Studio
    bhw98的专栏:http://www.csdn.net/develop/author/netauthor/bhw98/

  • 相关阅读:
    Java 实现 蓝桥杯 生兔子问题
    Java实现 蓝桥杯 基因牛的繁殖
    Java实现 蓝桥杯 基因牛的繁殖
    Java实现 蓝桥杯 基因牛的繁殖
    Java实现 LeetCode 33 搜索旋转排序数组
    Java实现 LeetCode 33 搜索旋转排序数组
    Java实现 LeetCode 33 搜索旋转排序数组
    深入探究VC —— 资源编译器rc.exe(3)
    深入探究VC —— 编译器cl.exe(2)
    深入探究VC —— 编译器cl.exe(1)
  • 原文地址:https://www.cnblogs.com/lzjsky/p/1947886.html
Copyright © 2011-2022 走看看