惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
小众软件
小众软件
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
Martin Fowler
Martin Fowler
M
MIT News - Artificial intelligence
博客园 - 三生石上(FineUI控件)
博客园 - 【当耐特】
N
News | PayPal Newsroom
K
Kaspersky official blog
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
N
Netflix TechBlog - Medium
B
Blog
Recorded Future
Recorded Future
U
Unit 42
J
Java Code Geeks
Security Latest
Security Latest
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
Vulnerabilities – Threatpost
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Scott Helme
Scott Helme
Apple Machine Learning Research
Apple Machine Learning Research
aimingoo的专栏
aimingoo的专栏
T
Threatpost
Last Week in AI
Last Week in AI
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
NISL@THU
NISL@THU
有赞技术团队
有赞技术团队
博客园 - 叶小钗
N
News and Events Feed by Topic
V
V2EX
T
Troy Hunt's Blog
月光博客
月光博客
博客园 - Franky
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
C
Cisco Blogs
The Cloudflare Blog
T
Tor Project blog
Google Online Security Blog
Google Online Security Blog

博客园 - sizzle

ruby函数回调的实现方法 软件单元测试之我见 非空判断 在COM中使用数组参数-SafeArray[转载] 指针赋值 - sizzle - 博客园 进程的深度隐藏 文件隐藏 三键屏蔽 键盘知识 如何获取当前程序文件的路径 Net Remoting[转自Bruce Zhang's Blog] 解码 XML 和 DTD 【读书笔记】[Effective C++第3版][第27条]尽量不要使用类型转换 P2P之UDP穿透NAT的原理与实现 CommandBinding用法 窗体显示中Form.Show()和Form.ShowDialog()的区别 一段高深代码--未解 使用C#注销/关闭计算机 TEA加密算法的C/C++实现
Windows下网络数据报的监听和拦截技术
sizzle · 2007-09-29 · via 博客园 - sizzle

Windows下网络数据报的监听和拦截技术是一个比较古老的话题,应用也很广泛,例如

防火墙等等。这篇小文只是对该技术的一个总结,没有新技术,高手免看:)

        要监听和拦截Windows下的数据报,基本可以在两个层次进行,一个是用户态(user-mo

de),一个是核心态(kernel-mode)

        在用户态下,从高到低大概有四种方法。

        1原是套结字(Raw Socket)Winsock2以后提供了原始套结字功能,可以在用户态用

Winsock函数接收所有流经WinsockIP包。这种方法在MSDN里面有叙述,是MS官方支持

的方法,在网上也有很多资料。但是这种方法只能监听但是不能拦截数据报,所以可以

作为网络监视器的选择技术,但是不能实现防火墙等更高要求的功能。另外最致命的缺

点就是只能在Winsock层次上进行,而对于网络协议中底层协议的数据包例如TDI无法

进行处理。对于一些木马和病毒来说很容易避开这个层次的监听。

        2、替换系统自带的WINSOCK动态连接库。这种方法可以在很多文章里面找到详细的实现

细节。 通过替换系统Winsock库的部分导出函数,实现数据报的监听和拦截。缺点同1

        3Winsock服务提供者(SPI)SPIWinsock的另一面,是Winsock2的一个新特性。

起初的Winsock是围绕着TCP/IP协议运行的,但是在Winsock 2中却增加了对更多传输协

议的支持。Winsock2不仅提供了一个供应用程序访问网络服务的Windows socket应用程

序编程接口(API),还包含了由传输服务提供者和名字解析服务提供者实现的Winsock

服务提供者接口(SPI)和ws2_32.dll Winsock 2的传输服务提供者是以动态链接库的

形式(DLL)存在的。以下是winsock 2在传输服务提供者上的WOSAWindows开放服务结

:

----------------------------

|Windows socket 2 应用程序|

----------------------------Windows socket 2 API

|       WS2_32.DLL        |

----------------------------Windows socket 2 传输SPI

|   传输服务提供者(DLL  |

----------------------------

Windows socket SPI提供三种协议:分层协议,基础协议和协议链。分层协议是在基础

协议的上层,依靠底层基础协议实现更高级的通信服务。基础协议是能够独立,安全地

和远程端点实现数据通信的协议,它是相对与分层协议而言的。协议链是将一系列的基

协议和分层协议按特点的顺序连接在一起的链状结构,可以通过Platform SDK附带的

工具Sporder.exe察看系统已经安装的SPI,请参见下图:

API------------------------

   |      WS2_32.DLL     |

SPI------------------------

   | 分层协议 |

SPI-------------

   | 分层协议 |

SPI------------------------

   |       基础协议       |

   ------------------------

        每个应用程序通过Ws2_32.dll和相应的服务提供者进行严格的数据交换。Ws2_32.dl

l

据应用程序在创建套接字时所提供的参数来选择特定的服务提供者,然后把应用程序的

实现过程转发由所选创建套接字的服务提供者来管理。也就是说,Ws2_32.dll只是一个

中间过程,而应用程序只是一个接口,数据通信的实现却是由服务提供者来完成的。所

以我们通过适当的增加自己的分层协议服务提供者,使其位于SPI的顶端,那么就能将W

s2_32.dll传给服务提供者的数据报拦截下来。由于是MS的官方方法,具体的使用方法在

Platform SDK里面有详细的例子(LSP),在MSDN里面也有详细的解释。这种方法的优点

是能够获得调用Winsock的进程的详细信息,并能实现Qos和数据加密。所以SPI是用户态

数据拦截的较好地点。缺点同1

        4Windows2000包过滤接口。由于过滤规则限制太多不灵活而应用不多。

        5、网络监视器SDKMS官方的实时监视分析网络数据的方法。但是由于封装的?

?

使用起来不灵活。

        在核心态下,数据报的监视和拦截方法比较复杂,由于大多人防火墙都是在核心?

?

实现的,所以在这里比较详细的叙述一下。具体的请参见nt/2kDDK文档。大概有下面几

方法。

1     TDI过滤驱动程序(TDI Filter Driver)。

2     NDIS中间层驱动程序(NDIS Intermediate Driver)。编写IM DRIVERNDIS中间层

MINIPORT(网卡驱动程序)和协议驱动程序之间的数据包进行拦截。这是微软提供的

一种技术。在DDKMS提供了Passthru例子,很多中间层过滤驱动都可以由之改编。但编

写该过滤程序拦截程序非常的复杂,安装也很麻烦。

3     Win2k Filter-Hook Driver

4     NDIS Hook Driver。这种方法又有两种实现方式。

1)向NDIS注册假协议(fake protocol)。这是在协议层上的处理。在Windows内核中

,所有已注册的协议是通过一个单向的协议链表来维护的。这个单向链表保存了所有已

注册协议的NDIS_PROTOCOL_BLOCK结构的地址,在这个结构中保存了协议驱动所指定的相

应的派发函数的地址如RECEIVE_HANDLER等。

struct _NDIS_PROTOCOL_BLOCK

{

PNDIS_OPEN_BLOCK OpenQueue; // queue of opens for this protocol

REFERENCE Ref; // contains spinlock for OpenQueue

UINT Length; // of this NDIS_PROTOCOL_BLOCK struct

NDIS50_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics;// handler addresses

struct _NDIS_PROTOCOL_BLOCK * NextProtocol; // Link to next

ULONG MaxPatternSize;

#if defined(NDIS_WRAPPER)

//

// Protocol filters

//

struct _NDIS_PROTOCOL_FILTER * ProtocolFilter[NdisMediumMax+1];

WORK_QUEUE_ITEM WorkItem; // Used during NdisRegisterProtocol to

// notify protocols of existing drivers.

KMUTEX Mutex; // For serialization of Bind/Unbind requests

PKEVENT DeregEvent; // Used by NdisDeregisterProtocol

#endif

};

typedef struct _NDIS_PROTOCOL_BLOCK NDIS_PROTOCOL_BLOCK, *PNDIS_PROTOCOL_BLO

CK;并且,每个协议驱动还对应一个NDIS_OPEN_BLOCK的单向链表来维护其所绑定的网卡

信息。当协议驱动调用NdisRegisterProtocol之后,

EXPORT

VOID

NdisRegisterProtocol(

OUT PNDIS_STATUS Status,

OUT PNDIS_PROTOCOL_BLOCK NdisProtocolHandle, /*注意NDIS_HANDLE所指向的就是PN

DIS_PROTOCOL_BLOCK的结构,不要有什么怀疑。*/

IN PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,

IN UINT CharacteristicsLength

);

NDIS总是会把新注册的协议放在协议链表的表头并返回这张表,所以只要我们注册一个

新的协议通过新协议注册返回的链表头就可以轻而易举的遍历系统中所有协议表。但是

,如果要成功地挂接派发函数,还需要对协议所对应的NDIS_OPEN_BLOCK结构里的派发函

数进行挂接,因为NDIS并不是直接调用协议驱动在NDIS_PROTOCOL_CHARACTERISTICS所注

册的派发函数地址,而是调用NDIS_OPEN_BLOCK里的派发函数。

struct _NDIS_OPEN_BLOCK

{

PNDIS_MAC_BLOCK MacHandle; // pointer to our MAC

NDIS_HANDLE MacBindingHandle; // context when calling MacXX funcs

PNDIS_ADAPTER_BLOCK AdapterHandle; // pointer to our adapter

PNDIS_PROTOCOL_BLOCK ProtocolHandle; // pointer to our protocol

NDIS_HANDLE ProtocolBindingContext;// context when calling ProtXX funcs

PNDIS_OPEN_BLOCK AdapterNextOpen; // used by adapter's OpenQueue

PNDIS_OPEN_BLOCK ProtocolNextOpen; // used by protocol's OpenQueue

PFILE_OBJECT FileObject; // created by operating system

BOOLEAN Closing; // TRUE when removing this struct

BOOLEAN Unloading; // TRUE when processing unload

BOOLEAN NoProtRsvdOnRcvPkt; // Reflect the protocol_options

NDIS_HANDLE CloseRequestHandle; // 0 indicates an internal close

KSPIN_LOCK SpinLock; // guards Closing

PNDIS_OPEN_BLOCK NextGlobalOpen;

//

// These are optimizations for getting to MAC routines. They are not

// necessary, but are here to save a dereference through the MAC block.

//

SEND_HANDLER SendHandler;

TRANSFER_DATA_HANDLER TransferDataHandler;

//

// These are optimizations for getting to PROTOCOL routines. They are not

// necessary, but are here to save a dereference through the PROTOCOL block.

//

SEND_COMPLETE_HANDLER SendCompleteHandler;

TRANSFER_DATA_COMPLETE_HANDLER TransferDataCompleteHandler;

RECEIVE_HANDLER ReceiveHandler;

RECEIVE_COMPLETE_HANDLER ReceiveCompleteHandler;

//

// Extentions to the OPEN_BLOCK since Product 1.

//

RECEIVE_HANDLER PostNt31ReceiveHandler;

RECEIVE_COMPLETE_HANDLER PostNt31ReceiveCompleteHandler;

//

// NDIS 4.0 extensions

//

RECEIVE_PACKET_HANDLER ReceivePacketHandler;

SEND_PACKETS_HANDLER SendPacketsHandler;

//

// More NDIS 3.0 Cached Handlers

//

RESET_HANDLER ResetHandler;

REQUEST_HANDLER RequestHandler;

//

// Needed for PnP

//

UNICODE_STRING AdapterName; // Upcased name of the adapter we are bound to

};

这张表是一个单向链接表,并且存放了和PNDIS_OPEN_BLOCK->ProtocolCharacteristic

s

一样的数据收发派发函数,当第N块网卡发送数据包到第N协议时,就会调用第N协议

与第N网卡之间建立的

NDIS_OPEN_BLOCK表里的SendHandlerSendPacketHandler。所以我们还需要对这张表里

的派发函数进行处理(勾挂)。

值得注意的是,在Windows9x/Me/NTDDK中,NDIS_PROTOCOL_BLOCK的定义是很明确的,

而在Windows 2000/xpDDK中,并没有该结构的详细定义,也就是说该结构在Windows2

000/xp下是非公开的,因此开发人员需要利用各种调试工具来发掘该结构的详细定义。

也正是因为如此,这种方法对平台的依赖性比较大,需要在程序中判断不同的操作系统

版本而使用不同的结构定义。可以用NdisOpenProtocolConfiguration打开协议配置,用

NdisReadConfiguration查询NDIS版本。

下面的函数注册fake protocol并将PNDIS_PROTOCOL_BLOCK结构存在ProtHandleNDIS_

HANDLE GetProtocolBlock()

{

        NDIS_PROTOCOL_CHARACTERISTICS   PChars;

        NDIS_STRING                                             Name;

        NDIS_HANDLE                                             ProtHandle;

        NDIS_STATUS                                             Status;

        NdisZeroMemory(&PChars, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));

        PChars.MajorNdisVersion = 5;

        PChars.MinorNdisVersion = 0;

        NdisInitUnicodeString(&Name, L"WssFW"); // Protocol name

        PChars.Name = Name;

        PChars.OpenAdapterCompleteHandler = NULL;

        PChars.CloseAdapterCompleteHandler = NULL;

        PChars.SendCompleteHandler = NULL;

        PChars.TransferDataCompleteHandler = NULL;

        PChars.ResetCompleteHandler = NULL;

        PChars.RequestCompleteHandler = NULL;

        PChars.ReceiveHandler = NULL;

        PChars.ReceiveCompleteHandler = NULL;

        PChars.StatusHandler = NULL;

        PChars.StatusCompleteHandler = NULL;

        PChars.BindAdapterHandler = NULL;

        PChars.UnbindAdapterHandler = NULL;

        PChars.UnloadHandler = NULL;

        PChars.ReceivePacketHandler = NULL;

        PChars.PnPEventHandler= NULL;

        NdisRegisterProtocol(&Status,

                                                 &ProtHandle,

                                                 &PChars,

                                                 sizeof(NDIS_PROTOCOL_CHARACTERIS

TICS));

        ASSERT(Status == NDIS_STATUS_SUCCESS);

        if(Status == NDIS_STATUS_SUCCESS)

                return ProtHandle;

        else

                return NULL;

}

        下面的函数挂接PNDIS_PROTOCOL_BLOCKPNDIS_PROTOCOL_CHARACTERISTICS结构的R

ec

eiveHandlerReceivePacketHandler

PVOID

HookProtoFunc(

                          PNDIS_PROTOCOL_CHARACTERISTICS pCharacteristics,

                          DWORD dwFunctionCode,

                          PVOID pfuncNew,

                          DWORD dwNdisVersion)

{

        PVOID pOldFunc = NULL;

        //Check parameters

        if( (!pCharacteristics ) || (!pfuncNew) )

                return NULL;

        switch(dwFunctionCode)

        {

        case PROTO_RECEIVE_HANDLER:

                //Just hook once!

                if(pCharacteristics->ReceiveHandler != pfuncNew )

                {

                        pOldFunc = pCharacteristics->ReceiveHandler;

                        if( pOldFunc )

                                pCharacteristics->ReceiveHandler = pfuncNew;

                }

                break;

        case PROTO_RECEIVE_PACKET_HANDLER:

                if(pCharacteristics->ReceivePacketHandler != pfuncNew)

                {

                        //if pOpenBlock is NULL or pOpenBlock->ReceivePacketHandl

er is NULL,

                        //just hook Characteristics;

                        pOldFunc = pCharacteristics->ReceivePacketHandler;

                        if(pOldFunc)

                                pCharacteristics->ReceivePacketHandler = pfuncNew

;

                }

                break;

        default:

                break;

        }

        return pOldFunc;

}

下面的函数挂接PNDIS_OPEN_BLOCK结构里的ReceiveHandlerReceivePacketHandler

PVOID

HookBlockFunc(

                          PNDIS_OPEN_BLOCK pFirstOpenBlock,

                          DWORD dwFunctionCode,

                          PVOID pfuncNew,

                          DWORD dwNdisVersion)

{

        RECEIVE_HANDLER * pReceiveHandler = NULL;

        RECEIVE_PACKET_HANDLER * pReceivePacketHandler = NULL;

//      PVOID pFuncHandler = NULL;

        PVOID pOldFunc = NULL;

        PNDIS_OPEN_BLOCK pOpenBlock = pFirstOpenBlock;

        if(!pFirstOpenBlock)

                return NULL;

        if(!pfuncNew )

                return NULL;

        switch(dwFunctionCode)

        {

        case PROTO_RECEIVE_HANDLER:

                //travel all NDIS_OPEN_BLOCK

                for(;pOpenBlock;pOpenBlock = GetNextBlock( pOpenBlock,dwNdisVersi

on ))

                {

                        pReceiveHandler = GetReceiveHandler(pOpenBlock,dwNdisVers

ion);

                        //Just hook once!

                        if( *pReceiveHandler != pfuncNew )

                        {

                                pOldFunc = *pReceiveHandler;

                                *pReceiveHandler = pfuncNew;

                        }

                        if(dwNdisVersion == 0x00040001)//win2k ????

                        {

                                pReceiveHandler = GetPostNt31ReceiveHandler(pOpen

Block,dwNdisVersion);

                                if( *pReceiveHandler != pfuncNew )

                                {

                                        pOldFunc = *pReceiveHandler;

                                        *pReceiveHandler = pfuncNew;

                                }

                        }

                }

                break;

        case PROTO_RECEIVE_PACKET_HANDLER:

                //travel all NDIS_OPEN_BLOCK

                for(;pOpenBlock;pOpenBlock = GetNextBlock( pOpenBlock,dwNdisVersi

on ))

                        pReceivePacketHandler = GetReceivePacketHandler( pOpenBlo

ck,dwNdisVersion

 );

                        //Just hook once !

                        if(*pReceivePacketHandler != pfuncNew)

                        {

                                pOldFunc = *pReceivePacketHandler;

                                *pReceivePacketHandler = pfuncNew;

                        }

                }

                break;

        default:

                break;

        }

        return pOldFunc;

}