首页
社区
课程
招聘
[原创]挂钩KeUsermodeCallback函数来实现自己的“财产保镖“
发表于: 2012-1-19 13:06 18344

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

2012-1-19 13:06
18344

通过挂钩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期上。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
最新回复 (14)
雪    币: 12
活跃值: (773)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
黑防是不是倒闭了??
2012-1-19 13:22
0
雪    币: 603
活跃值: (40)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
3
先占座。。。。。。
2012-1-19 13:35
0
雪    币: 13621
活跃值: (3957)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
膜拜      
2012-1-19 13:59
0
雪    币: 379
活跃值: (152)
能力值: ( LV12,RANK:330 )
在线值:
发帖
回帖
粉丝
5
黑防没有倒闭,有兴趣去他网站看看
2012-1-19 14:15
0
雪    币: 1015
活跃值: (235)
能力值: ( LV12,RANK:440 )
在线值:
发帖
回帖
粉丝
6
先占位,再看贴。
2012-1-19 14:38
0
雪    币: 210
活跃值: (221)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
7
写得不错 赞一个
2012-1-19 21:31
0
雪    币: 1737
活跃值: (110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
现在还看不懂呢,慢慢来吧~~
2012-1-21 08:14
0
雪    币: 219
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
marking /.......
2012-1-24 15:20
0
雪    币: 878
活跃值: (496)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
好。。。。
KeXXXXXXXXX
2012-1-24 21:32
0
雪    币: 220
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
不错,看一下
2012-1-25 18:35
0
雪    币: 639
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
好多概念不懂啊,HOOK,上下文切换....不过代码量好少,编程这么多年,渐渐觉得程序有些意思了哈哈,继续学习
2012-1-30 11:15
0
雪    币: 22
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
不太懂,留个记号
2012-1-30 22:07
0
雪    币: 949
活跃值: (18)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
14
mark一下
2012-1-31 12:19
0
雪    币: 342
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
膜拜啊 能提供完整代码研究下吗?
2012-2-14 12:10
0
游客
登录 | 注册 方可回帖
返回
//