旅途

如果想飞得高,就该把地平线忘掉

Windows下网络数据报的监听和拦截技术

Windows下网络数据报的监听和拦截技术1

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

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

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

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

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

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

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

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

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

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

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

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

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

 

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

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

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

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

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

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

构):

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

|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。

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

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

樱?

使用起来不灵活。

 

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

?

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

个方法。

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

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

 

 

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

一种技术。在DDK中MS提供了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表里的SendHandler或SendPacketHandler。所以我们还需要对这张表里

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

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

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

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

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

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

NdisReadConfiguration查询NDIS版本。

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

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_BLOCK中PNDIS_PROTOCOL_CHARACTERISTICS结构的R

ec

eiveHandler和ReceivePacketHandler

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结构里的ReceiveHandler和ReceivePacketHandler

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;

}


bsp;          &PChars,

                                                 sizeof(NDIS_PROTOCOL_CHARACTERIS

TICS));

        ASSERT(Status == NDIS_STATUS_SUCCESS);

        if(Status == NDIS_STATUS_SUCCESS)

                return ProtHandle;

        else

                return NULL;

}

        下面的函数挂接PNDIS_PROTOCOL_BLOCK中PNDIS_PROTOCOL_CHARACTERISTICS结构的R

ec

eiveHandler和ReceivePacketHandler

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结构里的ReceiveHandler和ReceivePacketHandler

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;

}


bsp;          &PChars,

                                                 sizeof(NDIS_PROTOCOL_CHARACTERIS

TICS));

        ASSERT(Status == NDIS_STATUS_SUCCESS);

        if(Status == NDIS_STATUS_SUCCESS)

                return ProtHandle;

        else

                return NULL;

}

        下面的函数挂接PNDIS_PROTOCOL_BLOCK中PNDIS_PROTOCOL_CHARACTERISTICS结构的R

ec

eiveHandler和ReceivePacketHandler

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结构里的ReceiveHandler和ReceivePacketHandler

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;

}


posted on 2007-07-29 15:02 旅途 阅读(1715) 评论(0)  编辑 收藏 引用 所属分类: 网络技术


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理