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

Raw Socket: 原始套接字可以用它来发送和接收 IP 层以上的原始数据包。

AD: 51CTO云计算架构师峰会 抢票进行中!


    一. 摘要
    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.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 协议结构原理以及各协议之间的关系.


    Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值