【参加讨论】一. 摘要
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);
rivate: // user declarations
ansistring __fastcall getprotocoltxt(int protocol);
ublic: // 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 */
truct 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 协议结构原理以及各协议之间的关系.