正文

Raw Socket(原始套接字)实现Sniffer(嗅探)2005-12-03 19:57:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/ddtme/7753.html

分享到:

 


   一. 摘要
  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
//* http://shadowstar.126.com/
//********************************************

*****************************//
#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.S

rcAddr));
     

Item->SubItems->Add(inet_ntoa(*(in_addr*)&ip.D

stAddr));
      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 协议结构原

理以及各协议之间的关系.
 

阅读(5017) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册