閱讀379 返回首頁    go 阿裏雲 go 技術社區[雲棲]


淺談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 植入;

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

  上一篇:go 最近拜師風,收徒風的一點看法
  下一篇:go 在Linux(UNIX)下連接MS SQLserver的方法