(转)挂钩KeUsermodeCallback函数来实现自己的“财产保镖“

http://bbs.pediy.com/showthread.php?t=145687

作 者: liukeblue 
通过挂钩KeUserModeCallback这个未公开的函数可以实现对Ke_LoadLibrary、WH_KEYBOARD_LL等进行拦截,这个函数也可以用来在Ring0下调用Ring3代码,如果需要更进一步了解,可以去黑月教主的百度blog上去看看。下面我们来实现拦截dll注入的功能,解决两个问题:
一、  如何挂钩KeUserModeCallback函数,有两种方式IAT HOOK(QQ电脑管家)、inline HOOK(360保险箱)。
二、  如何拦截DLL注入,这个功能在KeUserModeCallback挂钩函数里实现。
实现的代码主要来源于对QQ电脑管家驱动文件TCSafeBox.sys的逆向分析,所以我尽量试着去还原TCSafeBox.sys的代码,核心代码如下:

  1 //////////////////////////////////////////////////////////////////////////////////////
  2 ULONG StartHook(IN PVOID fake_funcaddrss, OUT PULONG Original_funcaddrss)
  3 {
  4     ULONG    win32k_base;
  5     ULONG    result;
  6     if (fake_funcaddrss &&  Original_funcaddrss)
  7     {
  8         win32k_base = GetModuleBase("win32k.sys");
  9         if (win32k_base > 0)
 10             result = IATHook((PVOID)win32k_base,
 11                              "ntoskrnl.exe", 
 12                              "KeUserModeCallback",
 13                              fake_funcaddrss,
 14                              Original_funcaddrss);
 15     }
 16     else
 17         result = 0;
 18 
 19     return result;
 20 }
 21 
 22 //////////////////////////////////////////////////////////////////////////////////////
 23 ULONG GetModuleBase(IN PCHAR ModuleName)
 24 {
 25     ULONG        result;
 26     ULONG        dwNeedSize=0;
 27     NTSTATUS    status;
 28     PMODULES    pModules;
 29     int            i;
 30     char        imagename[255] = { 0 };
 31 
 32     ZwQuerySystemInformation(SystemModuleInformation, NULL, 0, &dwNeedSize);
 33     pModules = ExAllocatePoolWithTag(NonPagedPool, dwNeedSize,0);
 34     if (pModules)
 35     {
 36         memset(pModules, 0, dwNeedSize);   
 37         status = ZwQuerySystemInformation(SystemModuleInformation, pModules, dwNeedSize, NULL);
 38 
 39         if (NT_SUCCESS(status))
 40         {
 41             i = 0;
 42             while (i<pModules->dwNumberOfModules)
 43             {
 44                 strcpy(imagename, pModules->smi[i].ImageName + pModules->smi[i].ModuleNameOffset);
 45                 if (!strncmp(imagename, ModuleName, strlen(ModuleName)))
 46                 {
 47                     result = (ULONG)pModules->smi[i].Base;
 48                     break;
 49                 }
 50 
 51                 i++;
 52             }
 53         }
 54 
 55         ExFreePoolWithTag(pModules,0);
 56     }
 57 
 58     return result;
 59 }
 60 
 61 //////////////////////////////////////////////////////////////////////////////////////
 62 ULONG IATHook(IN PVOID ModlueBase,
 63     IN PCHAR ImportName,
 64     IN PCHAR ApiName,
 65     IN ULONG fakeFunctionAddr,
 66     OUT PULONG originalFuncAddr)
 67 {
 68     ULONG    reslut; 
 69     ULONG    size;
 70     PIMAGE_IMPORT_DESCRIPTOR    pImportModuleDirectory;
 71     DWORD    dwRVAModuleName;
 72     PCHAR    ModuleName;
 73     CHAR    RvAModuleNameIsZory;
 74     ULONG *    OriginalFirstThunk;
 75     ULONG *    FirstThunk;
 76     int        i;
 77     PIMAGE_IMPORT_BY_NAME Imageimportbyname;
 78     ULONG    result;
 79 
 80     result = 0;
 81     if (ModlueBase 
 82         && ImportName 
 83         && ApiName 
 84         && fakeFunctionAddr 
 85         && originalFuncAddr 
 86         && !KeGetCurrentIrql())
 87     {
 88         __try
 89         {
 90             size = 0;
 91             pImportModuleDirectory = (PIMAGE_IMPORT_DESCRIPTOR)RtlImageDirectoryEntryToData(ModlueBase,
 92                             TRUE,
 93                             IMAGE_DIRECTORY_ENTRY_IMPORT,
 94                             &size);
 95             if (pImportModuleDirectory)
 96             {
 97                 while (pImportModuleDirectory && pImportModuleDirectory->Name)
 98                 {
 99                     dwRVAModuleName = pImportModuleDirectory->Name;        //模块的dll名称
100                     RvAModuleNameIsZory = ((CHAR *)ModlueBase + dwRVAModuleName) == 0;
101                     ModuleName = (CHAR *)ModlueBase + dwRVAModuleName;
102 
103                     //先找到模块,再在模块里查找函数
104                     if (!RvAModuleNameIsZory 
105                         && !_strnicmp(ModuleName, ImportName, sizeof(ImportName)))
106                     {
107                         //得到输入表结构里指向INT和IAT的VA
108                         OriginalFirstThunk = (ULONG *)((CHAR *)ModlueBase + 
109                                              pImportModuleDirectory->OriginalFirstThunk);
110                         FirstThunk = (ULONG *)((CHAR *)ModlueBase + pImportModuleDirectory->FirstThunk);
111 
112                         for (i = 0; FirstThunk[i]; i++)
113                         {
114                             Imageimportbyname =  OriginalFirstThunk[i];    
115 
116                             if (Imageimportbyname < (ULONG)ModlueBase)
117                                 Imageimportbyname += (ULONG)ModlueBase;
118 
119                             if (Imageimportbyname)
120                             {
121                                 //以函数名称方式输入
122                                 if ( !_strnicmp((PCHAR)&Imageimportbyname->Name[0], ApiName, 
123                                             strlen(ApiName))
124                                     && MmIsAddressValid(FirstThunk[i]))
125                                 {
126                                     //这里请注意Imageimportbyname->Name得到的是函数名称的首字母*/
127                                     DbgPrint("IAT ENTRY=0x%x,IatAddress=0x%X, 
128                                         funcname=[%s]----hookfunc=[%s]\n",
129                                         (ULONG *)((CHAR *)ModlueBase + 
130                                             pImportModuleDirectory->FirstThunk+i*4),
131                                         FirstThunk[i],
132                                         (PCHAR)&Imageimportbyname->Name[0],ApiName);
133 
134                                     *originalFuncAddr = (ULONG)FirstThunk[i];
135                                     reslut = HookFunc(&FirstThunk[i], fakeFunctionAddr);
136 
137                                     goto Exit;
138                                 }
139                             }
140                         }
141 
142                         break;
143                     }
144 
145                     pImportModuleDirectory++;
146                 }
147             }
148         }
149         __except(EXCEPTION_EXECUTE_HANDLER)
150         {
151             reslut = 0;
152         }
153     }
154     else
155         reslut =0;
156 
157 Exit:  
158     return result;
159 }
160 
161 //////////////////////////////////////////////////////////////////////////////////////
162 ULONG HookFunc(IN PVOID ImportFuncVA, IN ULONG fake_func)
163 {     
164     PMDL   ImportFuncThunkEntry_MDL;
165     PVOID  ImportFuncMapAddress;
166     PVOID  ImportFuncThunkEntry;     //导入表函数的THUNK地址--指针地址
167     ULONG  result;
168     BOOL   IsMapped;
169 
170     IsMapped = 0;
171 
172     if (ImportFuncVA && fake_func)
173     {
174         ImportFuncThunkEntry = (PVOID)ImportFuncVA;
175 
176         ImportFuncThunkEntry_MDL = IoAllocateMdl(ImportFuncThunkEntry, sizeof(ULONG), FALSE, FALSE, NULL);
177 
178         if (ImportFuncThunkEntry_MDL)
179         {         
180             MmProbeAndLockPages(ImportFuncThunkEntry_MDL, KernelMode, IoWriteAccess);
181             IsMapped = 1;
182             if (ImportFuncThunkEntry_MDL->MdlFlags 
183                 & (MDL_MAPPED_TO_SYSTEM_VA | MDL_SOURCE_IS_NONPAGED_POOL))
184                 ImportFuncMapAddress = ImportFuncThunkEntry_MDL->MappedSystemVa;
185             else
186                 ImportFuncMapAddress = MmMapLockedPagesSpecifyCache(ImportFuncThunkEntry_MDL,
187                                             KernelMode, MmCached,
188                                             NULL, NULL, NormalPagePriority);
189 
190             if (MmIsAddressValid(ImportFuncMapAddress))
191             {
192                 InterlockedExchangePointer(ImportFuncMapAddress, fake_func);
193                 result = 1;
194             } 
195         }
196 
197         if (IsMapped)
198             MmUnlockPages(ImportFuncThunkEntry_MDL);
199     }
200     else
201         result = 0;
202     
203     return result;
204 }
205 
206 //////////////////////////////////////////////////////////////////////////////////////
207 NTSTATUS fake_KeUserModeCallback(IN ULONG ApiNumber,
208     IN PVOID InputBuffer,
209     IN ULONG InputLength,
210     OUT ULONG OutputBuffer,
211     IN PULONG OutputLength)
212 {
213     UNICODE_STRING   uniDllPath = { 0 };
214     STRING           aniDLLPath = { 0 };
215     CHAR             outDllPath[MAX_PATH] = { 0 };
216     ULONG            PID;   
217     CHAR             FullPath[MAX_PATH] = { 0 };   //被注入DLL的进程全路径
218 
219     if (g_IsDLLDefendMon 
220         && !KeGetCurrentIrql()
221         && ApiNumber == LOAD_IMAGE_API_NUM
222         && InputLength >= LOAD_IMAGE_APINAME_OFFSET
223         && MmIsAddressValid(InputBuffer)
224         && InputBuffer
225         )
226     {
227         PID = (ULONG)PsGetCurrentProcessId();
228 
229         uniDllPath.Length = *(WORD *)((CHAR *)InputBuffer + LOAD_IMAGE_APINAMELENGTH_OFFSET);
230         uniDllPath.MaximumLength = *(WORD *)((CHAR *)InputBuffer +  LOAD_IMAGE_APINAMEMAXLENGTH_OFFSET);
231         uniDllPath.Buffer =  (PWSTR)((CHAR *)InputBuffer + LOAD_IMAGE_APINAME_OFFSET);  
232         //这里得到加载的dll的符号全路径
233 
234         aniDLLPath.Buffer =  (PCHAR)&outDllPath;
235         aniDLLPath.Length =  256;
236         aniDLLPath.MaximumLength = 256;
237 
238         RtlUnicodeStringToAnsiString(&aniDLLPath, &uniDllPath, FALSE);
239         GetFullPathFromPID(PID, &FullPath, 256);
240 
241         if (VoteModule(FullPath, outDllPath) == 1)
242             return STATUS_UNSUCCESSFUL;
243     }
244 
245     return g_KeUserModeCallback(ApiNumber, InputBuffer, InputLength, OutputBuffer, OutputLength);
246 }
247 
248 //////////////////////////////////////////////////////////////////////////////////////
249 BOOL  GetFullPathFromPID(IN ULONG PID, OUT CHAR * FullPath, IN ULONG FullPathLen)
250 {
251     WCHAR    ProcessPathw[MAX_PATH*2] = { 0 };
252     WCHAR    ProcessDosPathw[MAX_PATH * 2] = { 0 };
253     BOOL    result;
254     PWCHAR    NamePosW;
255     WCHAR    singleWchar;
256     ULONG    ResultSize;
257     ULONG    BytesInUnicodeString;
258 
259     result = 0;
260     if (FullPath)
261     {
262         result =  QueryProcessPathW(PID,&ProcessPathw,0x100);
263         if (result)
264         {
265             result = QueryProcessDosPathW(&ProcessPathw,&ProcessDosPathw,0x100);
266             if (result)
267             {
268                 //将读取出来得DOSnAME的WCHAR路径转化为CHAR路径
269                 NamePosW = (PWCHAR)&ProcessDosPathw; 
270                 do
271                 {
272                     singleWchar = *NamePosW;
273                     NamePosW++;
274                 } while(singleWchar);
275 
276                 BytesInUnicodeString = 2 * ((ULONG)((char *)singleWchar - &ProcessDosPathw) >> 1);
277 
278                 RtlUnicodeToMultiByteN(FullPath,
279                     FullPathLen - 1,
280                     &ResultSize,
281                     &ProcessDosPathw,
282                     BytesInUnicodeString);
283 
284                 result = 1;
285             }
286         }
287     }
288 
289     return result;
290 }
291 
292 //////////////////////////////////////////////////////////////////////////////////////
293 BOOL QueryProcessPathW(IN ULONG PID, OUT wchar_t * outPathW, IN size_t stringwlen)
294 {
295     PEPROCESS            eprocess = NULL;
296     NTSTATUS            status;
297     HANDLE                handle;
298     PVOID                pbuff;
299     DWORD                ReturnLength;
300     PUNICODE_STRING        puniimageFilename;
301     ULONG                result;
302 
303     result =0;
304     if (PID && outPathW)
305     {
306         status = PsLookupProcessByProcessId(PID, &eprocess);
307 
308         if (NT_SUCCESS(status))
309         {
310             status = ObOpenObjectByPointer(eprocess,
311                             OBJ_KERNEL_HANDLE,
312                             NULL,
313                             0,
314                             NULL,
315                             KernelMode,
316                             &handle);
317             if (NT_SUCCESS(status))
318             {
319                 pbuff =  ExAllocatePoolWithTag(NonPagedPool, 0x800, 0);
320                 if (pbuff)
321                 {
322                     memset(pbuff, 0, 0x800u);
323 
324                     status = ZwQueryInformationProcess(handle,
325                                     ProcessImageFileName,
326                                     pbuff,
327                                     0x800,
328                                     &ReturnLength);
329                     if (NT_SUCCESS(status))
330                     {
331                         puniimageFilename = (PUNICODE_STRING)pbuff;
332 
333                         if (puniimageFilename->Length > 0)
334                         {
335                             wcsncpy(outPathW, puniimageFilename->Buffer, stringwlen);
336                             result = 1;
337                         }        
338                     }
339 
340                     ExFreePoolWithTag(pbuff, 0);
341                 }
342             }
343         }
344 
345         if (handle)
346             ZwClose(handle);
347 
348         if (eprocess)
349             ObfDereferenceObject(eprocess);
350     }
351     else
352         result =0;
353 
354     return result;
355 }
356 
357 //////////////////////////////////////////////////////////////////////////////////////
358 HANDLE MyOpenFile(IN PCWSTR ProcessPathw)
359 {
360     HANDLE                FileHandle;
361     OBJECT_ATTRIBUTES    oa;
362     IO_STATUS_BLOCK        iosb = { 0 };
363     UNICODE_STRING        uniProcessPath = { 0 };
364     NTSTATUS            status;
365     int                    result;
366 
367     result = 0;
368     if (ProcessPathw)
369     {
370         RtlInitUnicodeString(&uniProcessPath, ProcessPathw);
371         InitializeObjectAttributes(&oa,
372                 &uniProcessPath,
373                 OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
374                 NULL,
375                 NULL);
376         status = IoCreateFile(&FileHandle,
377             GENERIC_READ,
378             &oa,
379             &iosb,
380             0,
381             FILE_ATTRIBUTE_NORMAL,
382             FILE_SHARE_READ,
383             FILE_OPEN,
384             FILE_NON_DIRECTORY_FILE|FILE_SYNCHRONOUS_IO_NONALERT,
385             NULL,
386             0,
387             CreateFileTypeNone,
388             NULL,
389             IO_NO_PARAMETER_CHECKING);
390 
391         if (NT_SUCCESS(status))
392             result = FileHandle;
393     }
394 
395     return result; 
396 }
397 
398 //////////////////////////////////////////////////////////////////////////////////////
399 BOOL QueryProcessDosPathW(IN PWSTR ProcessPathw,
400     OUT PWSTR ProcessDosPathw,
401     IN size_t stringlen)
402 {
403     HANDLE            hFile;
404     NTSTATUS        status;
405     PFILE_OBJECT    fileobj;
406     size_t            dosstrlen;
407     int                result;
408     POBJECT_NAME_INFORMATION    ObjectNameInformation;
409 
410     result = 0;
411     hFile = MyOpenFile(ProcessPathw);
412     if (hFile)
413     {  
414         status = ObReferenceObjectByHandle(hFile, 0, *IoFileObjectType, KernelMode, &fileobj, NULL);
415 
416         if (NT_SUCCESS(status))
417         {
418             status = IoQueryFileDosDeviceName(fileobj, &ObjectNameInformation);
419 
420             if (NT_SUCCESS(status))
421             {
422                 dosstrlen = ObjectNameInformation->Name.Length;
423                 if (dosstrlen < 2 * stringlen)
424                 {
425                     memcpy(ProcessDosPathw, ObjectNameInformation->Name.Buffer, dosstrlen);
426                     result = 1;  
427                 }
428             }
429         }
430     }
431 
432     if (fileobj)
433         ObfDereferenceObject(fileobj);
434     
435     if (hFile)
436         ZwClose(hFile);
437     
438     if (ObjectNameInformation)
439         ExFreePoolWithTag(ObjectNameInformation, 0);
440     
441     return result;
442 }
443 
444 //////////////////////////////////////////////////////////////////////////////////////
445 //这里只是做简单路径比较,如果某些进程伪造自己的路径,则无法拦截,因此QQ管家还进行PID的判断
446 ULONG VoteModule(IN PCHAR FullProcessPath, IN PCHAR FullDllPath)
447 {
448     ULONG    reslut;
449     int        i;
450     CHAR    FDLLPATH[MAX_PATH] = { 0 };
451 
452     reslut=1;  //如果不是要保护的进程则返回
453     if (_strnicmp(FullProcessPath, g_Protectpath, 256) != 0)
454     {
455         reslut = 3;
456         return  reslut;
457     }
458 
459     strncpy(FDLLPATH,FullDllPath,256);
460     _strupr(FDLLPATH);
461 
462     for (i = 0; i < 10; i++)
463     {
464         if (strstr(FDLLPATH, SystemDllPath[i]))  //该可疑模块是正常的系统模块
465         {
466             reslut = 2;
467             break;
468         } 
469     }
470 
471     if (reslut == 2)
472         return reslut;
473     
474     //如果是可疑模块将注入我们要保护的进程
475     DbgPrint("PREVENT Process Info:[ProcessPath=%s,DLLPath=%s\n]", FullProcessPath, FullDllPath);
476 
477     return reslut;
478 }

 

这里有几点注意:
1、由于挂钩的是win32k.sys的导入函数KeUserModeCallback, 在DriverEntry例程是无法挂钩,因为DriverEntry例程是在system内核线程上下文里, 因为system进程的 SessionId=none,因此在system内核线程是无法访问win32k.sys内存空间,因此我在进行IAT挂钩KeUserModeCallback函数时,使用在IRP_DEVICE_CONTROL的例程,让其切换到一个GUI线程里,这样就可以访问win32k.sys空间,shadow ssdt挂钩必须从system内核线程切换到别的线程上下文里也是这个原因。

2、 KeUserModeCallback 函数的参数 ApiNumber= LOAD_IMAGE_API_NUM 时,是指进程触发 LoadLibrary DLL的状态,通过InputBuffer参数指针可以得到LoadLibrary DLL的名称。 这里注意我是使用的XP SP3系统,使用不同操作系统LOAD_IMAGE_API_NUM,以及 InputBuffer参数偏移到dll名称的距离值是有所不同的,这个请读者注意。

3、源代码里将Unicode string转换为PCHAR的函数,是逆向所得,原来的qq管家回将拦截的信息传出来由用户判定,这里我直接拦截阻止,并且只是对explorer.exe进程(桌面进程)进行判定。需要详细了解可见源代码。下面我使用自己写的一个防止拷贝的程序,dll通过Setwindowhookex来挂钩全局钩子,通过挂钩入explorer.exe来防止拷贝,最后使用上面写好的取得进行拦截,编程调试环境:

Wdk7600+WinXP 3p3,下面是效果示意图:
禁止拷贝ustopcpy32.dll注入explorer.exe
禁止拷贝ustopcpy32.dll注入explorer.exe
图4
开启所写的“财产保护“阻止禁止拷贝dll注入explorer.exe
实验结果表明该dll已经无法注入explorer.exe,已经被自己写的驱动TsSafeBox. sys成功拦截阻止。
文章到这里就该结束了,但是有几点问题想要说明一下,其实你可以自己建立策略,也就是黒白名单,这只不过需要你建立链表而已;还有这只是QQ电脑管家的“财产保护 “的一部分,QQ电脑管家对网页、一些支付宝等安全插件进行过滤,这个我将继续的逆向分析qq电脑管家;还有就是本人比较懒,应用层与驱动层之间同步通信没写,有兴趣的读者可以继续完善。

posted @ 2012-12-20 14:45  himessage  阅读(1881)  评论(0编辑  收藏  举报