您的位置:首页 > 运维架构 > 网站架构

360HOOK表,Hook过滤架构搭建

2017-10-31 23:19 127 查看
分析了一下360的HOOK,通过直接hook KiFastCallEntry实现以所有系统调用的过滤。
我分析的版本如下:
主程序版本: 6.0.1.1003
HookPort.sys版本: 1, 0, 0, 1005
HookPort.sys的TimeStamp: 4A8D4AB8

简单说明:360把所有被hook的系统服务的过滤函数放在了一个表里,索引即对应的系统服务在该过滤函数表中的索引。
所有列出来的函数都会被hook掉的,是否处理指某个系统服务有没有相应的过滤函数进行处理,拒绝还是放行就是在过滤函数中完成判断的。
不处理的系统服务,将会直接调用原始服务例程。
函数如下:
服务名称 索引 是否处理 备注
==============================================================================
NtCreateKey 0x00 否
NtQueryValueKey 0x01 是
NtDeleteKey 0x02 是
NtDeleteValueKey 0x03 是
NtRenameKey 0x04 是
NtReplaceKey 0x05 是
NtRestoreKey 0x06 是
NtSetValueKey 0x07 是
NtCreateFile 0x08 是
NtFsControl 0x09 是
NtSetInformationFile 0x0A 是
NtWriteFile 0x0B 是
NtWriteFileGather 0x0B 是 //和NtWriteFile共用一个过滤函数
NtCreateProcess 0x0D 是
NtCreateProcessEx 0x0E 是
NtCreateUserProcess 0x0F 是 //Only on Vista or later
NtCreateThread 0x10 是
NtCreateThreadEx 0x10 是 //和NtCreateThread共用一个过滤函数,for vista or later
NtOpenThread 0x11 是
NtDeleteFile 0x12 是
NtOpenFile 0x13 是
NtReadVirtualMemory 0x14 否
NtTerminateProcess 0x15 是
NtQueueApcThread 0x16 是
NtSetContextThread 0x17 是
NtSetInformationThread 0x18 否
NtProtectVirtualMemory 0x19 否
NtWriteVirtualMemory 0x1A 是
NtAdjustGroupToken 0x1B 否
NtAdjustPrivilegesToken 0x1C 否
NtRequestWaitReplyPort 0x1D 是
NtCreateSection 0x1E 是
NtOpenSecton 0x1F 是
NtCreateSymbolicLinkObject 0x20 是
NtOpenSymbolicLinkObject 0x21 否
NtLoadDriver 0x22 是
NtUnloadDriver 0x22 是 //和NtLoadDriver共用一个过滤函数
NtQuerySystemInformation 0x23 是
NtSetSystemTime 0x25 否
NtSystemDebugControl 0x26 是
NtUserBuildHwndList 0x27 是
NtUserQueryWindow 0x28 是
NtUserFindWindowEx 0x29 是
NtUserWindowFromPoint 0x2A 是
NtUserMessageCall 0x2B 是
NtUserPostMessage 0x2C 是
NtUserSetWindowsHookEx 0x2D 是
NtUserPostThreadMessage 0x2E 是
NtOpenProcess 0x2F 是
NtDeviceIoControlFile 0x30 是
NtUserSetParent 0x31 是
NtOpenKey 0x32 是
NtDuplicateObject 0x33 是
NtResumeThread 0x34 否
NtUserChildWindowFromPointEx 0x35 是
NtUserDestroyWindow 0x36 是
NtUserInternalGetWindowText 0x37 否
NtUserMoveWindow 0x38 是 //和NtSetParent共用一个过滤函数
NtUserRealChildWindowFromPoint 0x39 是 //和NtUserChildWindowFromPointEx共用一个过滤函数
NtUserSetInformationThread 0x3A 否
NtUserSetInternalWindowPos 0x3B 是 //和NtSetParent共用一个过滤函数
NtUserSetWindowLong 0x3C 是 //和NtSetParent共用一个过滤函数
NtUserSetWindowPlacement 0x3D 是 //和NtSetParent共用一个过滤函数
NtUserSetWindowPos 0x3E 是 //和NtSetParent共用一个过滤函数
NtUserSetWindowRgn 0x3F 是 //和NtSetParent共用一个过滤函数
NtUserShowWindow 0x40 是
NtUserShowWindowAsync 0x41 是 //和NtUserShowWindow共用一个过滤函数
NtQueryAttributesFile 0x42 否
NtUserSendInput 0x43 否
NtAlpcSendWaitReceivePort 0x44 是 //for vista or later
NtUnmapViewOfSection 0x46 是
NtUserSetWinEventHook 0x47 否
NtSetSecurityObject 0x48 是
NtUserCallHwndParamLock 0x49 是
NtUserRegisterUserApiHok 0x4A 否

仿照了下360 的过滤架构,搭建了个Hook 框架,360的Hook架构的确很优秀,我觉得很值得我们学习与研究。这里我按照大牛们已经逆向出来的思路实现了下代码(都逆向出来了坐下代码工作不会怎么样吧?….只是学习架构)。不要鄙视我等代码工………,好吧大牛们想BS就BS吧,我表示毫无压力~~~~,我是菜鸟我怕谁!

废话不多说发代码,如果有错误和白痴的地方请指出,我水平有限…….
搭建这个架构大致需要以下几个模块,一是安装KiFastCallEntry的Hook模块,二是FakeKiFastCallEntry代理模块,三是SysCallFilter系统调用是否过滤的判断模块。其余的模块主要是过滤函数了,还有个获取KiFastCallEntry的patch地址的模块,最后是释放模块和初始化模块,这样大致的架构就搭建起来了。
接下来看看每个模块是怎么工作的。

首先是安装KiFastCallEntry的Hook模块,这个原理我就不多说了,大家都懂的
/************************************************************************
* 函数名称:HookKiFastCallEntry
* 功能描述:安装KiFastCallEntry钩子
* 参数列表:

* 返回值:状态
*************************************************************************/
NTSTATUS HookKiFastCallEntry()
{
NTSTATUS status=STATUS_SUCCESS;
if (!GetKiFastCallEntryPatchAddr())
{
KdPrint(("(HookKiFastCallEntry) GetKiFastCallEntryPatchAddr failed"));
return STATUS_UNSUCCESSFUL;
}
RtlCopyMemory(OriginalHead2,(PVOID)PatchAddr,5);
*(ULONG *)(ReplaceHead2+1)=(ULONG)FakeKiFastCallEntry-(PatchAddr+5);
KIRQL Irql;
Irql=WOFF();
//写入新的函数头
RtlCopyMemory((BYTE *)PatchAddr,ReplaceHead2,5);
WON(Irql);
return status;

}
这个模块有个地方就是GetKiFastCallEntryPatchAddr()获取KiFastCallEntry的Patch点这个有点小技巧,大家可以学习下,360是用SetEvent钩子栈回朔实现的,这个大家听了应该都能明白,就是调用函数时候会PUSH 返回到的EIP,这个EIP就是KiFastCallEntry中的了。
要patch的地方是按特征码搜索的,这个是xp sp3的
BOOL GetKiFastCallEntryPatchAddr()
{
ULONG ulCallNum;
PULONG pHookAddr;
PBYTE pCode;
ULONG i;
BOOL bRet=true;
KIRQL Irql;
hFakeEvent=(HANDLE)FakeHandle;
ulCallNum=*(PULONG)((PBYTE)ZwSetEvent+1);
pHookAddr=(PULONG)(pSysCallFilterInfo->ulSSDTAddr+ulCallNum*4);
RealNtSetEvent=*pHookAddr;//保存真实地址
Irql=WOFF();
*pHookAddr=(ULONG)FakeNtSetEvent; // 写入代理地址
WON(Irql);
ZwSetEvent(hFakeEvent,NULL);
Irql=WOFF();
*pHookAddr=RealNtSetEvent; // 写回真实地址
WON(Irql);
if (MmIsAddressValid((PVOID)BackTrackingAddr))
{
pCode=(PBYTE)BackTrackingAddr;
for (i=0;i<SearchByte;i++)
{
if (*(pCode-i)==0xe1&&*(pCode-i-1)==0x2b)
{
PatchAddr=(ULONG)(pCode-i-1);
break;
}
if (*(pCode-i)==0xfc&&*(pCode-i-1)==0x8b)
{
RetAddress=(ULONG)(pCode-i-1);
}

}
}
if (!PatchAddr||!RetAddress)
{
bRet=false;
}
return bRet;
}

这个代理函数里面获取EIP
NTSTATUS FakeNtSetEvent (
__in HANDLE EventHandle,
__out_opt PLONG PreviousState
)
{
NTSTATUS status=STATUS_SUCCESS;
if (EventHandle!=hFakeEvent||ExGetPreviousMode()==UserMode)// 不是自己调用,或者调用来自UserMode,直接调用原函数
{
status=((NTSETEVENT)RealNtSetEvent)(&EventHandle, PreviousState);
}
else
{
_asm
{
mov eax,dword ptr [ebp+4h]
mov BackTrackingAddr,eax
}
}
return status;
}

安装好Hook后就是Hook的代理函数了
这段代码 ……..好吧被BS咱也莫有办法,代理函数传入三个参数,这三个参数的含义可以参考内核情景分析一书中有详细介绍,给SysCallFileter来判断是否过滤。
_declspec (naked) NTSTATUS FakeKiFastCallEntry()
{
_asm
{
mov edi,edi
pushfd
pushad
push edi
push ebx
push eax
call SysCallfilter
mov dword ptr [esp+10h],eax
popad
popfd
sub esp, ecx
shr ecx, 2
push RetAddress
retn

}
}

接下来就是判断过滤的函数了,这里我略去了SHADOW SSDT,这段代码也……
/************************************************************************
* 函数名称:SysCallfilter
* 功能描述:过滤系统调用
* 参数列表:
ULONG SysCallNum:系统调用号
ULONG FunAddr:系统调用函数入口地址
ULONG ServiceBase:系统调用表指针
* 返回值:过滤则返回代理函数地址,否则返回真实地址
*************************************************************************/
ULONG SysCallfilter(ULONG SysCallNum,ULONG FunAddr,ULONG ServiceBase)
{

if( ServiceBase==pSysCallFilterInfo->ulSSDTAddr&&SysCallNum<=pSysCallFilterInfo->ulSSDTNum)
{
if(pSysCallFilterInfo->SSDTSwitchTable[SysCallNum]&&HookOrNot(SysCallNum,FALSE))
{
return pSysCallFilterInfo->ProxySSDTTable[SysCallNum];//
}
}
return FunAddr;

}

这个模块可以考虑添加适当的过滤规则,但最好效率点,这里我没加什么过滤,主要是搭建框架。

/************************************************************************
* 函数名称:HookOrNot
* 功能描述:判断是否过滤系统调用
* 参数列表:
ULONG SysCallNum:系统调用号
BOOL Flags:SSDT还是SDOWSSDT标志
* 返回值:返回表示不过滤,表示过滤
*************************************************************************/
ULONG HookOrNot(ULONG SysCallNum,BOOL Flags)
{
if (ExGetPreviousMode()==KernelMode)
{
return 0;
}
if (Flags)
{
return 1;
}
else
return 1;
}

好了基本功能模块搭建好了,现在就要初始化这些模块内所要使用的数据结构,来运作起来。
初始化里面我直接把savessdttable原始函数表填充为文件获取的原始地址表了,这里大家可以不必这么做。
/************************************************************************
* 函数名称:InitSysCallFilter
* 功能描述:初始化系统调用过滤
* 参数列表:

* 返回值:状态
*************************************************************************/
NTSTATUS InitSysCallFilter()
{
NTSTATUS status=STATUS_SUCCESS;
PVOID FileBuffer,FunBuffer;
ULONG ulSSDTLimit;
PKSERVICE_TABLE_DESCRIPTOR pServiceDescriptor;
//init

//Init SysCallFilterInfo buffer
pSysCallFilterInfo=(PSYSCALL_FILTER_INFO_TABLE)ExAllocatePoolWithTag(
NonPagedPool,
sizeof(SYSCALL_FILTER_INFO_TABLE),
MM_TAG_FILT);
RtlZeroMemory(pSysCallFilterInfo,sizeof(SYSCALL_FILTER_INFO_TABLE));
//Init SSDT address
pServiceDescriptor=(PKSERVICE_TABLE_DESCRIPTOR)GetKeServiceDescriptorTable();
pSysCallFilterInfo->ulSSDTAddr=(ULONG)pServiceDescriptor->Base;
//Init SSDT Table

FileBuffer=ExAllocatePoolWithTag(NonPagedPool,(SSDT_MAX_NUM)*sizeof(ULONG),MM_TAG_FILT);
FunBuffer=ExAllocatePoolWithTag(NonPagedPool,(SSDT_MAX_NUM)*sizeof(ULONG),MM_TAG_FILT);
if (!FileBuffer||!FunBuffer)
{
KdPrint(("(InitSysCallFilter) MmBuffer FunBuffer failed"));
return STATUS_UNSUCCESSFUL;
}
status=EnumOriginalSSDT(FileBuffer,FunBuffer,&ulSSDTLimit);
if (!NT_SUCCESS(status))
{
KdPrint(("(InitSysCallFilter) EnumOriginalSSDT failed"));
ExFreePool(FileBuffer);
ExFreePool(FunBuffer);
return STATUS_UNSUCCESSFUL;
}
memcpy(pSysCallFilterInfo->SavedSSDTTable,FileBuffer,ulSSDTLimit*4);
ExFreePool(FileBuffer);
ExFreePool(FunBuffer);
pSysCallFilterInfo->ulSSDTNum=ulSSDTLimit;
//Init Proxy SSDT table
pSysCallFilterInfo->ProxySSDTTable[97]=(ULONG)FakeNtLoadDriver;
//这里就可以随意添加Hook,相当方便
//Init SSDT Swicth table
pSysCallFilterInfo->SSDTSwitchTable[97]=1;
//记得要开开关
return status;
}

最后是释放清理模块了。
void UnHookKiFastCallEntry()
{
KIRQL Irql;
if (*(PULONG)OriginalHead2)
{
Irql=WOFF();
//写回原来的函数头
RtlCopyMemory((BYTE *)PatchAddr,OriginalHead2,5);
WON(Irql);
}
};

NTSTATUS FreeSysCallFilter()
{
NTSTATUS status=STATUS_SUCCESS;
UnHookKiFastCallEntry();
if (pSysCallFilterInfo)
{
ExFreePool(pSysCallFilterInfo);
}
return status;
}
这里顺带发个过滤函数以及R3通信架构的搭建好了
这个过滤是NtLoadDriver的
NTSTATUS FakeNtLoadDriver( __in PUNICODE_STRING DriverServiceName)
{
PEPROCESS pCurProcess;
DRIVER_TRANS_INFO DriverTransInfo;
if (DriverServiceName==NULL)
{
return ((NTLOADDRIVER)pSysCallFilterInfo->SavedSSDTTable[97])(DriverServiceName);
}
DriverTransInfo.Size=sizeof(DRIVER_TRANS_INFO);
pCurProcess=PsGetCurrentProcess();
if (pCurProcess)
{
GetProcessFullPathW((ULONG)pCurProcess,DriverTransInfo.ProcessFullPath);
}
RtlStringCchCopyW(DriverTransInfo.WarmReason,MAX_REASON*sizeof(WCHAR),L"尝试加载驱动,一旦加载驱动进程将会获得最高权限,允许此操作将可能导致危险发生,驱动文件为:");
RtlStringCchCatW(DriverTransInfo.WarmReason,MAX_PATH*sizeof(WCHAR),DriverServiceName->Buffer);
if (!GoOrNot((PVOID)&DriverTransInfo,TYPE_DRIVER_MONITOR))
{
return STATUS_ACCESS_DENIED;
}
else
{
return ((NTLOADDRIVER)pSysCallFilterInfo->SavedSSDTTable[97])(DriverServiceName);
}

}

然后是GoOrNot与R3通信等待R3命令
BOOL GoOrNot(__in PVOID pMonitorInfo,__in ULONG Type)
{
BOOL bRet=false;
switch (Type)
{
case TYPE_DRIVER_MONITOR:
bRet=GetUserCommand(g_DeviceExtension->DriverMonitorInfo.pNotifyEvent,
g_DeviceExtension->DriverMonitorInfo.SharedMemInfo.pShareMemory,
pMonitorInfo,
sizeof(DRIVER_TRANS_INFO));
break;
default:
;
}
return bRet;

}
//获取用户层命令
BOOL GetUserCommand(__in PKEVENT pNotifyEvent,
__in PVOID pShareMemory,
__in PVOID pTransInfo,
__in ULONG pTransLen)
{
BOOL bRet;
PDRIVER_TRANS_INFO pDriverTransInfo;
memcpy(pShareMemory,pTransInfo,pTransLen);
KeSetEvent(pNotifyEvent,0,false);
KeWaitForSingleObject(
pNotifyEvent,
Executive,
KernelMode,
false,
NULL);
pDriverTransInfo=(PDRIVER_TRANS_INFO)pShareMemory;
if (pDriverTransInfo->Command==COMMAND_GO)
{
bRet=true;
}
else if (pDriverTransInfo->Command==COMMAND_STOP)
{
bRet=false;
}
return bRet;
}

这里发段R3和R0共享内存的,用的是内核创建pool在建MDL映射到用户空间的方法。
BOOL CreateSharedMemory(__out PSHARE_MEMORY_INFO pShareMemInfo,
__in ULONG MemorySize)
{
BOOL bRet=true;
PMDL pMdl;
PVOID UserVAToReturn;
PIO_STACK_LOCATION pIoStackLocation;
ULONG ulBufferLengthOut;
PVOID pSharedBuffer;
pSharedBuffer=ExAllocatePoolWithTag(NonPagedPool,MemorySize,MM_TAG_ANTI);
if (!pSharedBuffer)
{
KdPrint(("(IrpCreateSharedMemory) pSharedBuffer allocate failed"));
return false;
}
pMdl=IoAllocateMdl(pSharedBuffer,MemorySize,false,false,NULL);
if (!pMdl)
{
KdPrint(("(IrpCreateSharedMemory) IoAllocateMdl( failed"));
ExFreePool(pSharedBuffer);
return false;
}
MmBuildMdlForNonPagedPool(pMdl);
UserVAToReturn=MmMapLockedPagesSpecifyCache(pMdl,
UserMode,
MmCached,
NULL,
false,
NormalPagePriority);
if (!UserVAToReturn)
{
IoFreeMdl(pMdl);
ExFreePool(pSharedBuffer);
return false;
}
RtlZeroMemory(pSharedBuffer,MemorySize);
KdPrint(("UserVAToReturn:0x%08x",UserVAToReturn));
//输出
pShareMemInfo->pShareMemory=pSharedBuffer;
pShareMemInfo->pSharedMdl=pMdl;
pShareMemInfo->UserVA=(ULONG)UserVAToReturn;
return bRet;
}
R3的创建事件和开线程我就不发了,很简单大家可以自己尝试下。
最后附下整个架构的部分数据结构

//GoOrNot Type宏定义
#define TYPE_DRIVER_MONITOR 0x01
//GoOrNot Command宏定义
#define COMMAND_GO 0x01
#define COMMAND_STOP 0x02
//危险拦截提示语句
#define WARM_DRI_LOAD L"尝试加载驱动,一旦加载驱动进程将会获得系统最高权限,允许此操作将可能导致危险发生,驱动文件路径:"
//************数据定义***************************************************
typedef struct _SYSCALL_FILTER_INFO_TABLE
{
ULONG ulSSDTAddr;
ULONG ulSHADOWSSDTAddr;
ULONG ulSSDTNum;
ULONG ulSHADOWSSDTNum;
ULONG SavedSSDTTable[SSDT_FILTER_NUM]; //SSDT原始函数地址表
ULONG ProxySSDTTable[SHADOWSSDT_FILTER_NUM]; //SSDT代理函数地址表
ULONG SavedShadowSSDTTable[SSDT_FILTER_NUM]; //ShadowSSDT原始函数地址表
ULONG ProxyShadowSSDTTable[SHADOWSSDT_FILTER_NUM]; //ShadowSSDT代理函数地址表
ULONG SSDTSwitchTable[SSDT_FILTER_NUM]; //SSDT Hook开关表
ULONG ShadowSSDTSwitchTable[SHADOWSSDT_FILTER_NUM];//ShadowSSDT Hook开关表
}SYSCALL_FILTER_INFO_TABLE,*PSYSCALL_FILTER_INFO_TABLE;
好的宏定义也可以简化工程,这里大家可自行考虑。

这样差不多整个架构就搭建起来了,一个小型的监控系统就可以完成了。优秀的架构的确可以事半功倍,不过过滤函数的规则其实才是重中之重呀……………..。大家可以多讨论下,这个过滤规则是很需要仔细研究的。当然你要藏着咱也没办法呵…………
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: