欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

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

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

通过挂钩KeUserModeCallback这个未公开的函数可以实现对Ke_LoadLibrary、WH_KEYBOARD_LL等进行拦截,这个函数也可以用来在Ring0下调用Ring3代码,如果需要更进一步了解,可以去黑月教主的百度blog上去看看。下面我们来实现拦截dll注入的功能,解决两个问题:
一、  如何挂钩KeUserModeCallback函数,有两种方式IAT HOOK(QQ电脑管家)、inline HOOK(360保险箱)。
二、  如何拦截DLL注入,这个功能在KeUserModeCallback挂钩函数里实现。
实现的代码主要来源于对QQ电脑管家驱动文件TCSafeBox.sys的逆向分析,所以我尽量试着去还原TCSafeBox.sys的代码,核心代码如下:
ULONG StartHook(IN  PVOID fake_funcaddrss,OUT PULONG Original_funcaddrss)
{
    ULONG win32k_base; 
  ULONG result;  
   if (fake_funcaddrss &&  Original_funcaddrss)
     {
        win32k_base = GetModuleBase("win32k.sys");
      if ( win32k_base>0 )                                  
       result = IATHook((PVOID)win32k_base,"ntoskrnl.exe", "KeUserModeCallback",fake_funcaddrss,Original_funcaddrss);  
   }
   else
      result = 0;
   return result;  
}
ULONG GetModuleBase(IN PCHAR ModuleName)
{
   ULONG result;
   ULONG   dwNeedSize=0;
   NTSTATUS status;
   PMODULES pModules;
   int i;
   char imagename[255]={0};
  ZwQuerySystemInformation(SystemModuleInformation,NULL,0,&dwNeedSize);
  pModules = ExAllocatePoolWithTag(NonPagedPool,dwNeedSize,0);
  if (pModules)
  {
    memset(pModules,0,dwNeedSize);    
    status = ZwQuerySystemInformation(SystemModuleInformation,pModules,dwNeedSize,NULL);
  
  if (NT_SUCCESS(status))
  {
     
      i = 0;
    while ( i<pModules->dwNumberOfModules )
      {
        strcpy(imagename,pModules->smi[i].ImageName + pModules->smi[i].ModuleNameOffset);
      if (!strncmp(imagename,ModuleName,strlen(ModuleName)))
      {
          result = (ULONG)pModules->smi[i].Base;
        break;
    }
    
      i++;
    }
  }
     ExFreePoolWithTag(pModules,0);
  } 
return result; 
}
ULONG IATHook(IN PVOID ModlueBase,IN PCHAR ImportName,IN PCHAR ApiName,IN ULONG fakeFunctionAddr,OUT PULONG originalFuncAddr)
{
   ULONG reslut;  
   ULONG size;
   PIMAGE_IMPORT_DESCRIPTOR pImportModuleDirectory; 
   DWORD dwRVAModuleName;
   PCHAR ModuleName;
   CHAR RvAModuleNameIsZory;
   ULONG *OriginalFirstThunk; 
   ULONG *FirstThunk; 
   int i;
   PIMAGE_IMPORT_BY_NAME Imageimportbyname;
   ULONG result;
   result = 0;
   if (ModlueBase && ImportName && ApiName && fakeFunctionAddr && originalFuncAddr && !KeGetCurrentIrql())
   {
  __try
  {
     size = 0;
     pImportModuleDirectory = (PIMAGE_IMPORT_DESCRIPTOR)RtlImageDirectoryEntryToData(ModlueBase,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);   
  
     if (pImportModuleDirectory)
     {
        while (pImportModuleDirectory && pImportModuleDirectory->Name)
        {
            dwRVAModuleName = pImportModuleDirectory->Name;    //模块的dll名称
          RvAModuleNameIsZory = ((CHAR *)ModlueBase + dwRVAModuleName) == 0; 
            ModuleName = (CHAR *)ModlueBase + dwRVAModuleName;
          
        //先找到模块,再在模块里查找函数 
        if (!RvAModuleNameIsZory && !_strnicmp(ModuleName,ImportName,sizeof(ImportName)))
              {
                   //得到输入表结构里指向INT和IAT的VA
           OriginalFirstThunk    = (ULONG *)((CHAR *)ModlueBase + pImportModuleDirectory->OriginalFirstThunk);
           FirstThunk            = (ULONG *)((CHAR *)ModlueBase + pImportModuleDirectory->FirstThunk);
                 
             for (i=0;FirstThunk[i];i++)
             {
                Imageimportbyname =  OriginalFirstThunk[i];     
            if ( Imageimportbyname < (ULONG)ModlueBase )
                Imageimportbyname += (ULONG)ModlueBase;
              if (Imageimportbyname)
              {
              //以函数名称方式输入
              if ( !_strnicmp((PCHAR)&Imageimportbyname->Name[0],ApiName,strlen(ApiName))
                && MmIsAddressValid(FirstThunk[i]))
                 {       
                                 
              /* DbgPrint("i=%d,funcname=[%s]----hookfunc=[%s]\n",i,(PCHAR)&Imageimportbyname->Name[0],ApiName);
                  这里请注意Imageimportbyname->Name得到的是函数名称的首字母*/
                DbgPrint("IAT ENTRY=0x%x,IatAddress=0x%X,funcname=[%s]----hookfunc=[%s]\n",(ULONG *)((CHAR *)ModlueBase + pImportModuleDirectory->FirstThunk+i*4),FirstThunk[i],(PCHAR)&Imageimportbyname->Name[0],ApiName);
                   
                *originalFuncAddr = (ULONG)FirstThunk[i];
                reslut = HookFunc(&FirstThunk[i],fakeFunctionAddr);
                              goto Exit;                   
               
             }                                             
            }                              
           }
           break;
        }
       
         pImportModuleDirectory++; 
      }
     }
  }
    __except(EXCEPTION_EXECUTE_HANDLER)
  {
         reslut =0;
  }
   }
   else
     reslut =0;
Exit:   
  return result;
}

ULONG HookFunc(IN PVOID ImportFuncVA,IN ULONG fake_func)
{      
   PMDL   ImportFuncThunkEntry_MDL; 
   PVOID  ImportFuncMapAddress; 
   PVOID  ImportFuncThunkEntry;     //导入表函数的THUNK地址--指针地址 
   ULONG  result;
   BOOL   IsMapped;
   IsMapped = 0;
  
  if (ImportFuncVA && fake_func)
  {
      ImportFuncThunkEntry = (PVOID)ImportFuncVA;
      ImportFuncThunkEntry_MDL = IoAllocateMdl(ImportFuncThunkEntry,sizeof(ULONG),FALSE,FALSE,NULL);
    
      if (ImportFuncThunkEntry_MDL)
      {          
      MmProbeAndLockPages(ImportFuncThunkEntry_MDL,KernelMode,IoWriteAccess);
      IsMapped =1;
      if (ImportFuncThunkEntry_MDL->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA|MDL_SOURCE_IS_NONPAGED_POOL))
        ImportFuncMapAddress = ImportFuncThunkEntry_MDL->MappedSystemVa;
      else
        ImportFuncMapAddress = MmMapLockedPagesSpecifyCache(ImportFuncThunkEntry_MDL,KernelMode,MmCached,NULL,NULL,NormalPagePriority); 
           if ( MmIsAddressValid(ImportFuncMapAddress) )
       {
          InterlockedExchangePointer(ImportFuncMapAddress,fake_func);
          result = 1;  
       }  
    }
    if (IsMapped)
        MmUnlockPages(ImportFuncThunkEntry_MDL);
  }else
   result = 0;
  return result;
}
NTSTATUS fake_KeUserModeCallback(IN ULONG ApiNumber,IN PVOID InputBuffer,IN ULONG InputLength,OUT ULONG OutputBuffer,IN PULONG OutputLength)
{
  UNICODE_STRING   uniDllPath={0};
  STRING           aniDLLPath={0};
  CHAR             outDllPath[MAX_PATH]={0};
  ULONG            PID;    
  CHAR             FullPath[MAX_PATH]={0};   //被注入DLL的进程全路径
   
  if (g_IsDLLDefendMon &&
    !KeGetCurrentIrql() &&
      ApiNumber == LOAD_IMAGE_API_NUM &&
    InputLength >= LOAD_IMAGE_APINAME_OFFSET &&
    MmIsAddressValid(InputBuffer)  &&
    InputBuffer
     )
   {
       PID = (ULONG)PsGetCurrentProcessId();
     uniDllPath.Length = *(WORD *)((CHAR *)InputBuffer + LOAD_IMAGE_APINAMELENGTH_OFFSET);
     uniDllPath.MaximumLength = *(WORD *)((CHAR *)InputBuffer +  LOAD_IMAGE_APINAMEMAXLENGTH_OFFSET);
     uniDllPath.Buffer =  (PWSTR)((CHAR *)InputBuffer + LOAD_IMAGE_APINAME_OFFSET);  //这里得到加载的dll的符号全路径
     
     aniDLLPath.Buffer =  (PCHAR)&outDllPath;
     aniDLLPath.Length =  256;
     aniDLLPath.MaximumLength = 256;
     
     RtlUnicodeStringToAnsiString(&aniDLLPath, &uniDllPath, FALSE); 
     GetFullPathFromPID(PID,&FullPath,256);
       if (VoteModule(FullPath,outDllPath)==1)
      return STATUS_UNSUCCESSFUL; 
  }
 return g_KeUserModeCallback(ApiNumber,InputBuffer,InputLength,OutputBuffer,OutputLength);
}
BOOL  GetFullPathFromPID(IN ULONG PID,OUT CHAR *FullPath,IN ULONG FullPathLen)
{
  WCHAR  ProcessPathw[MAX_PATH*2]={0};
    WCHAR  ProcessDosPathw[MAX_PATH*2]={0};
  BOOL   result;
  PWCHAR NamePosW;
  WCHAR  singleWchar;
    ULONG  ResultSize;
    ULONG  BytesInUnicodeString;
  result = 0;
    if (FullPath)
    {
    
    result =  QueryProcessPathW(PID,&ProcessPathw,0x100);
    if (result)
    {
       result = QueryProcessDosPathW(&ProcessPathw,&ProcessDosPathw,0x100);
     if (result)
     {
        //将读取出来得DOSnAME的WCHAR路径转化为CHAR路径
        NamePosW = (PWCHAR)&ProcessDosPathw;  
        do 
        {
           singleWchar = *NamePosW;
         NamePosW++;
      } while(singleWchar);
           BytesInUnicodeString = 2 * ((ULONG)((char *)singleWchar - &ProcessDosPathw) >> 1);
      
           RtlUnicodeToMultiByteN(FullPath, 
                            FullPathLen - 1,
                            &ResultSize,
                  &ProcessDosPathw,     
                            BytesInUnicodeString); 
   
       result = 1;
     }            
    }
  }
return result;
}
BOOL QueryProcessPathW(IN ULONG PID,OUT wchar_t *outPathW,IN size_t stringwlen)
{
   PEPROCESS eprocess= NULL;
   NTSTATUS  status;
   HANDLE    handle;
   PVOID     pbuff;
   DWORD     ReturnLength;
   PUNICODE_STRING  puniimageFilename;
   ULONG     result;
   
   result =0;
   if (PID && outPathW)
   {
      status = PsLookupProcessByProcessId(PID,&eprocess);    
    if (NT_SUCCESS(status))
    {
       status = ObOpenObjectByPointer(eprocess,OBJ_KERNEL_HANDLE,NULL,0,NULL,KernelMode,&handle);
       
     if (NT_SUCCESS(status))
     {
        pbuff =  ExAllocatePoolWithTag(NonPagedPool,0x800,0);
      
      if (pbuff)
      {
         memset(pbuff, 0, 0x800u);
         status = ZwQueryInformationProcess(handle,ProcessImageFileName,pbuff,0x800,&ReturnLength);
         
         if (NT_SUCCESS(status))
         {     
            puniimageFilename = (PUNICODE_STRING)pbuff;
          
          if (puniimageFilename->Length >0)
          {
             wcsncpy(outPathW,puniimageFilename->Buffer,stringwlen);
             result = 1;          
          
          }         
         }
          ExFreePoolWithTag(pbuff,0); 
      }     
     }  
    }
    
     if (handle)
       ZwClose(handle);
   if (eprocess)
     ObfDereferenceObject(eprocess);
   }else
     result =0;
   return result;
}
HANDLE MyOpenFile(IN PCWSTR  ProcessPathw)
{
  HANDLE FileHandle;
  OBJECT_ATTRIBUTES oa;
  IO_STATUS_BLOCK   iosb={0}; 
  UNICODE_STRING uniProcessPath={0};
  NTSTATUS status;
  int result;
  result = 0;
  if (ProcessPathw)
  {
       RtlInitUnicodeString(&uniProcessPath,ProcessPathw);
     InitializeObjectAttributes(&oa,&uniProcessPath,OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,NULL,NULL);
     
       status = IoCreateFile(&FileHandle,
                         GENERIC_READ,
               &oa,
               &iosb,
               0,
               FILE_ATTRIBUTE_NORMAL,
               FILE_SHARE_READ,
               FILE_OPEN,
               FILE_NON_DIRECTORY_FILE|FILE_SYNCHRONOUS_IO_NONALERT,
                             NULL,
               0,
               CreateFileTypeNone,
               NULL,
                             IO_NO_PARAMETER_CHECKING);
  
    if (NT_SUCCESS(status))
      result = FileHandle; 
  }
 return result;  
}
BOOL QueryProcessDosPathW(IN PWSTR ProcessPathw,OUT PWSTR ProcessDosPathw,IN size_t stringlen)
{
  HANDLE hFile;
  NTSTATUS status;
  PFILE_OBJECT  fileobj;
  POBJECT_NAME_INFORMATION ObjectNameInformation;
  size_t  dosstrlen; 
  int result;
  result = 0;
  hFile = MyOpenFile(ProcessPathw);
  if (hFile)
  {   
    status = ObReferenceObjectByHandle(hFile,0,*IoFileObjectType,KernelMode,&fileobj,NULL);
      
    if (NT_SUCCESS(status))
      {
         status = IoQueryFileDosDeviceName(fileobj,&ObjectNameInformation); 
       
     if (NT_SUCCESS(status))
     {
        dosstrlen = ObjectNameInformation->Name.Length; 
        if (dosstrlen < 2*stringlen)
      {
           memcpy(ProcessDosPathw,ObjectNameInformation->Name.Buffer,dosstrlen);
           result = 1;   
      }
     }
    }
  }
  if ( fileobj )
    ObfDereferenceObject(fileobj);
  if ( hFile )
    ZwClose(hFile);
  if ( ObjectNameInformation )
    ExFreePoolWithTag(ObjectNameInformation, 0);
 return result;
}
//这里只是做简单路径比较,如果某些进程伪造自己的路径,则无法拦截,因此QQ管家还进行PID的判断
ULONG VoteModule(IN PCHAR FullProcessPath,IN PCHAR FullDllPath)
{
  ULONG reslut;
  int i;
  CHAR FDLLPATH[MAX_PATH]={0}; 
  reslut=1;  //如果不是要保护的进程则返回
    if (_strnicmp(FullProcessPath,g_Protectpath,256)!=0)
  {
      reslut = 3;
    return  reslut;
  }
  strncpy(FDLLPATH,FullDllPath,256); 
  _strupr(FDLLPATH);
 
  for (i=0;i<10;i++)
  {
     if (strstr(FDLLPATH,SystemDllPath[i]))  //该可疑模块是正常的系统模块
     {
       reslut = 2; 
     break;
   }  
  }   
  if (reslut==2)
    return  reslut;
  //如果是可疑模块将注入我们要保护的进程
  DbgPrint("PREVENT Process Info:[ProcessPath=%s,DLLPath=%s\n]",FullProcessPath, FullDllPath);      
 return reslut;
}  
这里有几点注意: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通过SetWindowsHookEx来挂钩全局钩子,通过挂钩入Explorer.exe来防止拷贝,最后使用上面写好的取得进行拦截,编程调试环境:
开启所写的“财产保护“阻止禁止拷贝dll注入explorer.exe图4  
实验结果表明该dll已经无法注入explorer.exe,已经被自己写的驱动TsSafeBox.sys成功拦截阻止。
   文章到这里就该结束了,但是有几点问题想要说明一下,其实你可以自己建立策略,也就是黑白名单,这只不过需要你建立链表而已;还有这只是QQ电脑管家的“财产保护“ 的一部分,QQ电脑管家对网页、一些支付宝等安全插件进行过滤,这个我将继续的逆向分析QQ电脑管家;还有就是本人比较懒,应用层与驱动层之间同步通信没写,有兴趣的读者可以继续完善!
本文已发表在黑客防线2011第9期上。

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功