淺談NT下Ring3無驅進入Ring0的方法
[原創]淺談NT下Ring3無驅進入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]:PACL
[email protected]:PSECURITY_DESCRIPTOR
[email protected]:EXPLICIT_ACCESS
[email protected]
invokeRtlZeroMemory,addr @NewDACLs,sizeof @NewDACLs
invokeRtlZeroMemory,addr @SecurityDescriptor,/
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
NO_MULTIPLE_TRUSTEE
[email protected],TRUSTEE_IS_NAME
[email protected],TRUSTEE_IS_USER
push_ptusrname
invokeGetCurrentProcess
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
.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
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],@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]
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 (機場)辦公室