sniffer中文翻译过来就是嗅探器,在当前网络技术中使用得非常得广泛。sniffer既可以做为网络故障的诊断工具,也可以作为黑客嗅探和监听的工具。最近两年,网络监听(sniffer)技术出现了新的重要特征。传统的sniffer技术是被动地监听网络通信、用户名和口令。而新的sniffer技术出现了主动地控制通信数据的特点,把sniffer技术扩展到了一个新的领域。Sniffer 技术除了目前在传统的网络侦测管理外,也开始被应用在资讯保全的领域。可以这样说,sniffer技术是一把双刃剑,如何更好的利用它,了解它的一些特性,将能使这项技术更好的为我们带来便利。

sniffer的编程方法比较通用的有以下几种,

1.winpcap 这是一个比较通用的库,相信做过抓包的工具大多数人都不会太陌生

2.raw socket 在2000以后的版本都支持此项功能,2000 server有个网络监视器就是基于raw socket

3.tdi,ndis,spi,hook socket技术,这种技术比较大的不同是可以将包截取而不是仅仅获得包的一份拷贝。总的说来,一般以前两者居多。

一)winpcap驱动简介

  winpcap(windows packet capture)是windows平台下一个免费,公共的网络访问系统。开发winpcap这个项目的目的在于为win32应用程序提供访问网络底层的能力。它提供了以下的各项

功能:

  1> 捕获原始数据报,包括在共享网络上各主机发送/接收的以及相互之间交换的数据报;

  2> 在数据报发往应用程序之前,按照自定义的规则将某些特殊的数据报过滤掉;

  3> 在网络上发送原始的数据报;

  4> 收集网络通信过程中的统计信息。

 

winpcap的主要功能在于独立于主机协议(如TCP-IP)而发送和接收原始数据报。也就是说,winp

cap不能阻塞,过滤或控制其他应用程序数据报的发收,它仅仅只是监听共享网络上传送的数据报。因此,它不能用于QoS调度程序或个人防火墙。

Raw Socket(原始套接字)实现Sniffer(嗅探)

一. 摘要

  Raw Socket: 原始套接字

  可以用它来发送和接收 IP 层以上的原始数据包, 如 ICMP, TCP, UDP...

    int sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

  这样我们就创建了一个 Raw Socket

  Sniffer: 嗅探器

  关于嗅探器的原理我想大多数人可能都知道

  1. 把网卡置于混杂模式;

  2. 捕获数据包;

  3. 分析数据包.

  但具体的实现知道的人恐怕就不是那么多了. 好, 现在让我们用 Raw Socket 的做一个自已的 Sniffer.

二. 把网卡置于混杂模式

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

  一种是与自己硬件地址相匹配的数据帧

  一种是发向所有机器的广播数据帧

  如果要网卡接收所有通过它的数据, 而不管是不是发给它的, 那么必须把网卡置于混杂模式. 也就是说让它的思维混乱, 不按正常的方式工作. 用 Raw Socket 实现代码如下:

    setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag); //设置 IP 头操作选项

    bind(sockRaw, (PSOCKADDR)&addrLocal, sizeof(addrLocal); //把 sockRaw 绑定到本地网卡上

    ioctlsocket(sockRaw, SIO_RCVALL, &dwValue);       //让 sockRaw 接受所有的数据

  flag 标志是用来设置 IP 头操作的, 也就是说要亲自处理 IP 头: bool flag = ture;

  addrLocal 为本地地址: SOCKADDR_IN addrLocal;

  dwValue 为输入输出参数, 为 1 时执行, 0 时取消: DWORD dwValue = 1;

  没想到这么简单吧?

三. 捕获数据包

  你的 sockRaw 现在已经在工作了, 可以在局域网内其它的电脑上用 Sniffer 检测工具检测一下, 看你的网卡是否处于混杂模式(比如 DigitalBrain 的 ARPKiller).

  不能让他白白的浪费资源啊, 抓包!

    recv(sockRaw, RecvBuf, BUFFER_SIZE, 0); //接受任意数据包

  #define BUFFER_SIZE 65535

  char RecvBuf[BUFFER_SIZE];

  越来越发现 Sniffer 原来如此的简单了, 这么一个函数就已经完成抓取数据包的任务了.

四. 分析数据包

  这回抓来的包和平常用 Socket 接受的包可就不是一回事儿了, 里面包含 IP, TCP 等原始信息. 要分析它首先得知道这些结构.

  数据包的总体结构:

---------------------------------------------- 
  | ip header | tcp header(or x header) | data | 
  ---------------------------------------------- 
  IP header structure: 
       4    8    16                    32 bit 
  |--------|--------|----------------|--------------------------------| 
  | Ver  | IHL  |Type of service |     Total length     | 
  |--------|--------|----------------|--------------------------------| 
  | Identification |   Flags   |     Fragment offset    | 
  |--------|--------|----------------|--------------------------------| 
  | Time to live  |  Protocol  |     Header checksum    | 
  |--------|--------|----------------|--------------------------------| 
  |             Source address              | 
  |--------|--------|----------------|--------------------------------| 
  |            Destination address             | 
  |--------|--------|----------------|--------------------------------| 
  |            Option + Padding              | 
  |--------|--------|----------------|--------------------------------| 
  |                Data                | 
  |--------|--------|----------------|--------------------------------| 
  TCP header structure: 
                  16                32 bit 
  |--------------------------------|--------------------------------| 
  |     Source port      |    Destination port    | 
  |--------------------------------|--------------------------------| 
  |             Sequence number             | 
  |--------------------------------|--------------------------------| 
  |           Acknowledgement number           | 
  |--------------------------------|--------------------------------| 
  | Offset | Resrvd |U|A|P|R|S|F|      Window       | 
  |--------------------------------|--------------------------------| 
  |      Checksum       |    Urgent pointer     | 
  |--------------------------------|--------------------------------| 
  |             Option + Padding            | 
  |--------------------------------|--------------------------------| 
  |               Data                | 
  |--------------------------------|--------------------------------|

五. 实现 Sniffer

  OK!

  现在都清楚了, 还等什么.

  下面是我用 BCB6 写的一个 Simple Sniffer 的代码, 仅供参考.

  (需要在工程文件里加入WS2_32.LIB这个文件)

//*************************************************************************// 
//* CPP File: WMain.cpp 
//* Simple Sniffer by shadowstar 
//*************************************************************************// 
#include <vcl.h> 
#pragma hdrstop 
#include <winsock2.h> 
#include <ws2tcpip.h> 
#include <mstcpip.h> 
#include <netmon.h> 
#include "WMain.h" 
//--------------------------------------------------------------------------- 
#pragma package(smart_init) 
#pragma resource "*.dfm" 
TMainForm *MainForm; 
//--------------------------------------------------------------------------- 
__fastcall TMainForm::TMainForm(TComponent* Owner) 
  : TForm(Owner) 
{ 
WSADATA WSAData; 
  BOOL  flag  = true; 
  int   nTimeout = 1000; 
  char  LocalName[16]; 
  struct hostent *pHost; 
  //检查 Winsock 版本号 
  if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) 
    throw Exception("WSAStartup error!"); 
  //初始化 Raw Socket 
  if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == INVALID_SOCKET) 
    throw Exception("socket setup error!"); 
  //设置IP头操作选项 
  if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag)) == SOCKET_ERROR) 
    throw Exception("setsockopt IP_HDRINCL error!"); 
  //获取本机名 
  if (gethostname((char*)LocalName, sizeof(LocalName)-1) == SOCKET_ERROR) 
    throw Exception("gethostname error!"); 
  //获取本地 IP 地址 
  if ((pHost = gethostbyname((char*)LocalName)) == NULL) 
    throw Exception("gethostbyname error!"); 
  addr_in.sin_addr  = *(in_addr *)pHost->h_addr_list[0]; //IP 
  addr_in.sin_family = AF_INET; 
  addr_in.sin_port  = htons(57274); 
  //把 sock 绑定到本地地址上 
  if (bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR) 
    throw Exception("bind error!"); 
  iSortDirection = 1; 
} 
//--------------------------------------------------------------------------- 
__fastcall TMainForm::~TMainForm() 
{ 
  WSACleanup(); 
} 
//--------------------------------------------------------------------------- 
void __fastcall TMainForm::btnCtrlClick(TObject *Sender) 
{ 
  TListItem *Item; 
  DWORD dwValue; 
  int nIndex = 0; 
  if (btnCtrl->Caption == "&Start") 
  { 
    dwValue = 1; 
    //设置 SOCK_RAW 为SIO_RCVALL,以便接收所有的IP包 
    if (ioctlsocket(sock, SIO_RCVALL, &dwValue) != 0) 
      throw Exception("ioctlsocket SIO_RCVALL error!"); 
    bStop = false; 
    btnCtrl->Caption = "&Stop"; 
    lsvPacket->Items->Clear(); 
  } 
  else 
  { 
    dwValue = 0; 
    bStop = true; 
    btnCtrl->Caption = "&Start"; 
    //设置SOCK_RAW为SIO_RCVALL,停止接收 
    if (ioctlsocket(sock, SIO_RCVALL, &dwValue) != 0) 
      throw Exception("WSAIoctl SIO_RCVALL error!"); 
  } 
  while (!bStop) 
  { 
    if (recv(sock, RecvBuf, BUFFER_SIZE, 0) > 0) 
    { 
      nIndex++; 
       
      ip = *(IP*)RecvBuf; 
      tcp = *(TCP*)(RecvBuf + (ip.HdrLen & IP_HDRLEN_MASK)); 
      Item = lsvPacket->Items->Add(); 
      Item->Caption = nIndex; 
      Item->SubItems->Add(GetProtocolTxt(ip.Protocol)); 
      Item->SubItems->Add(inet_ntoa(*(in_addr*)&ip.SrcAddr)); 
      Item->SubItems->Add(inet_ntoa(*(in_addr*)&ip.DstAddr)); 
      Item->SubItems->Add(tcp.SrcPort); 
      Item->SubItems->Add(tcp.DstPort); 
      Item->SubItems->Add(ntohs(ip.TotalLen)); 
    } 
    Application->ProcessMessages(); 
  }   
} 
//--------------------------------------------------------------------------- 
AnsiString __fastcall TMainForm::GetProtocolTxt(int Protocol) 
{ 
  switch (Protocol) 
  { 
    case IPPROTO_ICMP :      //1        /* control message protocol */ 
      return PROTOCOL_STRING_ICMP_TXT; 
    case IPPROTO_TCP :      //6        /* tcp */ 
      return PROTOCOL_STRING_TCP_TXT; 
    case IPPROTO_UDP :      //17       /* user datagram protocol */ 
      return PROTOCOL_STRING_UDP_TXT; 
    default : 
      return PROTOCOL_STRING_UNKNOWN_TXT; 
  } 
} 
//--------------------------------------------------------------------------- 
//*************************************************************************// 
//* Header File: WMain.h for WMain.cpp class TMainForm 
//*************************************************************************// 
//--------------------------------------------------------------------------- 
#ifndef WMainH 
#define WMainH 
//--------------------------------------------------------------------------- 
#define BUFFER_SIZE 65535 
#include <Classes.hpp> 
#include <Controls.hpp> 
#include <StdCtrls.hpp> 
#include <Forms.hpp> 
#include <ComCtrls.hpp> 
#include <ExtCtrls.hpp> 
#include <winsock2.h> 
#include "netmon.h" 
//--------------------------------------------------------------------------- 
class TMainForm : public TForm 
{ 
__published: // IDE-managed Components 
  TPanel *Panel1; 
  TButton *btnCtrl; 
  TListView *lsvPacket; 
  TLabel *Label1; 
  void __fastcall btnCtrlClick(TObject *Sender); 
  void __fastcall lsvPacketColumnClick(TObject *Sender, 
     TListColumn *Column); 
  void __fastcall lsvPacketCompare(TObject *Sender, TListItem *Item1, 
     TListItem *Item2, int Data, int &Compare); 
  void __fastcall Label1Click(TObject *Sender); 
private: // User declarations 
  AnsiString __fastcall GetProtocolTxt(int Protocol); 
public: // User declarations 
  SOCKET   sock; 
  SOCKADDR_IN addr_in; 
  IP     ip; 
  TCP     tcp; 
  PSUHDR   psdHeader; 
  char    RecvBuf[BUFFER_SIZE]; 
  bool    bStop; 
  int iSortDirection; 
  int iColumnToSort; 
   
  __fastcall TMainForm(TComponent* Owner); 
  __fastcall ~TMainForm(); 
}; 
//--------------------------------------------------------------------------- 
extern PACKAGE TMainForm *MainForm; 
//--------------------------------------------------------------------------- 
#endif 
  偷了个懒, IP, TCP 头及一些宏定义用了 netmon.h 的头, 这个文件在 BCB6 的 include 目录下可以找得到, 其中与本程序相关内容如下: 
//*************************************************************************// 
//* Header File: netmon.h 
//*************************************************************************// 
// 
// IP Packet Structure 
// 
typedef struct _IP 
{ 
  union 
  { 
    BYTE  Version; 
    BYTE  HdrLen; 
  }; 
  BYTE ServiceType; 
  WORD TotalLen; 
  WORD ID; 
  union 
  { 
    WORD  Flags; 
    WORD  FragOff; 
  }; 
  BYTE TimeToLive; 
  BYTE Protocol; 
  WORD HdrChksum; 
  DWORD  SrcAddr; 
  DWORD  DstAddr; 
  BYTE Options[0]; 
} IP; 
typedef IP * LPIP; 
typedef IP UNALIGNED * ULPIP; 
// 
// TCP Packet Structure 
// 
typedef struct _TCP 
  { 
  WORD SrcPort; 
  WORD DstPort; 
  DWORD SeqNum; 
  DWORD AckNum; 
  BYTE DataOff; 
  BYTE Flags; 
  WORD Window; 
  WORD Chksum; 
  WORD UrgPtr; 
  } TCP; 
typedef TCP *LPTCP; 
typedef TCP UNALIGNED * ULPTCP; 
// upper protocols 
#define PROTOCOL_STRING_ICMP_TXT    "ICMP" 
#define PROTOCOL_STRING_TCP_TXT    "TCP" 
#define PROTOCOL_STRING_UDP_TXT    "UDP" 
#define PROTOCOL_STRING_SPX_TXT    "SPX" 
#define PROTOCOL_STRING_NCP_TXT    "NCP" 
#define PROTOCOL_STRING_UNKNOW_TXT   "UNKNOW" 
  这个文件也有人声称没有. 
//*************************************************************************// 
//* Header File: mstcpip.h 
//*************************************************************************// 
// Copyright (c) Microsoft Corporation. All rights reserved. 
#if _MSC_VER > 1000 
#pragma once 
#endif 
/* Argument structure for SIO_KEEPALIVE_VALS */ 
struct tcp_keepalive { 
  u_long onoff; 
  u_long keepalivetime; 
  u_long keepaliveinterval; 
}; 
// New WSAIoctl Options 
#define SIO_RCVALL      _WSAIOW(IOC_VENDOR,1) 
#define SIO_RCVALL_MCAST   _WSAIOW(IOC_VENDOR,2) 
#define SIO_RCVALL_IGMPMCAST _WSAIOW(IOC_VENDOR,3) 
#define SIO_KEEPALIVE_VALS  _WSAIOW(IOC_VENDOR,4) 
#define SIO_ABSORB_RTRALERT  _WSAIOW(IOC_VENDOR,5) 
#define SIO_UCAST_IF     _WSAIOW(IOC_VENDOR,6) 
#define SIO_LIMIT_BROADCASTS _WSAIOW(IOC_VENDOR,7) 
#define SIO_INDEX_BIND    _WSAIOW(IOC_VENDOR,8) 
#define SIO_INDEX_MCASTIF   _WSAIOW(IOC_VENDOR,9) 
#define SIO_INDEX_ADD_MCAST  _WSAIOW(IOC_VENDOR,10) 
#define SIO_INDEX_DEL_MCAST  _WSAIOW(IOC_VENDOR,11) 
// Values for use with SIO_RCVALL* options 
#define RCVALL_OFF       0 
#define RCVALL_ON       1 
#define RCVALL_SOCKETLEVELONLY 2

  现在我们自已的 Sniffer 就做好了, Run, Start......哇, 这么多数据包, 都是从这一台机器上发出的, 它在干什么? 原来 Adminstrator 密码为空, 中了尼姆达病毒!

六. 小结

  优点: 实现简单, 不需要做驱动程序就可实现抓包.

数据包头不含帧信息, 不能接收到与 IP 同层的其它数据包, 如 ARP, RARP...

  这里提供的程序仅仅是一个 Sniffer 的例子, 没有对数据包进行进一步的分析. 写此文的目的在于熟悉Raw Socket 编程方法, 了解 TCP/IP 协议结构原理以及各协议之间的关系.