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

自制Sniffer教程
本文介绍如何使用RawSocket创建简易的Sniffer(网络嗅探器),包括设置网卡为混杂模式、捕获并分析数据包等内容。适用于希望深入了解TCP/IP协议及RawSocket编程的学习者。

一. 摘要

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

 
本文章将介绍如何使用RawSocket原始套接字)开发网络嗅探器: 首先我们得了解什么是套接字,这个我就不多说,自己百度,百度百科比我说的好。 那么什么又是原始套接字呢,常用的套接字分为 SOCK_STREAM(流套接字) 用于TCPXY通讯。 SOCK_DGRAM(数据报套接字) 同于UDPXY通讯。 那么原始呢,他则是和名字一样原始套接字;举例:要想用流套接字进行一次TCP的发包,那么直接连接上对方服务器然后用Send就可以发送指定的内容,但其实发送的数据并不止你的那些内容,有一些东西是流套接字会给你自动补上的。TCP是属于IPXY的一个子XY,那么要发送一个TCP数据包就得加上(以太网XY报头这个先不提),IPXY的报头,和TCPXY报头,这些东西流套接字都会帮你处理,而原始套接字则不会(当然也可以设置让原始套接字构造IP报头)原始套接字他有更多的用途,但相对来说也比流套接字或数据报套接字麻烦。 原始套接字还可以设置成允许接收本地所有的套接字数据。那么我们就利用这个功能来做嗅探器! 首先:1.使用  WSAStartup (合并短整数 (2, 2), WSADATA)  来初始化Winsocket服务 其参数有2个  第一个 (短整数型/双字节型):wVersionRequired  这个参数表明使用的winsock版本号,高位指定修订版本号,低位指定主版本号。第二个参数 WSADATA类型 用于接收Winsocket细节东西,咱不用管它。 //下面就不说那么详细了,源码里面全是注释,自己看。 2.然后使用socket (#AF_INET, #SOCK_RAW, #IPPROTO_IP)  来创建一个套接字   第一个参数应该是表明Internet地址格式反正只能固定这个,仅仅支持这个  参数2:表明要创建的是一个原始套接字,参数3:指定IPXY  IPXY包括其子XY TCP UDP 等。成功返回套接字句柄 3.  bind (s, addr, sizeof (addr))  将套接字绑定至指定网卡,参数1=套接字句柄    参数2为一个addr结构的值,该值表明要绑定的网卡IP及端口号 4.  ioctlsocket (Socket, 2550136833, 1) 将套接字的模式改变为允许接收所有数据 顺利完成上面的操作后咱就可以用Recv来接收数据包了,只要不断的调用Recv就OK。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值