欢迎来到 嗅灵易学

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

[原创]Hips篇高操之正确的全局Patch/拦截模块姿势

[原创]Hips篇高操之正确的全局Patch/拦截模块姿势

潜水了两个月,由于工作的关系没有什么多余的时间发新帖了,由于临近端午准备休息一下,于是乎决定在周末搞点事情,恰逢北京今日又下起雨来,在这朦朦胧胧的雨天里给大家呈上一份Hips的高端操作,让大家燃一下。


这也是初入安全时我一直想实现的一个东西,但是由于当时技术局限,很菜,其实今天来看只是当时没有找到好的学习方法,没有办法实现很是遗憾。然而今日在团队的带领下,我改进了自己的学习方法,学会了如何有效看待问题分析问题以及解决问题。在此感谢我的导师以及leader。


这个一直想实现的东西就是用驱动拦截模块,看似很简单的一个问题但是实际上要操作起来并非易事,要看得懂代码,得先理解了这张图:


可以看到,调用API加载模块的时候最后都逃不过ntdll!ZwMapViewOfSection,最后进入系统调用然后触发模块回调,我们唯一能做的就是在模块回调里干一些猥琐的事情。直接Patch?那是不可能的,因为模块回调有限制,这时候有EProcess.AddressCreationLock的限制不说,模块还未能初始化完成,直接去搞肯定是不正确的做法。那么可以想到的是用Apc延迟去Patch模块,因为在ZwMapViewOfSection在内核中最后会返回至Ring3,在返回的过程中调用Apc去Patch这样就完美了。


话又说回来,模块回调中为每一个模块都插一个Apc? 这显然也不现实,因此这种方法最好是有一个Ring3层进程来制定策略指定对哪一个进程加载哪一个模块时执行哪种Patch操作那么这就涉及到Ring0与Ring3的通信问题。说到这里就有三种方法可供选择,一种是LPC(ZwConnetPort...),一种是内核可等待对象,还有一种就是Minifilter的双向通信机制。


当然,我选择的是最后一种,现在貌似运用这种方法做通信的不多,我也没怎么接触过,一切都是从0开始,那就试试吧...

NTSTATUS FyFsFilterStart(PDRIVER_OBJECT DriverObject)

{

	NTSTATUS Status;

	PSECURITY_DESCRIPTOR sd = NULL;

	PFLT_FILTER FltFilter = NULL;

	OBJECT_ATTRIBUTES oa = {0};

	UNICODE_STRING uniString;

	__try 

	{

		Status = FltRegisterFilter(DriverObject,

			&FilterRegistration,

			&FltFilter);

		if (!NT_SUCCESS(Status)) {

			__leave;

		}

		Status = FltBuildDefaultSecurityDescriptor(&sd,

			FLT_PORT_ALL_ACCESS);

		if (!NT_SUCCESS(Status)) {

			__leave;

		}

		RtlInitUnicodeString(&uniString, FY_FSFILTER_PORT_NAME);

		InitializeObjectAttributes(&oa,

			&uniString,

			OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,

			NULL,

			sd);

		Status = FltCreateCommunicationPort( FltFilter,

			&g_FyFsFltGlobalCtx.ServerPort,

			&oa,

			NULL,

			(PFLT_CONNECT_NOTIFY)FyFsFilterConnect,

			(PFLT_DISCONNECT_NOTIFY)FyFsFilterDisconnect,

			(PFLT_MESSAGE_NOTIFY)FyFsFilterMessage,

			1 );

		if (!NT_SUCCESS(Status)) {

			__leave;

		}

		Status = FltStartFiltering(FltFilter);

		if (!NT_SUCCESS(Status)) {

			__leave;

		}

		g_FyFsFltGlobalCtx.Filter = FltFilter;

		DbgPrint("[%s] Init FileSystem--minifilter success", __FUNCTION__);

	}

	__except (EXCEPTION_EXECUTE_HANDLER)

	{

		if (sd != NULL) {

			FltFreeSecurityDescriptor( sd );

		}

		if (g_FyFsFltGlobalCtx.ServerPort) {

			FltCloseCommunicationPort(g_FyFsFltGlobalCtx.ServerPort);

		}

		if (FltFilter) {

			FltUnregisterFilter(FltFilter);

			g_FyFsFltGlobalCtx.Filter = NULL;

		}

	}

	if (!NT_SUCCESS(Status) ) {

		if (sd != NULL) {

			FltFreeSecurityDescriptor( sd );

		}

		if (g_FyFsFltGlobalCtx.ServerPort) {

			FltCloseCommunicationPort(g_FyFsFltGlobalCtx.ServerPort);

		}

		if (FltFilter) {

			FltUnregisterFilter(FltFilter);

			g_FyFsFltGlobalCtx.Filter = NULL;

		}     

	} 

	return Status;

}


首先由Ring3进程指定策略,这里拿金山的两个模块开刀:

POLICY_DATA g_TotalPolicyList[] = {

	{L"*\\iexplore.exe", L"*\\kisfdpro64.dll"},

	{L"*\\iexplore.exe", L"*\\kshmpg.dll"},

	{L"*\\explorer.exe", L"*\\kisfdpro64.dll"},

	{L"*\\explorer.exe", L"*\\kshmpg.dll"},

};

BOOL FilterSetLoadImagePolicy()

{

	ULONG uIndex = 0;

	ULONG ulReturned = 0;

	HRESULT hr = S_OK;

	ULONG ulInputSize = sizeof(POLICY_DATA);

	FySendPolicyDat HipsData = {0};

	HipsData.MessageType = Lowlayer_Msg_SetImagePolicy;

	HipsData.Signature = FY_FSFILTER_SIGNATURE;

	for (uIndex ; uIndex < sizeof(g_TotalPolicyList) / sizeof(g_TotalPolicyList[0]) ; uIndex++)

	{

		HipsData.DataLength = ulInputSize;

		memcpy(&HipsData.Data, &g_TotalPolicyList[uIndex], sizeof(POLICY_DATA));

		hr = FilterSendMessage(

			g_hPort,

			&HipsData,

			sizeof(FySendPolicyDat),

			NULL,

			NULL,

			&ulReturned);

		if (FAILED(hr)) 

		{

			printf("[%s] FilterSendMessage GetLastError:%d\r\n", __FUNCTION__, GetLastError());

			break;

		}

	}

	return TRUE;

}



注册这个通信是奠定与Ring3进程通信的基础,应用层进程可以通过连接之后发送策略(即要对哪个进程的哪个模块进行监控),然后由模块回调在匹配策略之后对应用程序进行通知

VOID FyLoadImageNotifyRoutine(

	IN PUNICODE_STRING FullImageName,

	IN HANDLE ProcessId,

	IN PIMAGE_INFO ImageInfo

	)

{

	WCHAR              lpszProcessName[MAX_PATH] = { 0 };

	UNICODE_STRING     uniProcessName;

	BOOLEAN            bHitPolicy = FALSE;

	FyLoadImageInfo    SendInfo = { 0 };

	LARGE_INTEGER      liTimeOut;

	ULONG              ulCopySize;

	FYFSFLT_REPLY_DATA RelpyData;

	ULONG              ulRelpyDataSize = sizeof(FYFSFLT_REPLY_DATA);

	NTSTATUS           Status;

	if (!ImageInfo->SystemModeImage) 

	{

		if (GetProcessImageFileNameSafeIrql(ProcessId, lpszProcessName, MAX_PATH))

		{

			RtlInitUnicodeString(&uniProcessName, lpszProcessName);

			if (g_ListHeaderProcessImagePolicy)

			{

				bHitPolicy = HitProcessImagePolicy(&uniProcessName, FullImageName);

			}

			if (bHitPolicy)

			{

				DbgPrint("[%s] Hit Policy!!!\r\n", __FUNCTION__);

				DbgPrint("[%s] uniProcessName:%wZ\r\n", __FUNCTION__, &uniProcessName);

				DbgPrint("[%s] FullImageName:%wZ\r\n", __FUNCTION__, FullImageName);

				if (g_FyFsFltGlobalCtx.ClientPort)

				{

					liTimeOut.QuadPart = -150 * 1000000; // 15s

					if (uniProcessName.Length >= (MAX_PATH - 1) * sizeof(WCHAR))

						ulCopySize = (MAX_PATH - 1) * sizeof(WCHAR);

					else

						ulCopySize = uniProcessName.Length;

					memcpy(SendInfo.wzProcessImageName, uniProcessName.Buffer, ulCopySize);

					if (FullImageName->Length >= (MAX_PATH - 1) * sizeof(WCHAR))

						ulCopySize = (MAX_PATH - 1) * sizeof(WCHAR);

					else

						ulCopySize = FullImageName->Length;

					memcpy(SendInfo.wzImageName, FullImageName->Buffer, ulCopySize);

					SendInfo.ImageBase = ImageInfo->ImageBase;

					SendInfo.ImageSize = ImageInfo->ImageSize;

					SendInfo.ProcessId = ProcessId;

					SendInfo.ThreadId = PsGetCurrentThreadId();

					SendInfo.bX64Image = (SIZE_T)CheckIsX64Image(ImageInfo->ImageBase);

					// Reply不能传NULL,否则卡不住,在Ring3 FilterGetMessage之后直接就飞走了,来不及在这次系统调用返回前插入Apc

					Status = FltSendMessage(g_FyFsFltGlobalCtx.Filter, 

						&g_FyFsFltGlobalCtx.ClientPort,

						&SendInfo,

						sizeof(SendInfo),

						&RelpyData,

						&ulRelpyDataSize,

						&liTimeOut);

					if (Status == STATUS_TIMEOUT)

					{

						DbgPrint("[%s] FltSendMessage return STATUS_TIMEOUT", __FUNCTION__);

					}

					DbgPrint("[%s] FltSendMessage finished", __FUNCTION__);

				}	

			}

		}

	}	

}



这里一定要注意,FltSendMessage这个函数msdn上说的很清楚,如果RelpyData为NULL, 只要是Ring3进程调用了FilterGetMessage取得了数据,那么这个函数不会再等待。而我们需要做的是在GetMessage之后发消息给驱动,让它为LoadImage这个当前线程插入Apc,那么在模块回调中必须卡死直到应用进程调用FilterRelpyMessage回应消息,否则这一次系统调用就返回了。


Ring3进程拿到模块监控数据之后决定对该模块采取什么样的Patch措施,然后将决定结果送达至Ring0,这里我就以Patch基地址即DosHeader.e_magic为例了。

DWORD WINAPI FilterMessageProc(PVOID lpParam)

{

	FyLoadImageRecvDat Msg = {0};

	FyReplyData ReplyMsg = {0};

	while (TRUE)

	{

		DWORD bytesReturned = 0;

		DWORD hResult;

		TCHAR buf[260] = {0};

		hResult = FilterGetMessage(

			g_hPort,

			(PFILTER_MESSAGE_HEADER)&Msg,

			sizeof(Msg),

			NULL);

		if (hResult == S_OK) 

		{

			// 拿到当前正在映射的模块信

			// Now can Send Patch Info

			FySendPatchInfoDat HipsData = {0};

			HipsData.MessageType = Lowlayer_Msg_BlockImage;

			HipsData.Signature = FY_FSFILTER_SIGNATURE;

			HipsData.DataLength = sizeof(FyPatchInfo);

			HipsData.Data.PatchType = 1;

			HipsData.Data.ProcessId = Msg.Data.ProcessId;

			HipsData.Data.ThreadId = Msg.Data.ThreadId;

			HipsData.Data.PatchAddress = (ULONG64)Msg.Data.ImageBase;

			*(DWORD*)HipsData.Data.lpPatchContent = 0x00905a4b;

			HipsData.Data.PatchSize = 4;

			// don't wait

			FilterSendMessage(g_hPort, 

				&HipsData,

				sizeof(FySendPatchInfoDat),

				NULL,

				NULL,

				&bytesReturned);

			// Reply load image message

			ReplyMsg.ReplyHeader.MessageId = Msg.MsgHeader.MessageId;

			ReplyMsg.ReplyHeader.Status = 0;

			ReplyMsg.Signature = FY_FSFILTER_SIGNATURE;

			hResult = FilterReplyMessage(

				g_hPort,

				(PFILTER_REPLY_HEADER)&ReplyMsg,

				sizeof(ReplyMsg));

		}

	}

	return 0;

}



这里要提一点的是,32位进程与64位驱动通信的时候一定要注意结构体的对齐粒度,不然会吃大亏的...

NTSTATUS FyFsFilterMessage (

	__in PVOID ConnectionCookie,

	__in_bcount_opt(InputBufferSize) PVOID InputBuffer,

	__in ULONG InputBufferSize,

	__out_bcount_part_opt(OutputBufferSize,*ReturnOutputBufferLength) PVOID OutputBuffer,

	__in ULONG OutputBufferSize,

	__out PULONG ReturnOutputBufferLength

	)

{

	NTSTATUS Status = STATUS_SUCCESS;

	ULONG Command;

	PVOID pContent = NULL;

	ULONG ContentLength = 0;

	UINT32 Signature;

	UNREFERENCED_PARAMETER(ConnectionCookie);

	PAGED_CODE();

#if defined(_WIN64)

	if (IoIs32bitProcess( NULL )) {

		if (!IS_ALIGNED(OutputBuffer,sizeof(ULONG))) {

			Status = STATUS_DATATYPE_MISALIGNMENT;

			return Status;

		}

	} else {

#endif

		if (!IS_ALIGNED(OutputBuffer,sizeof(PVOID))) {

			Status = STATUS_DATATYPE_MISALIGNMENT;

			return Status;

		}

#if defined(_WIN64)

	}

#endif

	if ((InputBuffer != NULL) &&

		(InputBufferSize >= sizeof(FYFSFLT_SEND_DATA))) {

			__try {

				Command = ((PFYFSFLT_SEND_DATA) InputBuffer)->MessageType;

				Signature = ((PFYFSFLT_SEND_DATA) InputBuffer)->Signature;

				if (Signature != FY_FSFILTER_SIGNATURE)

					return Status;

				pContent = &((PFYFSFLT_SEND_DATA) InputBuffer)->Data;

				ContentLength = ((PFYFSFLT_SEND_DATA) InputBuffer)->DataLength;

			} __except( EXCEPTION_EXECUTE_HANDLER ) {

				return GetExceptionCode();

			}

			DbgPrint("[%s]Command:%x\n", __FUNCTION__, Command);

			switch (Command)

			{

			case Lowlayer_Msg_SetImagePolicy:

				DbgPrint("[%s] Lowlayer_Msg_SetImagePolicy\n", __FUNCTION__);

				SetProcessImagePolicy((PPOLICY_DATA)pContent, ContentLength);

				break;

			case Lowlayer_Msg_BlockImage:

				DbgPrint("[%s] Lowlayer_Msg_BlockImage\n", __FUNCTION__);

				ExecuteKernelPatchProcedure((PFyPatchInfo)pContent, ContentLength);

				break;

			default:

				break;

			}

	}

	return STATUS_SUCCESS;

}



当然在Patch的时候还是不能忘了改页属性,这个我也不想多说了,一定调用的是ZwProtectVirtualMemory,不导出就去搜...

BOOLEAN ApcRealProcedure1(PFyPatchInfo lpPatchInfo)

{

	BOOLEAN   bRet           = FALSE;

	NTSTATUS  status;

	ULONG     ulCompareSize;

	PVOID     lpPatchAddr    = lpPatchInfo->PatchAddress;

	SIZE_T    PatchSize      = lpPatchInfo->PatchSize;

	ULONG     dwOldProtect;

	if ( lpPatchInfo->PatchAddress < MmSystemRangeStart )

		ulCompareSize = (ULONG)RtlCompareMemory(lpPatchInfo->lpPatchContent, lpPatchInfo->PatchAddress, lpPatchInfo->PatchSize);

		if ( ulCompareSize != lpPatchInfo->PatchSize )

		{

			DbgPrint("[%s] RtlCompareMemory:%d\r\n", __FUNCTION__, ulCompareSize);

			if ( g_ZwProtectVirtualMemory )

				status = g_ZwProtectVirtualMemory(NtCurrentProcess(), &lpPatchAddr, &PatchSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);

			else

				status = STATUS_UNSUCCESSFUL;

			if ( !NT_SUCCESS(status) )

			{

				if (!g_dwSysBuildNumber) {

					DbgPrint("[%s] g_dwSysBuildNumber:%d\r\n", __FUNCTION__, g_dwSysBuildNumber);

					PsGetVersion(&g_dwSysMajorNumber, &g_dwSysMinorNumber, &g_dwSysBuildNumber, NULL);

				}

				if (g_dwSysBuildNumber > 9600)

					bRet = CopyMemoryUsingMdl(lpPatchInfo->PatchAddress, lpPatchInfo->lpPatchContent, 

							(ULONG)lpPatchInfo->PatchSize);

			}

			else

			{

				ProbeForWrite(lpPatchInfo->PatchAddress, lpPatchInfo->PatchSize, 1);

				memcpy(lpPatchInfo->PatchAddress, lpPatchInfo->lpPatchContent, lpPatchInfo->PatchSize)

				if ( g_ZwProtectVirtualMemory )

					status = g_ZwProtectVirtualMemory(NtCurrentProcess(), &lpPatchAddr, &PatchSize, dwOldProtect, &dwOldProtect);

			}

		}

	}

	return bRet;

}



正确的现象是在ntdll!NtMapViewOfSection刚回来的时候就已经执行了Patch操作,原本为0x905a4d


在ZwMapViewOfSection之后LdrFindOrMapDll会调用RtlImageNtHeaderEx校验DosHeader和PEHeader,不对就卸载



另外,由于minifilter驱动需要inf文件安装,为了避免这样的情况,我就用服务启动驱动了(即用即加载),不用手动去加,直接双击exe就行。

LZ测试平台是Win7 x64,其他平台未经过测试,应该可以兼容到Win10... exe与sys放同一层目录下.


上传的附件 Fydisk64.zip
Fy3disk.zip

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

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

0 0 0 举报
复制成功