用C#下的Raw Socket编程实现网络封包监视(如何具体实现,请高手指点!)

lingxingok 2007-05-16 08:48:56
用C#下的Raw Socket编程实现网络封包监视

谈起socket编程,大家也许会想起QQ和IE,没错。还有许多网络工具如P2P、NetMeeting等在应用层实现

的应用程序,也是用socket来实现的。Socket是一个网络编程接口,实现于网络应用层,Windows Socket

包括了一套系统组件,充分利用了Microsoft Windows 消息驱动的特点。Socket规范1.1版是在1993年1月

发行的,并广泛用于此后出现的Windows9x操作系统中。Socket规范2.2版(其在Windows平台上的版本是

Winsock2.2,也叫Winsock2)在 1996 年 5 月发行,Windows NT 5.0及以后版本的Windows系统支持

Winsock2,在Winsock2中,支持多个传输协议的原始套接字,重叠I/O模型、服务质量控制等。

本文向大家介绍Windows Sockets的一些关于用C#实现的原始套接字(Raw Socket)的编程,以及在此基础

上实现的网络封包监视技术。同Winsock1相比,Winsock2最明显的就是支持了Raw Socket套接字类型,使

用Raw Socket,可把网卡设置成混杂模式,在这种模式下,我们可以收到网络上的IP包,当然包括目的不

是本机的IP包,通过原始套接字,我们也可以更加自如地控制Windows下的多种协议,而且能够对网络底

层的传输机制进行控制。

在本文例子中,我在nbyte.BasicClass命名空间实现了RawSocket类,它包含了我们实现数据包监视的核

心技术。在实现这个类之前,需要先写一个IP头结构,来暂时存放一些有关网络封包的信息:


[StructLayout(LayoutKind.Explicit)]
public struct IPHeader
{
[FieldOffset(0)] public byte ip_verlen; //I4位首部长度+4位IP版本号
[FieldOffset(1)] public byte ip_tos; //8位服务类型TOS
[FieldOffset(2)] public ushort ip_totallength; //16位数据包总长度(字节)
[FieldOffset(4)] public ushort ip_id; //16位标识
[FieldOffset(6)] public ushort ip_offset; //3位标志位
[FieldOffset(8)] public byte ip_ttl; //8位生存时间 TTL
[FieldOffset(9)] public byte ip_protocol; //8位协议(TCP, UDP, ICMP, Etc.)
[FieldOffset(10)] public ushort ip_checksum; //16位IP首部校验和
[FieldOffset(12)] public uint ip_srcaddr; //32位源IP地址
[FieldOffset(16)] public uint ip_destaddr; //32位目的IP地址
}

这样,当每一个封包到达时候,可以用强制类型转化把包中的数据流转化为一个个IPHeader对象。
下面就开始写RawSocket类了,一开始,先定义几个参数,包括:
private bool error_occurred; //套接字在接收包时是否产生错误
public bool KeepRunning; //是否继续进行
private static int len_receive_buf; //得到的数据流的长度
byte [] receive_buf_bytes; //收到的字节
private Socket socket = null; //声明套接字
还有一个常量:
const int SIO_RCVALL = unchecked((int)0x98000001);//监听所有的数据包

这里的SIO_RCVALL是指示RawSocket接收所有的数据包,在以后的IOContrl函数中要用,在下面的构造函

数中,实现了对一些变量参数的初始化:

public RawSocket() //构造函数
{
error_occurred=false;
len_receive_buf = 4096;
receive_buf_bytes = new byte[len_receive_buf];
}

下面的函数实现了创建RawSocket,并把它与终结点(IPEndPoint:本机IP和端口)绑定:
public void CreateAndBindSocket(string IP) //建立并绑定套接字
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
socket.Blocking = false; //置socket非阻塞状态
socket.Bind(new IPEndPoint(IPAddress.Parse(IP), 0)); //绑定套接字

if (SetSocketOption()==false) error_occurred=true;
}
其中,在创建套接字的一句socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw,

ProtocolType.IP);中有3个参数:

第一个参数是设定地址族,MSDN上的描述是“指定 Socket 实例用来解析地址的寻址方案”,当要把套接

字绑定到终结点(IPEndPoint)时,需要使用InterNetwork成员,即采用IP版本4的地址格式,这也是当

今大多数套接字编程所采用一个寻址方案(AddressFamily)。

第二个参数设置的套接字类型就是我们使用的Raw类型了,SocketType是一个枚举数据类型,Raw套接字类

型支持对基础传输协议的访问。通过使用 SocketType.Raw,你不光可以使用传输控制协议(Tcp)和用户数

据报协议(Udp)进行通信,也可以使用网际消息控制协议 (Icmp) 和 Internet 组管理协议 (Igmp) 来进

行通信。在发送时,您的应用程序必须提供完整的 IP 标头。所接收的数据报在返回时会保持其 IP 标头

和选项不变。

第三个参数设置协议类型,Socket 类使用 ProtocolType 枚举数据类型向 Windows Socket API 通知所

请求的协议。这里使用的是IP协议,所以要采用ProtocolType.IP参数。
...全文
1259 17 打赏 收藏 转发到动态 举报
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
zfuwen 2009-09-01
  • 打赏
  • 举报
回复
http://zfuwen.com
谢谢了,学习学习!

验证码识别制作/验证码识别库/验证码软件定制/群发软件定制
zfuwen 2009-09-01
  • 打赏
  • 举报
回复
http://zfuwen.com
谢谢了,学习学习!

验证码识别制作/验证码识别库/验证码软件定制/群发软件定制
RedGoldFish 2007-07-01
  • 打赏
  • 举报
回复
关注
wzuomin 2007-07-01
  • 打赏
  • 举报
回复
我在vb中也实现了这部分功能,但是数据包中没有Mac头,从Ip头开始的。
不知lz实现的如何,交流一下,呵呵。
QQ:463688351
ylqmf 2007-06-30
  • 打赏
  • 举报
回复
mark,我也正在研究这方面,正考虑用C++,嘿嘿。
火雷 2007-05-17
  • 打赏
  • 举报
回复
好像高深了点
wzuomin 2007-05-17
  • 打赏
  • 举报
回复
抓到的数据包中包含Mac地址吗?
Red_angelX 2007-05-17
  • 打赏
  • 举报
回复
rawsocket在xp sp2某些机器下有bug只能抓接收包不能抓发送包
Red_angelX 2007-05-17
  • 打赏
  • 举报
回复
public void Run()
{
IAsyncResult ar = socket.BeginReceive(receive_buf_bytes, 0, len_receive_buf, SocketFlags.None, new AsyncCallback(CallReceive), this);
}

private void CallReceive(IAsyncResult ar)
{
int received_bytes;
received_bytes = socket.EndReceive(ar);
Receive(receive_buf_bytes, received_bytes);
if (KeepRunning) Run();
}

public class PacketArrivedEventArgs : EventArgs
{
/*******************************************
* ReceiveBuffer 所有的包
* MessageBuffer 去除IPHeader后的包
* MessageData 去除UserHeader后的包
* UserHeader s_port d_port length checksum
*******************************************/
public PacketArrivedEventArgs()
{
this.protocol = "";
this.destination_port = "";
this.origination_port = "";
this.destination_address = "";
this.origination_address = "";
this.ip_version = "";

this.total_packet_length =0;
this.message_length =0;
this.header_length =0;

this.receive_buf_bytes=new byte[len_receive_buf];
this.ip_header_bytes=new byte[len_receive_buf];
//this.message_bytes=new byte[len_receive_buf];
}
//是否为接收包
public bool IsSend
{
get { return OriginationAddress.Equals(localip); }
}
public string Protocol
{
get {return protocol;}
set {protocol=value;}
}
public string DestinationPort
{
get {return destination_port;}
set {destination_port=value;}
}
public string OriginationPort
{
get {return origination_port;}
set {origination_port=value;}
}
public string DestinationAddress
{
get {return destination_address;}
set {destination_address=value;}
}
public string OriginationAddress
{
get {return origination_address;}
set {origination_address=value;}
}
public string IPVersion
{
get {return ip_version;}
set {ip_version=value;}
}
public uint PacketLength
{
get {return total_packet_length;}
set {total_packet_length=value;}
}
public uint MessageLength
{
get {return message_length;}
set {message_length=value;}
}
public uint HeaderLength
{
get {return header_length;}
set {header_length=value;}
}
public byte [] ReceiveBuffer
{
get {return receive_buf_bytes;}
set {receive_buf_bytes=value;}
}
public byte [] IPHeaderBuffer
{
get {return ip_header_bytes;}
set {ip_header_bytes=value;}
}
public byte [] MessageBuffer
{
get {return message_bytes;}
set {message_bytes=value;}
}
/// <summary>
/// 数据包正文
/// </summary>
public byte[] MessageData
{
get { return message_datas; }
set { message_datas = value; }
}

private string protocol;
private string destination_port;
private string origination_port;
private string destination_address;
private string origination_address;
private string ip_version;
private uint total_packet_length;
private uint message_length;
private uint header_length;
private byte []receive_buf_bytes = null;
private byte []ip_header_bytes = null;
private byte []message_bytes = null;
private byte []message_datas = null;
}

public delegate void PacketArrivedEventHandler(
Object sender, PacketArrivedEventArgs args);

public event PacketArrivedEventHandler PacketArrival;

protected virtual void OnPacketArrival(PacketArrivedEventArgs e)
{
if (PacketArrival != null)
{
PacketArrival(this, e);
}
}
}
}
Red_angelX 2007-05-17
  • 打赏
  • 举报
回复
namespace QQFamilyAnalytics
{
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Windows.Forms;

[StructLayout(LayoutKind.Explicit)]
public struct IPHeader
{
[FieldOffset(0)] public byte ip_verlen; //IP version and IP Header length Combined
[FieldOffset(1)] public byte ip_tos; //Type of Service
[FieldOffset(2)] public ushort ip_totallength; //Total Packet Length
[FieldOffset(4)] public ushort ip_id; //Unique ID
[FieldOffset(6)] public ushort ip_offset; //Flags and Offset
[FieldOffset(8)] public byte ip_ttl; //Time To Live
[FieldOffset(9)] public byte ip_protocol; //Protocol (TCP, UDP, ICMP, Etc.)
[FieldOffset(10)] public ushort ip_checksum; //IP Header Checksum
[FieldOffset(12)] public uint ip_srcaddr; //Source IP Address
[FieldOffset(16)] public uint ip_destaddr; //Destination IP Address
}

public class RawSocket
{
private bool error_occurred;
public bool KeepRunning;
private static int len_receive_buf;
byte [] receive_buf_bytes;
private Socket socket = null;
private static string localip;

public RawSocket()
{
error_occurred=false;
len_receive_buf = 4096;
receive_buf_bytes = new byte[len_receive_buf];
}

public void CreateAndBindSocket(string IP)
{
localip = IP;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
socket.Blocking = false;
socket.Bind(new IPEndPoint(IPAddress.Parse(IP), 0));

if (SetSocketOption()==false) error_occurred=true;
}

public void Shutdown()
{
if(socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}

private bool SetSocketOption()
{
bool ret_value = true;
try
{
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, 1);

byte []IN = new byte[4]{1, 0, 0, 0};
byte []OUT = new byte[4];
int SIO_RCVALL = unchecked((int)0x98000001);
int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT);
ret_code = OUT[0] + OUT[1] + OUT[2] + OUT[3];
if(ret_code != 0) ret_value = false;
}
catch(SocketException)
{
ret_value = false;
}
return ret_value;
}

public bool ErrorOccurred
{
get
{
return error_occurred;
}
}

unsafe private void Receive(byte [] buf, int len)
{
byte temp_protocol=0;
uint temp_version=0;
uint temp_ip_srcaddr=0;
uint temp_ip_destaddr=0;
short temp_srcport=0;
short temp_dstport=0;
IPAddress temp_ip;

PacketArrivedEventArgs e=new PacketArrivedEventArgs();

fixed(byte *fixed_buf = buf)
{
IPHeader * head = (IPHeader *) fixed_buf;
e.HeaderLength=(uint)(head->ip_verlen & 0x0F) << 2;

temp_protocol = head->ip_protocol;
switch(temp_protocol)
{
case 1: e.Protocol="ICMP"; break;
case 2: e.Protocol="IGMP"; break;
case 6: e.Protocol="TCP"; break;
case 17: e.Protocol="UDP"; break;
default: e.Protocol= "UNKNOWN"; break;
}

temp_version =(uint)(head->ip_verlen & 0xF0) >> 4;
e.IPVersion = temp_version.ToString();

temp_ip_srcaddr = head->ip_srcaddr;
temp_ip_destaddr = head->ip_destaddr;
temp_ip = new IPAddress(temp_ip_srcaddr);
e.OriginationAddress =temp_ip.ToString();
temp_ip = new IPAddress(temp_ip_destaddr);
e.DestinationAddress = temp_ip.ToString();

temp_srcport = *(short *)&fixed_buf[e.HeaderLength];
temp_dstport = *(short *)&fixed_buf[e.HeaderLength+2];
e.OriginationPort=IPAddress.NetworkToHostOrder(temp_srcport).ToString();
e.DestinationPort=IPAddress.NetworkToHostOrder(temp_dstport).ToString();

e.PacketLength =(uint)len;
e.MessageLength =(uint)len - e.HeaderLength;
//这里确定长度
e.MessageBuffer = new byte[e.MessageLength];
e.MessageData = new byte[e.MessageLength - 8];

e.ReceiveBuffer=buf;
Array.Copy(buf,0,e.IPHeaderBuffer,0,(int)e.HeaderLength);
Array.Copy(buf,(int)e.HeaderLength,e.MessageBuffer,0,(int)e.MessageLength);
Array.Copy(e.MessageBuffer, 8, e.MessageData, 0, e.MessageData.Length);
}

OnPacketArrival(e);
}
Red_angelX 2007-05-17
  • 打赏
  • 举报
回复
so 我正好在做.
hzg_1998 2007-05-17
  • 打赏
  • 举报
回复
sswwee(最有价值球员) :
hzg_1998@126.com 给我一个好吗?谢谢!
liukai60268 2007-05-17
  • 打赏
  • 举报
回复
软件开发与技术交流群40271625
sswwee 2007-05-17
  • 打赏
  • 举报
回复
我这里有一个做好的,怎么给你呢。
lingxingok 2007-05-16
  • 打赏
  • 举报
回复
小弟很菜,看了好长时间,不知如何具体实现!比如怎么得到数据包的具体内容!我想用它来进行模式匹配,检测数据包中有没有预定的特征码?!如何实现!请高手们指点!急急急!
email:lingxingok@126.com
lingxingok 2007-05-16
  • 打赏
  • 举报
回复
下面的函数实现的数据包的接收:

//解析接收的数据包,形成PacketArrivedEventArgs事件数据类对象,并引发PacketArrival事件
unsafe private void Receive(byte [] buf, int len)
{
byte temp_protocol=0;
uint temp_version=0;
uint temp_ip_srcaddr=0;
uint temp_ip_destaddr=0;
short temp_srcport=0;
short temp_dstport=0;
IPAddress temp_ip;

PacketArrivedEventArgs e=new PacketArrivedEventArgs();//新网络数据包信息事件

fixed(byte *fixed_buf = buf)
{
IPHeader * head = (IPHeader *) fixed_buf;//把数据流整和为IPHeader结构
e.HeaderLength=(uint)(head->ip_verlen & 0x0F) << 2;

temp_protocol = head->ip_protocol;
switch(temp_protocol)//提取协议类型
{
case 1: e.Protocol="ICMP"; break;
case 2: e.Protocol="IGMP"; break;
case 6: e.Protocol="TCP"; break;
case 17: e.Protocol="UDP"; break;
default: e.Protocol= "UNKNOWN"; break;
}

temp_version =(uint)(head->ip_verlen & 0xF0) >> 4;//提取IP协议版本
e.IPVersion = temp_version.ToString();

//以下语句提取出了PacketArrivedEventArgs对象中的其他参数
temp_ip_srcaddr = head->ip_srcaddr;
temp_ip_destaddr = head->ip_destaddr;
temp_ip = new IPAddress(temp_ip_srcaddr);
e.OriginationAddress =temp_ip.ToString();
temp_ip = new IPAddress(temp_ip_destaddr);
e.DestinationAddress = temp_ip.ToString();

temp_srcport = *(short *)&fixed_buf[e.HeaderLength];
temp_dstport = *(short *)&fixed_buf[e.HeaderLength+2];
e.OriginationPort=IPAddress.NetworkToHostOrder(temp_srcport).ToString();
e.DestinationPort=IPAddress.NetworkToHostOrder(temp_dstport).ToString();

e.PacketLength =(uint)len;
e.MessageLength =(uint)len - e.HeaderLength;

e.ReceiveBuffer=buf;
//把buf中的IP头赋给PacketArrivedEventArgs中的IPHeaderBuffer
Array.Copy(buf,0,e.IPHeaderBuffer,0,(int)e.HeaderLength);
//把buf中的包中内容赋给PacketArrivedEventArgs中的MessageBuffer
Array.Copy(buf,(int)e.HeaderLength,e.MessageBuffer,0,(int)e.MessageLength);
}
//引发PacketArrival事件
OnPacketArrival(e);
}

大家注意到了,在上面的函数中,我们使用了指针这种所谓的不安全代码,可见在C#中指针和移位运算这

些原始操作也可以给程序员带来编程上的便利。在函数中声明PacketArrivedEventArgs类对象,以便通过

OnPacketArrival(e)函数通过事件把数据包信息传递出去。其中PacketArrivedEventArgs类是RawSocket

类中的嵌套类,它继承了系统事件(Event)类,封装了数据包的IP、端口、协议等其他数据包头中包含

的信息。在启动接收数据包的函数中,我们使用了异步操作的方法,以下函数开启了异步监听的接口:

public void Run() //开始监听
{
IAsyncResult ar = socket.BeginReceive(receive_buf_bytes, 0, len_receive_buf,

SocketFlags.None, new AsyncCallback(CallReceive), this);
}

Socket.BeginReceive函数返回了一个异步操作的接口,并在此接口的生成函数BeginReceive中声明了异

步回调函数CallReceive,并把接收到的网络数据流传给receive_buf_bytes,这样就可用一个带有异步操

作的接口参数的异步回调函数不断地接收数据包:

private void CallReceive(IAsyncResult ar)//异步回调
{
int received_bytes;
received_bytes = socket.EndReceive(ar);
Receive(receive_buf_bytes, received_bytes);
if (KeepRunning) Run();
}

此函数当挂起或结束异步读取后去接收一个新的数据包,这样能保证让每一个数据包都能够被程序探测到


下面通过声明代理事件句柄来实现和外界的通信:

public delegate void PacketArrivedEventHandler(Object sender, PacketArrivedEventArgs args);
//事件句柄:包到达时引发事件
public event PacketArrivedEventHandler PacketArrival;//声明时间句柄函数

这样就可以实现对数据包信息的获取,采用异步回调函数,可以提高接收数据包的效率,并通过代理事件

把封包信息传递到外界。既然能把所有的封包信息传递出去,就可以实现对数据包的分析了:)不过

RawSocket的任务还没有完,最后不要望了关闭套接字啊:

public void Shutdown() //关闭raw socket
{
if(socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}

以上介绍了RawSocket类通过构造IP头获取了包中的信息,并通过异步回调函数实现了数据包的接收,并

使用时间代理句柄和自定义的数据包信息事件类把数据包信息发送出去,从而实现了网络数据包的监视,

这样我们就可以在外部添加一些函数对数据包进行分析了。
lingxingok 2007-05-16
  • 打赏
  • 举报
回复
在CreateAndBindSocket函数中有一个自定义的SetSocketOption函数,它和Socket类中的

SetSocketOption不同,我们在这里定义的是具有IO控制功能的SetSocketOption,它的定义如下:

private bool SetSocketOption() //设置raw socket
{
bool ret_value = true;
try
{
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, 1);
byte []IN = new byte[4]{1, 0, 0, 0};
byte []OUT = new byte[4];

//低级别操作模式,接受所有的数据包,这一步是关键,必须把socket设成raw和IP Level才可用

SIO_RCVALL
int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT);
ret_code = OUT[0] + OUT[1] + OUT[2] + OUT[3];//把4个8位字节合成一个32位整数
if(ret_code != 0) ret_value = false;
}
catch(SocketException)
{
ret_value = false;
}
return ret_value;
}

其中,设置套接字选项时必须使套接字包含IP包头,否则无法填充IPHeader结构,也无法获得数据包信息


int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT);是函数中最关键的一步了,因为,在windows

中我们不能用Receive函数来接收raw socket上的数据,这是因为,所有的IP包都是先递交给系统核心,

然后再传输到用户程序,当发送一个raws socket包的时候(比如syn),核心并不知道,也没有这个数据

被发送或者连接建立的记录,因此,当远端主机回应的时候,系统核心就把这些包都全部丢掉,从而到不

了应用程序上。所以,就不能简单地使用接收函数来接收这些数据报。要达到接收数据的目的,就必须采

用嗅探,接收所有通过的数据包,然后进行筛选,留下符合我们需要的。可以通过设置SIO_RCVALL,表示

接收所有网络上的数据包。接下来介绍一下IOControl函数。MSDN解释它说是设置套接字为低级别操作模

式,怎么低级别操作法?其实这个函数与API中的WSAIoctl函数很相似。WSAIoctl函数定义如下:

int WSAIoctl(
SOCKET s, //一个

指定的套接字
DWORD dwIoControlCode, //控制操作码
LPVOID lpvInBuffer, //指向输

入数据流的指针
DWORD cbInBuffer, //输入数据

流的大小(字节数)
LPVOID lpvOutBuffer, // 指向输出

数据流的指针
DWORD cbOutBuffer, //输出数据流

的大小(字节数)
LPDWORD lpcbBytesReturned, //指向输出字节流

数目的实数值
LPWSAOVERLAPPED lpOverlapped, //指向一个

WSAOVERLAPPED结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//指向操作完成时执行的例程
);

C#的IOControl函数不像WSAIoctl函数那么复杂,其中只包括其中的控制操作码、输入字节流、输出字节

流三个参数,不过这三个参数已经足够了。我们看到函数中定义了一个字节数组:byte []IN = new

byte[4]{1, 0, 0, 0}实际上它是一个值为1的DWORD或是Int32,同样byte []OUT = new byte[4];也是,

它整和了一个int,作为WSAIoctl函数中参数lpcbBytesReturned指向的值。
因为设置套接字选项时可能会发生错误,需要用一个值传递错误标志:

public bool ErrorOccurred
{
get
{
return error_occurred;
}
}

110,539

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧