From 2d3d9850f5832d8c688405e37c3aa2ee8c3b571f Mon Sep 17 00:00:00 2001 From: jlucansky Date: Fri, 24 Jul 2020 16:42:10 +0200 Subject: [PATCH] Add network sniffer feature --- Source/Swiddler/App.xaml.cs | 22 +- Source/Swiddler/Channels/MonitorChannel.cs | 2 +- Source/Swiddler/Channels/SnifferChannel.cs | 240 +++++++++++- Source/Swiddler/Channels/TcpChannel.cs | 2 +- Source/Swiddler/Common/Injector.cs | 3 +- Source/Swiddler/Common/Session.cs | 50 ++- Source/Swiddler/Images/Eye.xaml | 18 + .../MarkupExtensions/GroupedIPAddresses.cs | 3 +- Source/Swiddler/NetworkSniffer/IPFragment.cs | 101 +++++ Source/Swiddler/NetworkSniffer/IPParser.cs | 119 ++++++ .../Swiddler/NetworkSniffer/PacketFilter.cs | 72 ++++ .../NetworkSniffer/PacketReassembly.cs | 208 ++++++++++ Source/Swiddler/NetworkSniffer/RawPacket.cs | 97 +++++ Source/Swiddler/NetworkSniffer/TCPParser.cs | 54 +++ Source/Swiddler/NetworkSniffer/TCPState.cs | 231 +++++++++++ Source/Swiddler/NetworkSniffer/UDPParser.cs | 26 ++ Source/Swiddler/Properties/AssemblyInfo.cs | 2 +- .../SocketSettings/SnifferSettings.cs | 64 ++++ Source/Swiddler/Swiddler.csproj | 31 ++ Source/Swiddler/Utils/AssemblyExtensions.cs | 13 + Source/Swiddler/Utils/Firewall.cs | 360 ++++++++++++++++++ .../Swiddler/ViewModels/ConnectionSettings.cs | 58 ++- Source/Swiddler/ViewModels/IPAddressItem.cs | 24 +- Source/Swiddler/ViewModels/QuickActionItem.cs | 11 + .../Swiddler/ViewModels/RecentlyUsedItem.cs | 6 + Source/Swiddler/Views/MainWindow.xaml.cs | 44 ++- Source/Swiddler/Views/NewConnection.xaml | 7 +- Source/Swiddler/Views/NewConnection.xaml.cs | 3 + .../Views/SocketSettings/Sniffer.xaml | 92 +++++ .../Views/SocketSettings/Sniffer.xaml.cs | 24 ++ 30 files changed, 1956 insertions(+), 31 deletions(-) create mode 100644 Source/Swiddler/Images/Eye.xaml create mode 100644 Source/Swiddler/NetworkSniffer/IPFragment.cs create mode 100644 Source/Swiddler/NetworkSniffer/IPParser.cs create mode 100644 Source/Swiddler/NetworkSniffer/PacketFilter.cs create mode 100644 Source/Swiddler/NetworkSniffer/PacketReassembly.cs create mode 100644 Source/Swiddler/NetworkSniffer/RawPacket.cs create mode 100644 Source/Swiddler/NetworkSniffer/TCPParser.cs create mode 100644 Source/Swiddler/NetworkSniffer/TCPState.cs create mode 100644 Source/Swiddler/NetworkSniffer/UDPParser.cs create mode 100644 Source/Swiddler/SocketSettings/SnifferSettings.cs create mode 100644 Source/Swiddler/Utils/AssemblyExtensions.cs create mode 100644 Source/Swiddler/Utils/Firewall.cs create mode 100644 Source/Swiddler/Views/SocketSettings/Sniffer.xaml create mode 100644 Source/Swiddler/Views/SocketSettings/Sniffer.xaml.cs diff --git a/Source/Swiddler/App.xaml.cs b/Source/Swiddler/App.xaml.cs index a68ca92..0568798 100644 --- a/Source/Swiddler/App.xaml.cs +++ b/Source/Swiddler/App.xaml.cs @@ -3,11 +3,10 @@ using Swiddler.ViewModels; using Swiddler.Views; using System; -using System.Globalization; using System.IO; using System.Linq; -using System.Net; -using System.Text; +using System.Reflection; +using System.Threading.Tasks; using System.Windows; namespace Swiddler @@ -36,10 +35,25 @@ protected override void OnStartup(StartupEventArgs e) EnsureUserFolders(); - if (e.Args.FirstOrDefault()?.Equals("-nonew", StringComparison.OrdinalIgnoreCase) == true) + Task.Run(() => Firewall.Instance.GrantAuthorizationToSelf()); + + var firstArg = e.Args.FirstOrDefault(); + + if (firstArg?.Equals("-nonew", StringComparison.OrdinalIgnoreCase) == true) { new MainWindow().Show(); } + else if (firstArg?.Equals("-settings", StringComparison.OrdinalIgnoreCase) == true && e.Args.Length == 2) + { + var cs = ConnectionSettings.Deserialize(File.ReadAllBytes(e.Args[1])); + var session = cs.CreateSession(); + if (session != null) + { + var mainWindow = new MainWindow(); + mainWindow.Show(); + mainWindow.AddSessionAndStart(session); + } + } else { Rect rect = Rect.Empty; diff --git a/Source/Swiddler/Channels/MonitorChannel.cs b/Source/Swiddler/Channels/MonitorChannel.cs index 43dcb19..4c2716c 100644 --- a/Source/Swiddler/Channels/MonitorChannel.cs +++ b/Source/Swiddler/Channels/MonitorChannel.cs @@ -123,7 +123,7 @@ void NewCapturedPacked(CapturedPacket capPacket) static string GetSessionName(CapturedPacket capPacket) { if (capPacket.LocalEndPoint != null && capPacket.RemoteEndPoint != null) - return $"{capPacket.Protocol.ToString().Substring(0, 1)} :{ capPacket.LocalEndPoint.Port } -> {capPacket.RemoteEndPoint}"; + return $"{capPacket.Protocol.ToString().Substring(0, 1)} :{ capPacket.LocalEndPoint.Port } > {capPacket.RemoteEndPoint}"; else return $"0x{capPacket.Handle:X8}"; } diff --git a/Source/Swiddler/Channels/SnifferChannel.cs b/Source/Swiddler/Channels/SnifferChannel.cs index 7d68e56..096644a 100644 --- a/Source/Swiddler/Channels/SnifferChannel.cs +++ b/Source/Swiddler/Channels/SnifferChannel.cs @@ -1,16 +1,240 @@ -using System; +using Swiddler.Common; +using Swiddler.DataChunks; +using Swiddler.NetworkSniffer; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Net; +using System.Net.Sockets; +using System.Windows; namespace Swiddler.Channels { - class SnifferChannel + public class SnifferChannel : Channel, IDisposable { - /* - https://github.com/proxytype/NetworkSnifferLib + public IPAddress LocalAddress { get; set; } + public Tuple[] CaptureFilter { get; set; } - */ + + private Socket socket; + + readonly byte[] RCVALL_ON = new byte[4] { 1, 0, 0, 0 }; // promiscuous mode + readonly byte[] RCVALL_IPLEVEL = new byte[4] { 3, 0, 0, 0 }; + + private readonly byte[] buffer = new byte[0x10000]; + + private readonly PacketReassembly reassembly = new PacketReassembly(); + + private readonly Dictionary connections = new Dictionary(); + + class ConnectionKey + { + public IPEndPoint LocalEP, RemoteEP; + + public ConnectionKey(RawPacket raw, IPAddress localIP) + { + if (raw.Source.Address.Equals(localIP)) + { + LocalEP = raw.Source; + RemoteEP = raw.Destination; + } + else + { + LocalEP = raw.Destination; + RemoteEP = raw.Source; + } + } + + public override bool Equals(object obj) + { + if (!(obj is ConnectionKey other)) return false; + + if (LocalEP.Equals(other.LocalEP) && RemoteEP.Equals(other.RemoteEP)) + return true; + if (RemoteEP.Equals(other.LocalEP) && LocalEP.Equals(other.RemoteEP)) + return true; + + return false; + } + + public override int GetHashCode() + { + var sh = LocalEP.GetHashCode(); + var dh = RemoteEP.GetHashCode(); + if (sh < dh) return sh ^ dh; else return dh ^ sh; + } + } + + private class Mediator : Channel + { + public ConnectionKey EP { get; set; } + public bool IsServer { get; set; } + public Mediator(Session session) : base(session) { } + protected override void OnReceiveNotification(Packet packet) => throw new NotImplementedException(); + public void Send(Packet packet) => NotifyObservers(packet); // write to session UI + } + + public SnifferChannel(Session session) : base(session) { } + + protected override void StartOverride() + { + var ip = LocalAddress; + + foreach (var item in CaptureFilter) + { + reassembly.Filter.Add(item.Item1, item.Item2); + } + + try + { + if (ip.AddressFamily == AddressFamily.InterNetworkV6) + { + // IPv6 support is experimental - currently doesn't work because IP headers are not captured + socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Raw, ProtocolType.Raw); + socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.HeaderIncluded, true); + } + else + { + try + { + socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP); + } + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AccessDenied) + { + StartAsAdmin(); + throw; + } + } + + socket.Bind(new IPEndPoint(ip, 0)); + socket.IOControl(IOControlCode.ReceiveAll, RCVALL_IPLEVEL, null); + + BeginReceive(); + } + catch (Exception ex) + { + HandleError(ex); + } + } + + void StartAsAdmin() + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + if (MessageBox.Show( + "You don’t have permission to create raw sockets.\n\nDo you want to launch Swiddler as Administrator?", + "Access Denied", MessageBoxButton.YesNo, MessageBoxImage.Exclamation) == MessageBoxResult.Yes) Session.StartAsAdmin(); + })); + } + + void BeginReceive() + { + try + { + socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null); + } + catch (Exception ex) + { + HandleError(ex); + } + } + + private void ReceiveCallback(IAsyncResult result) + { + try + { + int received = socket.EndReceive(result); + + if (received > 0) + { + byte[] data = new byte[received]; + Array.Copy(buffer, 0, data, 0, received); + + var packets = reassembly.GetPackets(data); + + if (packets?.Length > 0) + { + foreach (var raw in packets) + { + var packet = new Packet() { Payload = new byte[raw.DataLength] }; + Array.Copy(raw.Buffer, raw.HeaderLength, packet.Payload, 0, raw.DataLength); + + // TODO: raw.Dropped + + var mediator = GetChild(raw); + + packet.LocalEndPoint = mediator.EP.LocalEP; + packet.RemoteEndPoint = mediator.EP.RemoteEP; + + if (packet.LocalEndPoint.Equals(raw.Source)) + packet.Flow = TrafficFlow.Outbound; + else + packet.Flow = TrafficFlow.Inbound; + + mediator.Send(packet); + + if (raw.Flags.HasFlag(TCPFlags.RST) || raw.Flags.HasFlag(TCPFlags.FIN)) + { + mediator.Session.Stop(); + } + } + } + } + + BeginReceive(); + } + catch (Exception ex) + { + HandleError(ex); + } + } + + Mediator GetChild(RawPacket raw) + { + var ep = new ConnectionKey(raw, LocalAddress); + lock (connections) + { + if (connections.TryGetValue(ep, out var mediator) == false) + { + var child = Session.NewChildSession(); + + Session.Storage.Write(new MessageData() { Text = $"New connection observed {raw.Source} -> :{raw.Destination}", Type = MessageType.Connecting }); + + mediator = new Mediator(child) { EP = ep }; + + string protoStr = raw.Protocol.ToString().ToUpperInvariant(); + + if (raw.Destination.Address.Equals(LocalAddress)) + { + child.Name = $"{raw.Source} > :{raw.Destination.Port} - {protoStr}"; + mediator.IsServer = true; // when first packet is directed toward local IP, then local EP is probably server + } + else + { + child.Name = $":{raw.Source.Port} > {raw.Destination} - {protoStr}"; + } + + mediator.Observe(child.SessionChannel); // received packets write to session Log + + child.Start(); // immediately set session state to stared + + child.ResolveProcessIdAsync(ep.LocalEP); + + connections.Add(ep, mediator); + } + + return mediator; + } + } + + protected override void OnReceiveNotification(Packet packet) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + socket?.Dispose(); + socket = null; + } } } diff --git a/Source/Swiddler/Channels/TcpChannel.cs b/Source/Swiddler/Channels/TcpChannel.cs index 3579cae..d9c56aa 100644 --- a/Source/Swiddler/Channels/TcpChannel.cs +++ b/Source/Swiddler/Channels/TcpChannel.cs @@ -118,7 +118,7 @@ protected override void OnReceiveNotification(Packet packet) HandleError(ex); } } - + public virtual void Dispose() { NetworkStream?.Dispose(); diff --git a/Source/Swiddler/Common/Injector.cs b/Source/Swiddler/Common/Injector.cs index 4ca15e6..5301687 100644 --- a/Source/Swiddler/Common/Injector.cs +++ b/Source/Swiddler/Common/Injector.cs @@ -157,8 +157,7 @@ byte[] content() static string GetExecutingDirectoryName() { - var location = new Uri(Assembly.GetEntryAssembly().GetName().CodeBase); - return new FileInfo(location.LocalPath).Directory.FullName; + return new FileInfo(Assembly.GetEntryAssembly().GetLocalPath()).Directory.FullName; } string GetVersion() diff --git a/Source/Swiddler/Common/Session.cs b/Source/Swiddler/Common/Session.cs index fcce40c..a5de449 100644 --- a/Source/Swiddler/Common/Session.cs +++ b/Source/Swiddler/Common/Session.cs @@ -6,6 +6,8 @@ using Swiddler.Utils; using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; @@ -43,9 +45,12 @@ public class Session : BindableBase, IDisposable public StorageHandle Storage { get; private set; } + public string SettingsFileName { get; set; } + public ServerSettingsBase ServerSettings { get; set; } public ClientSettingsBase ClientSettings { get; set; } public RewriteRule[] Rewrites { get; set; } + public SnifferSettings Sniffer { get; set; } public Injector Injector { get; set; } // TODO: MonitorSettings public SessionChannel SessionChannel { get; } // input/output to FragmentView @@ -141,6 +146,10 @@ public void Start() { StartMonitor(); } + else if (Sniffer != null) + { + StartSniffer(); + } StartChannels(); @@ -154,6 +163,27 @@ public void Start() } } + private void StartSniffer() + { + var settings = Sniffer; + + var localIP = IPAddress.Parse(settings.InterfaceAddress); + + Name = $"Sniffing at {localIP}"; + + WriteMessage("Starting network sniffer at " + localIP); + + var sniffer = new SnifferChannel(this) + { + LocalAddress = localIP, + CaptureFilter = settings.CaptureFilter.Select(x => x.ToTuple()).ToArray() + }; + + ServerChannel = sniffer; + + shouldStopChildren = false; + } + private void StartServer() { var settings = ServerSettings; @@ -303,7 +333,7 @@ private void StartClient() } else { - Name = $":{localEP.Port} -> {targetEP}"; + Name = $":{localEP.Port} > {targetEP}"; nameWhenStopped = null; ResolveProcessIdAsync(targetEP); if (!SessionChannel.Observers.Contains(ClientChannel)) // broadcast/multicast is oneway @@ -504,5 +534,23 @@ public object GetCachedObject(long key, Func valueFactory) return obj; } + public bool StartAsAdmin() + { + if (!string.IsNullOrEmpty(SettingsFileName) && File.Exists(SettingsFileName)) + { + using (Process proc = new Process()) + { + proc.StartInfo.FileName = System.Reflection.Assembly.GetEntryAssembly().GetLocalPath(); + proc.StartInfo.Arguments = $"-settings \"{SettingsFileName}\""; + proc.StartInfo.UseShellExecute = true; + proc.StartInfo.Verb = "runas"; + proc.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory(); + proc.Start(); + return true; + } + } + + return false; + } } } diff --git a/Source/Swiddler/Images/Eye.xaml b/Source/Swiddler/Images/Eye.xaml new file mode 100644 index 0000000..841f585 --- /dev/null +++ b/Source/Swiddler/Images/Eye.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Swiddler/MarkupExtensions/GroupedIPAddresses.cs b/Source/Swiddler/MarkupExtensions/GroupedIPAddresses.cs index 8209664..40e47a8 100644 --- a/Source/Swiddler/MarkupExtensions/GroupedIPAddresses.cs +++ b/Source/Swiddler/MarkupExtensions/GroupedIPAddresses.cs @@ -11,6 +11,7 @@ namespace Swiddler.MarkupExtensions public class GroupedIPAddresses : MarkupExtension { public AddressFamily? AddressFamily { get; set; } + public bool Loopback { get; set; } = true; class IPAddressGroupDescription : GroupDescription { @@ -26,7 +27,7 @@ public override object ProvideValue(IServiceProvider serviceProvider) { var addrViewSource = new CollectionViewSource(); addrViewSource.GroupDescriptions.Add(new IPAddressGroupDescription()); - addrViewSource.Source = AddressFamily.HasValue ? IPAddressItem.GetAll(AddressFamily.Value) : IPAddressItem.GetAll(); + addrViewSource.Source = AddressFamily.HasValue ? IPAddressItem.GetAll(AddressFamily.Value, Loopback) : IPAddressItem.GetAll(); return addrViewSource.View; } diff --git a/Source/Swiddler/NetworkSniffer/IPFragment.cs b/Source/Swiddler/NetworkSniffer/IPFragment.cs new file mode 100644 index 0000000..db15c0c --- /dev/null +++ b/Source/Swiddler/NetworkSniffer/IPFragment.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +namespace Swiddler.NetworkSniffer +{ + class IPFragmentReassembly + { + public RawPacket ReassembledPacket { get; private set; } + public readonly DateTime TimestampUtc = DateTime.UtcNow; + + readonly List fragments = new List(); + int totalLength = 0; + + public void AddFragment(RawPacket packet, int fragmentOffset, bool moreFragments) + { + var f = new IPFragment() { IncompletePacket = packet, FragmentOffset = fragmentOffset, MoreFragments = moreFragments }; + + var index = fragments.BinarySearch(f); + if (index < 0) + { + index = ~index; + + if (index > 0) + { + if (f.FragmentOffset < fragments[index - 1].FragmentOffset + fragments[index - 1].IncompletePacket.DataLength) + return; // overlapping data (DoS attack ???) + } + + fragments.Insert(index, f); + + totalLength = Math.Max(totalLength, fragmentOffset + packet.DataLength); + TryReassemble(packet); + } + } + + void TryReassemble(RawPacket packet) + { + if (fragments.Last().MoreFragments == false && fragments.Sum(x => x.IncompletePacket.DataLength) == totalLength) + { + int offset = 0; + var dstPosition = packet.HeaderLength; + var buffer = new byte[totalLength + dstPosition]; + + foreach (var f in fragments) + { + if (f.FragmentOffset != offset) return; + + var len = f.IncompletePacket.DataLength; + Buffer.BlockCopy(f.IncompletePacket.Buffer, f.IncompletePacket.HeaderLength, buffer, dstPosition, len); + dstPosition += len; + offset += len; + } + + packet.Buffer = buffer; + packet.DataLength = totalLength; + ReassembledPacket = packet; + } + } + } + + class IPFragmentKey + { + public IPAddress Address; + public int Identification; + + public override int GetHashCode() + { + return Address.GetHashCode() ^ Identification.GetHashCode(); + } + + public override bool Equals(object obj) + { + var other = obj as IPFragmentKey; + if (other == null) return false; + return other.Identification.Equals(Identification) && other.Address.Equals(Address); + } + + public override string ToString() + { + return $"id={Identification} address={Address}"; + } + } + + class IPFragment : IComparable + { + public int FragmentOffset; + public bool MoreFragments; // true in all but the last fragment + + public RawPacket IncompletePacket; + + public int CompareTo(IPFragment other) => FragmentOffset.CompareTo(other.FragmentOffset); + + public override string ToString() + { + return "off=" + FragmentOffset; + } + } + +} diff --git a/Source/Swiddler/NetworkSniffer/IPParser.cs b/Source/Swiddler/NetworkSniffer/IPParser.cs new file mode 100644 index 0000000..cd1898e --- /dev/null +++ b/Source/Swiddler/NetworkSniffer/IPParser.cs @@ -0,0 +1,119 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace Swiddler.NetworkSniffer +{ + abstract class PacketParserBase + { + public abstract bool ParseHeader(RawPacket packet); + } + + class IPParser_v4 : PacketParserBase + { + const int SOURCE_IP_START_INDEX = 12; + const int DESTINATION_IP_START_INDEX = 16; + + public override bool ParseHeader(RawPacket packet) + { + var raw = packet.Buffer; + + if (raw.Length < 20) return false; + + var ihl = raw[0] & 0xf; // Internet Header Length + if (ihl < 5 || ihl > 15) + return false; + + packet.Source = new IPEndPoint(BitConverter.ToUInt32(raw, SOURCE_IP_START_INDEX), 0); + packet.Destination = new IPEndPoint(BitConverter.ToUInt32(raw, DESTINATION_IP_START_INDEX), 0); + packet.Protocol = (ProtocolType)raw[9]; + packet.HeaderLength = ihl * 4; + packet.DataLength = ((raw[2] << 8) | raw[3]) - packet.HeaderLength; + + if (packet.DataLength > raw.Length + packet.HeaderLength) + return false; + + return true; + } + + public bool ParseIdentification(RawPacket packet, out int identification, out int fragmentOffset, out bool moreFragments) + { + var raw = packet.Buffer; + + identification = (raw[4] << 8) | raw[5]; + moreFragments = ((raw[6] >> 5) & 1) == 1; // take 3rd bit + fragmentOffset = (((byte)(raw[6] << 3) << 5) + raw[7]) * 8; // take remaining 5bits + 8bits + + return true; + } + } + + class IPParser_v6 : PacketParserBase + { + const int SOURCE_IP_START_INDEX = 8; + const int DESTINATION_IP_START_INDEX = 24; + + public override bool ParseHeader(RawPacket packet) + { + var raw = packet.Buffer; + + if (raw.Length < 40) return false; + + var ip = new byte[16]; + + Buffer.BlockCopy(raw, SOURCE_IP_START_INDEX, ip, 0, ip.Length); + packet.Source = new IPEndPoint(new IPAddress(ip), 0); + + Buffer.BlockCopy(raw, DESTINATION_IP_START_INDEX, ip, 0, ip.Length); + packet.Destination = new IPEndPoint(new IPAddress(ip), 0); + + packet.Protocol = (ProtocolType)raw[6]; + packet.HeaderLength = 40; + packet.DataLength = (raw[4] << 8) | raw[5]; + + if (packet.DataLength > raw.Length + packet.HeaderLength) + return false; + + return true; + } + + public bool ParseIdentification(RawPacket packet, out int identification, out int fragmentOffset, out bool moreFragments) + { + var raw = packet.Buffer; + + if (packet.HeaderLength + 8 > raw.Length) // check minimum extension len + { + identification = 0; + fragmentOffset = 0; + moreFragments = false; + return false; + } + + var h = packet.HeaderLength; + + identification = BitConverter.ToInt32(raw, h + 4); + moreFragments = (raw[h + 3] & 1) == 1; + fragmentOffset = ((raw[h + 2] << 8) | (byte)((raw[h + 3] & 0xF8) >> 3)) * 8; // take 13 bits + + packet.Protocol = (ProtocolType)raw[h]; // update next protocol + packet.HeaderLength += 8; + + return true; + } + + public bool ParseExtensionHeader(RawPacket packet) + { + var raw = packet.Buffer; + + if (packet.HeaderLength + 8 > raw.Length) // check minimum extension len + return false; + + packet.Protocol = (ProtocolType)raw[packet.HeaderLength++]; // update next protocol + var len = raw[packet.HeaderLength++] * 8; + packet.HeaderLength += len + 6; // increment len header - in 8-octet units, not including the first 8 octets + + return true; + } + } + +} diff --git a/Source/Swiddler/NetworkSniffer/PacketFilter.cs b/Source/Swiddler/NetworkSniffer/PacketFilter.cs new file mode 100644 index 0000000..eb7d77a --- /dev/null +++ b/Source/Swiddler/NetworkSniffer/PacketFilter.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; + +namespace Swiddler.NetworkSniffer +{ + class CaptureFilterItem + { + public IPEndPoint EP; + public ProtocolType Protocol; + } + + public class PacketFilter + { + readonly List rules = new List(); + + public void Add(IPEndPoint ep, ProtocolType protocol = ProtocolType.Unspecified) + { + if (ep.AddressFamily != AddressFamily.InterNetwork) + throw new Exception("Only IPv4 is supported."); + + rules.Add(new CaptureFilterItem() { EP = ep, Protocol = protocol }); + } + + public bool ShouldCapture(IPAddress source, IPAddress destination) + { + if (rules.Count == 0) + return true; + + foreach (var filter in rules) + { + var addr = filter.EP.Address; + if (addr.Equals(IPAddress.Any) || + addr.Equals(source) || + addr.Equals(destination)) + return true; + } + + return false; + } + + public bool ShouldCapture(RawPacket packet) + { + if (rules.Count == 0) + return true; + + foreach (var filter in rules) + { + if (filter.Protocol == ProtocolType.Unspecified || + filter.Protocol == packet.Protocol) + { + var addr = filter.EP.Address; + if (addr.Equals(IPAddress.Any) || + addr.Equals(packet.Source.Address) || + addr.Equals(packet.Destination.Address)) + { + var port = filter.EP.Port; + if (port == 0 || + port == packet.Source.Port || + port == packet.Destination.Port) + return true; + } + } + } + + return false; + } + + } + +} diff --git a/Source/Swiddler/NetworkSniffer/PacketReassembly.cs b/Source/Swiddler/NetworkSniffer/PacketReassembly.cs new file mode 100644 index 0000000..a7f95aa --- /dev/null +++ b/Source/Swiddler/NetworkSniffer/PacketReassembly.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; + +namespace Swiddler.NetworkSniffer +{ + public class PacketReassembly + { + public PacketFilter Filter { get; set; } = new PacketFilter(); + public int FragmentCacheTimeout { get; set; } = 60; + public int StateTimeout { get; set; } = 100; + + DateTime fragmentCachePurgeSchedule; + private readonly Dictionary fragmentCache = new Dictionary(); + + DateTime tcpStatePurgeSchedule; + private readonly Dictionary tcpStates = new Dictionary(); + + readonly IPParser_v4 ipParser_v4; + readonly IPParser_v6 ipParser_v6; + readonly TCPParser tcpParser; + readonly UDPParser udpParser; + + readonly object syncObj = new object(); + + public PacketReassembly() + { + ipParser_v4 = new IPParser_v4(); + ipParser_v6 = new IPParser_v6(); + tcpParser = new TCPParser(); + udpParser = new UDPParser(); + } + + public RawPacket[] GetPackets(byte[] raw) + { + lock (syncObj) + { + PurgeFragmentCache(); + PurgeStates(); + + // ********************* + // *** Parse Layer 3 *** + // ********************* + + var version = (IPVersion)((raw[0] & 0xF0) >> 4); + + var packet = new RawPacket() { Buffer = raw, Version = version }; + + PacketParserBase l3parser = null; + + switch (packet.Version) + { + case IPVersion.IPv4: + l3parser = ipParser_v4; + break; + case IPVersion.IPv6: + l3parser = ipParser_v6; + break; + default: return null; + } + + if (!l3parser.ParseHeader(packet)) + return null; + + if (!Filter.ShouldCapture(packet.Source.Address, packet.Destination.Address)) + return null; + + + int identification = 0, fragmentOffset = 0; + bool moreFragments = false; + + if (packet.Version == IPVersion.IPv4) + { + if (!ipParser_v4.ParseIdentification(packet, out identification, out fragmentOffset, out moreFragments)) + return null; + } + if (packet.Version == IPVersion.IPv6) + { + while (true) + { + if (packet.Protocol == ProtocolType.IPv6HopByHopOptions || + packet.Protocol == ProtocolType.IPv6RoutingHeader || + packet.Protocol == ProtocolType.IPv6DestinationOptions) + { + if (!ipParser_v6.ParseExtensionHeader(packet)) return null; + continue; + } + if (packet.Protocol == ProtocolType.IPv6FragmentHeader) + { + if (!ipParser_v6.ParseIdentification(packet, out identification, out fragmentOffset, out moreFragments)) return null; + continue; + } + break; + } + } + + if (moreFragments || fragmentOffset > 0) + { + // Fragmented packet + + var fk = new IPFragmentKey() { Address = packet.Source.Address, Identification = identification }; + + if (!fragmentCache.TryGetValue(fk, out var frag)) + { + frag = new IPFragmentReassembly(); + fragmentCache.Add(fk, frag); + } + + frag.AddFragment(packet, fragmentOffset, moreFragments); + + if (frag.ReassembledPacket != null) // successfully reassembled + { + packet = frag.ReassembledPacket; + fragmentCache.Remove(fk); + } + } + + // ********************* + // *** Parse Layer 4 *** + // ********************* + + PacketParserBase l4parser = null; + + switch (packet.Protocol) + { + case ProtocolType.Tcp: + l4parser = tcpParser; + break; + case ProtocolType.Udp: + l4parser = udpParser; + break; + default: return null; + } + + if (!l4parser.ParseHeader(packet)) + return null; + + if (!Filter.ShouldCapture(packet)) + return null; + + if (packet.Protocol == ProtocolType.Tcp) + { + // TCP reassembly + + var stateKey = new TCPStateKey() { Source = packet.Source, Destination = packet.Destination }; + + if (!tcpStates.TryGetValue(stateKey, out var state)) + { + state = new TCPState() { AckedSequence = packet.Sequence }; + tcpStates.Add(stateKey, state); + } + + state.Put(packet); + + List packets = null; + RawPacket segment = null; + while ((segment = state.TryGet()) != null) + { + if (segment.Flags.HasFlag(TCPFlags.SYN) && !tcpStates.ContainsKey(stateKey.Invert())) + state.IsActiveOpener = true; // first SYN is client side + + if (packets == null) packets = new List(); + packets.Add(segment); + } + + return packets?.ToArray(); + } + else + { + return new[] { packet }; + } + } + } + + void PurgeFragmentCache() + { + if (DateTime.UtcNow > fragmentCachePurgeSchedule) + { + var oldTimestamp = DateTime.UtcNow.AddSeconds(-FragmentCacheTimeout); + + foreach (var item in fragmentCache.Where(x => x.Value.TimestampUtc < oldTimestamp).ToArray()) + fragmentCache.Remove(item.Key); + + fragmentCachePurgeSchedule = DateTime.UtcNow.AddSeconds(FragmentCacheTimeout); + } + } + + void PurgeStates() + { + if (DateTime.UtcNow > tcpStatePurgeSchedule) + { + foreach (var item in tcpStates.Where(x => x.Value.CanPurge()).ToArray()) + tcpStates.Remove(item.Key); + + tcpStatePurgeSchedule = DateTime.UtcNow.AddSeconds(StateTimeout); + } + } + + } + +} diff --git a/Source/Swiddler/NetworkSniffer/RawPacket.cs b/Source/Swiddler/NetworkSniffer/RawPacket.cs new file mode 100644 index 0000000..5bd2275 --- /dev/null +++ b/Source/Swiddler/NetworkSniffer/RawPacket.cs @@ -0,0 +1,97 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace Swiddler.NetworkSniffer +{ + public class RawPacket + { + public IPVersion Version; + + public IPEndPoint Source, Destination; + + public ProtocolType Protocol = ProtocolType.Unknown; + + public int HeaderLength; // = dataOffset + public int DataLength; + + /// + /// TCP/UDP checksum + /// + public int Checksum; + + public TCPFlags Flags; + public uint Sequence, Acknowledgment; + + public uint Dropped; + + public byte[] Buffer; // includes full copy of IP headers + data + + + internal bool Contains(long seq) + { + if (DataLength == 0) return Sequence == (uint)seq; + return Sequence <= seq && seq < Sequence + DataLength; + } + + public override string ToString() + { + return $"{Flags} | {Source} -> {Destination}, Len={DataLength}, Seq={Sequence}, Ack={Acknowledgment}"; + } + } + + public enum IPVersion + { + IPv4 = 4, + IPv6 = 6, + } + + [Flags] + public enum TCPFlags + { + /// + /// No more data from sender + /// + FIN = 0x1, + + /// + /// Synchronize sequence numbers + /// + SYN = 0x2, + + /// + /// Reset the connection + /// + RST = 0x4, + + /// + /// Push data immediately to receiver + /// + PSH = 0x8, + + /// + /// Acknowledgment field is significant + /// + ACK = 0x10, + + /// + /// Urgent pointer is significant + /// + URG = 0x20, + + /// + /// ECN-Echo [RFC3168] + /// + ECE = 0x40, + + /// + /// Congestion Window Reduced [RFC3168] + /// + CWR = 0x80, + + /// + /// Nonce sum [RFC3540, RFC3168] + /// + NS = 0x100 + } +} diff --git a/Source/Swiddler/NetworkSniffer/TCPParser.cs b/Source/Swiddler/NetworkSniffer/TCPParser.cs new file mode 100644 index 0000000..883bb84 --- /dev/null +++ b/Source/Swiddler/NetworkSniffer/TCPParser.cs @@ -0,0 +1,54 @@ +namespace Swiddler.NetworkSniffer +{ + class TCPParser : PacketParserBase + { + public override bool ParseHeader(RawPacket packet) + { + var raw = packet.Buffer; + var p = packet.HeaderLength; // IP header + + if (packet.DataLength < 20) + return false; // too small + + packet.Source.Port = (raw[p++] << 8) | raw[p++]; + packet.Destination.Port = (raw[p++] << 8) | raw[p++]; + + packet.Sequence = GetUInt32(raw, p); p += 4; + packet.Acknowledgment = GetUInt32(raw, p); p += 4; + + var dataOffset = (raw[p] & 0xF0) >> 4; + + if (dataOffset < 5 || dataOffset > 15) + return false; + + if ((raw[p++] & 0x01) != 0) packet.Flags |= TCPFlags.NS; + + if ((raw[p] & 0x80) != 0) packet.Flags |= TCPFlags.CWR; + if ((raw[p] & 0x40) != 0) packet.Flags |= TCPFlags.ECE; + if ((raw[p] & 0x20) != 0) packet.Flags |= TCPFlags.URG; + if ((raw[p] & 0x10) != 0) packet.Flags |= TCPFlags.ACK; + if ((raw[p] & 0x08) != 0) packet.Flags |= TCPFlags.PSH; + if ((raw[p] & 0x04) != 0) packet.Flags |= TCPFlags.RST; + if ((raw[p] & 0x02) != 0) packet.Flags |= TCPFlags.SYN; + if ((raw[p] & 0x01) != 0) packet.Flags |= TCPFlags.FIN; + + p += 2; // ignore window size + packet.Checksum = (raw[p++] << 8) + raw[p++]; + + var dataByteOffset = dataOffset * 4; + packet.HeaderLength += dataByteOffset; + packet.DataLength -= dataByteOffset; + + return true; + } + + uint GetUInt32(byte[] buffer, int index) + { + return (uint)( + (buffer[index++] << 24) | + (buffer[index++] << 16) | + (buffer[index++] << 8) | + buffer[index]); + } + } +} diff --git a/Source/Swiddler/NetworkSniffer/TCPState.cs b/Source/Swiddler/NetworkSniffer/TCPState.cs new file mode 100644 index 0000000..ef22f4e --- /dev/null +++ b/Source/Swiddler/NetworkSniffer/TCPState.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +namespace Swiddler.NetworkSniffer +{ + class TCPState + { + public int PurgeTimeout { get; set; } = 1000; + public int Backoff { get; set; } = 1024; + + public uint AckedSequence = 0; + + public bool IsActiveOpener = false; // first SYN (client) + public bool IsClosing = false; // FIN + public bool IsBroken = false; // RST + + DateTime LastAckUtc = DateTime.UtcNow; + readonly List UnackedSegments = new List(); + + public long Dropped = 0; + + public int UnackedCount => UnackedSegments.Count; + public TimeSpan IdleTime => DateTime.UtcNow - LastAckUtc; + + public bool CanPurge() + { + return LastAckUtc < DateTime.UtcNow.AddSeconds(-PurgeTimeout); + } + + class PacketComparer : IComparer + { + public uint Sequence; + public int Compare(RawPacket x, RawPacket y) => x.Sequence.CompareTo(Sequence); + } + static readonly PacketComparer PacketComparerInstance = new PacketComparer(); + + public void Put(RawPacket packet) + { + /* + if (prevSegment != null) + { + if (PacketEquals(prevSegment, packet)) return; // ignore duplicity + + var prevSeq = prevSegment.Sequence; + + if (packet.Sequence < prevSeq) return; // old segment appeared + if (packet.Sequence - prevSeq > int.MaxValue) return; // old segment appeared from previous epoch + } + */ + var index = SearchUnackedPacket(packet.Sequence); + if (index < 0) + { + index = ~index; + } + else + { + //if (PacketEquals(UnackedSegments[index], packet)) return; // ignore duplicity + + while (index < UnackedSegments.Count && UnackedSegments[index].Sequence == packet.Sequence) + index++; + } + + UnackedSegments.Insert(index, packet); + } + + int SearchUnackedPacket(uint sequence) + { + PacketComparerInstance.Sequence = sequence; + return UnackedSegments.BinarySearch(null, PacketComparerInstance); + } + + bool PacketEquals(RawPacket x, RawPacket y) + { + return x.Checksum == y.Checksum && x.Flags == y.Flags && x.Sequence == y.Sequence && x.Acknowledgment == y.Acknowledgment; + } + + public RawPacket TryGet() + { + if (UnackedSegments.Count == 0) return null; + + //var found = SearchUnackedPacket(AckedSequence); + + /* + if (found < 0) + { + if (UnackedSegments.Count > Backoff) + { + // drop some segments + + found = ~found; + if (found < 0 || found >= UnackedSegments.Count) + return null; + + AckedSequence = UnackedSegments[found].Sequence; + } + } + */ + + var packet = UnackedSegments[0]; + bool found = false; + + if (UnackedSegments.Count > Backoff) + { + RemoveAcked(); // purge duplicits + if (UnackedSegments.Count == 0) return null; + + packet = UnackedSegments[0]; + + packet.Dropped = packet.Sequence - AckedSequence; + if (packet.Dropped > int.MaxValue) packet.Dropped = 0; // overlapping segment, not dropped + + Dropped += packet.Dropped; + + found = true; + } + else + { + if (packet.Contains(AckedSequence)) + { + found = true; + } + else + { + if (UnackedSegments.Count > 1) + { + packet = UnackedSegments.Last(); + if (packet.Contains(AckedSequence)) + { + found = true; + } + } + } + } + + if (found) + { + var shift = AckedSequence - packet.Sequence; + if (shift > int.MaxValue) shift = 0; // dropped + + packet.HeaderLength += (int)shift; + + LastAckUtc = DateTime.UtcNow; + + AckedSequence = packet.Sequence + (uint)packet.DataLength; + + if (packet.Flags.HasFlag(TCPFlags.FIN)) + { + AckedSequence++; + IsClosing = true; + } + + if (packet.Flags.HasFlag(TCPFlags.SYN)) + AckedSequence++; + + if (packet.Flags.HasFlag(TCPFlags.RST)) + IsBroken = true; + + if (IsClosing || IsBroken) + PurgeTimeout = 100; // TIME_WAIT + + RemoveAcked(); + + if (UnackedSegments.FirstOrDefault() == packet) + UnackedSegments.RemoveAt(0); + + return packet; + } + + return null; + } + + void RemoveAcked() + { + int i; + for (i = 0; i < UnackedSegments.Count; i++) + { + var seg = UnackedSegments[i]; + if (seg.Sequence >= AckedSequence || seg.Contains(AckedSequence)) + { + break; + } + } + if (i > 0) UnackedSegments.RemoveRange(0, i); + + if (AckedSequence < int.MaxValue) + { + long ackedEpoch = AckedSequence + uint.MaxValue + 1; + for (i = UnackedSegments.Count - 1; i >= 0; i++) + { + var seg = UnackedSegments[i]; + if (seg.Sequence < int.MaxValue) + break; + if (seg.Contains(ackedEpoch)) + break; + UnackedSegments.RemoveAt(i--); + } + } + } + + public override string ToString() + { + return $"NextSeq={AckedSequence}, Unacked={UnackedSegments.Count}, Idle={DateTime.UtcNow - LastAckUtc}"; + } + } + + class TCPStateKey + { + public IPEndPoint Source, Destination; + + public override bool Equals(object obj) + { + var other = obj as TCPStateKey; + if (other == null) return false; + return Source.Equals(other.Source) && Destination.Equals(other.Destination); + } + + public override int GetHashCode() + { + return Source.GetHashCode() ^ Destination.GetHashCode(); + } + + public override string ToString() + { + return $"{Source} -> {Destination}"; + } + + public TCPStateKey Invert() => new TCPStateKey() { Destination = Source, Source = Destination }; + } +} diff --git a/Source/Swiddler/NetworkSniffer/UDPParser.cs b/Source/Swiddler/NetworkSniffer/UDPParser.cs new file mode 100644 index 0000000..4561828 --- /dev/null +++ b/Source/Swiddler/NetworkSniffer/UDPParser.cs @@ -0,0 +1,26 @@ +namespace Swiddler.NetworkSniffer +{ + class UDPParser : PacketParserBase + { + public override bool ParseHeader(RawPacket packet) + { + if (packet.DataLength < 8) + return false; // too small + + var raw = packet.Buffer; + var p = packet.HeaderLength; // IP header + + packet.Source.Port = (raw[p++] << 8) | raw[p++]; + packet.Destination.Port = (raw[p++] << 8) | raw[p++]; + + p += 2; // ignore Length field + + packet.Checksum = (raw[p++] << 8) + raw[p++]; + + packet.HeaderLength += 8; + packet.DataLength -= 8; + + return true; + } + } +} diff --git a/Source/Swiddler/Properties/AssemblyInfo.cs b/Source/Swiddler/Properties/AssemblyInfo.cs index a2fe1ad..865ec62 100644 --- a/Source/Swiddler/Properties/AssemblyInfo.cs +++ b/Source/Swiddler/Properties/AssemblyInfo.cs @@ -52,4 +52,4 @@ [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.1")] +[assembly: AssemblyInformationalVersion("1.0.2")] diff --git a/Source/Swiddler/SocketSettings/SnifferSettings.cs b/Source/Swiddler/SocketSettings/SnifferSettings.cs new file mode 100644 index 0000000..e24dc69 --- /dev/null +++ b/Source/Swiddler/SocketSettings/SnifferSettings.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Net; +using System.Net.Sockets; + +namespace Swiddler.SocketSettings +{ + public class SnifferSettings : SettingsBase + { + public SnifferSettings() + { + Caption = "Network sniffer"; + ImageName = "Eye"; + + CaptureFilter.CollectionChanged += (s, e) => CaptureFilterChanges++; + } + + string _InterfaceAddress; + public string InterfaceAddress { get => _InterfaceAddress; set => SetProperty(ref _InterfaceAddress, value); } + + public ObservableCollection CaptureFilter { get; set; } = new ObservableCollection(); + + public static CaptureProtocol[] CaptureProtocols { get; } = new[] { CaptureProtocol.Both, CaptureProtocol.TCP, CaptureProtocol.UDP }; + + public class CaptureFilterItem + { + public CaptureProtocol Protocol { get; set; } + public string IPAddress { get; set; } + public int? Port { get; set; } + + public Tuple ToTuple() + { + var ep = new IPEndPoint(System.Net.IPAddress.Any, 0); + var proto = ProtocolType.Unspecified; + + if (!string.IsNullOrEmpty(IPAddress)) + ep.Address = System.Net.IPAddress.Parse(IPAddress); + if (Port != null) + ep.Port = Port.Value; + + if (Protocol == CaptureProtocol.TCP) + proto = ProtocolType.Tcp; + if (Protocol == CaptureProtocol.UDP) + proto = ProtocolType.Udp; + + return Tuple.Create(ep, proto); + } + } + + public enum CaptureProtocol + { + Both = 0, + TCP, + UDP + } + + int _CaptureFilterChanges; + [System.Xml.Serialization.XmlIgnore] public int CaptureFilterChanges { get => _CaptureFilterChanges; set => SetProperty(ref _CaptureFilterChanges, value); } + + public static SnifferSettings DesignInstance => new SnifferSettings(); + + } +} diff --git a/Source/Swiddler/Swiddler.csproj b/Source/Swiddler/Swiddler.csproj index 22b21ca..7c596ff 100644 --- a/Source/Swiddler/Swiddler.csproj +++ b/Source/Swiddler/Swiddler.csproj @@ -117,6 +117,14 @@ + + + + + + + + @@ -136,10 +144,13 @@ + + + @@ -202,6 +213,9 @@ Listener.xaml + + Sniffer.xaml + Rewrite.xaml @@ -243,6 +257,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -403,6 +421,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile @@ -530,6 +552,15 @@ False True + + {58FBCF7C-E7A9-467C-80B3-FC65E8FCCA08} + 1 + 0 + 0 + tlbimp + False + True + diff --git a/Source/Swiddler/Utils/AssemblyExtensions.cs b/Source/Swiddler/Utils/AssemblyExtensions.cs new file mode 100644 index 0000000..699a6a1 --- /dev/null +++ b/Source/Swiddler/Utils/AssemblyExtensions.cs @@ -0,0 +1,13 @@ +using System; +using System.Reflection; + +namespace Swiddler.Utils +{ + public static class AssemblyExtensions + { + public static string GetLocalPath(this Assembly assembly) + { + return new Uri(Assembly.GetEntryAssembly().GetName().CodeBase).LocalPath; + } + } +} diff --git a/Source/Swiddler/Utils/Firewall.cs b/Source/Swiddler/Utils/Firewall.cs new file mode 100644 index 0000000..46343c5 --- /dev/null +++ b/Source/Swiddler/Utils/Firewall.cs @@ -0,0 +1,360 @@ +using NetFwTypeLib; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Swiddler.Utils +{ + // https://stackoverflow.com/questions/113755/programmatically-add-an-application-to-windows-firewall + + public class Firewall + { + #region Variables + + private static Firewall instance = null; + + /// + + /// Interface to the firewall manager COM object + /// + + private INetFwMgr fwMgr = null; + #endregion + #region Properties + /// + + /// Singleton access to the firewallhelper object. + /// Threadsafe. + /// + + public static Firewall Instance + { + get + { + lock (typeof(Firewall)) + { + if (instance == null) + instance = new Firewall(); + return instance; + } + } + } + #endregion + #region Constructivat0r + /// + + /// Private Constructor. If this fails, HasFirewall will return + /// false; + /// + + private Firewall() + { + // Get the type of HNetCfg.FwMgr, or null if an error occurred + Type fwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr", false); + + // Assume failed. + fwMgr = null; + + if (fwMgrType != null) + { + try + { + fwMgr = (INetFwMgr)Activator.CreateInstance(fwMgrType); + } + // In all other circumnstances, fwMgr is null. + catch (ArgumentException) { } + catch (NotSupportedException) { } + catch (System.Reflection.TargetInvocationException) { } + catch (MissingMethodException) { } + catch (MethodAccessException) { } + catch (MemberAccessException) { } + catch (InvalidComObjectException) { } + catch (COMException) { } + catch (TypeLoadException) { } + } + } + #endregion + #region Helper Methods + /// + + /// Gets whether or not the firewall is installed on this computer. + /// + + /// + public bool IsFirewallInstalled + { + get + { + if (fwMgr != null && + fwMgr.LocalPolicy != null && + fwMgr.LocalPolicy.CurrentProfile != null) + return true; + else + return false; + } + } + + /// + + /// Returns whether or not the firewall is enabled. + /// If the firewall is not installed, this returns false. + /// + + public bool IsFirewallEnabled + { + get + { + if (IsFirewallInstalled && fwMgr.LocalPolicy.CurrentProfile.FirewallEnabled) + return true; + else + return false; + } + } + + /// + + /// Returns whether or not the firewall allows Application "Exceptions". + /// If the firewall is not installed, this returns false. + /// + + /// + /// Added to allow access to this metho + /// + public bool AppAuthorizationsAllowed + { + get + { + if (IsFirewallInstalled && !fwMgr.LocalPolicy.CurrentProfile.ExceptionsNotAllowed) + return true; + else + return false; + } + } + + public void GrantAuthorizationToSelf() + { + try + { + GrantAuthorization(Assembly.GetEntryAssembly().GetLocalPath(), "Swiddler"); + } + catch + { + } + } + + /// + + /// Adds an application to the list of authorized applications. + /// If the application is already authorized, does nothing. + /// + + /// + /// The full path to the application executable. This cannot + /// be blank, and cannot be a relative path. + /// + /// + /// This is the name of the application, purely for display + /// puposes in the Microsoft Security Center. + /// + /// + /// When applicationFullPath is null OR + /// When appName is null. + /// + /// + /// When applicationFullPath is blank OR + /// When appName is blank OR + /// applicationFullPath contains invalid path characters OR + /// applicationFullPath is not an absolute path + /// + /// + /// If the firewall is not installed OR + /// If the firewall does not allow specific application 'exceptions' OR + /// Due to an exception in COM this method could not create the + /// necessary COM types + /// + /// + /// If no file exists at the given applicationFullPath + /// + public void GrantAuthorization(string applicationFullPath, string appName) + { + #region Parameter checking + if (applicationFullPath == null) + throw new ArgumentNullException("applicationFullPath"); + if (appName == null) + throw new ArgumentNullException("appName"); + if (!File.Exists(applicationFullPath)) + throw new FileNotFoundException("File does not exist", applicationFullPath); + // State checking + if (!IsFirewallInstalled) + throw new FirewallException("Cannot grant authorization: Firewall is not installed."); + if (!AppAuthorizationsAllowed) + throw new FirewallException("Application exemptions are not allowed."); + #endregion + + if (!HasAuthorization(applicationFullPath)) + { + // Get the type of HNetCfg.FwMgr, or null if an error occurred + Type authAppType = Type.GetTypeFromProgID("HNetCfg.FwAuthorizedApplication", false); + + // Assume failed. + INetFwAuthorizedApplication appInfo = null; + + if (authAppType != null) + { + try + { + appInfo = (INetFwAuthorizedApplication)Activator.CreateInstance(authAppType); + } + // In all other circumnstances, appInfo is null. + catch (ArgumentException) { } + catch (NotSupportedException) { } + catch (System.Reflection.TargetInvocationException) { } + catch (MissingMethodException) { } + catch (MethodAccessException) { } + catch (MemberAccessException) { } + catch (InvalidComObjectException) { } + catch (COMException) { } + catch (TypeLoadException) { } + } + + if (appInfo == null) + throw new FirewallException("Could not grant authorization: can't create INetFwAuthorizedApplication instance."); + + appInfo.Name = appName; + appInfo.ProcessImageFileName = applicationFullPath; + // ... + // Use defaults for other properties of the AuthorizedApplication COM object + + // Authorize this application + fwMgr.LocalPolicy.CurrentProfile.AuthorizedApplications.Add(appInfo); + } + // otherwise it already has authorization so do nothing + } + /// + + /// Removes an application to the list of authorized applications. + /// Note that the specified application must exist or a FileNotFound + /// exception will be thrown. + /// If the specified application exists but does not current have + /// authorization, this method will do nothing. + /// + + /// + /// The full path to the application executable. This cannot + /// be blank, and cannot be a relative path. + /// + /// + /// When applicationFullPath is null + /// + /// + /// When applicationFullPath is blank OR + /// applicationFullPath contains invalid path characters OR + /// applicationFullPath is not an absolute path + /// + /// + /// If the firewall is not installed. + /// + /// + /// If the specified application does not exist. + /// + public void RemoveAuthorization(string applicationFullPath) + { + + #region Parameter checking + if (applicationFullPath == null) + throw new ArgumentNullException("applicationFullPath"); + if (applicationFullPath.Trim().Length == 0) + throw new FileNotFoundException("File does not exist", applicationFullPath); + // State checking + if (!IsFirewallInstalled) + throw new FirewallException("Cannot remove authorization: Firewall is not installed."); + #endregion + + if (HasAuthorization(applicationFullPath)) + { + // Remove Authorization for this application + fwMgr.LocalPolicy.CurrentProfile.AuthorizedApplications.Remove(applicationFullPath); + } + // otherwise it does not have authorization so do nothing + } + /// + + /// Returns whether an application is in the list of authorized applications. + /// Note if the file does not exist, this throws a FileNotFound exception. + /// + + /// + /// The full path to the application executable. This cannot + /// be blank, and cannot be a relative path. + /// + /// + /// The full path to the application executable. This cannot + /// be blank, and cannot be a relative path. + /// + /// + /// When applicationFullPath is null + /// + /// + /// When applicationFullPath is blank OR + /// applicationFullPath contains invalid path characters OR + /// applicationFullPath is not an absolute path + /// + /// + /// If the firewall is not installed. + /// + /// + /// If the specified application does not exist. + /// + public bool HasAuthorization(string applicationFullPath) + { + #region Parameter checking + if (applicationFullPath == null) + throw new ArgumentNullException("applicationFullPath"); + if (!File.Exists(applicationFullPath)) + throw new FileNotFoundException("File does not exist.", applicationFullPath); + // State checking + if (!IsFirewallInstalled) + throw new FirewallException("Cannot remove authorization: Firewall is not installed."); + + #endregion + + // Locate Authorization for this application + foreach (string appName in GetAuthorizedAppPaths()) + { + // Paths on windows file systems are not case sensitive. + if (appName.Equals(applicationFullPath, StringComparison.OrdinalIgnoreCase)) + return true; + } + + // Failed to locate the given app. + return false; + + } + + /// + /// Retrieves a collection of paths to applications that are authorized. + /// + public IEnumerable GetAuthorizedAppPaths() + { + if (!IsFirewallInstalled) + throw new FirewallException("Firewall is not installed."); + + foreach (INetFwAuthorizedApplication app in fwMgr.LocalPolicy.CurrentProfile.AuthorizedApplications) + { + if (app.Enabled) + yield return app.ProcessImageFileName; + } + } + #endregion + } + + public class FirewallException : System.Exception + { + public FirewallException(string message) + : base(message) + { } + } +} diff --git a/Source/Swiddler/ViewModels/ConnectionSettings.cs b/Source/Swiddler/ViewModels/ConnectionSettings.cs index ab62429..bbe30f0 100644 --- a/Source/Swiddler/ViewModels/ConnectionSettings.cs +++ b/Source/Swiddler/ViewModels/ConnectionSettings.cs @@ -21,12 +21,15 @@ public class ConnectionSettings : BindableBase, IComparable, public TCPClientSettings TCPClient { get; set; } public UDPClientSettings UDPClient { get; set; } public UDPServerSettings UDPServer { get; set; } + public SnifferSettings Sniffer { get; set; } + // state properties must be after connection settings instances to preserve proper deserialization order - bool _TCPChecked, _UDPChecked; + bool _TCPChecked, _UDPChecked, _SnifferChecked; public bool TCPChecked { get => _TCPChecked; set { if (SetProperty(ref _TCPChecked, value)) OnTCPToggled(value); } } public bool UDPChecked { get => _UDPChecked; set { if (SetProperty(ref _UDPChecked, value)) OnUDPToggled(value); } } + public bool SnifferChecked { get => _SnifferChecked; set { if (SetProperty(ref _SnifferChecked, value)) OnSnifferToggled(value); } } bool _ClientChecked, _ServerChecked; public bool ClientChecked { get => _ClientChecked; set { if (SetProperty(ref _ClientChecked, value)) OnClientToggled(value); } } @@ -45,6 +48,9 @@ public class ConnectionSettings : BindableBase, IComparable, bool _AnyProtocolChecked; [XmlIgnore] public bool AnyProtocolChecked { get => _AnyProtocolChecked; set => SetProperty(ref _AnyProtocolChecked, value); } + bool _CanSelectProtocol = true; + [XmlIgnore] public bool CanSelectProtocol { get => _CanSelectProtocol; set => SetProperty(ref _CanSelectProtocol, value); } + [XmlIgnore] public DateTime CreatedAt { get; private set; } = DateTime.Now; [XmlIgnore] public string FileName { get; private set; } @@ -53,6 +59,7 @@ public class ConnectionSettings : BindableBase, IComparable, [XmlIgnore] public ObservableCollection Settings { get; } = new ObservableCollection(); + public RewriteSettings[] Rewrites { get => Settings.OfType().ToArray(); @@ -76,6 +83,7 @@ void EnsureChildren() if (TCPClient == null) TCPClient = new TCPClientSettings(); if (UDPClient == null) UDPClient = new UDPClientSettings(); if (UDPServer == null) UDPServer = new UDPServerSettings(); + if (Sniffer == null) Sniffer = new SnifferSettings(); } private void Init() // call after all properties are sets @@ -87,6 +95,7 @@ private void Init() // call after all properties are sets TCPServer.PropertyChanged += MakeDirty; UDPClient.PropertyChanged += MakeDirty; UDPServer.PropertyChanged += MakeDirty; + Sniffer.PropertyChanged += MakeDirty; TCPClient.DualModeChanged += Client_DualModeChanged; TCPServer.DualModeChanged += Server_DualModeChanged; @@ -161,10 +170,35 @@ protected void OnUDPToggled(bool value) } UDPToggleLocked = value; AnyProtocolChecked = _TCPChecked || _UDPChecked; + } + + protected void OnSnifferToggled(bool value) + { + if (value) + { + TCPChecked = false; + UDPChecked = false; + ClientChecked = false; + ServerChecked = false; + + Settings.Clear(); + Settings.Add(Sniffer); + + AnyProtocolChecked = false; + } + else + { + Settings.Remove(Sniffer); + } + + CanSelectProtocol = !SnifferChecked; } void ProtocolChanged() // after TCP/UDP swapped { + if (SnifferChecked) + SnifferChecked = false; + Settings.Remove(TCPClient); Settings.Remove(TCPServer); Settings.Remove(UDPClient); @@ -298,6 +332,7 @@ public Session CreateSession() ClientSettings = ClientChecked ? ClientSettings : null, ServerSettings = ServerChecked ? ServerSettings : null, Rewrites = RewriteSettings.GetRewriteRules(Rewrites), + Sniffer = SnifferChecked ? Sniffer : null, }; return session; @@ -358,7 +393,7 @@ public ServerSettingsBase ServerSettings void Validate() { - if (!Settings.Any(x => x is ClientSettingsBase || x is ServerSettingsBase)) + if (!Settings.Any(x => x is ClientSettingsBase || x is ServerSettingsBase || x is SnifferSettings)) throw new Exception("No socket to create!"); if (ServerChecked) @@ -419,6 +454,21 @@ void Validate() } } } + if (SnifferChecked) + { + ValidateHostname(Sniffer.InterfaceAddress, nameof(Sniffer.InterfaceAddress), allowOnlyIpAddress: true, allowOnlyIPv4: true); + foreach (var filter in Sniffer.CaptureFilter) + { + if (!string.IsNullOrEmpty(filter.IPAddress)) + { + if (!IPAddress.TryParse(filter.IPAddress, out var ip) || ip.AddressFamily != AddressFamily.InterNetwork) + throw new ValueException(nameof(Sniffer.CaptureFilter), "Enter valid IPv4 address in capture filter."); + } + + if (filter.Port != null) + ValidatePortRange(filter.Port, nameof(Sniffer.CaptureFilter), true); + } + } foreach (var item in Rewrites) ValidateRewrite(item); } @@ -607,11 +657,13 @@ public ConnectionSettings Copy(ConnectionSettings template = null) TCPServer = (TCPServerSettings)(template.TCPChecked && template.ServerChecked ? template : this).TCPServer.Clone(), UDPClient = (UDPClientSettings)(template.UDPChecked && template.ClientChecked ? template : this).UDPClient.Clone(), UDPServer = (UDPServerSettings)(template.UDPChecked && template.ServerChecked ? template : this).UDPServer.Clone(), + Sniffer = (SnifferSettings)template.Sniffer.Clone(), TCPChecked = template.TCPChecked, UDPChecked = template.UDPChecked, ClientChecked = template.ClientChecked, ServerChecked = template.ServerChecked, + SnifferChecked = template.SnifferChecked, Rewrites = template.Rewrites.Select(x => (RewriteSettings)x.Clone()).ToArray(), }; @@ -632,12 +684,14 @@ private ConnectionSettings(bool isDesignInstance) TCPClient = TCPClientSettings.DesignInstance; UDPClient = UDPClientSettings.DesignInstance; UDPServer = UDPServerSettings.DesignInstance; + Sniffer = SnifferSettings.DesignInstance; TCPChecked = true; ClientChecked = true; Settings.Clear(); // add every socket in design time to see all of them + Settings.Add(Sniffer); Settings.Add(TCPServer); Settings.Add(TCPClient); Settings.Add(UDPServer); diff --git a/Source/Swiddler/ViewModels/IPAddressItem.cs b/Source/Swiddler/ViewModels/IPAddressItem.cs index 476d08c..e0f767a 100644 --- a/Source/Swiddler/ViewModels/IPAddressItem.cs +++ b/Source/Swiddler/ViewModels/IPAddressItem.cs @@ -25,13 +25,23 @@ public class IPAddressItem public override string ToString() => IPAddress.ToString(); - public static List GetAll(bool mapToIPv6 = false) + public static List GetAll(bool mapToIPv6 = false, bool loopback = true) { - return new[] { - new IPAddressItem() { IPAddress = IPAddress.Any, IsAny = true, InterfaceName="Any" }, - new IPAddressItem() { IPAddress = IPAddress.IPv6Any, IsAny = true, InterfaceName="Any" }, + IPAddressItem[] items; + + if (loopback) + { + items = new[] { + new IPAddressItem() { IPAddress = IPAddress.Any, IsAny = true, InterfaceName = "Any" }, + new IPAddressItem() { IPAddress = IPAddress.IPv6Any, IsAny = true, InterfaceName = "Any" } + }; } - .Concat(NetworkInterface.GetAllNetworkInterfaces() + else + { + items = new IPAddressItem[0]; + } + + return items.Concat(NetworkInterface.GetAllNetworkInterfaces() .Select(iface => iface.GetIPProperties().UnicastAddresses .Select(adr => new IPAddressItem() { @@ -46,9 +56,9 @@ public static List GetAll(bool mapToIPv6 = false) .ToList(); } - public static List GetAll(AddressFamily family) + public static List GetAll(AddressFamily family, bool loopback) { - return GetAll(family == AddressFamily.InterNetworkV6).Where(x => x.IPAddress.AddressFamily == family).ToList(); + return GetAll(family == AddressFamily.InterNetworkV6, loopback).Where(x => x.IPAddress.AddressFamily == family && (loopback || !x.IsLoopback)).ToList(); } public override bool Equals(object obj) diff --git a/Source/Swiddler/ViewModels/QuickActionItem.cs b/Source/Swiddler/ViewModels/QuickActionItem.cs index 5208825..6bcb16a 100644 --- a/Source/Swiddler/ViewModels/QuickActionItem.cs +++ b/Source/Swiddler/ViewModels/QuickActionItem.cs @@ -20,6 +20,7 @@ public class QuickActionItem new QuickActionItem(QuickActionTemplate.ClientTCPv4), new QuickActionItem(QuickActionTemplate.ServerTCPv4), new QuickActionItem(QuickActionTemplate.TunnelTCPv4), + new QuickActionItem(QuickActionTemplate.Sniffer), //new QuickActionItem(QuickActionTemplate.Monitor), }; @@ -44,6 +45,15 @@ public QuickActionItem(QuickActionTemplate template) Description = "Tunnel (client & server)"; Builder = cs => { cs.TCPChecked = true; cs.ClientChecked = true; cs.ServerChecked = true; }; break; + case QuickActionTemplate.Sniffer: + Icon = "Eye"; + Description = "Network sniffer"; + Builder = cs => + { + cs.SnifferChecked = true; + cs.Sniffer.CaptureFilter.Add(new SocketSettings.SnifferSettings.CaptureFilterItem() { Port = 80, Protocol = SocketSettings.SnifferSettings.CaptureProtocol.TCP }); + }; + break; case QuickActionTemplate.Monitor: Icon = "Process"; Description = "Process traffic monitor"; @@ -77,6 +87,7 @@ public enum QuickActionTemplate ClientTCPv4, ServerTCPv4, TunnelTCPv4, + Sniffer, Monitor } diff --git a/Source/Swiddler/ViewModels/RecentlyUsedItem.cs b/Source/Swiddler/ViewModels/RecentlyUsedItem.cs index 92b6e58..a3e05a9 100644 --- a/Source/Swiddler/ViewModels/RecentlyUsedItem.cs +++ b/Source/Swiddler/ViewModels/RecentlyUsedItem.cs @@ -39,6 +39,12 @@ public RecentlyUsedItem(ConnectionSettings connectionSettings) : base(QuickActio ":" + cs.ServerSettings.Port + " > " + cs.ClientSettings.TargetHost + ":" + cs.ClientSettings.TargetPort; } + else if (cs.SnifferChecked) + { + Icon = "Eye"; + Caption = "Network sniffer"; + Description = cs.Sniffer.InterfaceAddress; + } else { throw new NotImplementedException(); diff --git a/Source/Swiddler/Views/MainWindow.xaml.cs b/Source/Swiddler/Views/MainWindow.xaml.cs index d83b5da..283bfcd 100644 --- a/Source/Swiddler/Views/MainWindow.xaml.cs +++ b/Source/Swiddler/Views/MainWindow.xaml.cs @@ -1,5 +1,6 @@ using Swiddler.Commands; using Swiddler.Common; +using Swiddler.DataChunks; using Swiddler.Serialization; using Swiddler.Utils; using Swiddler.ViewModels; @@ -113,6 +114,48 @@ private void SessionListView_SelectionChanged(object sender, SelectionChangedEve } } + // TODO: prec +#if DEBUG + private void Log(string message) + { + //chunkView.CurrentSession.Logs.Add(new RawData() { Data = Encoding.Default.GetBytes(message), Flow = TrafficFlow.Inbound }); + + /* + chunkView.CurrentSession.Logs.Add(new Message() { Text = "1 longa sdlkjsdflkj ls kd lkjasl fkkldfs jas f" + DateTime.Now.Ticks + "" }); + chunkView.CurrentSession.Logs.Add(new Message() { Text = "2 longa sdlkjsdflkj ls kd lkjasl fkkldfs jas f" + DateTime.Now.Ticks + "" }); + chunkView.CurrentSession.Logs.Add(new Message() { Text = "3 longa sdlkjsdflkj ls kd lkjasl fkkldfs jas f" + DateTime.Now.Ticks + "" }); + chunkView.CurrentSession.Logs.Add(new Message() { Text = "4 longa sdlkjsdflkj ls kd lkjasl fkkldfs jas f" + DateTime.Now.Ticks + "" }); + chunkView.CurrentSession.Logs.Add(new Message() { Text = DateTime.Now.Ticks + "" }); + chunkView.CurrentSession.Logs.Add(new Message() { Text = "5 longa sdlkjsdfldkj ls kd lkjasl fkkldfs jas f" + DateTime.Now.Ticks + "" }); + */ + + chunkView.CurrentSession.Storage.Write(new Packet() + { + Flow = TrafficFlow.Outbound, + Payload = Encoding.Default.GetBytes("\nSTART\nrfeoiewoi=éiľršonľf\néiľršonľf\néiľršonľf\n\n\n\néiľršonľf\néiľršonľf\néiľršonľf\n") + }); + + chunkView.CurrentSession.Storage.Write(new Packet() + { + Flow = TrafficFlow.Outbound, + Payload = Encoding.Default.GetBytes("\n\n") + }); + + chunkView.CurrentSession.Storage.Write(new Packet() + { + Flow = TrafficFlow.Inbound, + Payload = Encoding.Default.GetBytes("\nWWWWWWWWWaaaaaaaabbbbbbbbb77778\n\n\n\n\n613531aaaaxWWWWWWWWWaaaaaaaabbbbbbbbb77778613531aaaax") + }); + + chunkView.CurrentSession.Storage.Write(new Packet() + { + Flow = TrafficFlow.Outbound, + Payload = Encoding.Default.GetBytes("fwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqerfwqaewqer") + }); + + } +#endif + private void InputText_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Return && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Control) && @@ -156,7 +199,6 @@ public bool AddSessionAndStart(Session session) private void Open_Click(object sender, RoutedEventArgs e) { - } private void Save_Click(object sender, RoutedEventArgs e) diff --git a/Source/Swiddler/Views/NewConnection.xaml b/Source/Swiddler/Views/NewConnection.xaml index d9f1c66..450a0cf 100644 --- a/Source/Swiddler/Views/NewConnection.xaml +++ b/Source/Swiddler/Views/NewConnection.xaml @@ -173,7 +173,7 @@ - + @@ -273,6 +273,9 @@ + + + @@ -288,7 +291,7 @@ - diff --git a/Source/Swiddler/Views/NewConnection.xaml.cs b/Source/Swiddler/Views/NewConnection.xaml.cs index df245ab..519febc 100644 --- a/Source/Swiddler/Views/NewConnection.xaml.cs +++ b/Source/Swiddler/Views/NewConnection.xaml.cs @@ -118,6 +118,8 @@ private void CreateSession(object sender, RoutedEventArgs e) { Result = ConnectionSettings.CreateSession(); ConnectionSettings.SaveRecent(); + if (Result != null) + Result.SettingsFileName = ConnectionSettings.FileName; Close(); } catch (Exception ex) @@ -212,6 +214,7 @@ private void DeleteSettings_Click(object sender, RoutedEventArgs e) if (context is ServerSettingsBase) ConnectionSettings.ServerChecked = false; if (context is ClientSettingsBase) ConnectionSettings.ClientChecked = false; if (context is RewriteSettings r) ConnectionSettings.RemoveRewrite(r); + if (context is SnifferSettings) ConnectionSettings.SnifferChecked = false; } private void ClipboardLink_Click(object sender, RoutedEventArgs e) diff --git a/Source/Swiddler/Views/SocketSettings/Sniffer.xaml b/Source/Swiddler/Views/SocketSettings/Sniffer.xaml new file mode 100644 index 0000000..1b54ceb --- /dev/null +++ b/Source/Swiddler/Views/SocketSettings/Sniffer.xaml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Swiddler/Views/SocketSettings/Sniffer.xaml.cs b/Source/Swiddler/Views/SocketSettings/Sniffer.xaml.cs new file mode 100644 index 0000000..b8a8ecc --- /dev/null +++ b/Source/Swiddler/Views/SocketSettings/Sniffer.xaml.cs @@ -0,0 +1,24 @@ +using Swiddler.SocketSettings; +using System.Windows.Controls; + +namespace Swiddler.Views.SocketSettings +{ + /// + /// Interaction logic for Rewrite.xaml + /// + public partial class Sniffer : UserControl + { + public Sniffer() + { + InitializeComponent(); + + gridFilter.CellEditEnding += (s, e) => CaptureFilterChanged(); + } + + void CaptureFilterChanged() + { + var model = (SnifferSettings)DataContext; + model.CaptureFilterChanges++; // this forces property change notification to make settings dirty + } + } +}