淺談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 植入;
4 Call植入的調用門。
前麵已打通主要關節,現在進一步看看細節問題:
[零] 默認隻有 System 用戶有寫物理內存的權限 administrators 組的用戶 隻有讀的權限,但是通過修改用戶
安全對象中的DACL 可以增加寫的權限:
_SetPhyMemDACLs proc uses ebx edi esi /
_hPhymem:HANDLE,/
_ptusrname:dword
local @dwret:dword
local @htoken:HANDLE
local @hprocess:HANDLE
local @個
local @OldDACLs:PACL
local @SecurityDescriptor:PSECURITY_DESCRIPTOR
local @Access:EXPLICIT_ACCESS
mov @dwret,FALSE
invoke RtlZeroMemory,addr @NewDACLs,sizeof @NewDACLs
invoke RtlZeroMemory,addr @SecurityDescriptor,/
sizeof @SecurityDescriptor
invoke GetSecurityInfo,_hPhymem,SE_KERNEL_OBJECT,/
DACL_SECURITY_INFORMATION,NULL,NULL,/
addr @OldDACLs,NULL,/
addr @SecurityDescriptor
.if eax != ERROR_SUCCESS
jmp SAFE_RET
.endif
invoke RtlZeroMemory,addr @Access,sizeof @Access
mov @Access.grfAccessPermissions,SECTION_ALL_ACCESS
mov @Access.grfAccessMode,GRANT_ACCESS
mov @Access.grfInheritance,NO_INHERITANCE
mov @Access.stTRUSTEE.MultipleTrusteeOperation,/
NO_MULTIPLE_TRUSTEE
mov @Access.stTRUSTEE.TrusteeForm,TRUSTEE_IS_NAME
mov @Access.stTRUSTEE.TrusteeType,TRUSTEE_IS_USER
push _ptusrname
pop @Access.stTRUSTEE.ptstrName
invoke GetCurrentProcess
mov @hprocess,eax
invoke OpenProcessToken,@hprocess,TOKEN_ALL_ACCESS,/
addr @htoken
invoke SetEntriesInAcl,1,addr @Access,/
@OldDACLs,addr @NewDACLs
.if eax != ERROR_SUCCESS
jmp SAFE_RET
.endif
invoke SetSecurityInfo,_hPhymem,SE_KERNEL_OBJECT,/
DACL_SECURITY_INFORMATION,NULL,NULL,/
@NewDACLs,NULL
.if eax != ERROR_SUCCESS
jmp SAFE_RET
.endif
mov @dwret,TRUE
SAFE_RET:
.if @NewDACLs != NULL
invoke LocalFree,@NewDACLs
mov @NewDACLs,NULL
.endif
.if @SecurityDescriptor != NULL
invoke LocalFree,@SecurityDescriptor
mov @SecurityDescriptor,NULL
.endif
mov eax,@dwret
ret
_SetPhyMemDACLs endp
[一] 可以在Ring3下使用SGDT指令取得係統GDT表的虛擬地址,這條指令沒有被Intel設計成特權0級的指令。據我的
觀察,在 Windows 2000 SP4 中 GDT 表的基址都是相同的,
而且在 虛擬機VMware 5.5 虛擬的 Windows 2000 SP4中
執行 SGDT 指令後返回的是錯誤的結果,在虛擬的 Windows XP 中也有同樣情況,可能是虛擬機的問題,大家如果有條件可以試一下:
local @stGE:GDT_ENTRY
mov @dwret,FALSE
lea esi,@stGE
sgdt fword ptr [esi]
assume esi:ptr GDT_ENTRY
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;在 VMware 虛擬環境下用以下兩條指令替代
;隻用於 Windows 2000 SP4
;mov [esi].Base,80036000h
;mov [esi].Limit,03ffh
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
mov eax,[esi].Base
invoke @GetPhymemLite,eax
.if eax == FALSE
jmp quit
.endif
下麵就是虛擬地址轉換物理地址了,這在Ring0中很簡單,
直接調用MmGetPhysicalAddress 即可,但在Ring3中要
另想辦法,還好係統直接將 0x80000000 – 0xa0000000 影射到物理0地址開始的位置,所以可以寫一個輕量級的GetPhysicalAddress來替代 :)
@GetPhymemLite proc uses esi edi ebx _vaddr
local @dwret:dword
mov @dwret,FALSE
.if _vaddr < 80000000h
jmp quit
.endif
.if _vaddr >= 0a0000000h
jmp quit
.endif
mov eax,_vaddr
and eax,01ffff000h ;or sub eax,80000000h
mov @dwret,eax
quit:
mov eax,@dwret
ret
@GetPhymemLite endp
[二]調用門在保護模式中可以看成是低特權級代碼向高特權級代碼轉換的一種實現機製,如圖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的代碼:
mov eax,_FucAddr
mov @CallGate.OffsetL,ax ;Low Part Addr Of FucAddr
mov @CallGate.Selector,8h ;Ring0 Code Segment
mov @CallGate.DCount,1 ;1 Dword
mov @CallGate.GType,AT386CGate ;Must A CallGate
shr eax,16
mov @CallGate.OffsetH,ax ;Low Part Addr Of FucAddr
[三] 既然可以讀些物理內存了,也知道了GDT的物理基地址和長度,所以可以通過將GDT整個讀出,然後尋找一塊空閑的區域來植入前麵設置好的CallGate:
;申請一片空間,以便存放讀出的GDT
Invoke VirtualAlloc,NULL,@tmpGDTLimit,MEM_COMMIT,/
PAGE_READWRITE
.if eax == NULL
jmp quit
.endif
mov @pmem,eax
invoke @ReadPhymem,@tmpGDTPhyBase,@pmem,@tmpGDTLimit,/
_hmem
.if eax == FALSE
jmp quit
.endif
mov esi,@pmem
mov ebx,@tmpGDTLimit
shr ebx,3
;找到第一個GDT描述符中P位沒有置位的地址。
mov ecx,1
.while ecx < ebx
mov al,byte ptr [esi+ecx*8+5]
bt ax,7
.if CARRY?
.else
jmp lop0
.endif
Inc ecx
.endw
invoke VirtualFree,@pmem,0,MEM_RELEASE
jmp quit
lop0:
lea eax,[ecx*8]
mov @OffsetGatePos,eax
add @PhyGatePos,eax
mov esi,@pmem
add esi,eax
invoke RtlMoveMemory,addr oldgatebuf,esi,8
;釋放內存空間
invoke VirtualFree,@pmem,0,MEM_RELEASE
[四] 現在主要工作基本完成了,剩下的就是設計一個運行在Ring0中的子函數,在這個子函數中我將調用Ring0裏麵真正的MmGetPhysicalAddress來取得實際的物理地址,所以這個函數要有一個輸入參數用來傳遞要轉換的虛擬地址,並且還要考慮到如何獲取返回的物理地址(EDX:EAX)。在網絡上的C版本代碼中,這是通過定義幾個全局變量來傳遞的,因為沒有發生進程切換,所以可以使用原進程中的一些變量。然而我在傳遞虛擬地址上采用了另一種做法,就是通過實際形參來傳遞的:
Ring0Fuc proc ;_vaddr
;手動保存
push ebp
mov ebp,esp
sub esp,4
mov eax,[ebp+0ch]
mov [ebp-4],eax ;first local val
pushad
pushfd
cli
mov eax,[ebp-4]
;調用真正的 MmGetPhysicalAddress.
invoke MmGetPhysicalAddress,eax
mov phymem_L,eax
mov phymem_H,edx
popfd
popad
;手動還原
mov esp,ebp
pop ebp
retf 4
Ring0Fuc endp
最後,通過一個遠CALL來調用這個調用門:
lea edi,FarAddr
push _vaddr
call fword ptr [edi]
通過親手編碼,可以對調用門、遠調用等一些80386+保護模式中的概念在windows的實現中有了進一步的了解,不再像以前那樣模棱兩可了。看似全部寫完了,其實中間還有很多可以挖掘出來擴展說的細節,但我現在已沒有精力寫了…( :( ),還要準備其他東西,結尾就用這個不是結尾的結尾,結尾吧(繞口令?)。:)
侯佩|hopy
2006.01.14 17:09 (機場)辦公室
最後更新:2017-04-02 00:06:15