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