NetAnalyzer笔记 之 四. C#版的抓包软件

时间:2023-03-08 16:34:34

[创建时间:2015-09-10 22:37:04]

NetAnalyzer下载地址

不好意思啊,NetAnalyzer停更有点长了,今天继续填坑^&^

NetAnalyzer实现结构

在上一篇中介绍一点VC++开发环境的配置,与基本的运行方式。因为NetAnalyzer使用的C#作为开发语言,所以在此主要介绍一些在C#环境下的开发环境的配置,与一些基本开发情况,力求在完成本篇后后,读者可以制作一个简单的抓包程序。

在开始编程前先要介绍连个.Net类库SharpPcap.dll与PacketDotNet.dll。在2004年Tamir Gal为了完成他的毕业设计,其中有一些内容需要使用Winpcap来实现网络数据分析,但希望可以使用简单易用的C#开发语言。于是建立了SharpPcap项目,Tamir Gal为了节省时间,并没用将网络数据采集部分与分析部分分开,甚至有些代码混杂在UI代码中。而且在实现了很少的WinPcapAPI接口,并没用提供相关的开发文档。

因为这样,Tamir决定重新设计SharPcap,并推出了1.x一些列的版本,最终在2007年完成了SharpPcap1.6.2版本。

在2008年11月Chris Morgan接替了Tamir的工作,重新设计了SharpPcap的API,开始支持Linux 和MAC(在Linux 与MAC平台的相关技术请参见Mono开发平台)。之后将数据采集于协议分析分别封装在不同的程序集类库中即形成了SharpPcap于Packet.Net。

下载地址:http://sourceforge.net/projects/sharppcap/ 在GNU协议下的开源代码。

(1)     SharpPcap.dll

SharpPcap中封装了Winpcap提供的大部分API函数,在该类库中,我们可以获取主机网卡,及其信息。并定义了对网卡的各种操作方法,如打开网卡,开始抓包,停止抓包等,再抓包中提供了,同步方式与异步方式,方便进行不同的环境,对于更加详细的介绍,将会在下一章提到。

(2)     Packet.Net

在程序集中的名称为PacketDotNet,该类库负责对捕获的数据进行分析解析,目前提供可以分析的协议:

Ethernet、SLL (Linux Cooked-Mode Capture) 、ARP、IPv4 、IPv6 、TCP 、UDP 、ICMPv4、ICMPv6 、IGMPv2 、PPPoE 、PTP 、LLDP 、Wake-on-LAN(WOL)

在NetAnalyzer此基础上,在类库中增加了PPP、LCP、CiscoHDLC等协议。

为了可以分析应用层协议,在NetAnalyzer设计了ApplicationProtocol类库,目前提供HTTP、FTP、DNS、SMTP、POP3、SSDP协议的解析,本节中不会对应用层协议进行分析。

在以下的内容中,我们将分七个部分,来完成一个简单的抓包程序。同上面一样,所有的程序都在Visual Studio2013中完成,只是在这里我们首先要得到:

SharpPcap.dll版本4.0.0

PacketDotNet.dl版本0.11.0

可以通过上面的网址获取。

1 获取网络适配器(网卡)

首先新建工程,开发语言选择C#,类型选择Windows窗体应用程序,命名为MySniffer,图形界面如图1所示。

NetAnalyzer笔记 之 四. C#版的抓包软件

图1 建立MySniffer工程

点击确定,在解决方案资源管理器中右击引用添加引用, 此时打开添加应用对话框,选择浏览选项卡,并找到SharpPcap与PacketDotNet类库将其添加至工程中。如图2所示,

NetAnalyzer笔记 之 四. C#版的抓包软件

图2从引用中右击添加引用

在添加完成之后,项目应用中应该可以看到这两个类库的的引用文件。

首先我们来获取需要监听的网卡,在窗体中添加如下代码:

         private void loadDevice()// 获取网卡方法
{
try
{
foreach (var i in CaptureDeviceList.Instance)
{
comDeviceList.Items.Add(i.Description); //在combox中添加
}
if (comDeviceList.Items.Count > )
{
comDeviceList.SelectedIndex = ;//自动选中第一个
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
}

我们通过调用SharpPcap.CaptureDeviceList.Instance 单例,获取一个包含所用网卡的列表CaptureDeviceList,然后把以此把网卡的描述信息添加到顶部工具栏comDeviceList 中

在代码中我们加一个全局变量device用来指示当前控制的网卡,

         ICaptureDevice device;// 定义设备

所以当我们每次去选择不同的网卡时调用下面的方法

         //选择网卡
private void comDeviceList_SelectedIndexChanged(object sender, EventArgs e)
{
device = CaptureDeviceList.Instance[comDeviceList.SelectedIndex];
}

完成运行如图

NetAnalyzer笔记 之 四. C#版的抓包软件

图3获取的网卡以及其信息

接下来我们直接来点精彩的,加入如下代码:

         List<RawCapture> packetList = new List<RawCapture>();//捕获的数据列表
List<RawCapture> bufferList;//缓存列表
Thread AnalyzerThread;//分析数据的线程
object threadLock = new object();//线程锁定
bool isStartAnalyzer;//用于表示是否启动分析线程的标志
DeviceMode devMode = DeviceMode.Promiscuous;//数据采集模式
int readTimeOut = ;
delegate void DataGridRowsShowHandler(RawCapture packet); /// <summary>
/// 启动网卡
/// </summary>
private void Start()
{
if (device == null || device.Started)
return;
bufferList = new List<RawCapture>();
Clear();//清理原有的数据
isStartAnalyzer = true;
StartAnalyzer();//启动分析线程
try
{
device.OnPacketArrival += new PacketArrivalEventHandler(device_OnPacketArrival);
//默认使用混杂模式,超时 1000
device.Open(devMode, readTimeOut);
device.Filter = comFilter.Text;
device.StartCapture(); UIConfig(true);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message); UIConfig(false);
} }
/// <summary>
/// 启动分析
/// </summary>
private void StartAnalyzer()
{
AnalyzerThread = new Thread(new ThreadStart(analysrThreadMethod));
AnalyzerThread.IsBackground = true;
AnalyzerThread.Start(); }
/// <summary>
/// 停止
/// </summary>
private void Stop()
{
try
{
if (device != null && device.Started)
{
device.StopCapture();
device.Close();
} isStartAnalyzer = false;
if (AnalyzerThread !=null && AnalyzerThread.IsAlive)
{
AnalyzerThread.Abort();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
UIConfig(false);
} /// <summary>
/// Sharpcap 获取数据包之后的回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void device_OnPacketArrival(object sender, CaptureEventArgs e)
{
lock (threadLock)
{
bufferList.Add(e.Packet);
}
} /// <summary>
/// 数据分析线程 (使用缓存方式,可避免数据堵塞)
/// </summary> private void analysrThreadMethod()
{
while (isStartAnalyzer)
{
bool isShoudSleep = true;
lock (threadLock)
{
if (bufferList.Count != )
isShoudSleep = false;
}
if (isShoudSleep)//
{
Thread.Sleep();
}
else
{
List<RawCapture> tmpPacketList;
lock (threadLock) //获取数据
{
tmpPacketList = bufferList;
bufferList = new List<RawCapture>();//构建新列表
packetList.AddRange(tmpPacketList);
}
foreach (var i in tmpPacketList)
{
this.Invoke(new DataGridRowsShowHandler(ShowDataRows), i); }
}
}
} /// <summary>
/// 在datagridview中显示获取的网络数据
/// </summary>
/// <param name="packet"></param>
private void ShowDataRows(RawCapture packet)
{
try
{
dataGridPacket.Rows.Add(rowsBulider.Row(packet, ++packetIndex));//加载DataGridRows;
}
catch (Exception ex)
{ }
}

这里通过在抓包启动之前,预先启动一个分析线程,用于独立进行数据解析,接下来就是在DataGridView中添加数据了,这部分写的比较渣,请轻喷,毕竟是几年前的代码了,代码如下:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SharpPcap;
using PacketDotNet;
using System.Xml;
namespace MySniffer
{
class DataBuilder
{
//标记当前数据是否有效 #region 构建数据行
/// <summary>
/// DataGridRow
/// </summary>
/// <returns>返回字符串数据</returns>
public string[] Row(RawCapture rawPacket, uint packetID)
{
string[] rows = new string[]; rows[] = string.Format("{0:D7}", packetID);//编号
rows[] = "Unknown";
rows[] = rawPacket.Data.Length.ToString();//数据长度bytes
rows[] = "--";
rows[] = "--";
rows[] = "--"; Packet packet = Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data); EthernetPacket ep = EthernetPacket.GetEncapsulated(packet);
if (ep != null)
{
rows[] = "Ethernet(v2)";
rows[] = Format.MacFormat(ep.SourceHwAddress.ToString());
rows[] = Format.MacFormat(ep.DestinationHwAddress.ToString());
rows[] = "[" + ep.Type.ToString() + "]"; #region IP
IpPacket ip = IpPacket.GetEncapsulated(packet);
if (ip != null)
{
if (ip.Version == IpVersion.IPv4)
{
rows[] = "IPv4";
}
else
{
rows[] = "IPv6";
}
rows[] = ip.SourceAddress.ToString();
rows[] = ip.DestinationAddress.ToString();
rows[] = "[下层协议:" + ip.NextHeader.ToString() + "] [版本:" + ip.Version.ToString() + "]"; TcpPacket tcp = TcpPacket.GetEncapsulated(packet);
if (tcp != null)
{
rows[] = "TCP";
rows[] += " [" + tcp.SourcePort.ToString() + "]";
rows[] += " [" + tcp.DestinationPort.ToString() + "]"; return rows;
}
UdpPacket udp = UdpPacket.GetEncapsulated(packet);
if (udp != null)
{
rows[] = "UDP";
rows[] += " [" + udp.SourcePort.ToString() + "]";
rows[] += " [" + udp.DestinationPort.ToString() + "]";
return rows;
} ICMPv4Packet icmpv4 = ICMPv4Packet.GetEncapsulated(packet);
if (icmpv4 != null)
{
rows[] = "ICMPv4";
rows[] = "[校验:" + icmpv4.Checksum.ToString() + "] [类型:" + icmpv4.TypeCode.ToString() + "] [序列号:" + icmpv4.Sequence.ToString() + "]";
return rows;
}
ICMPv6Packet icmpv6 = ICMPv6Packet.GetEncapsulated(packet);
if (icmpv6 != null)
{
rows[] = "ICMPv6";
rows[] = "[Code:" + icmpv6.Code.ToString() + "] [Type" + icmpv6.Type.ToString() + "]";
return rows;
}
IGMPv2Packet igmp = IGMPv2Packet.GetEncapsulated(packet);
if (igmp != null)
{
rows[] = "IGMP";
rows[] = "[只适用于IGMPv2] [组地址:" + igmp.GroupAddress.ToString() + "] [类型:" + igmp.Type.ToString() + "]";
return rows;
}
return rows;
}
#endregion ARPPacket arp = ARPPacket.GetEncapsulated(packet);
if (arp != null)
{
rows[] = "ARP";
rows[] = Format.MacFormat(arp.SenderHardwareAddress.ToString());
rows[] = Format.MacFormat(arp.TargetHardwareAddress.ToString());
rows[] = "[Arp操作方式:" + arp.Operation.ToString() + "] [发送者:" + arp.SenderProtocolAddress.ToString() + "] [目标:" + arp.TargetProtocolAddress.ToString() + "]";
return rows;
}
WakeOnLanPacket wp = WakeOnLanPacket.GetEncapsulated(packet);
if (wp != null)
{
rows[] = "Wake On Lan";
rows[] = Format.MacFormat(ep.SourceHwAddress.ToString());
rows[] = Format.MacFormat(wp.DestinationMAC.ToString());
rows[] = "[唤醒网络地址:" + wp.DestinationMAC.ToString() + "] [有效性:" + wp.IsValid().ToString() + "]";
return rows;
}
PPPoEPacket poe = PPPoEPacket.GetEncapsulated(packet);
if (poe != null)
{
rows[] = "PPPoE";
rows[] = poe.Type.ToString() + " " + poe.Version.ToString();
return rows; }
LLDPPacket llp = LLDPPacket.GetEncapsulated(packet);
if (llp != null)
{
rows[] = "LLDP";
rows[] = llp.ToString();
return rows;
}
return rows;
}
//链路层
PPPPacket ppp = PPPPacket.GetEncapsulated(packet);
if (ppp != null)
{
rows[] = "PPP";
rows[] = "--";
rows[] = "--";
rows[] = "协议类型:" + ppp.Protocol.ToString();
return rows; }
//PPPSerial
PppSerialPacket ppps = PppSerialPacket.GetEncapsulated(packet);
if (ppps != null)
{
rows[] = "PPP";
rows[] = "--";
rows[] = "0x" + ppps.Address.ToString("X2");
rows[] = "地址:" + ppps.Address.ToString("X2") + " 控制:" + ppps.Control.ToString() + " 协议类型:" + ppps.Protocol.ToString();
return rows;
}
//Cisco HDLC
CiscoHDLCPacket hdlc = CiscoHDLCPacket.GetEncapsulated(packet);
if (hdlc != null)
{
rows[] = "Cisco HDLC";
rows[] = "--";
rows[] = "0x" + hdlc.Address.ToString("X2");
rows[] = "地址:" + hdlc.Address.ToString("X2") + " 控制:" + hdlc.Control.ToString() + " 协议类型:" + hdlc.Protocol.ToString();
return rows;
}
#warning 需要测试
PacketDotNet.Ieee80211.MacFrame ieee = Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data) as PacketDotNet.Ieee80211.MacFrame;
if (ieee != null)
{
rows[] = "IEEE802.11 MacFrame";
rows[] = "--";
rows[] = "--";
rows[] = "帧校验序列:" + ieee.FrameCheckSequence.ToString() + " 封装帧:" + ieee.FrameControl .ToString();
return rows;
}
PacketDotNet.Ieee80211.RadioPacket ieeePacket = Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data) as PacketDotNet.Ieee80211.RadioPacket;
if (ieeePacket != null)
{
rows[] = "IEEE Radio";
rows[] = "Version=" + ieeePacket.Version.ToString();
}
LinuxSLLPacket linux = Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data) as LinuxSLLPacket;
if (linux != null)
{
rows[] = "LinuxSLL";
rows[] = "Tyep=" + linux.Type.ToString() + " Protocol=" + linux.EthernetProtocolType.ToString();
}
return rows;
}
}
#endregion }

虽然写的比较渣,但是思路还是蛮清晰的,额~~有砖头~~~~~

先不管了,来一起看看结果吧

NetAnalyzer笔记 之 四. C#版的抓包软件

图4 采集到网络数据

终于有点激动了,那么我们接下来就是要实现详细的数据了,不过看了上面的方式代码的解析方式,你会不会有点想法呢。好吧,我这边只把TCP的解析方式放在这里了

  //Tcp
TreeNode TCPNode;
TreeNode TcpFlagNode;
TreeNode TcpChecksumNode;
TreeNode TcpOptionsNode;
private void TCP(TcpPacket tcp)
{
if (TCPNode == null)
{
TCPNode = CreatNode("TCP", );
}
TCPNode.Nodes.Clear();
//port
TCPNode.Nodes.Add("Source Port: " + tcp.SourcePort.ToString());
TCPNode.Nodes.Add("Destination Port: " + tcp.DestinationPort.ToString());
// Seq and Ack
TCPNode.Nodes.Add("Sequence Number: " + tcp.SequenceNumber.ToString() + " [0x" + tcp.SequenceNumber.ToString("X") + "]");
TCPNode.Nodes.Add("Acknowledgement Number: " + tcp.AcknowledgmentNumber.ToString() + " [0x" + tcp.AcknowledgmentNumber.ToString("X") + "]");
//Data Offset
TCPNode.Nodes.Add("Data Offset: " + (tcp.DataOffset * ).ToString() + " [0x" + tcp.DataOffset.ToString("X") + "]");
//Flags
#region Flags
if (TcpFlagNode == null)
{
TcpFlagNode = new TreeNode();
}
TcpFlagNode.Nodes.Clear();
TcpFlagNode.Text = "Flags: [" + Format.TcpFlagType(tcp) + "] [0x" + string.Format("{0:X2}", tcp.AllFlags) + "]";
TcpFlagNode.Nodes.Add("000. .... .... = Reserved");
TcpFlagNode.Nodes.Add("...0 .... .... = Nonce");
TcpFlagNode.Nodes.Add(".... " + Format.getStaus(tcp.CWR) + "... .... = CWR");
TcpFlagNode.Nodes.Add(".... ." + Format.getStaus(tcp.ECN) + ".. .... = ECN-Echo");
TcpFlagNode.Nodes.Add(".... .." + Format.getStaus(tcp.Urg) + ". .... = URG");
TcpFlagNode.Nodes.Add(".... ..." + Format.getStaus(tcp.Ack) + " .... = ACK");
TcpFlagNode.Nodes.Add(".... .... " + Format.getStaus(tcp.Psh) + "... = PSH");
TcpFlagNode.Nodes.Add(".... .... ." + Format.getStaus(tcp.Rst) + ".. = RST");
TcpFlagNode.Nodes.Add(".... .... .." + Format.getStaus(tcp.Syn) + ". = SYN");
TcpFlagNode.Nodes.Add(".... .... ..." + Format.getStaus(tcp.Fin) + " = FIN");
TCPNode.Nodes.Add(TcpFlagNode);
#endregion
//WinSize
TCPNode.Nodes.Add("Window Size: " + tcp.WindowSize.ToString());
//check Sum
if (TcpChecksumNode == null)
{
TcpChecksumNode = new TreeNode();
}
TcpChecksumNode.Nodes.Clear();
TcpChecksumNode.Text = "Checksum: 0x" + tcp.Checksum.ToString("X") + " [" + (tcp.ValidChecksum ? "Valid" : "Invalid") + "]";
if (!tcp.ValidChecksum)
{
TCPNode.BackColor = TcpChecksumNode.BackColor = System.Drawing.Color.Red;
TCPNode.ForeColor = TcpChecksumNode.ForeColor = System.Drawing.Color.White;
}
else
{
TCPNode.BackColor = TcpChecksumNode.BackColor = Tree.BackColor;
TCPNode.ForeColor = TcpChecksumNode.ForeColor = Tree.ForeColor;
} TcpChecksumNode.Nodes.Add("Correct: " + tcp.ValidTCPChecksum.ToString());
TCPNode.Nodes.Add(TcpChecksumNode);
//Urgent
TCPNode.Nodes.Add("Urgent Pointer: " + tcp.UrgentPointer.ToString() + " [0x" + tcp.UrgentPointer.ToString("X") + "]");
//Options
if (tcp.Options.Length > )
{
if (TcpOptionsNode == null)
{
TcpOptionsNode = new TreeNode();
}
TcpOptionsNode.Nodes.Clear();
TcpOptionsNode.Text = "Options: " + tcp.Options.Length.ToString() + " bytes";
// [0x" + BitConverter.ToString(tcp.Options).Replace("-", "").PadLeft(12, '0') + "]
if (tcp.OptionsCollection != null)
{
var tmpColl = tcp.OptionsCollection;
for (int i = ; i < tmpColl.Count; i++)
{
TcpOptionsNode.Nodes.Add(tmpColl[i].ToString());
}
}
TCPNode.Nodes.Add(TcpOptionsNode);
}
Tree.Nodes.Add(TCPNode); AppNode(tcp.PayloadData, tcp.SourcePort, tcp.DestinationPort);
}

好了接下来让我们看看最终的结果吧

NetAnalyzer笔记 之 四. C#版的抓包软件

图5 带有数据协议解析的成果图

接下来就是一个关于文件存取的功能,毕竟可以把数据保存下来也是一件很酷的事情,(好吧,我是真的想不出一个好的理由……)

         //打开文件
private void btnOpen_Click(object sender, EventArgs e)
{
OpenFileDialog od = new OpenFileDialog();
od.Filter = "pcap文件|*.pcap"; if (od.ShowDialog() == DialogResult.OK)
{
Clear(); ICaptureDevice offDev = new SharpPcap.LibPcap.CaptureFileReaderDevice(od.FileName);
RawCapture tempPacket;
offDev.Open();
while ((tempPacket = offDev.GetNextPacket()) != null)
{
packetList.Add(tempPacket);
ShowDataRows(tempPacket);
}
offDev.Close(); }
} //文件保存
private void btnSave_Click(object sender, EventArgs e)
{
SaveFileDialog sd = new SaveFileDialog();
sd.Filter = "Pcap文件|*.pcap";
if (sd.ShowDialog() == DialogResult.OK)
{
var offDev = new SharpPcap.LibPcap.CaptureFileWriterDevice(sd.FileName);
foreach (var i in packetList)
{
offDev.Write(i);
}
MessageBox.Show("文件保存成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}

因为这里我们是直接通过Sharppcap调用winpcap内置的数据包存取方式,这样的好处是,我们可以用Wrieshark等一些其他抓包工具打开这些文件,当然如果你愿意也可以用自己的方式存取,只要保证在内存中可以转为RawCapture的数据列表就可以了

 

到这里了,制作一个简单的协议分析基本是完成了,这里是代码下载地址:http://yun.baidu.com/s/1dDDELzF

最后是一些个别说明,我们在监听网卡的时候有个模式的选项

 DeviceMode devMode = DeviceMode.Promiscuous;//数据采集模式

这里有连个模式一个常规模式 DeviceMode.Normal 和一个混杂模式 DeviceMode.Promiscuous主要是用于判断在网卡的数据监听方式,混杂模式就是接收所有经过网卡的数据包,包括不是发给本机的包。默认情况下网卡只把发给本机的包(包括广播包)传递给上层程序,其它的包一律丢弃。简单的讲,混杂模式就是指网卡能接受所有通过它的数据流,不管是什么格式,什么地址的。事实上,计算机收到数据包后,由网络层进行判断,确定是递交上层(传输层),还是丢弃,还是递交下层(数据链路层、MAC子层)转发。(这段来自百度)

还有一个就是关于超时设定,我们直接在程序中定义为1000ms 也就是1秒,也就是说通过winpcap每个1s向程序推送一次监听到的数据,这个值一定要合理设置,如果设置过短,就可能以为频繁提交数据造成性能开销和引起丢包,而如果过长再回造成界面响应慢,总之自己看情况设定吧

好了今天就写到这里吧,不足之处还请指正,祝你阅读愉快

NetAnalyzer下载地址

NetAnalzyer交流群:39753670 (PS 只提供交流平台,群主基本不说话^_^)

[转载请保留作者信息  作者:冯天文  网址:http://www.cnblogs.com/twzy/p/4769797.html ]