浅谈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 (机场)办公室

你可能感兴趣的:(方法)