• [原创]浅谈NT下Ring3无驱进入Ring0的方法


    原文链接:浅谈NT下Ring3无驱进入Ring0的方法

    (测试环境:Windows 2000 SP4,Windows XP SP2.Windows 2003 未测试)

     

      在NT下无驱进入Ring0是一个老生常谈的方法了,网上也有一些C代码的例子,我之所以用汇编重写是因为上次在[原创/探讨]Windows 核心编程研究系列之一(改变进程 PTE)的帖子中自己没有实验成功(其实已经成功了,只是自己太马虎,竟然还不知道 -_-b),顺面聊聊PM(保护模式)中的调用门的使用情况。鉴于这些都是可以作为基本功来了解的知识点,所以对此已经熟悉的朋友就可以略过不看了,当然由于本人水平有限,各位前来“挑挑刺”也是非常欢迎的,呵呵。

      下面言归正传,我们知道在NT中进入Ring0的一般方法是通过驱动,我的Windows 核心编程研究系列 文章前两篇都使用了这个方法进入Ring0 完成特定功能。现在我们还可以通过在Ring3下直接写物理内存的方法来进入Ring0,其主要步骤是:

     

    • 0  以写权限打开物理内存对象;
    • 取得 系统 GDT 地址,并转换成物理地址;
    • 构造一个调用门;
    • 寻找 GDT 中空闲的位置,将 CallGate 植入;
    • Call植入的调用门。
    •  

      前面已打通主要关节,现在进一步看看细节问题:

    [0]  默认只有 System 用户有写物理内存的权限 administrators 组的用户 只有读的权限,但是通过修改用户安全对象中的DACL 可以增加写的权限:

      1 _SetPhyMemDACLs      proc       uses ebx edi esi /
      2 
      3                                        _hPhymem:HANDLE,/
      4 
      5                                        _ptusrname:dword
      6 
      7     local  @dwret:dword
      8 
      9     local  @htoken:HANDLE
     10 
     11     local  @hprocess:HANDLE
     12 
     13     local  @个
     14 
     15     local  @OldDACLs:PACL
     16 
     17     local  @SecurityDescriptor:PSECURITY_DESCRIPTOR
     18 
     19     local  @Access:EXPLICIT_ACCESS
     20 
     21  
     22 
     23     mov     @dwret,FALSE
     24 
     25       
     26 
     27     invoke RtlZeroMemory,addr @NewDACLs,sizeof @NewDACLs
     28 
     29            invoke RtlZeroMemory,addr @SecurityDescriptor,/
     30 
     31            sizeof @SecurityDescriptor
     32 
     33  
     34 
     35     invoke GetSecurityInfo,_hPhymem,SE_KERNEL_OBJECT,/
     36 
     37            DACL_SECURITY_INFORMATION,NULL,NULL,/
     38 
     39            addr @OldDACLs,NULL,/
     40 
     41            addr @SecurityDescriptor
     42 
     43  
     44 
     45     .if eax != ERROR_SUCCESS
     46 
     47            jmp SAFE_RET
     48 
     49     .endif
     50 
     51  
     52 
     53     invoke RtlZeroMemory,addr @Access,sizeof @Access
     54 
     55  
     56 
     57     mov     @Access.grfAccessPermissions,SECTION_ALL_ACCESS
     58 
     59     mov     @Access.grfAccessMode,GRANT_ACCESS
     60 
     61     mov     @Access.grfInheritance,NO_INHERITANCE
     62 
     63     mov     @Access.stTRUSTEE.MultipleTrusteeOperation,/
     64 
     65            NO_MULTIPLE_TRUSTEE
     66 
     67     mov     @Access.stTRUSTEE.TrusteeForm,TRUSTEE_IS_NAME
     68 
     69     mov     @Access.stTRUSTEE.TrusteeType,TRUSTEE_IS_USER
     70 
     71     push   _ptusrname
     72 
     73     pop     @Access.stTRUSTEE.ptstrName
     74 
     75  
     76 
     77     invoke GetCurrentProcess
     78 
     79     mov     @hprocess,eax
     80 
     81     invoke OpenProcessToken,@hprocess,TOKEN_ALL_ACCESS,/
     82 
     83            addr @htoken
     84 
     85  
     86 
     87     invoke SetEntriesInAcl,1,addr @Access,/
     88 
     89            @OldDACLs,addr @NewDACLs
     90 
     91    
     92 
     93     .if eax != ERROR_SUCCESS
     94 
     95            jmp SAFE_RET
     96 
     97     .endif
     98 
     99  
    100 
    101     invoke SetSecurityInfo,_hPhymem,SE_KERNEL_OBJECT,/
    102 
    103            DACL_SECURITY_INFORMATION,NULL,NULL,/
    104 
    105            @NewDACLs,NULL
    106 
    107    
    108 
    109     .if eax != ERROR_SUCCESS
    110 
    111            jmp SAFE_RET
    112 
    113     .endif
    114 
    115  
    116 
    117     mov     @dwret,TRUE
    118 
    119  
    120 
    121 SAFE_RET:
    122 
    123  
    124 
    125     .if @NewDACLs != NULL
    126 
    127            invoke LocalFree,@NewDACLs
    128 
    129            mov @NewDACLs,NULL
    130 
    131     .endif
    132 
    133  
    134 
    135     .if @SecurityDescriptor != NULL
    136 
    137            invoke LocalFree,@SecurityDescriptor
    138 
    139            mov @SecurityDescriptor,NULL
    140 
    141     .endif
    142 
    143  
    144 
    145     mov     eax,@dwret
    146 
    147     ret
    148 
    149  
    150 
    151 _SetPhyMemDACLs      endp

     

    [0] 可以在Ring3下使用SGDT指令取得系统GDT表的虚拟地址,这条指令没有被Intel设计成特权0级的指令。据我的观察,在 Windows 2000 SP4 中 GDT 表的基址都是相同的,而且在 虚拟机VMware 5.5 虚拟的 Windows 2000 SP4中执行 SGDT 指令后返回的是错误的结果,在虚拟的 Windows XP 中也有同样情况,可能是虚拟机的问题,大家如果有条件可以试一下:

     1 local  @stGE:GDT_ENTRY
     2 
     3    
     4 
     5     mov     @dwret,FALSE
     6 
     7    
     8 
     9     lea     esi,@stGE
    10 
    11     sgdt   fword ptr [esi]
    12 
    13    
    14 
    15     assume esi:ptr GDT_ENTRY
    16 
    17    
    18 
    19     ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    20 
    21     ;在 VMware 虚拟环境下用以下两条指令替代
    22 
    23    ;只用于 Windows 2000 SP4
    24 
    25     ;mov   [esi].Base,80036000h
    26 
    27     ;mov   [esi].Limit,03ffh
    28 
    29     ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    30 
    31    
    32 
    33     mov     eax,[esi].Base
    34 
    35     invoke @GetPhymemLite,eax
    36 
    37     .if eax == FALSE
    38 
    39            jmp quit
    40 
    41     .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

     

    [2] 调用门在保护模式中可以看成是低特权级代码向高特权级代码转换的一种实现机制,如图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的代码:

     1 mov     eax,_FucAddr
     2 
     3     mov     @CallGate.OffsetL,ax     ;Low Part Addr Of FucAddr
     4 
     5     mov     @CallGate.Selector,8h    ;Ring0 Code Segment
     6 
     7     mov     @CallGate.DCount,1       ;1 Dword
     8 
     9     mov     @CallGate.GType,AT386CGate  ;Must A CallGate
    10 
    11  
    12 
    13     shr     eax,16
    14 
    15     mov     @CallGate.OffsetH,ax     ;Low Part Addr Of FucAddr

     

    [3]  既然可以读些物理内存了,也知道了GDT的物理基地址和长度,所以可以通过将GDT整个读出,然后寻找一块空闲的区域来植入前面设置好的CallGate:

     1 ;申请一片空间,以便存放读出的GDT
     2 
     3  Invoke   VirtualAlloc,NULL,@tmpGDTLimit,MEM_COMMIT,/
     4 
     5 PAGE_READWRITE   
     6 
     7     .if eax == NULL
     8 
     9            jmp quit
    10 
    11     .endif
    12 
    13    
    14 
    15     mov     @pmem,eax
    16 
    17     invoke @ReadPhymem,@tmpGDTPhyBase,@pmem,@tmpGDTLimit,/
    18 
    19            _hmem
    20 
    21  
    22 
    23     .if eax == FALSE
    24 
    25            jmp quit
    26 
    27     .endif
    28 
    29    
    30 
    31     mov     esi,@pmem
    32 
    33     mov     ebx,@tmpGDTLimit
    34 
    35     shr     ebx,3
    36 
    37     ;找到第一个GDT描述符中P位没有置位的地址。
    38 
    39 mov     ecx,1
    40 
    41     .while ecx < ebx
    42 
    43            mov al,byte ptr [esi+ecx*8+5]
    44 
    45            bt  ax,7
    46 
    47        .if CARRY?
    48 
    49  
    50 
    51        .else
    52 
    53            jmp lop0
    54 
    55        .endif
    56 
    57        Inc     ecx
    58 
    59     .endw
    60 
    61    
    62 
    63     invoke VirtualFree,@pmem,0,MEM_RELEASE
    64 
    65     jmp     quit
    66 
    67  
    68 
    69 lop0:
    70 
    71     lea     eax,[ecx*8]
    72 
    73     mov     @OffsetGatePos,eax
    74 
    75     add     @PhyGatePos,eax
    76 
    77  
    78 
    79     mov     esi,@pmem
    80 
    81     add     esi,eax
    82 
    83  
    84 
    85     invoke RtlMoveMemory,addr oldgatebuf,esi,8
    86 
    87    
    88 
    89     ;释放内存空间
    90 
    91     invoke VirtualFree,@pmem,0,MEM_RELEASE

    [4] 现在主要工作基本完成了,剩下的就是设计一个运行在Ring0中的子函数,在这个子函数中我将调用Ring0里面真正的MmGetPhysicalAddress来取得实际的物理地址,所以这个函数要有一个输入参数用来传递要转换的虚拟地址,并且还要考虑到如何获取返回的物理地址(EDX:EAX)。在网络上的C版本代码中,这是通过定义几个全局变量来传递的,因为没有发生进程切换,所以可以使用原进程中的一些变量。然而我在传递虚拟地址上采用了另一种做法,就是通过实际形参来传递的:

     

     1 Ring0Fuc proc          ;_vaddr
     2 
     3    
     4 
     5        ;手动保存
     6 
     7        push   ebp
     8 
     9        mov     ebp,esp
    10 
    11        sub     esp,4
    12 
    13        mov     eax,[ebp+0ch]
    14 
    15        mov     [ebp-4],eax       ;first local val
    16 
    17        pushad
    18 
    19        pushfd
    20 
    21        cli
    22 
    23    
    24 
    25        mov     eax,[ebp-4]
    26 
    27        ;调用真正的 MmGetPhysicalAddress.
    28 
    29        invoke MmGetPhysicalAddress,eax
    30 
    31        mov     phymem_L,eax
    32 
    33        mov     phymem_H,edx
    34 
    35  
    36 
    37        popfd
    38 
    39        popad
    40 
    41        ;手动还原
    42 
    43        mov     esp,ebp
    44 
    45       pop ebp
    46 
    47        retf   4
    48 
    49  
    50 
    51 Ring0Fuc   endp

    最后,通过一个远CALL来调用这个调用门:

     

    1 lea     edi,FarAddr
    2 
    3 push   _vaddr
    4 
    5 call   fword ptr [edi]
    6 
    7  

    通过亲手编码,可以对调用门、远调用等一些80386+保护模式中的概念在windows的实现中有了进一步的了解,不再像以前那样模棱两可了。看似全部写完了,其实中间还有很多可以挖掘出来扩展说的细节,但我现在已没有精力写了…( :( ),还要准备其他东西,结尾就用这个不是结尾的结尾,结尾吧(绕口令?)。:)

     

                                           

     

     

              大熊猫侯佩

                                2006.01.14 17:09 (机场)办公室

  • 相关阅读:
    jenkins+tomcat+python+pytest的web自动化化部署
    windows的jenkins+tomcat工作目录配置(这个要安装前就提前确定好,这种方法会导致整个jenkins重置)
    pytest xunit2 in pytest6.0
    Jenkins基本配置一
    React生命周期函数的使用场景
    React中的生命周期函数(老版本V16.0之前)
    React中ref的使用
    React中setState注意事项
    React之虚拟DOM中的Diff算法
    React之深入了解虚拟DOM
  • 原文地址:https://www.cnblogs.com/hopy/p/3829091.html
Copyright © 2020-2023  润新知