1. 程式人生 > >淺談NT下Ring3無驅進入Ring0的方法

淺談NT下Ring3無驅進入Ring0的方法

               

[原創]淺談NTRing3無驅進入Ring0的方法

關鍵字:NT,Ring0,無驅

(測試環境:Windows 2000 SP4,Windows XP SP2.

Windows 2003未測試)

NT下無驅進入Ring0是一個老生常談的方法了,網上也有一些C程式碼的例子,我之所以用匯編重寫是因為上次在

[原創/探討]Windows核心程式設計研究系列之一(改變程序 PTE)

的帖子中自己沒有實驗成功(其實已經成功了,只是自己太馬虎,竟然還不知道 -_-b),順面聊聊PM(保護模式)中的呼叫門的使用情況。鑑於這些都是可以作為基本功來了解的知識點,所以對此已經熟悉的朋友就可以略過不看了,當然由於本人水平有限,各位前來“挑挑刺”也是非常歡迎的,呵呵。

下面言歸正傳,我們知道在NT中進入Ring0的一般方法是通過驅動,我的Windows核心程式設計研究系列文章前兩篇都使用了

這個方法進入Ring0完成特定功能。現在我們還可以通過在Ring3下直接寫實體記憶體的方法來進入Ring0,其主要步驟是:

0以寫許可權開啟實體記憶體物件;

1取得系統 GDT地址,並轉換成實體地址;

2構造一個呼叫門;

3尋找 GDT中空閒的位置,將 CallGate植入;

4Call植入的呼叫門。

前面已打通主要關節,現在進一步看看細節問題:

[]預設只有 System使用者有寫實體記憶體的許可權 administrators組的使用者只有讀的許可權,但是通過修改使用者

安全物件中的DACL

可以增加寫的許可權:

_SetPhyMemDACLsprocuses ebx edi esi /

_hPhymem:HANDLE,/

_ptusrname:dword

[email protected]:dword

[email protected]:HANDLE

local[email protected]:HANDLE

[email protected]

[email protected]:PACL

[email protected]:PSECURITY_DESCRIPTOR

[email protected]:EXPLICIT_ACCESS

[email protected]

,FALSE

invokeRtlZeroMemory,addr @NewDACLs,sizeof @NewDACLs

invokeRtlZeroMemory,addr @SecurityDescriptor,/

[email protected]

invokeGetSecurityInfo,_hPhymem,SE_KERNEL_OBJECT,/

DACL_SECURITY_INFORMATION,NULL,NULL,/

addr @OldDACLs,NULL,/

addr @SecurityDescriptor

.ifeax != ERROR_SUCCESS

jmpSAFE_RET

.endif

invokeRtlZeroMemory,addr @Access,sizeof @Access

[email protected],SECTION_ALL_ACCESS

[email protected],GRANT_ACCESS

[email protected],NO_INHERITANCE

[email protected],/

NO_MULTIPLE_TRUSTEE

[email protected],TRUSTEE_IS_NAME

[email protected],TRUSTEE_IS_USER

push_ptusrname

[email protected]

invokeGetCurrentProcess

[email protected],eax

invokeOpenProcessToken,@hprocess,TOKEN_ALL_ACCESS,/

addr @htoken

invokeSetEntriesInAcl,1,addr @Access,/

@OldDACLs,addr @NewDACLs

.ifeax != ERROR_SUCCESS

jmpSAFE_RET

.endif

invokeSetSecurityInfo,_hPhymem,SE_KERNEL_OBJECT,/

DACL_SECURITY_INFORMATION,NULL,NULL,/

@NewDACLs,NULL

.ifeax != ERROR_SUCCESS

jmpSAFE_RET

.endif

[email protected],TRUE

SAFE_RET:

[email protected] != NULL

invokeLocalFree,@NewDACLs

[email protected],NULL

.endif

[email protected] != NULL

invokeLocalFree,@SecurityDescriptor

[email protected],NULL

.endif

moveax,@dwret

ret

_SetPhyMemDACLsendp

[]可以在Ring3下使用SGDT指令取得系統GDT表的虛擬地址,這條指令沒有被Intel設計成特權0級的指令。據我的

觀察,在 Windows 2000 SP4 GDT 表的基址都是相同的,

而且在 虛擬機器VMware 5.5虛擬的 Windows 2000 SP4

執行 SGDT指令後返回的是錯誤的結果,在虛擬的 Windows XP 中也有同樣情況,可能是虛擬機器的問題,大家如果有條件可以試一下:

[email protected]:GDT_ENTRY

[email protected],FALSE

leaesi,@stGE

sgdtfword ptr [esi]

assumeesi:ptr GDT_ENTRY

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

;VMware 虛擬環境下用以下兩條指令替代

;只用於 Windows 2000 SP4

;mov[esi].Base,80036000h

;mov[esi].Limit,03ffh

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

moveax,[esi].Base

[email protected],eax

.ifeax == FALSE

jmpquit

.endif

下面就是虛擬地址轉換實體地址了,這在Ring0中很簡單,

直接呼叫MmGetPhysicalAddress即可,但在Ring3中要

另想辦法,還好系統直接將 0x80000000 – 0xa0000000影射到物理0地址開始的位置,所以可以寫一個輕量級的GetPhysicalAddress來替代:)

@GetPhymemLiteprocuses esi edi ebx_vaddr

[email protected]:dword

[email protected],FALSE

.if_vaddr < 80000000h

jmpquit

.endif

.if_vaddr >= 0a0000000h

jmpquit

.endif

moveax,_vaddr

andeax,01ffff000h;or sub eax,80000000h

[email protected],eax

quit:

moveax,@dwret

ret

@GetPhymemLiteendp

[]呼叫門在保護模式中可以看成是低特權級程式碼向高特權級程式碼轉換的一種實現機制,如圖1所示(由於本人較懶,所以借用李彥昌先生所著的80x86保護模式系列教程中的部分截圖,希望李先生看到後不要見怪 ^-^:

           

1

要說明的是呼叫門也可以完成相同特權級的轉換。一般門的結構如圖2所示:

門描述符

m+7

m+6

m+5

m+4

m+3

m+2

m+1

m+0

Offset(31...16)

Attributes

Selector

Offset(15...0)

門描述符屬性

Byte m+5

Byte m+4

BIT7

BIT6

BIT5

BIT4

BIT3

BIT2

BIT1

BIT0

BIT7

BIT6

BIT5

BIT4

BIT3

BIT2

BIT1

BIT0

P

DPL

DT0

TYPE

000

Dword Count

2

   簡單的介紹一下各個主要位置的含義:

Offset Selector共同組成目的地址的48位全指標,這意味著,如果遠CALL指令指向一個呼叫門,則CALL指令中的偏移被丟棄;

P位置位代表門有效,DPL是門描述符的特權級,後面要設定成3,以便在Ring3中可以訪問。TYPE是門的型別,386呼叫門是 0xC ,Dword Count是系統要拷貝的雙字引數的個數,後面也將

用到。下面是設定CallGate的程式碼:

moveax,_FucAddr

[email protected],ax;Low Part Addr Of FucAddr

[email protected],8h;Ring0 Code Segment

[email protected],1;1 Dword

[email protected],AT386CGate;Must A CallGate

shreax,16

[email protected],ax;Low Part Addr Of FucAddr

[]既然可以讀些實體記憶體了,也知道了GDT的物理基地址和長度,所以可以通過將GDT整個讀出,然後尋找一塊空閒的區域來植入前面設定好的CallGate

;申請一片空間,以便存放讀出的GDT

InvokeVirtualAlloc,NULL,@tmpGDTLimit,MEM_COMMIT,/

PAGE_READWRITE

.ifeax == NULL

jmpquit

.endif

[email protected],eax

[email protected],@tmpGDTPhyBase,@pmem,@tmpGDTLimit,/

_hmem

.ifeax == FALSE

jmpquit

.endif

movesi,@pmem

movebx,@tmpGDTLimit

shrebx,3

;找到第一個GDT描述符中P位沒有置位的地址。

movecx,1

.whileecx < ebx

moval,byte ptr [esi+ecx*8+5]

btax,7

.ifCARRY?

.else

jmplop0

.endif

Incecx

.endw

invokeVirtualFree,@pmem,0,MEM_RELEASE

jmpquit

lop0:

leaeax,[ecx*8]

[email protected],eax

[email protected],eax

movesi,@pmem

addesi,eax

invokeRtlMoveMemory,addr oldgatebuf,esi,8

;釋放記憶體空間

invokeVirtualFree,@pmem,0,MEM_RELEASE

[]現在主要工作基本完成了,剩下的就是設計一個執行在Ring0中的子函式,在這個子函式中我將呼叫Ring0裡面真正的MmGetPhysicalAddress來取得實際的實體地址,所以這個函式要有一個輸入引數用來傳遞要轉換的虛擬地址,並且還要考慮到如何獲取返回的實體地址(EDX:EAX)。在網路上的C版本程式碼中,這是通過定義幾個全域性變數來傳遞的,因為沒有發生程序切換,所以可以使用原程序中的一些變數。然而我在傳遞虛擬地址上採用了另一種做法,就是通過實際形參來傳遞的:

Ring0Fucproc;_vaddr

;手動儲存

pushebp

movebp,esp

subesp,4

moveax,[ebp+0ch]

mov[ebp-4],eax;first local val

pushad

pushfd

cli

moveax,[ebp-4]

;呼叫真正的 MmGetPhysicalAddress.

invokeMmGetPhysicalAddress,eax

movphymem_L,eax

movphymem_H,edx

popfd

popad

;手動還原

movesp,ebp

popebp

retf4

Ring0Fucendp

最後,通過一個遠CALL來呼叫這個呼叫門:

leaedi,FarAddr

push_vaddr

callfword ptr [edi]

通過親手編碼,可以對呼叫門、遠呼叫等一些80386+保護模式中的概念在windows的實現中有了進一步的瞭解,不再像以前那樣模稜兩可了。看似全部寫完了,其實中間還有很多可以挖掘出來擴充套件說的細節,但我現在已沒有精力寫了:( ,還要準備其他東西,結尾就用這個不是結尾的結尾,結尾吧(繞口令?)。:)

侯佩|hopy

2006.01.14 17:09 (機場)辦公室