通过挂钩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期上。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!