2005年05月16日

Windows系列操作系统对线程间通信的支持           ——管道与邮路

Windows应用程序间数据通讯的基本方式有四种。最简单的是利用剪切板;另一种是DDEDynamic Data Exchange动态数据交换),它利用一种公共的协议实现两个或多个应用程序之间的通讯;再者是通过内存映射文件,内存映射可以将一个进程的一段虚拟地址映射为一个文件,然后其它的进程可以共享该段虚拟地址;最后就是通过管道与邮路实现进程间数据通信。

要讨论管道与邮路之前先让我们来复习一下这几个概念:进程、线程。微软官方对进程和线程的定义如下

进程:用最简洁的话来说,进程就是一个正在执行的程序,一个或多个线程在进程中运行,线程是操作系统分配CPU运算时间的最小单位。每一个进程都提供了运行一个程序所必需的资源,一个进程具有4GB的虚拟地址空间(Windows NT Server Enterprise EditionWindows 2000 Advanced Server中低3GB虚拟地址空间供进程使用,高1GB供操作系统的内核代码使用。Windows NT/2000中低2GB供进程使用,高2GB供操作系统内核代码使用。Windows9X0——64K只读空间用来装入Microsoft DOS信息,64K——4M装入DOS的兼容代码,4M——2GB的私有空间供进程使用,2GB——3GB的共享空间装入各种DLL代码,3GB——4GB为共享的系统内核代码空间,其中 共享的2GB——4GB的空间是99%的“内存无效页错误”、“General Protect Error(GPE)”及蓝屏的罪魁祸首。),可执行代码,数据,对象句柄,环境变量,优先权以及设置最大化最小化的功能。每一个进程都从一个主线程开始执行,但可以在它所拥有的线程中创建额外的线程。一个进程的所有线程共享进程的虚拟地址空间和系统资源,一个线程的资源包括线程的机器寄存器设置,内核堆栈,线程环境变量和进程虚拟地址中的用户堆栈。

让我们再来看看微软官方对管道和邮路是怎么解释的。

管道(pipe)是进程用来通讯的共享内存区域。一个进程往管道中写入信息,而其它的进程可以从管道中读出信息。如其名,管道是进程间数据交流的通道。邮路(Mailslots)的功能与管道类似,也是进程间通讯(interprocess communicationsIPC)的媒介,只不过其具体实现方式与管道有些差别。一个基于Win32的应用程序可以在邮路中储存消息,这些消息通常通过网络发往一个指定的计算机或某域名(域是共享一个组名的一组工作站或服务器。)下的所有计算机。你也可以使用命名管道代替邮路来进行进程间通信。命名管道最适合用来两个进程间的消息传递,邮路则更适合一个进程向多个进程广播消息。邮路具有一个重要的特点,它使用数据包广播消息。广播(broadcast)是网络传输中使用的术语,它意味着接收方收到数据后不发送确认消息通知发送方。而管道(这里的管道指命名管道,有关命名管道以下详解。)则不同,它更类似于打电话,你只对一个当事人说话,但是你却非常清楚你的话都被对方听到。邮路和管道一样,也是一个虚拟文件,它保存在内存中,但是你却必须使用普通的Win32文件函数访问它,比如CreateFileReadFileWriteFile等。邮路中储存的数据可以是任何形式的,唯一的要求是不得超过64K。与磁盘文件不同的是,邮路是一个临时的对象,当某个邮路所有的句柄都关闭的时候,该邮路及其中的数据就被删除。

管道的类型有两种:匿名管道和命名管道。匿名管道是不命名的,它最初用于在本地系统中父进程与它启动的子进程之间的通信。命名管道更高级,它由一个名字来标识,以使客户端和服务端应用程序可以通过它进行彼此通信。而且,Win32命名管道甚至可以在不同系统的进程间使用,这使它成为许多客户/服务器应用程序的理想之选。

就像水管连接两个地方并输送水一样,软件的管道连接两个进程并输送数据。一个一个管道一旦被建立,它就可以象文件一样被访问,并且可以使用许多与文件操作同样的函数。可以使用CreateFile函数获取一个已打开的管道的句柄,或者由另一个进程提供一个句柄。使用WriteFile函数向管道写入数据,之后这些数据可以被另外的进程用ReadFile函数读取。管道是系统对象,因此管道的句柄在不需要时必须使用CloseHandle函数关闭。

匿名管道只能单向传送数据,而命名管道可以双向传送。管道可以以比特流形式传送任意数量的数据。命名管道还可以将数据集合到称为消息的数据块中。命名管道甚至具有通过网络连接多进程的能力。但遗憾的是Windows9X不支持创建命名管道,它只能在WindowsNT系列(如Windows NTWindows 2000Windows XP)的操作系统上创建。

当讨论管道时,通常涉及到两个进程:客户进程和服务进程。服务进程负责创建管道。客户进程连接到管道。服务进程可以创建一个管道的多个实例,以此支持多个客户进程。

匿名管道用以下函数创建:

BOOL CreatePipe(


      PHANDLE hReadPipe,                       // 用于读操作的句柄
      PHANDLE hWritePipe,                      // 用于写操作的句柄
      LPSECURITY_ATTRIBUTES lpPipeAttributes,  // 描述安全信息的一个结构
      DWORD nSize                              // 管道大小

);

命名管道用以下函数创建:

HANDLE CreateNamedPipe(


  LPCTSTR lpName,                             // 管道名
      DWORD dwOpenMode,                           // 管道打开方式
      DWORD dwPipeMode,                           // 管道模式
  DWORD nMaxInstances,                        // 该管道最大的实例数量
  DWORD nOutBufferSize,                       // 输出缓冲区大小
  DWORD nInBufferSize,                        // 输入缓冲区大小
  DWORD nDefaultTimeOut,                      // 指定默认的超时时间
  LPSECURITY_ATTRIBUTES lpSecurityAttributes  // 描述安全信息的一个结构

);

管道由以下函数删除:

BOOL CloseHandle(


  HANDLE hObject   // 管道句柄

);

其它的管道函数简介如下:

CallNamedPipe:连接到一个命名管道,读取或写入数据之后关闭它。

ConnectNamedPipe:服务进程准备好一个连接到客户进程的管道,并等待一个客户进程连接上为止。

DisconnectNamedPipe:服务端用来断开与客户端的连接。

GetNamedPipeHandleState:获取一个命名管道的状态信息。

GetNamedPipeInfo:获取一个命名管道的信息。

PeekNamedPipe:从一个匿名或命名管道中拷贝数据到一个缓冲区。

SetNamedPipeHandleState:设置管道的类型及其它状态信息,比如说是比特流还是消息流管道。

TransactNamedPipe:从一个消息管道读消息或向其写入消息。

WaitNamedPipe:客户进程用来连接到一个命名管道。

邮路是由邮路服务进程创建。当邮路服务进程创建了一个邮路后,便返回该邮路句柄。当某个进程需要从该邮路中读取消息时,它必须提供该句柄。只有创建该邮路的进程,或以某种机制(比如继承)获得该邮路句柄的进程能够从邮路中读取消息。与管道不同,所有的邮路都是从属于创建它的本地进程的,你无法创建一个远程的邮路。邮路的客户进程具有向邮路写入消息的权限。任何进程只要获得了邮路的名字,就可以往里面写入消息,新的消息将放在邮路的消息队列后面。

邮路能在一个域中广播消息。如果域中几个进程每个都用相同的名字创建了一个邮路,则它们都会收到送往该邮路的消息。

在邮路中传递的消息如果小于425字节,那么它们会以数据包的形式传递。大于425字节的消息会以其它的方式传输,在这种情况下,你只能从一个客户进程传递给一个服务进程,而不能从一个客户进程传递给多个服务进程。且Windows NT不支持大于等于425个字节的消息传递。

邮路由以下函数创建:

HANDLE CreateMailslot(


  LPCTSTR lpName,                            // 邮路名
  DWORD nMaxMessageSize,                     // 消息最大的大小
  DWORD lReadTimeout,                        // 读取操作的超时时间
  LPSECURITY_ATTRIBUTES lpSecurityAttributes // 描述安全信息的一个结构

);

邮路由以下函数删除:

BOOL CloseHandle(


  HANDLE hObject   // 邮路句柄

);

其它邮路函数简介如下:

GetMailslotInfo:获取指定邮路的相关信息。

SetMailslotInfo:设置指定邮路的相关信息。

Windows系列操作系统对管道和邮路的支持就是这样,除此之外,内存映射文件也是进程间数据交流的重要方式,但限于篇幅,本文不再具体介绍。

2005年05月15日

考了线代,,终于考完了,轻松呀~~昨天晚上整了欢送会,,只可惜,因为今天有考试了,先回来了,

2005年05月10日

转了一圈,,试用了一下,,还不错,很爽,真像是一个社区的感觉,,里面可以共享音乐什么特方便,我还有几个邀请名额 ,,谁想要,我也可以邀请,,

2005年05月05日

我搞到几个邀请,想要的我可以给发

C++中的模板(template)

  作者:张笑猛   来源:http://objects.nease.net/

网上我最喜欢的技术文章是类似某何君所著“CVS快速入门”或者“UML reference card”之类,简短扼要,可以非常快的领着你进入一个新天地。而对于比较长的文章我通常是将其保存到硬盘上,然后准备着“以后有时间”的时候再看,但它们通常的命运都是“闲坐说玄宗”,直到某一天在整理硬盘时将它们以“不知所云”入罪,一并删除。
 这篇小文主要是针对刚刚接触模板概念的读者,希望能帮助读者学习模板的使用。为了避免本文也在诸公的硬盘上遭逢厄运,我决定写的短些。“以后有时间”的时候再补充些内容。
TOC
1. 简介
2. 语法
3. 使用技巧
  3.1 语法检查
  3.2 继承
  3.3 静态成员
3.4 模板类的运用
4. 参考资料
 
1. 简介
模板是C++在90年代引进的一个新概念,原本是为了对容器类(container classes)的支持[1],但是现在模板产生的效果已经远非当初所能想象。
简单的讲,模板就是一种参数化(parameterized)的类或函数,也就是类的形态(成员、方法、布局等)或者函数的形态(参数、返回值等)可以被参数改变。更加神奇的是这里所说的参数,不光是我们传统函数中所说的数值形式的参数,还可以是一种类型(实际上稍微有一些了解的人,更多的会注意到使用类型作为参数,而往往忽略使用数值作为参数的情况)。
举个常用的例子来解释也许模板就从你脑袋里的一个模糊的概念变成活生生的代码了:
在C语言中,如果我们要比较两个数的大小,常常会定义两个宏:
#define min(a,b) ((a)>(b)?(b):(a))
#define max(a,b) ((a)>(b)?(a):(b))
这样你就可以在代码中:
return min(10, 4);
或者:
return min(5.3, 18.6);
这两个宏非常好用,但是在C++中,它们并不像在C中那样受欢迎。宏因为没有类型检查以及天生的不安全(例如如果代码写为min(a++, b–);则显然结果非你所愿),在C++中被inline函数替代。但是随着你将min/max改为函数,你立刻就会发现这个函数的局限性 —— 它不能处理你指定的类型以外的其它类型。例如你的min()声明为:
int min(int a, int b);
则它显然不能处理float类型的参数,但是原来的宏却可以很好的工作!你随后大概会想到函数重载,通过重载不同类型的min()函数,你仍然可以使大部分代码正常工作。实际上,C++对于这类可以抽象的算法,提供了更好的办法,就是模板:
template <class T> const T & min(const T & t1, const T & t2) {
    return t1>t2?t2:t1;
}
这是一个模板函数的例子。在有了模板之后,你就又自由了,可以像原来在C语言中使用你的min宏一样来使用这个模板,例如:
return min(10,4);
也可以:
return min(5.3, 18.6)
你发现了么?你获得了一个类型安全的、而又可以支持任意类型的min函数,它是否比min宏好呢?
当然上面这个例子只涉及了模板的一个方面,模板的作用远不只是用来替代宏。实际上,模板是泛化编程(Generic Programming)的基础。所谓的泛化编程,就是对抽象的算法的编程,泛化是指可以广泛的适用于不同的数据类型。例如我们上面提到的min算法。
2. 语法
你千万不要以为我真的要讲模板的语法,那太难为我了,我只是要说一下如何声明一个模板,如何定义一个模板以及常见的语法方面的问题。
template<> 是模板的标志,在<>中,是模板的参数部分。参数可以是类型,也可以是数值。例如:
template<class T, T t>
class Temp{
public:
    …
    void print() { cout << t << endl; }
private:
    T t_;
};
在这个声明中,第一个参数是一个类型,第二个参数是一个数值。这里的数值,必须是一个常量。例如针对上面的声明:
Temp<int, 10> temp; // 合法
int i = 10;
Temp<int, i> temp; // 不合法
const int j = 10;
Temp<int, j> temp; // 合法
参数也可以有默认值:
template<class T, class C=char> …
默认值的规则与函数的默认值一样,如果一个参数有默认值,则其后的每个参数都必须有默认值。
参数的名字在整个模板的作用域内有效,类型参数可以作为作用域内变量的类型(例如上例中的T t_),数值型参数可以参与计算,就象使用一个普通常数一样(例如上例中的cout << t << endl)。
模板有个值得注意的地方,就是它的声明方式。以前我一直认为模板的方法全部都是隐含为inline的,即使你没有将其声明为inline并将函数体放到了类声明以外。这是模板的声明方式给我的错觉,实际上并非如此。我们先来看看它的声明,一个作为接口出现在头文件中的模板类,其所有方法也都必须与类声明出现在一起。用通俗的话来说,就是模板类的函数体也必须出现在头文件中(当然如果这个模板只被一个C++程序文件使用,它当然也可以放在.cc中,但同样要求类声明与函数体必须出现在一起)。这种要求与inline的要求一样,因此我一度认为它们隐含都是inline的。但是在Thinking In C++[2]中,明确的提到了模板的non-inline function,就让我不得不改变自己的想法了。看来正确的理解应该是:与普通类一样,声明为inline的,或者虽然没有声明为inline但是函数体在类声明中的才是inline函数。
澄清了inline的问题候,我们再回头来看那些我们写的包含了模板类的丑陋的头文件,由于上面提到的语法要求,头文件中除了类接口之外,到处充斥着实现代码,对用户来说,十分的不可读。为了能像传统头文件一样,让用户尽量只看到接口,而不用看到实现方法,一般会将所有的方法实现部分,放在一个后缀为.i或者.inl的文件中,然后在模板类的头文件中包含这个.i或者.inl文件。例如:
// start of temp.h
template<class T> class Temp{
public:
    void print();
};
 #include "temp.inl"
// end of temp.h
// start of temp.inl
template<class T> void Temp<T>::print() {
    …
}
// end of temp.inl
通过这样的变通,即满足了语法的要求,也让头文件更加易读。模板函数也是一样。
普通的类中,也可以有模板方法,例如:
class A{
public:
    template<class T> void print(const T& t) { …}
    void dummy();
};
对于模板方法的要求与模板类的方法一样,也需要与类声明出现在一起。而这个类的其它方法,例如dummy(),则没有这样的要求。
3. 使用技巧
知道了上面所说的简单语法后,基本上就可以写出自己的模板了。但是在使用的时候还是有些技巧。
3.1 语法检查
对模板的语法检查有一部分被延迟到使用时刻(类被定义[3],或者函数被调用),而不是像普通的类或者函数在被编译器读到的时候就会进行语法检查。因此,如果一个模板没有被使用,则即使它包含了语法的错误,也会被编译器忽略,这是语法检查问题的第一个方面,这不常遇到,因为你写了一个模板就是为了使用它的,一般不会放在那里不用。与语法检查相关的另一个问题是你可以在模板中做一些假设。例如:
template<class T> class Temp{
public:
    Temp(const T & t): t_(t) {}
    void print() { t.print();}
private:
    T t_;
};
在这个模板中,我假设了T这个类型是一个类,并且有一个print()方法(t.print())。我们在简介中的min模板中其实也作了同样的假设,即假设T重载了’>’操作符。
因为语法检查被延迟,编译器看到这个模板的时候,并不去关心T这个类型是否有print()方法,这些假设在模板被使用的时候才被编译器检查。只要定义中给出的类型满足假设,就可以通过编译。
之所以说“有一部分”语法检查被延迟,是因为有些基本的语法还是被编译器立即检查的。只有那些与模板参数相关的检查才会被推迟。如果你没有写class结束后的分号,编译器不会放过你的。
3.2 继承
模板类可以与普通的类一样有基类,也同样可以有派生类。它的基类和派生类既可以是模板类,也可以不是模板类。所有与继承相关的特点模板类也都具备。但仍然有一些值得注意的地方。
假设有如下类关系:
template<class T> class A{ … };
 |
+– A<int> aint;
 |
+– A<double> adouble;
则aint和adouble并非A的派生类,甚至可以说根本不存在A这个类,只有A<int>和A<doubl>这两个类。这两个类没有共同的基类,因此不能通过类A来实现多态。如果希望对这两个类实现多态,正确的类层次应该是:
class Abase {…};
template<class T> class A: public Abase {…};
 |
+– A<int> aint;
 |
+– A<double> adouble;
也就是说,在模板类之上增加一个抽象的基类,注意,这个抽象基类是一个普通类,而非模板。
再来看下面的类关系:
template<int i> class A{…};
 |
+– A<10> a10;
 |
+– A<5> a5;
在这个情况下,模板参数是一个数值,而不是一个类型。尽管如此,a10和a5仍然没有共同基类。这与用类型作模板参数是一样的。
3.3 静态成员
与上面例子类似:
template<class T> class A{ static char a_; };
 |
+– A<int> aint1, aint2;
 |
+– A<double> adouble1, adouble2;
这里模板A中增加了一个静态成员,那么要注意的是,对于aint1和adouble1,它们并没有一个共同的静态成员。而aint1与aint2有一个共同的静态成员(对adouble1和adouble2也一样)。
这个问题实际上与继承里面讲到的问题是一回事,关键要认识到aint与adouble分别是两个不同类的实例,而不是一个类的两个实例。认识到这一点后,很多类似问题都可以想通了。
3.4 模板类的运用
模板与类继承都可以让代码重用,都是对具体问题的抽象过程。但是它们抽象的侧重点不同,模板侧重于对于算法的抽象,也就是说如果你在解决一个问题的时候,需要固定的step1 step2…,那么大概就可以抽象为模板。而如果一个问题域中有很多相同的操作,但是这些操作并不能组成一个固定的序列,大概就可以用类继承来解决问题。以我的水平还不足以在这么高的层次来清楚的解释它们的不同,这段话仅供参考吧。
模板类的运用方式,更多情况是直接使用,而不是作为基类。例如人们在使用STL提供的模板时,通常直接使用,而不需要从模板库中提供的模板再派生自己的类。这不是绝对的,我觉得这也是模板与类继承之间的以点儿区别,模板虽然也是抽象的东西,但是它往往不需要通过派生来具体化。
在设计模式[4]中,提到了一个模板方法模式,这个模式的核心就是对算法的抽象,也就是对固定操作序列的抽象。虽然不一定要用C++的模板来实现,但是它反映的思想是与C++模板一致的。
4. 参考资料
[1] 深度C++对象模型,Stanley B.Lippman, 侯捷译
[2] Thinking In C++ 2nd Edition Volumn 1, Bruce Eckel
[3] 定义– 英文为definition,意思是"Make this variable here",参见[2] p93
[4] Design Patterns – Elements of Reusable Object-Oriented Software GOF
2005年05月04日
C++之模板(Template)
原创:Greenfood 2002年4月22日

模板是C++提供进行编程的一种类书工具。所以在看本文以前请先了解一下C++类书的概念及相关的知识。
1.模板的概念
模办是实现类属机制的一种工具,它的功能非常强,它是无约束类属机制和约束类属机制的集合。 它可以让用户构造模板函数。 
模板,对象, 函数之间的关系见下图:

  

2.函数模板与模板函数:

先见下例:

#include <iostream.h>

template<class T>            //模板声明

T max(T x,T y)               //定义模板
{ 
    return
(x>y)? x:y;
}

main()
{
      int i=10,j=56;
      float x1=50.34, x2=56.34;
      double y1=673.36, y2=465.972;
      cout<<"the max of i, j is:"<<max(i, j)<<"\n";
      cout<<"the max of x1, x2 is:" <<max(x1,x2)<<"\n";
      cout<<"the max of y1, y2 is:" <<max(y1,y2)<<"\n";
      return 1;
}

    上面的这个程序虽然只是实现一个很简单的比较大小的问题, 但如果不用模板, 而用我们以前的方法, 由于参数类型和返回值类型不同将需要三个函数来实现, 这样是十分麻烦的。模板成功的解决了这个问题, 程序中生成了三个模板函数, 其中max(i,j)用模板实参int将类型实参数T进行了实例化;max(x1,x1)用模板实参float将类型参数T进行了实例化;max(y1,y2)用模板实参double将类型参数T进行了实例化。

    从上面的例子我们可以看出, 函数模板提供了一类函数的抽象, 它以任意类型T为参数及函数返回值。由一个函数模板产生的函数称为模板函数, 它是函数模板的具体实例。

函数模板和模板函数的关系图:

   要提醒大家注意的一点是:虽然模板参数T可以实例化成各种类型, 但是采用模板参数T的各种参数T的各参数之间必须保持完全一致的类型。例如:

T max(T x,T y)
{
return (x>y)?x:y;
}

void func(int i, char c, float)
{
   max(i,i);   //正确
   max(c,c);   //正确
   max(c,i);   //错误
   max(f,i);   //错误
}

    解决上面的问题就需要引入一个型的概念—重载, 既可以用非模板函数重载一个同名的函数模板。 重载有两种表述方式:

    1.利用函数模板的函数体

        使用次方法是必须注意各模板参数的实参类型必须一致。例如:

        int max(int,int)

    2.重新定义函数体

        对于要重新定义函数体的重载函数, 所带参数的类型可以随意, 就象一般的重载函数一样定义。

    定义重载函数特别要注意避免产生预期的和非预期的二义性。例如:

int max(int, int);

char max(int x,char y)
{
       //……
}


   当进行函数调用时有这样一个调用形式:
msx(i,j);
   此处iint,ffloat,这个函数调用就存在着二义性。这时的调用就需要按照一定的规则安排先后顺序, 这些规则就是函数模板与同名的非模板函数的重载方法均遵循的约定:

    (1)寻找一个参数完全匹配的函数, 如果找到了就调用它。

    (2)(1)失败后, 寻找一个函数模板, 使其实例化, 产生一个匹配的模板函数, 若找到了,就调用它。

    (3)(1)(2)均失败后, 再试一试低一级的对函数的重载方法,例如通过类型转换可产生参数匹配等, 若找到了,就调用它。

    (4)(1)(2)(3)均失败, 则这是一个错误的调用。

3.类模板与模板类

    类模板与模板类的概念

    一个类模板可以让用户为类定义一种模式, 使得类中的某些数据成员,某些成员函数的参数,某些成员函数的返回值,能取任意类型。

    定义一个类模板, 一般由两方面的内容:

    (1)首先要定义类, 其格式为:

template<class T>  //声明一个模板

class name
{
    //….
}

    name为类名,在类定义体中如采用数据类型的成员, 函数参数的前面需加上T。例如:

template<class T>

class vector
{
     T * data;
     int size;
     pulic:
     vetor(int);
     T&operator[](int);
     //…
};

    (2)在类定义体外定义成员函数时, 若此成员函数中有模板参数存在, 则需在函数体外进行模板声明, 并且在函数名前的类名后缀上“<T>”.例如:

template<class T>

vector<T>::vector(int i)
{
     //….
}

template<class T>

T&vector<T>::operator[](int i)
{
     //….
}

    类模板的使用

    类模板的使用实际上是将类模板实例化成一个具体的类, 它的格式为:

    类名<实际的类型>
    例如:

main()
{
     vector<int>x(5);
     for(int i=0; i<=5;i++)
     x[i]=i;
     for(i=0;i<5;++i)
     cout<<x[i]<<"";
     cout<<"\n";
}

    类模板和模板类之间的关系, 如图:

   

    类模板使用需要注意的几点:

    (1)在每个模板定义之前, 不管是类模板还是函数模板, 都需要在前面加上模板声明:

template<class T>

    (2)类模板和结构模板在使用时, 必须在名字后面缀上模板参数<T>,如:list<T>,node<T>.

利用WinSock2 SPI进行网络内容访问控制

来源:张兵
2003-4-11 13:25:00
 

  编者按:与传统的包过滤防火墙技术不同,本文从应用层网关技术入手,深入探讨了利用WinSock2 SPI进行网络内容访问控制的问题。这是网络安全的一项新内容,或者说,它为网络安全技术的爱好者和研发人员提供了一个新的思路。
  
  张兵
  
  防火墙可以实施和执行网络访问策略,但是,传统的防火墙技术集中于如何防范外部网络对内部网络的入侵和攻击上,而对于如何控制内部用户对外部网络的访问问题研究不够深入,相关的控制技术也不多。据权威资料显示,全球现有大约25万色情网站,单纯依靠传统的包过滤等防火墙技术,势必会严重影响网络性能。针对这一问题,我们从应用层网关技术入手,利用WinSock2 SPI技术,进行了研究和探讨。
  
  Winsock2 SPI原理图
  Winsock2 SPI(Service Provider Interface)服务提供者接口建立在Windows开放系统架构WOSA(Windows Open System Architecture)之上,是Winsock系统组件提供的面向系统底层的编程接口。Winsock系统组件向上面向用户应用程序提供一个标准的API接口;向下在Winsock组件和Winsock服务提供者(比如TCP/IP协议栈)之间提供一个标准的SPI接口。各种服务提供者是Windows支持的DLL,挂靠在Winsock2 的Ws2_32.dll模块下。对用户应用程序使用的Winsock2 API中定义的许多内部函数来说,这些服务提供者都提供了它们的对应的运作方式(例如API函数WSAConnect有相应的SPI函数WSPConnect)。多数情况下,一个应用程序在调用Winsock2 API函数时,Ws2_32.dll会调用相应的Winsock2 SPI函数,利用特定的服务提供者执行所请求的服务。
  
  Winsock2 SPI允许开发两类服务提供者——传输服务提供者和名字空间服务提供者。“传输提供者”(Transport Providers, 一般称作协议堆栈,例如TCP/IP)能够提供建立通信、传输数据、日常数据流控制和错误控制等传输功能方面的服务。“名字空间提供者”(Name Space Providers,例如DNS名字解析服务)则把一个网络协议的地址属性和一个或多个用户友好名称关联到一起,以便启用与应用无关的名字解析方案。
  
  Winsock2中使用的传输服务提供者有两类:基础服务提供者和分层服务提供者。基础服务提供者执行网络传输协议(比如TCP/IP)的具体细节,其中包括在网络上收发数据之类的核心网络协议功能。“分层式”(Layered)服务提供者只负责执行高级的自定义通信功能,并依靠下面的基础服务提供者,在网络上进行真正的数据交换。
  
  为了进行内部用户对外访问控制,我们需要在现有的基础提供者TCP/IP提供者上设立一个分层式的URL过滤管理者。通过URL过滤管理者我们可以截获用户请求的HTTP数据包中的URL地址,继而可以通过高效的数据检索算法(如利用Fibonacci散列函数的哈希表),在访问规则库(被禁止访问的IP集合)中查找指定的IP,根据结果拒绝或提供访问服务。
  
  传输服务提供者的安装方式决定了它不仅是一个分层提供者,还是一个基础服务提供者。Winsock 2使用系统配置数据库配置传输服务提供者。配置数据库让Winsock2得知服务提供者的存在,并定义了提供的服务类型。要在Winsock2服务提供者数据库内成功安装和管理服务提供者,需要四个函数:WSCEnumProtocols、WSCInstallProvider、WSCWriteProvider Order、WSCDeInstallProvider。这些函数利用WSAPROTOCOL_INFOW结构,对服务提供者数据库进行查询和操作。要安装分层式服务提供者,需要建立两个WSPPROTOCOL_INFOW目录条目结构。一个代表分层提供者(协议链长度等于0),另一个将代表一个协议链(协议长度大于1),该协议链把分层提供者与一个基础服务提供者链接起来。应该使用现有服务提供者的WSAPROTOCOL_INFOW目录条目结构的属性来初始化这两个结构。调用WSCEnumProtocols可以获得已有的服务提供者的WSAPROTOCOL_INFOW目录条目结构。初始化之后,首先需要使用WSCInstallProvider来安装我们的访问控制分层服务提供者目录条目,然后,利用WSCEnumProtocols列举出所有的目录条目,获得安装之后为这个结构分配的目录ID。然后,用这个目录条目来设置一个协议链目录条目,通过它,将我们的访问控制服务提供者和另一个提供者(TCP基础提供者)链接起来。然后再次调用WSCInstallProvider来安装我们的分层链式服务提供者。
  
  在用WSCInstallProvider安装一个服务提供者时,目录条目自动成为配置数据库中的最后一个条目。要实现访问控制就必须使我们的URL过滤服务提供者成为默认的TCP/IP提供者,必须通过调用WSCWriteProviderOrder函数来完成此项工作,对数据库中提供者目录条目进行重新排序,并把协议链目录条目放在TCP/IP基础提供者之前。
  
  Winsock2传输服务提供者随标准的Windows动态链接库模块一起执行。我们必须在我们的服务提供者动态链接库模块中导入DLLMain函数,同时还必须导入一个名为WSPStartup的单一函数条目。我们的URL过滤服务提供者必须提供对WSPStartup函数和其他30个SPI函数的支持。调用WSAStartup期间,Winsock根据WSASocket调用的地址家族、套接字类型和协议参数,来决定需要加载哪个服务提供者。只有在一个应用程序通过socket或WSASocket API调用建立一个采用地址家族AF_INET、套接字类型为SOCK_STREAM的套接字时,Winsock才会搜索并加载与之相应的、能够提供TCP/IP能力的传输服务提供者。WSPStartup的参数UpcallTable取得Ws2_32.dll的SPI函数派遣表,我们的访问控制分层服务提供者利用这些函数来管理自身和Winsock2之间的I/O操作。
  
  我们利用WSPConnect函数来实现访问控制功能。在用户请求HTTP服务时,需要首先建立与目标站点的连接,连接成功后,在此连接基础上发送HTTP请求数据包。用户应用程序调用connect或WSAConnect函数建立连接时,SPI会调用对应的WSPConnect函数:INT WSPAPI WSPConnect(…,const struct sockaddr FAR *name,…,INT FAR *lpErrno)。在sockaddr类型的参数name中包含了用户将要访问的目标站点的IP地址信息。我们将name参数传递到IP可访问性判定例程IPFilter。如果IPFilter函数返回代表授权访问的结果,我们采用协议链命令路由,调用下一层的基础服务提供者(TCP/IP)来完成连接请求。如果IPFilter函数返回代表拒绝服务的结果,我们设置lpErrno参数为相应的错误码,然后返回,不进行协议链下一层服务提供者的调用,从而实现访问控制。
  
  分层式服务提供者大大发挥了联网服务的潜能,增强了Winsock的应用,在我们的URL过滤服务中发挥了巨大的作用,基本实现了对内部用户访问外部网络的访问控制,为用户提供了对互联网的健康性的访问服务

基于SPI的数据报过滤原理与实现

Author: TOo2y [原创]
E-Mail: TOo2y@safechina.net
HomePage: www.safechina.net fz5fz.yeah.net
Date: 11-02-2002

一. 个人防火墙技术概述
二. Winsock 2 SPI介绍
三. 相关程序代码分析
四. 小结与后记
五. 附录之源代码

一)个人防火墙技术概述
随着网络安全问题日益严重,广大用户对网络安全产品也越来越关注。防火墙作为一种网络安全工具,早已受到大家的青睐。在PC机上使用的个人防火墙,很大程度上成为广大网民的安全保护者。Windows下的个人防火墙都是基于对数据报的拦截技术之上。当然在具体的实现方式上它们却有很大的不同。总的来说可分为用户级和内核级数据报拦截两类。其中内核级主要是TDI过滤驱动程序,NDIS中间层过滤驱动程序,NDIS过滤钩子驱动程序等,它们都是利用网络驱动来实现的;而用户级的过滤包括SPI接口,Windows2000包过滤接口等。本文主要讲述基于SPI的包过滤实现,它是Winsock 2的一个新特性。

二)Winsock 2 SPI介绍
Winsock 2 是一个接口,而不是协议,所以它可以用于发现和使用任意数量的底层传输协议所提供的通信能力。起初的Winsock是围绕着TCP/IP协议运行的,但是在Winsock 2中却增加了对更多传输协议的支持。Winsock 2不仅提供了一个供应用程序访问网络服务的Windows socket应用程序编程接口(API),还包含了由传输服务提供者和名字解析服务提供者实现的Winsock服务提供者接口(SPI)和ws2_32.dll。本文仅讨论传输服务提供者及其应用,暂不对名字解析服务提供者进行分析。
Winsock 2的传输服务提供者是以动态链接库的形式(DLL)存在的,它是通过WSPStartup函数为上层函数提供接口,而其他的传输服务提供者函数则是通过分配表的方式来访问WS2_32.DLL。传输服务提供者的动态链接库只有在应用程序需要时才由Ws2_32.dll来装入内存中的,在不需要时则会被自动卸载。以下是winsock 2在传输服务提供者上的WOSA(Windows开放服务结构):

—————————-
│Windows socket 2 应用程序│
—————————-Windows socket 2 API
│ WS2_32.DLL │
—————————-Windows socket 2 传输SPI
│ 传输服务提供者(DLL) │
—————————-

Windows socket SPI在服务提供者中使用了以下的函数前缀命名方式:WSP(Windows socket服务提供者),WPU(Windows socket提供者向上调用),WSC(Windows socket配置)。每一个传输服务提供者都有它自己所支持的传输协议,它是使用WSAPROTCOL_INFOW结构来实现的。传输服务提供者把所有的相关信息都存放在这个结构中,而应用程序就是通过这个结构的内容来将自己和相应的传输服务提供者相关联。
Windows socket SPI提供三种协议:分层协议,基础协议和协议链。分层协议是在基础协议的上层,依靠底层基础协议实现更高级的通信服务。基础协议是能够独立,安全地和远程端点实现数据通信的协议,它是相对与分层协议而言的。协议链是将一系列的基础协议和分层协议按特点的顺序连接在一起的链状结构,请参见下图:

API————————
│ WS2_32.DLL │
SPI————————
│ 分层协议 │
SPI————-
│ 分层协议 │
SPI————————
│ 基础协议 │
————————

Ws2_32.dll数据传输部分的主要功能是在服务提供者和应用程序之间提供流量管理的功能。每个应用程序通过Ws2_32.dll和相应的服务提供者进行严格的数据交换。Ws2_32.dll根据应用程序在创建套接字时所提供的参数来选择特定的服务提供者,然后把应用程序的实现过程转发由所选创建套接字的服务提供者来管理。也就是说,Ws2_32.dll只是一个中间过程,而应用程序只是一个接口,数据通信的实现却是有服务提供者来完成的。我们说过,Ws2_32.dll是通过创建套接字的API函数WSASocket或socket的参数来确定使用哪一个服务提供者。而WSASocket/socket的参数中包括了地址族,套接字类型和协议类型,这三个因素共同决定了创建套接字的服务提供者。Ws2_32.dll在服务提供者中寻找第一个和前面三因素相匹配的WSAPROTOCOL_INFOW结构,然后就调用这个WSAPROTOCOL_INFOW结构相应的WSPStartup函数,(所有的数据传输服务提供者以DLL的形式,它们对外的接口就只有WSPStartup,其他的服务提供者函数都是通过WSPStartup来调用的),进而调用如WSPSocket的函数来创建套接字,WSPConnect的函数来建立连接等等。除了流量管理功能外,Ws2_32.dll还提供了其他的服务,比如协议枚举,基于线程的阻塞钩子管理和在Ws2_32.dll和服务提供者之间进行版本协商。
传输服务提供者实现的功能包括建立连接,传输数据,实现流控制和差错控制等函数。其实Ws2_32.dll并不知道服务提供者的请求等活动是如何实现了,Ws2_32.dll在应用程序和服务提供者之间实现了媒介的功能。传输服务提供者可分为两类:套接字描述符是可安装的文件系统(IFS)句柄的提供者;剩下的是非IFS的提供者。在我们的程序中选用了非IFS提供者。可见,服务提供者实现了底层的与网络相关的协议。Ws2_32.dll提供了介质级别的流量管理,应用程序则提供了有关如何实现网络相关的操作,它实现了用户所希望的功能。
在传输服务提供者的实现过程中,安装顺序是非常重要的。我们不仅要正确的安装服务提供者,而且还必须在Windows socket中注册,将相关的系统信息保存在数据库中,这样Ws2_32.dll才能够方便的获得下层服务提供者的相关信息。在Ws2_32.dll中提供了用来安装服务提供者的函数WSCInstallProvider,它需要服务提供者的有关数据,比如DLL的名称和路径。同时Ws2_32.dll还提供了卸载服务提供者的函数WSCDeinstallProvider,在不需要时通过它将特定的服务提供者从系统中删除。为什么说传输服务者的安装顺序很重要呢?在服务提供者配置函数中的WSCEnumProtocols是用来枚举系统中所有已安装的服务提供者,它按照服务提供者的安装顺序相应的列出他们。在前面我们也提到过,Ws2_32.dll在服务提供者中按安装顺序搜寻和WSASocket/socket提供的三个参数相匹配的服务提供者,所以安装顺序在一定程度上是决定了服务提供者是否被正确调用的关键。Windows socket 2还提供了一个动态链接库Sporder.dll,它提供了对已安装的所有服务提供者顺序的重新排列(此DLL系统没有自带,common目录中已提供)。在附录中的T-Sporder.exe是一个查询当前已安装所有数据传输服务提供者属性的工具。
服务提供者系统中区分基础协议,分层协议和协议链是通过结构WSAPROTOCOL_INFOW中的Protocolchain结构的ChainLen值来实现的。分层协议的ChainLen值为0,基础协议的值为1,而协议链的值是大于1。在数据传输服务提供者的实现方式中分层协议和基础协议几乎是相同的,它们的不同之处在安装上。Windows中,现有的系统服务提供者(系统自带)几乎已提供了所有基本的服务,因此我们所写的服务提供者程序,都可以对数据报进行适当“修饰”后调用系统服务提供者来完成绝大部分剩下的功能,无论是基础服务提供者还是分层服务提供者都可以使用这种技术,免去不必要的劳动。基础服务提供者的实现过程主要是替换当前系统服务提供者的安装路径为自己的服务提供者的安装路径即可,当然我们必须保存所以系统服务者的相关数据,在我们卸载自己的服务提供者还原系统服务提供者时要用到这些信息,如系统服务者DLL的名称和路径。而协议链就不同了,首先我们必须安装好所有的基础协议和分层协议后,再构造协议链的WSAPROTOCOL_INFOW结构链,组成协议链的每个协议都会在协议链的ProtocolChain.ChainEntries数组中被定义,协议链数组中的第一个协议应该是第一个分层服务提供者。当然在安装分层协议及协议链时我们不会改变系统服务提供者,最多只是改变系统服务提供者的安装顺序罢了。在此,我们以分层服务提供者为例来说明数据传输服务提供者的安装过程。
Ws2_32.dll是使用标准的动态链接库来加载服务提供者接口的DLL到系统中去的,并调用WSPStartup来初始化。WSPStartup是Windows Socket 2应用程序调用SPI程序的初始化函数,也就是入口函数。WSPStartup的参数LPWSAPROTOCOL_INFOW指针提供应用程序所期望的协议信息,然后通过这个结构指针我们可以获得所保存的系统服务提供者的DLL名称和路径,加载系统服务提供者后查找到系统SPI程序的WSPStartup函数的指针,通过这个指针我们就可以将自己服务提供者的WSPStartup函数和系统SPI程序的WSPStartup函数相关联,进而调用系统的各个服务提供者函数。在数据传输服务提供者的实现中,我们需要两个程序,一个是可执行文件用来安装传输服务提供者;另一个就是DLL形式的数据传输服务提供者。下面我们就对安装程序(instif.exe)和实现程序(ipfilter.dll)所使用的主要函数进行简要分析。

三)相关程序代码分析
1.instif.exe
可执行程序instif.exe的主要功能是安装我们自己的分层传输服务提供者,并重新排列所有传输服务提供者的顺序,使我们的服务提供者位于协议链的顶端,这样相应类型的应用程序就会首先进入我们的传输服务提供者接口。本程序只有一个参数,就是安装(-install)或卸载(-remove)。作为演示,本程序只安装了IP分层协议及与UDP相关的协议链。(在ipfilter.dll中,我们只过滤目标端口为8000的UDP数据报)
自定义函数:
BOOL getfilter(); //获得所有已经安装的传输服务提供者
void freefilter(); //释放存储空间
void installfilter(); //安装分层协议,协议链及排序
void removefilter(); //卸载分层协议和协议链

代码分析:
protoinfo=(LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR,protoinfosize);
//分配WSAPROTOCOL_INFOW结构的存储空间
totalprotos=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode);
//获得系统中已安装的所有服务提供者
GetCurrentDirectory(MAX_PATH,filter_path);
//得到当前的路径
_tcscpy(filter_name,_T("\\ipfilter.dll"));
//构造服务提供者文件ipfilter.dll的路径全名
WSCInstallProvider(&filterguid,filter_path,&iplayerinfo,1,&errorcode);
//安装自定义的IP分层协议
iplayercataid=protoinfo[i].dwCatalogEntryId;
//获得已安装自定义IP分层协议的由Ws2_32.dll分配的唯一标志
udpchaininfo.ProtocolChain.ChainEntries[0]=iplayercataid;
//将自定义的IP分层协议作为自定义UDP协议链的根分层服务提供者安装在协议链的顶端
WSCInstallProvider(&filterchainguid,filter_path,chainarray,provcnt,&errorcode);
//安装协议链
WSCWriteProviderOrder(cataentries,totalprotos);
//更新所有服务提供者的安装顺序,把自定义的服务提供者排在所有协议的最前列
WSCDeinstallProvider(&filterguid,&errorcode);
//卸载IP分层协议
WSCDeinstallProvider(&filterchainguid,&errorcode);
//卸载协议链

2.ipfilter.dll
传输服务提供者都是以动态链接库的形式存在的,在应用程序需要时由Ws2_32.dll加载,在用完之后就被卸载。本文的ipfilter.dll提供了对发送的UDP数据报进行过滤的功能。也就是自定义WSPSendTo函数,在调用系统服务提供者之前进行过滤,判断是否继续向下调用,而其他的函数都是直接调用下层的系统服务提供者由它们直接处理。传输服务提供者只有一个入口函数就是WSPStartup,它是Windows Socket 应用程序调用SPI的初始化函数,其他SPI函数的调用都是通过WSPStartup的参数WSPUPCALLTABLE来实现的。
自定义函数:
int WSPAPI WSPSendTo(SOCKET s,LPWSABUF lpbuffer,DWORD dwbuffercount,LPDWORD lpnumberofbytessent,
DWORD dwflags,const struct sockaddr FAR *lpto,int itolen,LPWSAOVERLAPPED lpoverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpcompletionroutine,LPWSATHREADID lpthreadid,LPINT lperrno);
//SPI函数WSPSendTo和Windows Socket 2的API函数WSASendTo相对应
int WSPAPI WSPStartup(WORD wversionrequested,LPWSPDATA lpwspdata,LPWSAPROTOCOL_INFOW lpprotoinfo,
WSPUPCALLTABLE upcalltable,LPWSPPROC_TABLE lpproctable);
//SPI函数WSPStartup和Windows Socket 2的API函数WSAStartup相对应,WSPStartup是唯一的入口函数,剩下的30个SPI函数则是通过参数upcalltable来实现的,它们只能在内部调用,不向外提供入口

代码分析:
GetModuleFileName(NULL,processname,MAX_PATH);
//获得调用本服务提供者动态链接库的可执行文件的全名
OutputDebugString(_T("WSPSendTo Tencent Filtered"));
//输出调试信息
nextproctable.lpWSPSendTo(s,lpbuffer,dwbuffercount,lpnumberofbytessent,dwflags,lpto,
itolen,lpoverlapped,lpcompletionroutine,lpthreadid,lperrno);
//如果数据报满足发送条件,调用下层系统服务提供者发送数据
layerid=protoinfo[i].dwCatalogEntryId;
//获得已安装自定义IP分层协议的由Ws2_32.dll分配的唯一标志
nextlayerid=lpprotoinfo->ProtocolChain.ChainEntries[i+1];
//获得下一层传输服务提供者的标志信息
WSCGetProviderPath(&protoinfo[i].ProviderId,filterpath,&filterpathlen,&errorcode);
//获得下一层传输服务提供者的安装路径
ExpandEnvironmentStrings(filterpath,filterpath,MAX_PATH);
//扩展环境变量
hfilter=LoadLibrary(filterpath));
//装载下一层传输服务提供者
wspstartupfunc=(LPWSPSTARTUP)GetProcAddress(hfilter,"WSPStartup"));
//获得下一层传输服务提供者的入口函数WSPStartup,以便调用
wspstartupfunc(wversionrequested,lpwspdata,lpprotoinfo,upcalltable,lpproctable);
//调用下一层传输服务提供者的WSPStartup函数,实现钩子功能
nextproctable=*lpproctable;
//保存下一层服务提供者的30个服务函数指针
lpproctable->lpWSPSendTo=WSPSendTo;
//调用自定义函数WSPSendTo

由于以动态链接库形式的服务提供者要向外提供一个入口函数,因此还须一个配置文件ipfilter.def:
EXPORTS WSPStartup
//向外提供入口函数WSPStartup

3.T-Sporder.exe
T-Sporder.exe是一个辅助工具,用来查看当前系统中所有已经安装的传输服务提供者的属性。
totalprotocols=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode);
//获得系统中的所有传输服务提供者,然后根据参数输出它们的各项属性。

四)小结与后记
本文向大家介绍了Windows Socket 2的一个新特性,那就是服务提供者接口SPI(Service Provider Interface)。它不仅包括我们主要讲解的传输服务提供者接口,还包括名字空间服务提供者接口。当然,基于SPI的包过滤安全措施并不是特别的好,因为很多建立在TDI上面的数据传输并不会受到SPI的影响,所以当前流行的防火墙大都是建立在NDIS之上的。
传输服务提供者是以DLL的形式存在于系统之中的,在基于IP协议的网络程序运行时,如果参数相匹配就会加载我们的传输服务提供者程序。而且在Windows下有很多系统网络服务,它们都是在系统启动时自动加载的,这就为我们隐藏木马的进程提供了有利的条件。也就是说在传输服务提供者程序里嵌入木马程序,很多基于IP协议的网络系统程序在开机时运行,这样我们嵌入的木马程序就会在系统启动时自动加载,在系统关闭时才会卸载。它的特点是只要安装一次后每每系统启动就会加载我们的传输服务提供者(里面包含木马程序),而不必像远程注入线程那样在系统每次启动时执行安装程序,并且它可同时被多个系统网络程序加载。
已编译好的可执行文件(过滤QQ数据报),您可以在我们的网站(http://fz5fz.yeah.net)下载。

五)附录之源程序
1.instif.exe

#define UNICODE
#define _UNICODE

#include <stdio.h>
#include <tchar.h>
#include <string.h>
#include <ws2spi.h>
#include <sporder.h>

GUID filterguid={0×4d1e91fd,0×116a,0×44aa,{0×8f,0xd4,0×1d,0×2c,0xf2,0×7b,0xd9,0xa9}};

GUID filterchainguid={0xd3c21121,0×85e1,0×48f3,{0×9a,0xb6,0×23,0xd9,0×0c,0×73,0×07,0xef}};

BOOL getfilter();
void freefilter();
void installfilter();
void removefilter();
void start();
void usage();

int totalprotos=0;
DWORD protoinfosize=0;
LPWSAPROTOCOL_INFOW protoinfo=NULL;

int main(int argc,char *argv[])
{
start();

if(argc==2)
{
if(strcmp(argv[1],"-install")==0)
{
installfilter();
return 0;
}
else if(strcmp(argv[1],"-remove")==0)
{
removefilter();
return 0;
}
}
usage();
return 0;
}

BOOL getfilter()
{
int errorcode;

protoinfo=NULL;
totalprotos=0;
protoinfosize=0;

if(WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode)==SOCKET_ERROR)
{
if(errorcode!=WSAENOBUFS)
{
_tprintf(_T("First WSCEnumProtocols Error: %d\n"),errorcode);
return FALSE;
}
}

if((protoinfo=(LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR,protoinfosize))==NULL)
{
_tprintf(_T("GlobalAlloc in getfilter Error: %d\n"),GetLastError());
return FALSE;
}

if((totalprotos=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode))==SOCKET_ERROR)
{
_tprintf(_T("Second WSCEnumProtocols Error: %d\n"),GetLastError());
return FALSE;
}

_tprintf(_T("Found %d protocols!\n"),totalprotos);
return TRUE;
}

void freefilter()
{
GlobalFree(protoinfo);
}

void installfilter()
{
int i;
int provcnt;
int cataindex;
int errorcode;
BOOL rawip=FALSE;
BOOL udpip=FALSE;
DWORD iplayercataid=0,udporigcataid=0;
TCHAR filter_path[MAX_PATH];
TCHAR filter_name[MAX_PATH];
TCHAR chainname[WSAPROTOCOL_LEN+1];
LPDWORD cataentries;
WSAPROTOCOL_INFOW iplayerinfo,udpchaininfo,chainarray[1];

getfilter();

for(i=0;i<totalprotos;i++)
{
if(!rawip
&& protoinfo[i].iAddressFamily==AF_INET
&& protoinfo[i].iProtocol==IPPROTO_IP)
{
rawip=TRUE;
memcpy(&iplayerinfo,&protoinfo[i],sizeof(WSAPROTOCOL_INFOW));
iplayerinfo.dwServiceFlags1=protoinfo[i].dwServiceFlags1 & (~XP1_IFS_HANDLES);
}

if(!udpip
&& protoinfo[i].iAddressFamily==AF_INET
&& protoinfo[i].iProtocol==IPPROTO_UDP)
{
udpip=TRUE;
udporigcataid=protoinfo[i].dwCatalogEntryId;
memcpy(&udpchaininfo,&protoinfo[i],sizeof(WSAPROTOCOL_INFOW));
udpchaininfo.dwServiceFlags1=protoinfo[i].dwServiceFlags1 & (~XP1_IFS_HANDLES);
}
}

_tcscpy(iplayerinfo.szProtocol,_T("T-IpFilter"));
iplayerinfo.ProtocolChain.ChainLen=LAYERED_PROTOCOL;

if(GetCurrentDirectory(MAX_PATH,filter_path)==0)
{
_tprintf(_T("GetCurrentDirectory Error: %d\n"),GetLastError());
return ;
}
_tcscpy(filter_name,_T("\\ipfilter.dll"));
_tcscat(filter_path,filter_name);

if(WSCInstallProvider(&filterguid,filter_path,&iplayerinfo,1,&errorcode)==SOCKET_ERROR)
{
_tprintf(_T("WSCInstallProvider Error: %d\n"),errorcode);
return ;
}

freefilter();

getfilter();

for(i=0;i<totalprotos;i++)
{
if(memcmp(&protoinfo[i].ProviderId,&filterguid,sizeof(GUID))==0)
{
iplayercataid=protoinfo[i].dwCatalogEntryId;
break;
}
}

provcnt=0;
if(udpip)
{
_tcscpy(chainname,_T("T-UdpFilter"));
_tcscpy(udpchaininfo.szProtocol,chainname);

if(udpchaininfo.ProtocolChain.ChainLen==BASE_PROTOCOL)
{
udpchaininfo.ProtocolChain.ChainEntries[1]=udporigcataid;
}
else
{
for(i=udpchaininfo.ProtocolChain.ChainLen;i>0;i–)
{
udpchaininfo.ProtocolChain.ChainEntries[i+1]=udpchaininfo.ProtocolChain.ChainEntries[i];
}
}

udpchaininfo.ProtocolChain.ChainLen++;
udpchaininfo.ProtocolChain.ChainEntries[0]=iplayercataid;

memcpy(&chainarray[provcnt++],&udpchaininfo,sizeof(WSAPROTOCOL_INFOW));
}

if(WSCInstallProvider(&filterchainguid,filter_path,chainarray,provcnt,&errorcode)==SOCKET_ERROR)
{
_tprintf(_T("WSCInstallProvider for chain Error: %d\n"),errorcode);
return ;
}

freefilter();

getfilter();

if((cataentries=(LPDWORD)GlobalAlloc(GPTR,totalprotos*sizeof(WSAPROTOCOL_INFOW)))==NULL)
{
_tprintf(_T("GlobalAlloc int installfilter Error: %d\n"),errorcode);
return ;
}

cataindex=0;
for(i=0;i<totalprotos;i++)
{
if(memcmp(&protoinfo[i].ProviderId,&filterguid,sizeof(GUID))==0 ││ memcmp(&protoinfo[i].ProviderId,&filterchainguid,sizeof(GUID))==0)
{
cataentries[cataindex++]=protoinfo[i].dwCatalogEntryId;
}
}

for(i=0;i<totalprotos;i++)
{
if(memcmp(&protoinfo[i].ProviderId,&filterguid,sizeof(GUID))!=0 && memcmp(&protoinfo[i].ProviderId,&filterchainguid,sizeof(GUID))!=0)
{
cataentries[cataindex++]=protoinfo[i].dwCatalogEntryId;
}
}

if((errorcode==WSCWriteProviderOrder(cataentries,totalprotos))!=ERROR_SUCCESS)
{
_tprintf(_T("WSCWriteProviderOrder Error: %d\n"),GetLastError());
return ;
}

freefilter();

_tprintf(_T("\nInstall IP Filter Successfully"));
return ;
}

void removefilter()
{
int errorcode;
BOOL signal=TRUE;

if(WSCDeinstallProvider(&filterguid,&errorcode)==SOCKET_ERROR)
{
_tprintf(_T("WSCDeinstall filterguid Error: %d\n"),errorcode);
signal=FALSE;
}

if(WSCDeinstallProvider(&filterchainguid,&errorcode)==SOCKET_ERROR)
{
_tprintf(_T("WSCDeinstall filterchainguid Error: %d\n"),errorcode);
signal=FALSE;
}

if(signal)
{
_tprintf(_T("Deinstall IP Filter Successfully"));
}
return ;
}

void start()
{
_tprintf(_T("Install IP Filter, by TOo2y\n"));
_tprintf(_T("E-Mail: TOo2y@safechina.net\n"));
_tprintf(_T("HomePage: www.safechina.net\n"));
_tprintf(_T("Date: 10-29-2002\n\n"));
return ;
}
void usage()
{
_tprintf(_T("Usage: instif [ -install │ -remove ]\n"));
return ;
}

2.ipfilter.dll

#define UNICODE
#define _UNICODE

#include <ws2spi.h>
#include <tchar.h>

GUID filterguid={0×4d1e91fd,0×116a,0×44aa,{0×8f,0xd4,0×1d,0×2c,0xf2,0×7b,0xd9,0xa9}};

LPWSAPROTOCOL_INFOW protoinfo=NULL;
WSPPROC_TABLE nextproctable;
DWORD protoinfosize=0;
int totalprotos=0;

BOOL getfilter()
{
int errorcode;

protoinfo=NULL;
protoinfosize=0;
totalprotos=0;

if(WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode)==SOCKET_ERROR)
{
if(errorcode!=WSAENOBUFS)
{
OutputDebugString(_T("First WSCEnumProtocols Error!"));
return FALSE;
}
}

if((protoinfo=(LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR,protoinfosize))==NULL)
{
OutputDebugString(_T("GlobalAlloc Error!"));
return FALSE;
}

if((totalprotos=WSCEnumProtocols(NULL,protoinfo,&protoinfosize,&errorcode))==SOCKET_ERROR)
{
OutputDebugString(_T("Second WSCEnumProtocols Error!"));
return FALSE;
}

return TRUE;
}

void freefilter()
{
GlobalFree(protoinfo);
}

BOOL WINAPI DllMain(HINSTANCE hmodule,
DWORD reason,
LPVOID lpreserved)
{
TCHAR processname[MAX_PATH];
TCHAR showmessage[MAX_PATH+25];


if(reason==DLL_PROCESS_ATTACH)
{
GetModuleFileName(NULL,processname,MAX_PATH);
_tcscpy(showmessage,processname);
_tcscat(showmessage,_T(" Loading IPFilter …"));
OutputDebugString(showmessage);
}
return TRUE;
}

int WSPAPI WSPSendTo(SOCKET s,
LPWSABUF lpbuffer,
DWORD dwbuffercount,
LPDWORD lpnumberofbytessent,
DWORD dwflags,
const struct sockaddr FAR *lpto,
int itolen,
LPWSAOVERLAPPED lpoverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpcompletionroutine,
LPWSATHREADID lpthreadid,
LPINT lperrno)
{

struct sockaddr_in sin;

sin=*(const struct sockaddr_in *)lpto;
if(sin.sin_port==htons(8000))
{
OutputDebugString(_T("WSPSendTo Tencent Filtered"));
return 0;
}
else
{
return nextproctable.lpWSPSendTo(s,lpbuffer,dwbuffercount,lpnumberofbytessent,dwflags,lpto,itolen,lpoverlapped,lpcompletionroutine,lpthreadid,lperrno);
}
}

int WSPAPI WSPStartup(
WORDwversionrequested,
LPWSPDATA lpwspdata,
LPWSAPROTOCOL_INFOWlpprotoinfo,
WSPUPCALLTABLEupcalltable,
LPWSPPROC_TABLElpproctable
)
{
OutputDebugString(_T("IPFilter WSPStartup …"));

int i;
int errorcode;
int filterpathlen;
DWORD layerid=0;
DWORD nextlayerid=0;
TCHAR *filterpath;
HINSTANCE hfilter;
LPWSPSTARTUP wspstartupfunc=NULL;

if(lpprotoinfo->ProtocolChain.ChainLen<=1)
{
OutputDebugString(_T("ChainLen<=1"));
return FALSE;
}

getfilter();

for(i=0;i<totalprotos;i++)
{
if(memcmp(&protoinfo[i].ProviderId,&filterguid,sizeof(GUID))==0)
{
layerid=protoinfo[i].dwCatalogEntryId;
break;
}
}

for(i=0;i<lpprotoinfo->ProtocolChain.ChainLen;i++)
{
if(lpprotoinfo->ProtocolChain.ChainEntries[i]==layerid)
{
nextlayerid=lpprotoinfo->ProtocolChain.ChainEntries[i+1];
break;
}
}

filterpathlen=MAX_PATH;
filterpath=(TCHAR*)GlobalAlloc(GPTR,filterpathlen);
for(i=0;i<totalprotos;i++)
{
if(nextlayerid==protoinfo[i].dwCatalogEntryId)
{
if(WSCGetProviderPath(&protoinfo[i].ProviderId,filterpath,&filterpathlen,&errorcode)==SOCKET_ERROR)
{
OutputDebugString(_T("WSCGetProviderPath Error!"));
return WSAEPROVIDERFAILEDINIT;
}
break;
}
}

if(!ExpandEnvironmentStrings(filterpath,filterpath,MAX_PATH))
{
OutputDebugString(_T("ExpandEnvironmentStrings Error!"));
return WSAEPROVIDERFAILEDINIT;
}

if((hfilter=LoadLibrary(filterpath))==NULL)
{
OutputDebugString(_T("LoadLibrary Error!"));
return WSAEPROVIDERFAILEDINIT;
}

if((wspstartupfunc=(LPWSPSTARTUP)GetProcAddress(hfilter,"WSPStartup"))==NULL)
{
OutputDebugString(_T("GetProcessAddress Error!"));
return WSAEPROVIDERFAILEDINIT;
}

if((errorcode=wspstartupfunc(wversionrequested,lpwspdata,lpprotoinfo,upcalltable,lpproctable))!=ERROR_SUCCESS)
{
OutputDebugString(_T("wspstartupfunc Error!"));
return errorcode;
}

nextproctable=*lpproctable;
lpproctable->lpWSPSendTo=WSPSendTo;

freefilter();
return 0;
}

本文由[ 阳光狮人 www.sun-lion.com ]收集整理

嗅探的基本原理(转载)嗅探的基本原理(转载)

            一 前言

              SNIFF真是一个古老的话题,关于在网络上采用SNIFF来获取敏感信息已经不是什么新鲜事,也不乏很多成功的案例,那么,SNIFF究竟是什么呢?SNIFF就是嗅探器,就是窃听器,SNIFF静悄悄的工作在网络的底层,把你的秘密全部记录下来。看过威尔史密斯演的《全民公敌》吗?SNIFF就象里面精巧的窃听器一样,让你防不胜防。


              SNIFF可以是软件,也可以是硬件,既然是软件那就要分平台,有WINDOWS下的、UNXI下的等,硬件的SNIFF称为网络分析仪,反正不管硬件软件,目标只有一个,就是获取在网络上传输的各种信息。本文仅仅介绍软件的SNIFF。


              当你舒适的坐在家里,惬意的享受网络给你带来的便利,收取你的EMAIL,购买你喜欢的物品的时候,你是否会想到你的朋友给你的信件,你的信用卡帐号变成了一个又一个的信息包在网络上不停的传送着,你是否曾经这些信息包会通过网络流入别人的机器呢?你的担忧不是没有道理的,因为SNIFF可以让你的担忧变成实实在在的危险。就好象一个人躲在你身后偷看一样。。。。。。


            二 网络基础知识

             “网络基础知识”,是不是听起来有点跑题了?虽然听起来这和我们要谈的SNIFF没什么关系,可是还是要说一说的,万丈高楼平地起,如果连地基都没打好,怎么盖楼?!如果你对网络还不是十分清楚的话,最好能静下心来好好看看,要知道,这是基础的基础,在这里我只是简单的说一下,免得到时候有人迷糊,详细的最好能够自己去找书看看。


            (1)TCP/IP体系结构

              开放系统互连(OSI)模型将网络划分为七层模型,分别用以在各层上实现不同的功能,这七层分别为:应用层、表示层、会话层、传输层、网络层、数据链路层及物理层。而TCP/IP体系也同样遵循这七层标准,只不过在某些OSI功能上进行了压缩,将表示层及会话层合并入应用层中,所以实际上我们打交道的TCP/IP仅仅有5层而已,网络上的分层结构决定了在各层上的协议分布及功能实现,从而决定了各层上网络设备的使用。实际上很多成功的系统都是基于OSI模型的,如:如帧中继、ATM、ISDN等。


               TCP/IP的网络体系结构(部分)


            ———————————–
            | SMTP | DNS | HTTP | FTP | TELNET| 应用层
            ———————————–
            |    TCP    |   UDP      |  传输层
            ———————————–
            |  IP  |   ICMP   | ARP RARP | 网络层 
            ————————
            | IEEE 802 以太网 SLIP/PPP PDN etc| 数据链路层
            ———————————–
            |    网卡 电缆 双绞线 etc    | 物理层
            ———————————–

              从上面的图中我们可以看出,第一层物理层和第二层数据链路层是TCP/IP的基础,而TCP/IP本身并不十分关心低层,因为处在数据链路层的网络设备驱动程序将上层的协议和实际的物理接口隔离开来。网络设备驱动程序位于介质访问子层(MAC)。   
                             

            (2)网络上的设备                          

            中继器:中继器的主要功能是终结一个网段的信号并在另一个网段再生该信号,一句话,就是简单的放大而已,工作在物理层上。   

            网 桥:网桥使用MAC物理地址实现中继功能,可以用来分隔网段或连接部分异种网络,工作在数据链路层。

            路由器:路由器使用网络层地址(IP,X.121,E.164等),主要负责数据包的路由寻径,也能处理物理层和数据链路层上的工作。

            网 关:主要工作在网络第四层以上,主要实现收敛功能及协议转换,不过很多时候网关都被用来描述任何网络互连设备。

            (3)TCP/IP与以太网

              以太网和TCP/IP可以说是相互相成的,可以说两者的关系几乎是密不可分,以太网在一二层提供物理上的连线,而TCP/IP工作在上层,使用32位的IP地址,以太网则使用48位的MAC地址,两者间使用ARP和RARP协议进行相互转换。从我们上面TCP/IP的模型图中可以清楚的看到两者的关系。


              载波监听/冲突检测(CSMA/CD)技术被普遍的使用在以太网中,所谓载波监听是指在以太网中的每个站点都具有同等的权利,在传输自己的数据时,首先监听信道是否空闲,如果空闲,就传输自己的数据,如果信道被占用,就等待信道空闲。而冲突检测则是为了防止发生两个站点同时监测到网络没有被使用时而产生冲突。以太网采用广播机制,所有与网络连接的工作站都可以看到网络上传递的数据。


              为了加深你的理解,我们来看看下面的图,一个典型的在以太网中客户与服务器使用TCP/IP协议的通信。


            用户进程 FTP客户 <————————-> FTP服务器  应用层
            | |
            内核中的协议栈 TCP <————————-> TCP  传输层
            | |
            内核中的协议栈 IP <————————-> IP  网络层
            | |
            以太网驱动程序 <————————-> 以太网驱动程序  数据链路层

            ──────——————————- 
            以太网

              ??唆唆了这么多,有人烦了吧?相信我,这是基础的基础,可以说是说得是很简单拉,如果需要,拿出个几十万字来说上面的内容,我想也不嫌多,好了,让我们进入下一节,sniff的原理。


            三 SNIFF的原理

             要知道在以太网中,所有的通讯都是广播的,也就是说通常在同一个网段的所有网络接口都可以访问在物理媒体上传输的所有数据,而每一个网络接口都有一个唯一的硬件地址,这个硬件地址也就是网卡的MAC地址,大多数系统使用48比特的地址,这个地址用来表示网络中的每一个设备,一般来说每一块网卡上的MFC地址都是不同的,每个网卡厂家得到一段地址,然后用这段地址分配给其生产的每个网卡一个地址。在硬件地址和IP地址间使用ARP和RARP协议进行相互转换。


              在正常的情况下,一个网络接口应该只响应这样的两种数据帧:

               1.与自己硬件地址相匹配的数据帧。                                                
                 2.发向所有机器的广播数据帧。

             在一个实际的系统中,数据的收发是由网卡来完成的,网卡接收到传输来的数据,网卡内的单片程序接收数据帧的目的MAC地址,根据计算机上的网卡驱动程序设置的接收模式判断该不该接收,认为该接收就接收后产生中断信号通知CPU,认为不该接收就丢掉不管,所以不该接收的数据网卡就截断了,计算机根本就不知道。CPU得到中断信号产生中断,操作系统就根据网卡的驱动程序设置的网卡中断程序地址调用驱动程序接收数据,驱动程序接收数据后放入信号堆栈让操作系统处理。而对于网卡来说一般有四种接收模式:


               广播方式:该模式下的网卡能够接收网络中的广播信息。                                      
                 组播方式:设置在该模式下的网卡能够接收组播数据。                                       
                 直接方式:在这种模式下,只有目的网卡才能接收该数据。                                   
                  混杂模式:在这种模式下的网卡能够接收一切通过它的数据,而不管该数据是否是传给它的。

             好了,现在我们总结一下,首先,我们知道了在以太网中是基于广播方式传送数据的,也就是说,所有的物理信号都要经过我的机器,再次,网卡可以置于一种模式叫混杂模式(promiscuous),在这种模式下工作的网卡能够接收到一切通过它的数据,而不管实际上数据的目的地址是不是他。这实际上就是我们SNIFF工作的基本原理:让网卡接收一切他所能接收的数据。


            (图一)

             我们来看一个简单的例子,如图一所示,机器A、B、C与集线器HUB相连接,集线器HUB通过路由器Router访问外部网络。这是一个很简单也很常见的情况,比如说在公司大楼里,我所在的网络部办公室里的几台机器通过集线器连接,而网络部、开发部、市场部也是同样如此,几个部门的集线器通过路由器连接。还是回到我们的图一上来,值得注意的一点是机器A、B、C使用一个普通的HUB连接的,不是用SWITCH,也不是用ROUTER,使用SWITCH和ROUTER的情况要比这复杂得多。


             我们假设一下机器A上的管理员为了维护机器C,使用了一个FTP命令向机器C进行远程登陆,那么在这个用HUB连接的网络里数据走向过程是这样的。首先机器A上的管理员输入的登陆机器C的FTP口令经过应用层FTP协议、传输层TCP协议、网络层IP协议、数据链路层上的以太网驱动程序一层一层的包裹,最后送到了物理层,我们的网线上。接下来数据帧送到了HUB上,现在由HUB向每一个接点广播由机器A发出的数据帧,机器B接收到由HUB广播发出的数据帧,并检查在数据帧中的地址是否和自己的地址相匹配,发现不是发向自己的后把这数据帧丢弃,不予理睬。而机器C也接收到了数据帧,并在比较之后发现是发现自己的,接下来他就对这数据帧进行分析处理。


             在上面这个简单的例子中,机器B上的管理员如果很好奇,他很想知道究竟登陆机器C上FTP口令是什么?那么他要做的很简单,仅仅需要把自己机器上的网卡置于混杂模式,并对接收到的数据帧进行分析,从而找到包含在数据帧中的口令信息。


            四 做一个自己的sniff

             在上一节里,我们已经知道了SNIFF的基本原理是怎么一回事,这一节我们来亲自动手做一个自己的sniff,毕竟,用程序代码来说话比什么都要来得真实,也容易加深理解。


              回头想一想我们上面说的原理,我们要做的事情有几件:

               1. 把网卡置于混杂模式。                                                   
                    2. 捕获数据包。                                                   
                          3.分析数据包。

            注:下面的源代码取至Chad Renfro的<< Basic Packet-SnifferConstruction from the
            Ground Up>>一文中
            /************************Tcp_sniff_2.c********************/
            1.#include  
            2.#include  
            3.#include
            4.#include
            5.#include
            6.#include
            7.#include  
            8.#include
            9.#include "headers.h"

            #define INTERFACE "eth0"

             /*Prototype area*/

            10.int Open_Raw_Socket(void); 
            11.int Set_Promisc(char *interface, intsock); 
            12.int main() {  
            13.int sock, bytes_recieved, fromlen;  
            14.char buffer[65535];
            15.struct sockaddr_in from; 
            16.struct ip *ip;
            17.struct tcp *tcp;  
            18.sock = Open_Raw_Socket();
            19. Set_Promisc(INTERFACE, sock);

            20. while(1)
            22. {
            23. fromlen = sizeof from;
            24. bytes_recieved = recvfrom(sock, buffer, sizeofbuffer, 0, (struct
            sockaddr *)&from, &fromlen);
            25. printf("\nBytes received :::%5d\n",bytes_recieved);
            26. printf("Source address :::%s\n",inet_ntoa(from.sin_addr));
            27. ip = (struct ip *)buffer;
            /*See if this is a TCP packet*/
            28. if(ip->ip_protocol == 6) {
            29. printf("IP header length :::%d\n",ip->ip_length);
            30. printf("Protocol :::%d\n",ip->ip_protocol);
            31. tcp = (struct tcp *)(buffer +(4*ip->ip_length));
            32. printf("Source port :::%d\n",ntohs(tcp->tcp_source_port));
            33. printf("Dest port :::%d\n",ntohs(tcp->tcp_dest_port));
            34. }

            35. }
            36.}
            37.int Open_Raw_Socket() {    
            38. int sock;
            39. if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0){
            /*Then the socket was not created properly and must die*/
            40. perror("The raw socket was not created");
            41. exit(0);
            42. };  
            43. return(sock);  
            44. }

            45.int Set_Promisc(char *interface, int sock ) {  
            46. struct ifreq ifr;      
            47. strncpy(ifr.ifr_name, interface,strnlen(interface)+1);
            48. if((ioctl(sock, SIOCGIFFLAGS, &ifr) == -1)) {  
            /*Could not retrieve flags for the interface*/
            49. perror("Could not retrive flags for the interface");
            50. exit(0);
            51. } 
            52. printf("The interface is ::: %s\n", interface);  
            53. perror("Retrieved flags from interface successfully");
            54. ifr.ifr_flags |= IFF_PROMISC;  
            55. if (ioctl (sock, SIOCSIFFLAGS, &ifr) == -1 ) {  
            /*Could not set the flags on the interface */  
            56. perror("Could not set the PROMISC flag:");
            57. exit(0);    
            58. }
            59. printf("Setting interface ::: %s ::: to promisc",interface);
            60. return(0);
            61. }

            /***********************EOF**********************************/

              上面这段程序中有很详细的注解,不过我想还是有必要说一说,首先第10行–intOpen_Raw_Socket(void); 是我们的自定义函数,具体内容如下:

            37.int Open_Raw_Socket() {    
            38. int sock;
            39. if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0){
            /*Then the socket was not created properly and must die*/
            40. perror("The raw socket was not created");
            41. exit(0);
            42. };  
            43. return(sock);  
            44. }


                        

            第39行 if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) {

            这里我们调用了socket函数,使创建了了一个原始套接口,使之收到TCP/IP信息包。

              接下来第11行-int Set_Promisc(char *interface,
            intsock),这也是我们的自定义函数,目的是把网卡置于混杂模式,具体内容如下:
            45.int Set_Promisc(char *interface, int sock ) {  
            46. struct ifreq ifr;      
            47. strncpy(ifr.ifr_name, interface,strnlen(interface)+1);
            48. if((ioctl(sock, SIOCGIFFLAGS, &ifr) == -1)) {  
            /*Could not retrieve flags for the interface*/
            49. perror("Could not retrive flags for the interface");
            50. exit(0);
            51. } 
            52. printf("The interface is ::: %s\n", interface);  
            53. perror("Retrieved flags from interface successfully");
            54. ifr.ifr_flags |= IFF_PROMISC;  
            55. if (ioctl (sock, SIOCSIFFLAGS, &ifr) == -1 ) {  
            /*Could not set the flags on the interface */  
            56. perror("Could not set the PROMISC flag:");
            57. exit(0);    
            58. }
            59. printf("Setting interface ::: %s ::: to promisc",interface);
            60. return(0);
            61. }

              首先 struct ifreq ifr;
            定一了一个ifrreg的结构ifr,接下来strncpy(ifr.ifr_name,interface,strnlen(interface)+1);,就是把我们网络设备的名字填充到ifr结构中,在这里#define
            INTERFACE "eth0" ,让我们再往下看,ioctl(sock,
            SIOCGIFFLAGS,&ifr),SIOCGIFFLAGS请求表示需要获取接口标志,现在到了第54行,在我们成功的获取接口标志后把他设置成混杂模式,ifr.ifr_flags|=
            IFF_PROMISC;ioctl (sock,
            SIOCSIFFLAGS,&ifr)。OK,现在我们所说的第一步已经完成——–把网卡置于混杂模式。

              现在进入第二步,捕获数据包。从第20行开始,我们进入了一个死循环,while(1),在第24行,recvfrom(sock,buffer,
            sizeof buffer, 0, (struct sockaddr
            *)&from,&fromlen),这个函数要做的就是接收数据,冰把接收到的数据放入buffer中。就是这么简单,已经完成了我们要捕获数据包的任务。


              到了第三步,分析数据包。27行,ip = (struct
            ip*)buffer,使我们在头文件中的IP结构对应于所接收到的数据,接下来判断在网络层中是否使用的是TCP协议,if(ip->ip_protocol==
            6) ,如果答案是,tcp信息包从整个IP/TCP包 buffer +(4*ip->ip_length) 地址处开始,所以31行 tcp
            = (struct tcp*)(buffer +(4*ip->ip_length)),然后对应结构把你所需要的信息输出。
            /*************************headers.h**************************/
            /*structure of an ip header*/ 
            struct ip {   
            unsigned int ip_length:4; /*little-endian*/ 
            unsigned int ip_version:4;
            unsigned char ip_tos; 
            unsigned short ip_total_length;  
            unsigned short ip_id;  
            unsigned short ip_flags;
            unsigned char ip_ttl;
            unsigned char ip_protocol;
            unsigned short ip_cksum;
            unsigned int ip_source; unsigned int ip_dest;  
            };

            /* Structure of a TCP header */
            struct tcp {
            unsigned short tcp_source_port;
            unsigned short tcp_dest_port;
            unsigned int tcp_seqno;  
            unsigned int tcp_ackno;
            unsigned int tcp_res1:4, /*little-endian*/
            tcp_hlen:4,
            tcp_fin:1,
            tcp_syn:1,
            tcp_rst:1,
            tcp_psh:1,
            tcp_ack:1,
            tcp_urg:1,
            tcp_res2:2;
            unsigned short tcp_winsize;
            unsigned short tcp_cksum;
            unsigned short tcp_urgent;
            };
            /*********************EOF***********************************/

             从上面的分析我们可以清楚的认识到,认识一个SNIFF需要对TCP/IP协议有着详细的了解,否则你根本无法找到你需要的信息。有了上面的基础,你可以自己来做一个你需要的SNIFF了。

            五 常用的SNIFF

             很少有原因会让你自己亲自动手来做一个自己的SNIFF,除非你是想了解他的原理,或者是其他一些特别的原因,比如你要在某个特殊的环境拦截一些特殊的数据包。下面我们就来看看一些在网络上经常使用的SNIFF。


            (1)windows环境下

             
            windows环境下当然是大名鼎鼎的netxray以及snifferpro了,实际上很多人都是用他在windows环境下抓包来分析,不过我想很少有人笨到去在别人的机器上安装一个图形界面的SNIFF,除非他和管理员很熟悉……..netxray的使用就不多说了,反正windows下的东西就是click,click,click,非常的方便用户。


            (2)UNUX环境下

             UNUX环境下的sniff可以说是百花齐放,一抓就是一大把,如sniffit,snoop,tcpdump,dsniff等都是比较常见的,他们都有一个好处就是发布源代码,可以让你研究,当然也都是免费的:)


            1. sniffit

              sniffit可以运行在Solaris、SGI和Linux等平台上,由LawrenceBerkeley Laboratory
            实验室开发的一个免费的网络监听软件。最近Sniffit0.3.7也推出了NT版本,并也支持WINDOWS2000.

            使用方法:
            -v 显示版本信息
            -a 以ASCII形式将监听的结果输出。
            -A 在进行记录时,所有不可打印的字符都用代替
            -b 等同于同时使用参数-t & -s。    
            -d 将监听所得内容以十六进制方式显示在当前终端 
            -p 记录连接到的包,0为所有端口。缺省为0。   
            -P protocol选择要检查的协议,缺省为TCP。可能的选择有IP、TCP、ICMP、UDP和他们的组合。
            -s 指定sniffer 检查从 发送的数据包。 -t 指定sniffer检查发送到的数据包。
            -i 进入交互模式   
            -l 设定数据包大小,default是300字节   
            注:参数可以用@来表示一个IP范围,比如 -t 192.168.@
            -t和-s只适用于TCP/UDP数据包,对于ICMP和IP也进行解释。但如果只选择了-p参数,则只用于TCP和UDP包。

            举例说明:

            #sniffit -a -p 21 -t xxx.xxx.xxx.xxx

            监听流向机器xxx.xxx.xxx.xxx的21端口(FTP)的信息,并以ASCII显示

            #sniffit -d -p 23 -b xxx.xxx.xxx.xxx

            监听所有流出或流入机器xxx.xxx.xxx.xxx的23端口(telnet)的信息,并以16进制显示

            你可以在这里找到sniffithttp://reptile.rug.ac.be/~coder/sniffit/sniffit.html

            2. snoop

             snoop默认情况安装在Solaris下,是一个用于显示网络交通的程序,不过SNIFF是把双刃剑,既然管理员能用他来监视自己的网络,当然一个心怀恶意的入侵者也可以用他来SNIFF自己感兴趣的内容。值得一提的是,SNOOP被发现存在一个缓冲区溢出漏洞,当以导致入侵者以运行snoop(通常为root)的身份远程进入系统。这是题外话,暂且就此打住。


            使用方法:
            [ -a ] # Listen to packets on audio
            [ -d device ] # settable to le?, ie?, bf?, tr?
            [ -s snaplen ] # Truncate packets
            [ -c count ] # Quit after count packets
            [ -P ] # Turn OFF promiscuous mode
            [ -D ] # Report dropped packets
            [ -S ] # Report packet size
            [ -i file ] # Read previously captured packets
            [ -o file ] # Capture packets in file
            [ -n file ] # Load addr-to-name table from file
            [ -N ] # Create addr-to-name table
            [ -t r|a|d ] # Time: Relative, Absolute or Delta
            [ -v ] # Verbose packet display
            [ -V ] # Show all summary lines
            [ -p first[,last] ] # Select packet(s) to display
            [ -x offset[,length] ] # Hex dump from offset for length
            [ -C ] # Print packet filter code


            例如:

            #snoop -o saved A B

            监听机器A与B的谈话,并把内容存储于文件saved中

            3. tcpdump

             tcpdmp也算是一个很有名气的网络监听软件,FREEBSD还把他附带在了系统上,是一个被很多UNIX高手认为是一个专业的网络管理工具。


            使用方法:
              tcpdump采用命令行方式,它的命令格式为:
            tcpdump [ -adeflnNOpqStvx ][ -c 数量 ][ -F 文件名 ][ -i网络接口 ][ -r 文件名][
            -s snaplen ][ -T 类型 ][ -w 文件名 ][表达式]
            1. tcpdump的选项介绍
            -a    将网络地址和广播地址转变成名字;
            -d    将匹配信息包的代码以人们能够理解的汇编格式给出;
            -dd    将匹配信息包的代码以c语言程序段的格式给出;
            -ddd   将匹配信息包的代码以十进制的形式给出;
            -e    在输出行打印出数据链路层的头部信息;
            -f    将外部的Internet地址以数字的形式打印出来;
            -l    使标准输出变为缓冲行形式;
            -n    不把网络地址转换成名字;
            -t    在输出的每一行不打印时间戳;
            -v   输出一个稍微详细的信息,例如在ip包中可以包括ttl和服务类型的信息;
            -vv    输出详细的报文信息;
            -c    在收到指定的包的数目后,tcpdump就会停止;
            -F    从指定的文件中读取表达式,忽略其它的表达式;
            -i    指定监听的网络接口;
            -r    从指定的文件中读取包(这些包一般通过-w选项产生);
            -w    直接将包写入文件中,并不分析和打印出来;
            -T   将监听到的包直接解释为指定的类型的报文,常见的类型有rpc和snmp

            2. tcpdump的表达式介绍

             表达式是一个正则表达式,tcpdump利用它作为过滤报文的条件,如果一个报文满足表达式的条件,则这个报文将会被捕获。如果没有给出任何条件,则网络上所有的信息包将会被截获。


             在表达式中一般如下几种类型的关键字,一种是关于类型的关键字,主要包括host,net,port,例如 host
            210.27.48.2,指明 210.27.48.2是一台主机,net 202.0.0.0指明
            202.0.0.0是一个网络地址,port 23指明端口号是23。如果没有指定类型,缺省的类型是host.

              第二种是确定传输方向的关键字,主要包括src , dst ,dst or src, dstand src ,这些关键字指明了
            传输的方向。举例说明,src 210.27.48.2,指明ip包中源地址是210.27.48.2 , dst net 202.0.0.0
            指明目的网络地址是202.0.0.0 。如果没有指明方向关键字,则缺省是src ordst关键字。

             
            第三种是协议的关键字,主要包括fddi,ip,arp,rarp,tcp,udp等类型。Fddi指明是在FDDI(分布式光纤数据接口网络)上的特定的网络协议,实际上它是"ether"的别名,fddi和ether具有类似的源地址和目的地址,所以可以将fddi协议包当作ether的包进行处理和分析。其他的几个关键字就是指明了监听的包的协议内容。如果没有指定任何协议,则tcpdump将会监听所有协议的信息包。


              除了这三种类型的关键字之外,其他重要的关键字如下:gateway,broadcast,less,greater,还有三种
            逻辑运算,取非运算是 ‘not ‘ ‘! ‘,与运算是’and’,'&&’;或运算 是’or’ ,’||’。

            举例使用:

            #tcpdump host AAA.BBB.CCC.DDD

            将监听IP地址为AAA.BBB.CCC.DDD的机器的通话

            #tcpdump tcp port 23 host AAA.BBB.CCC.DDD

            将监听IP地址为AAA.BBB.CCC.DDD的机器的23端口的通话

            4. dsniff

             之所以要谈谈dsniff,是因为他不仅仅是一个sniff,在他的整个套件包中,包含了很多其它有用的工具,如arpspoof,dnsspoof,macof,tcpkill等等,SNIFF的手段更加的多样和复杂化。dsniff是由DugSong开发的你可以在他的主页上找到这个工具。
            目前dsniff支持OpenBSD (i386),Redhat Linux (i386), 和Solaris (sparc).
            并且在FreeBSD, DebianLinux, Slackware Linux,
            AIX,和HP-UX上也能运转得很好。但是dsniff需要几个其他的第三方软件进行支持,他们分别是,BerkeleyDB
            ,OpenSSL, libpcap,
            libnet,libnids。如果条件允许的话,你最好能够亲自读一读dsniff的源代码,你可以在http://naughty.monkey.org/~dugsong/
            找到dsniff。


            六 深入sniff

             单纯的sniff的功能始终是局限的,所以在大多数的情况下,sniff往往和其他手段结合起来使用,sniff和spoof已及其他技术手段结合在一起对网络构成的危害是巨大的。单纯的sniff好比缺了一只腿,无法发挥大的作用,例如在sniff原理一节中我们讨论的例子里,我一再的强调我们使用的是一个普通的HUB进行连接是有原因的,如果我们把在图一中的HUB用一个switch代替,那情况就要复杂一些了,如图二所示:


            图(二)

             在图二中,我们的机器A、B、C与Switch相连接,而Switch通过路由器Router访问外部网络。我们先来了解Switch的工作原理:


              在我们图一中的
            HUB只是简单地把所接收到的信号通过所有端口(除了信号来的那个口)重复发送出去不同,而图二中的Switch却可以检查每一个收到的数据包,并对数据包进行相应的处理。在Switch内保存着每一个网段上所有节点的物理地址,只允许必要的网络流量通过Switch。举例来说,当Switch接收到一个数据包之后,根据自身保存的网络地址表检查数据包内包含的发送和接收方地址。如果接收方位于发送方网段,该数据包就会被Switch丢弃,不能通过交换机传送到其它的网段;如果接收方和发送方位于两个不同的网段,该数据包就会被Switch转发到目标网段。这样,通过交换机的过滤和转发,可以有效避免网络广播风暴,减少误包和错包的出现。顺便说一句,现在Switch和HUB的价格相去无几,所以hub正逐渐被网络交换机取代。


             现在回到我们的例子中来,在图二中仍然和图一一样,我们假设机器A上的管理员为了维护机器C,使用了一个FTP命令向机器C进行远程登陆,那么在这里,数据是这样走的:首先机器A上的管理员输入的登陆机器C的FTP口令经过应用层FTP协议、传输层TCP协议、网络层IP协议、数据链路层上的以太网驱动程序一层一层的包裹,最后送到了物理层,我们的网线上。接下来数据帧送到了Switch上,而Switch检查数据帧中的目的地址,并在他自身保存的网络地址表中知道了他应该把这数据帧发到机器C那里,于是,接下来机器C接收到了从A发来的信息,发现他是发给自己的信息,于是进行分析处理。


             OK,现在我们机器B上的管理员的好奇心只能深深的埋藏在心里了,因为数据包根本就没有经过他,就算他把自己的网卡设置成混杂模式也是有力无处使。


             在了解在一个Switch环境下原理后,我们结合一些手段去设法sniff,是的,我们可以做到这一点,有许多的手段可以让管理员B满足他的好奇心,在下面我会提出几个办法,当然只是其中的一些办法而已。


            1 ARP Spoof

              在基于IP通信的内部网中,我们可以使用 ARP
            Spoof的手段,了解什么是ARPSpoof的前提你先要明白一下什么是ARP和RARP协议,什么是MAC地址,什么又是IP地址。ARP协议是地址转换协议,RARP被称为反向地址转换协议,他们负责把IP地址和MAC地址进行相互转换对应。


              ARP Spoof 攻击的根本原理是因为计算机中维护着一个
            ARP高速缓存,并且这个ARP高速缓存是随着计算机不断的发出ARP请求和收到ARP响应而不断的更新的,ARP高速缓存的目的是把机器的IP地址和MAC地址相互映射。你可以使用
            arp命令来查看你自己的
            ARP高速缓存。现在设想一下,一个Switch工作在数据链路层,他根据MAC地址来转发他所接收的数据包,而计算器维护的ARP
            高速缓存却是动态的……你想到什么了吗?

              为了加深理解,现在给我们的机器编上号,机器A:IP地址为 10.0.0.1,MAC地址为 20-53-52-43-00-01
            ,机器B:IP地址为 10.0.0.2 ,MAC地址为20-53-52-43-00-02,机器C:IP地址为 10.0.0.3
            ,MAC地址为20-53-52-43-00-03 。现在机器B上的管理员是个聪明的家伙,他向机器A发出一个 ARP
            Reply(协议没有规定一定要等ARP Request出现才 能发送ARPReply,也没有规定一定要发送过ARP
            Request才能接收ARPReply),其中的目的IP地址为10.0.0.1,目的MAC地址为20-53-52-43-00-01
            ,而源IP地址为10.0.0.3,源MAC地址为20-53-52-43-00-02 ,好了,现在机器A更新了他的
            ARP高速缓存,并相信了IP地址为10.0.0.3的机器的MAC地址是20-53-52-43-00-02
            。当机器A上的管理员发出一条FTP命令时—ftp10.0.0.3,数据包被送到了Switch,Switch查看数据包中的目的地址,发现MAC为20-53-52-43-00-02,于是,他把数据包发到了机器B上。再设想一下,如果不想影响A和C之间的通信该怎么办?你可以同时欺骗他们双方,来一个
            man-in-middle 。

             当然,在实际的操作中你还需要考虑到一些其他的事,比如某些操作系统在会主动的发送ARP请求包来更新相应的ARP入口等。

            2. MAC Flooding

             在上面我们曾经提到过,Switch之所以能够由数据包中目的MAC地址判断出他应该把数据包发送到那一个端口上是根据他本身维护的一张地址表。这张地址表可能是动态的也可能是静态的,这要看Switch的厂商和Switch的型号来定,对于某些Switch来说,他维护的是一张动态的地址表,并且地址表的大小是有上限的,比如 3comSuperstack
            Switch 3300 (3c16981 Hardware v.1
            Softwarev.2.10) 就是这样一种Switch,我们可以通过发送大量错误的地址信息而使SWITCH维护的地址表“溢出”,从而使他变成广播模式来达到我们要sniff
            机器A与机器C之间的通信的目的。

            3. Fake the MAC address

             伪造MAC地址也是一个常用的办法,不过这要基于你网络内的Switch是动态更新其地址表,这实际上和我们上面说到的ARP
            Spoof有些类似,只不过现在你是想要Switch相信你,而不是要机器A相信你。因为Switch是动态更新其地址表的,你要做的事情就是告诉Switch:HI,我是机器C。换成技术上的问题你只不过需要向Switch发送伪造过的数据包,其中源MAC地址对应的是机器C的MAC地址,现在Switch就把机器C和你的端口对应起来了。不过其中存在一个问题,现在机器C也会说了:HI,Switch老大,我才是机器C呢!,现在你该怎么办?切,还用问!让他说不了话就可以了,DOS还是其他什么,随便你了……


            4. ICMP Router Advertisements

             
            这主要是由ICMP路由器发现协议(IRDP)的缺陷引起的,在Windows95、98、2000及SunOS、Solaris2.6等系统中,都使用了IRDP协议,SunOS系统只在某些特定的情况下使用该协议,而Windows95,Windows95b,
            Windows98, Windows98se,
            和Windows2000都是默认的使用IRDP协议。IRDP协议的主要内容就是告诉人们谁是路由器,设想一下,一个HACK利用IRDP宣称自己是路由器的情况会有多么的糟糕!所有相信HACK的请求的机器把他们所有的数据都发送给HACK所控制的机器………


            5. ICMP Redirect

             所谓ICMP重定向,就是指告诉机器向另一个不同的路由发送他的数据包,ICMP重定向通常使用在这样的场合下,假设A与B两台机器分别位于同一个物理网段内的两个逻辑子网内,而A和B都不知道这一点,只有路由器知道,当A发送给B的数据到达路由器的时候,路由器会向A送一个ICMP重定向包裹,告诉A:HI,别再送数据给我转交了,你就直接送到B那里就可以了。设想一下,一个hack完全可以利用这一点,使得A发送给B的数据经过他。


             上面提到的这些方法只不是其中的一些,为了配合sniff能够工作得更有效率,还有其他许多的办法,其实sniff的目的说穿了只有一个,就是抓包,从抓包这个概念上引伸下去,所有为了能够抓到网络上的信息包而采用的技术都可以归入sniff,单纯的sniff是没有什么效率的。你还能想到什么吗?进攻路由器,在路由器上放置sniff……,在系统内核中植入sniff……等等。

            七 如何防止SNIFF

             防止sniff最有效的手段就是进行合理的网络分段,并在网络中使用交换机和网桥,在理想的情况下使每一台机器都拥有自己的网络段,当然这会使你的网络建设费用增加很多,所以你可以尽量使相互信任的机器属于同一个网段,使他们互相之间不必担心sniff的存在。并在网段于网段间进行硬件屏障。你也可以使用加密技术对你在网络中传送的敏感数据如户ID或口令,你的银行帐号,商业机密等进行加密,你可以选用SSH等加密手段。为了防止ARP欺骗,你可以使用永久的ARP缓存条目,反正上面的攻击手段和原理你也看了,你就反过来想想该怎么办好了。不过有盾必有矛,平时的安全意识才是最重要的。


             (注:以下关于AntiSniff的介绍取至backend翻译整理的L0pht AntiSniff技术文档一文)

             
            当你做做层层保护后,你还是怀疑自己的网络上存在sniff该怎么办?L0pht小组为了探测sniff专门发布了一个软件 AntiSniff,当然这个软件不是免费的:),AntiSniff工具用于检测局域网中是否有机器处于混杂模式,AntiSniff
            Version1.x被设计为运行在以太网的Windows系统中,提供了简单易用的图形用户界面,AntiSniffVersion1.x 主要工作在非交换环境下的本地网段中,如果运行在交换环境下其功能将大打折扣。AntiSniffVer
            2.0将不但能在本地网段中,而且能够穿过路由器和交换机进行工作。

            ◆ 操作系统类特殊测试

            Linux 内核测试

             旧版本的Linux内核存在一个奇怪的特性,可被用于确定机器是否处于混杂模式。在正常情形下,网卡会过滤和丢弃那些目标地址不是本机MAC地址或以太网广播地址的数据包。如果数据包的目标地址为本机以太网地址或广播地址,将传送给内核进行处理,因为其认为该以太网数据帧包含了本机的正确IP地址或该网络广播地址。如果网卡处于混杂模式,则每个数据包都会传递给操作系统进行分析或处理。许多版本的Linux内核只检查数据包中的IP地址以确定是否存放到IP堆栈中进行处理。为了利用这一点,AntiSniff构造一个无效以太网地址而IP地址有效的数据包。对于使用了这些内核版本和处于混杂模式的Linux系统,由于只检查到IP地址有效而将其接收并存放到相应堆栈中。通过在这个伪造的以太网数据帧中构造一个ICMPECHO请求,这些系统会返回响应包(如果处于混杂模式)或忽略(如果不处于混杂模式),从而暴露其工作模式。当伪造的以太网数据帧中的IP地址设置为网络广播地址时这个测试非常有效。AntiSniff的使用者可以修改伪造的以太网址,缺省值为66:66:66:66:66:66。


            NetBSD

             许多NetBSD内核具有与上述Linux内核相同的特性,不过伪造以太网数据帧中的IP地址必须设为广播地址。

            Windows 95/98/NT

             根据对网络驱动程序头文件的了解,可以知道当处于混杂模式时,Microsoft的操作系统会确切地检查每个包的以太网地址。如果与网卡的以太网地址匹配,将作为目标IP地址为本机的数据包存放到相应堆栈中处理。可以被利用的一点是系统对以太网广播包的分析。在正常情形下,例如机器工作在非混杂模式下,网卡只向系统内核传输那些目标以太网址与其匹配或为以太网广播地址(ff:ff:ff:ff:ff:ff)的数据包。如果机器处于混杂模式下,网络驱动程序仍然会检查每个数据包的以太网地址,但检查是否为广播包时却只检查头8位地址是否为0xff。因此,为了使处于混杂模式的系统返回响应信息,AntiSniff构造以太网地址为ff:00:00:00:00:00且含有正确目标IP地址的数据包,当Microsoft的操作系统接收到这个数据包时,将根据网络驱动程序检查到的细微差别而返回响应包(如果处于混杂模式)或丢弃这个数据包(如果处于非混杂模式)。


             需要注意的是,这个检查与使用的网络驱动程序有关。Microsoft缺省的网络驱动程序具有以上特性,大多数的厂商为了保持兼容性也继承了这些特性。不过有些网卡会在其硬件层中检查以太网地址的头8位,所以可能会无论系统真正的状态是什么都总是返回正值。关于这类网卡和驱动程序请访问AntiSniff
            Ver1.x的web网站。

            ◆ DNS 测试

             进行DNS测试的原因是许多攻击者使用的网络数据收集工具都对IP地址进行反向DNS解析,因为他们希望根据域名寻找更有价值的主机。例如joepc1.foo.bar对攻击者的吸引力往往不如payroll.foo.bar这种商业域名。此时这些工具就由被动型网络工具变为主动型网络工具了。而不监听网络通讯的机器不会试图反向解析数据包中的IP地址。为了利用这一点,AntiSniff
            Ver1.x使自身处于混杂模式下,向网络发送虚假目标IP地址的数据包,然后监听是否有机器发送该虚假目标IP地址的反向DNS查询。伪造数据包的以太网地址、检查目标、虚假目标IP地址可由用户定制。


            ◆ 网络和主机响应时间测试

             这种测试已被证明是最有效的。它能够发现网络中处于混杂模式的机器,而不管其操作系统是什么。警告,这个测试会在很短的时间内产生巨大的网络通讯流量。进行这种测试的理由是不处于混杂模式的网卡提供了一定的硬件底层过滤机制。也就是说,目标地址非本地(广播地址除外)的数据包将被网卡的固件丢弃。在这种情况下,骤然增加、但目标地址不是本地的网络通讯流量对操作系统的影响只会很小。而处于混杂模式下的机器则缺乏此类底层的过滤,骤然增加、但目标地址不是本地的网络通讯流量会对该机器造成较明显的影响(不同的操作系统/内核/用户方式会有不同)。这些变化可以通过网络通讯流量工具监视到。


              根据以上要点,AntiSniff Ver 1.x
            首先利用ICMPECHO请求及响应计算出需要检测机器的响应时间基准和平均值。在得到这个数据后,立刻向本地网络发送大量的伪造数据包。与此同时再次发送测试数据包以确定平均响应时间的变化值。非混杂模式的机器的响应时间变化量会很小,而混杂模式的机器的响应时间变化量则通常会有1-4个数量级。为了对付攻击者和入侵者们最常用的多种工具,AntiSniff进行了三种网络饱和度测试:SIXTYSIX、TCPSYN和THREEWAY。


               
             *SIXTYSIX测试构造的数据包数据全为0×66。这些数据包不会被非混杂模式的机器接收,同时方便使用常见的网络监听/分析工具(如tcpdump和snoop等)记录和捕获。


                 *TCPSYN测试构造的数据包包含有效的TCP头和IP头,同时TCP标志域的SYN位被设置。

              
             *THREEWAY测试采取的原理基本上与TCPSYN一样,但更复杂些。在这种测试中两个实际不存在的机器间多次建立完整的TCP三方握手通讯。它能够更好地欺骗那些骇客工具。


              AntiSniff Ver
            1.x中能够通过以上三种数据包测试发现正处于混杂模式机器的测试方法最好周期性地进行和与以前的数据比较。响应时间测试第一次运行的数据还能够用于分析一个大型网络在flooding和非flooding状态时的性能,并帮助工程师调整网络性能。一旦确信本地网络已运行在正常(没有未经允许而处于混杂模式的机器)状态,就应该设置AntiSniff工具周期性运行。只要发现某台机器性能(响应时间)发生数量级的变化,一般就能确定其正处于混杂模式。这种方法不需比较两台独立系统间的性能数据,而只需比较同一台机器不同时候的数据就能确定该机器是否处于混杂模式。


            八 结尾

             本文旨在向你描述sniff的基本原理,为的是要使你不仅仅能够了解什么是sniff而已,而是要明白sniff运转的根本原理,文章参考了大量的资料,牵涉到直接引用的已经注明出处和作者,非常的感谢他们。在此还要感谢W.Richhard.Stevens,虽然其人已逝,但留下的TCP/IP三卷本真是造福了大家,文章的很多地方也是拜他老人家指点迷经才得以感悟。最后还要感谢雀巢咖啡,让我得以熬夜把这篇文章写完,呵呵,谢谢大家。

            妖狐安全网 15safety.com 版权所有 &copy; 2001
            所有文章版权属于原作者所有,转载请与原作者联系并注明出处-妖狐安全资讯网

2005年05月02日

上传一个东西怎么就这么费劲呀,,哎~~