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

360HOOK表,Hook过滤架构搭建

2012-08-15 18:34 274 查看
分析了一下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;

好的宏定义也可以简化工程,这里大家可自行考虑。

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