diff --git a/AscNet.GameServer/Packet.cs b/AscNet.GameServer/Packet.cs index 1a0a392..d709af8 100644 --- a/AscNet.GameServer/Packet.cs +++ b/AscNet.GameServer/Packet.cs @@ -1,6 +1,4 @@ -using System.Net.Sockets; -using System.Reflection; -using AscNet.Common.MsgPack; +using System.Reflection; using AscNet.Logging; using MessagePack; diff --git a/AscNet.PcapParser/App.config b/AscNet.PcapParser/App.config new file mode 100644 index 0000000..f802fd4 --- /dev/null +++ b/AscNet.PcapParser/App.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AscNet.PcapParser/AscNet.PcapParser.csproj b/AscNet.PcapParser/AscNet.PcapParser.csproj new file mode 100644 index 0000000..f61c722 --- /dev/null +++ b/AscNet.PcapParser/AscNet.PcapParser.csproj @@ -0,0 +1,102 @@ + + + + + Debug + AnyCPU + {E2C4ED9F-8360-4D6D-888C-EF31B551C064} + Exe + AscNet.PcapParser + AscNet.PcapParser + v4.8.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MessagePack.2.5.140\lib\netstandard2.0\MessagePack.dll + + + ..\packages\MessagePack.Annotations.2.5.140\lib\netstandard2.0\MessagePack.Annotations.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.NET.StringTools.17.6.3\lib\net472\Microsoft.NET.StringTools.dll + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Base.dll + + + ..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Core.dll + + + ..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Core.Extensions.dll + + + ..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Packets.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + ..\packages\System.Collections.Immutable.6.0.0\lib\net461\System.Collections.Immutable.dll + + + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AscNet.PcapParser/Crypto.cs b/AscNet.PcapParser/Crypto.cs new file mode 100644 index 0000000..ae87f94 --- /dev/null +++ b/AscNet.PcapParser/Crypto.cs @@ -0,0 +1,91 @@ +using System; +using System.Security.Cryptography; + +namespace AscNet.Common.Util +{ + public static class Crypto + { + public static byte[] XORCrypt(byte[] data, byte[] key) + { + byte[] encryptedData = new byte[data.Length]; + + for (int i = 0; i < data.Length; i++) + encryptedData[i] = (byte)(data[i] ^ key[i % key.Length]); + + return encryptedData; + } + + public static class HaruCrypt + { + private static readonly byte[] key = new byte[] { 103, 40, 227, 236, 173, 175, 148, 243, 66, 252, 58, 22, 68, 192, 159, 15, 187, 15, 15, 29, 209, 209, 212, 66, 104, 16, 252, 194, 227, 14, 116, 112, 196, 221, 5, 1, 4, 173, 165, 69, 45, 193, 95, 10, 67, 38, 167, 239, 96, 184, 133, 75, 152, 196, 36, 121, 251, 7, 73, 82, 219, 25, 118, 70, 153, 232, 120, 120, 147, 10, 88, 106, 214, 187, 216, 49, 224, 57, 1, 233, 110, 40, 65, 85, 246, 197, 4, 20, 56, 74, 245, 41, 63, 169, 188, 104, 89, 49, 115, 254, 100, 77, 79, 11, 148, 242, 95, 88, 241, 111, 48, 130, 169, 200, 224, 135, 121, 161, 72, 84, 5, 100, 135, 70, 141, 94, 244, 114, 58, 28, 87, 181, 205, 221, 154, 184, 197, 98, 210, 202, 252, 124, 144, 9, 112, 163, 24, 254, 119, 188, 5, 230, 40, 79, 171, 17, 156, 212, 134, 41, 79, 134, 26, 251, 123, 219, 191, 136, 21, 84, 192, 91, 24, 33, 68, 101, 85, 61, 186, 215, 191, 37, 45, 51, 117, 227, 14, 145, 56, 43, 32, 67, 48, 98, 192, 41, 136, 223, 50, 163, 97, 251, 174, 59, 59, 147, 237, 177, 31, 159, 52, 243, 245, 247, 148, 139, 21, 92, 139, 80, 47, 4, 105, 59, 227, 220, 180, 231, 176, 187, 205, 203, 148, 121, 98, 90, 87, 131, 245, 3, 63, 239, 57, 117, 102, 134, 40, 172, 60, 128, 108, 102, 216, 247, 133, 102 }; + private static readonly RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); + private static readonly byte[] signature = new byte[128]; +#pragma warning disable SYSLIB0021 // Type or member is obsolete + private static readonly SHA1 sha = new SHA1CryptoServiceProvider(); +#pragma warning restore SYSLIB0021 // Type or member is obsolete + private const string PUBLIC_KEY = "kZE/f0ifi0DH3uP3KCWOPqTyQ3MsQKHf9X4Z65S36s226RkdkZL2kHTz20n+IlOvGChi3ByDMFLawlyB0MCW94WDnc1Mc/PtVKo6D8gBEcSvdjDbhC4Ly0f2hMHS/SNdGPMAMkEWGNvIvfuT1TEaWTPsxRLZbfASp2KPG7Wjdck=AQAB"; + + static HaruCrypt() + { + rsa.FromXmlString(PUBLIC_KEY); + } + + public static void Encrypt(byte[] content) + { + Encrypt(content, 0, content.Length); + } + + public static void Encrypt(byte[] content, int offset, int count) + { + int num = count % key.Length; + for (int i = 0; i < count; i++) + { + int num2 = i + offset; + int num3 = (int)content[num2]; + num3 ^= (int)key[num]; + if (i > 0) + { + num3 ^= (int)content[num2 - 1]; + } + num3 ^= (int)key[i % key.Length]; + int num4 = ((int)((i + 1 < count) ? content[num2 + 1] : 0) + count) % 8; + num3 = (num3 << 8 - num4 | num3 >> num4); + content[num2] = (byte)num3; + } + } + + public static void Decrypt(byte[] content) + { + Decrypt(content, 0, content.Length); + } + + private static void Decrypt(byte[] bytes, int offset, int count) + { + int num = count % key.Length; + for (int i = count - 1; i >= 0; i--) + { + int num2 = i + offset; + int num3 = (int)bytes[num2]; + int num4 = ((int)((i + 1 < count) ? bytes[num2 + 1] : 0) + count) % 8; + num3 = (num3 >> 8 - num4 | num3 << num4); + num3 ^= (int)key[i % key.Length]; + if (num2 > offset) + { + num3 ^= (int)bytes[num2 - 1]; + } + num3 ^= (int)key[num]; + bytes[num2] = (byte)num3; + } + } + + private static bool Verify(byte[] content, ref int offset, ref int count) + { + Buffer.BlockCopy(content, offset, signature, 0, signature.Length); + offset += signature.Length; + count -= signature.Length; + byte[] hash = sha.ComputeHash(content, offset, count); + return rsa.VerifyHash(hash, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + } + } + } +} diff --git a/AscNet.PcapParser/Packet.cs b/AscNet.PcapParser/Packet.cs new file mode 100644 index 0000000..a6a389e --- /dev/null +++ b/AscNet.PcapParser/Packet.cs @@ -0,0 +1,104 @@ +using MessagePack; + +namespace AscNet.PcapParser +{ + [MessagePackObject(false)] + public class Packet + { + [Key(0)] + public int No; + + [Key(1)] + public ContentType Type; + + [Key(2)] + public byte[] Content; + + public enum ContentType + { + Request, + Response, + Push, + Exception + } + + [MessagePackObject(false)] + public class Request + { + [Key(0)] + public int Id; + + [Key(1)] + public string Name; + + [Key(2)] + public byte[] Content; + + public T Deserialize() + { + return MessagePackSerializer.Deserialize(Content); + } + + public object Deserialize() + { + return MessagePackSerializer.Typeless.Deserialize(Content); + } + } + + [MessagePackObject(false)] + public class Response + { + [Key(0)] + public int Id; + + [Key(1)] + public string Name; + + [Key(2)] + public byte[] Content; + + public T Deserialize() + { + return MessagePackSerializer.Deserialize(Content); + } + + public object Deserialize() + { + return MessagePackSerializer.Typeless.Deserialize(Content); + } + } + + [MessagePackObject(false)] + public class Push + { + [Key(0)] + public string Name; + + [Key(1)] + public byte[] Content; + + public T Deserialize() + { + return MessagePackSerializer.Deserialize(Content); + } + + public object Deserialize() + { + return MessagePackSerializer.Typeless.Deserialize(Content); + } + } + + [MessagePackObject(false)] + public class Exception + { + [Key(0)] + public int Id; + + [Key(1)] + public int Code; + + [Key(2)] + public string Message; + } + } +} diff --git a/AscNet.PcapParser/PcapParser.cs b/AscNet.PcapParser/PcapParser.cs new file mode 100644 index 0000000..8c81f85 --- /dev/null +++ b/AscNet.PcapParser/PcapParser.cs @@ -0,0 +1,240 @@ +using Newtonsoft.Json; +using PcapDotNet.Core; +using System.IO; +using System; +using System.Linq; +using System.Buffers.Binary; +using System.Runtime.InteropServices; +using AscNet.Common.Util; +using MessagePack; +using System.Collections.Generic; +using PcapDotNet.Packets.IpV4; + +namespace AscNet.PcapParser +{ + class PcapParser + { + private static readonly MessagePackSerializerOptions lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4Block); + private static readonly Dictionary buffer = new Dictionary(); + private static readonly MemoryStream memoryStream = new MemoryStream(); + private static readonly List packets = new List(); + + static void Main(string[] args) + { + // Check command line + if (args.Length != 1) + { + Console.WriteLine("usage: " + Environment.GetCommandLineArgs()[0] + " "); + return; + } + + // Create the offline device + OfflinePacketDevice selectedDevice = new OfflinePacketDevice(args[0]); + + // Open the capture file + using (PacketCommunicator communicator = + selectedDevice.Open(65536, // portion of the packet to capture + // 65536 guarantees that the whole packet will be captured on all the link layers + PacketDeviceOpenAttributes.Promiscuous, // promiscuous mode + 1000)) // read timeout + { + // Read and dispatch packets until EOF is reached + communicator.ReceivePackets(0, DispatcherHandler); + } + + // ProcessPackets(); + + Console.WriteLine($"Got {packets.Count} packet(s)"); + File.WriteAllText("packets.json", JsonConvert.SerializeObject(packets)); + } + + private static void DispatcherHandler(PcapDotNet.Packets.Packet packet) + { + var payload = packet.Ethernet.IpV4.Tcp.Payload; + + if (payload.Length < 1) + return; + + byte[] bytes; + if (buffer.ContainsKey(packet.Ethernet.IpV4.Source)) + { + bytes = buffer[packet.Ethernet.IpV4.Source].Concat(payload.ToArray()).ToArray(); + } + else + { + bytes = payload.ToArray(); + } + + int readLen = 0; + + while (true) + { + int len = (int)BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(readLen)); + readLen += Marshal.SizeOf(); + + if (len > bytes.Length - readLen) + { + buffer[packet.Ethernet.IpV4.Source] = bytes.AsSpan(readLen - 4).ToArray(); + break; + } + + byte[] packetBuffer = new byte[len]; + Buffer.BlockCopy(bytes, readLen, packetBuffer, 0, len); + readLen += len; + + Crypto.HaruCrypt.Decrypt(packetBuffer); + + try + { + var msgPackPacket = MessagePackSerializer.Deserialize(packetBuffer, lz4Options); + var packetObj = new Dictionary + { + ["No"] = msgPackPacket.No, + ["Type"] = msgPackPacket.Type.ToString() + }; + + switch (msgPackPacket.Type) + { + case Packet.ContentType.Request: + var req = MessagePackSerializer.Deserialize(msgPackPacket.Content); + packetObj["Content"] = new + { + req.Id, + req.Name, + Content = req.Deserialize() + }; + Console.WriteLine($"{req.Name}, " + JsonConvert.SerializeObject(req.Deserialize())); + break; + case Packet.ContentType.Response: + var rsp = MessagePackSerializer.Deserialize(msgPackPacket.Content); + packetObj["Content"] = new + { + rsp.Id, + rsp.Name, + Content = rsp.Deserialize() + }; + Console.WriteLine($"{rsp.Name}, " + JsonConvert.SerializeObject(rsp.Deserialize())); + break; + case Packet.ContentType.Push: + var push = MessagePackSerializer.Deserialize(msgPackPacket.Content); + packetObj["Content"] = new + { + push.Name, + Content = push.Deserialize() + }; + Console.WriteLine($"{push.Name}, " + JsonConvert.SerializeObject(push.Deserialize())); + break; + case Packet.ContentType.Exception: + var ex = MessagePackSerializer.Deserialize(msgPackPacket.Content); + packetObj["Content"] = ex; + Console.WriteLine(JsonConvert.SerializeObject(ex)); + break; + default: + break; + } + + packets.Add(packetObj); + } + catch (Exception) + { + Console.WriteLine("len: " + len + ", pack: " + Convert.ToBase64String(packetBuffer)); + byte[] rawBuf = new byte[len + 4]; + Buffer.BlockCopy(bytes, (readLen - len - 4), rawBuf, 0, len + 4); + + Console.WriteLine("Ended abruptly while reading, processing: " + Convert.ToBase64String(rawBuf)); + break; + } + + if (readLen >= bytes.Length) + { + buffer.Remove(packet.Ethernet.IpV4.Source); + break; + } + } + + // print packet timestamp and packet length + /*Console.WriteLine(packet.Timestamp.ToString("yyyy-MM-dd hh:mm:ss.fff") + " length:" + payload.Length); + memoryStream.Write(payload.ToArray(), 0, payload.Length);*/ + } + + private static void ProcessPackets() + { + List packets = new List(); + byte[] msBytes = memoryStream.ToArray(); + int readLen = 0; + + while (readLen < msBytes.Length) + { + int len = BinaryPrimitives.ReadInt32LittleEndian(msBytes.AsSpan(readLen)); + readLen += Marshal.SizeOf(); + + byte[] packetBuffer = new byte[len]; + Buffer.BlockCopy(msBytes, readLen, packetBuffer, 0, len); + readLen += len; + + Crypto.HaruCrypt.Decrypt(packetBuffer); + + try + { + var packet = MessagePackSerializer.Deserialize(packetBuffer, lz4Options); + var packetObj = new Dictionary + { + ["No"] = packet.No, + ["Type"] = packet.Type.ToString() + }; + + switch (packet.Type) + { + case Packet.ContentType.Request: + var req = MessagePackSerializer.Deserialize(packet.Content); + packetObj["Content"] = new + { + req.Id, + req.Name, + Content = req.Deserialize() + }; + Console.WriteLine($"{req.Name}, " + JsonConvert.SerializeObject(req.Deserialize())); + break; + case Packet.ContentType.Response: + var rsp = MessagePackSerializer.Deserialize(packet.Content); + packetObj["Content"] = new + { + rsp.Id, + rsp.Name, + Content = rsp.Deserialize() + }; + Console.WriteLine($"{rsp.Name}, " + JsonConvert.SerializeObject(rsp.Deserialize())); + break; + case Packet.ContentType.Push: + var push = MessagePackSerializer.Deserialize(packet.Content); + packetObj["Content"] = new + { + push.Name, + Content = push.Deserialize() + }; + Console.WriteLine($"{push.Name}, " + JsonConvert.SerializeObject(push.Deserialize())); + break; + case Packet.ContentType.Exception: + var ex = MessagePackSerializer.Deserialize(packet.Content); + packetObj["Content"] = ex; + Console.WriteLine(JsonConvert.SerializeObject(ex)); + break; + default: + break; + } + + packets.Add(packetObj); + } + catch (Exception) + { + Console.WriteLine("len: " + len + ", pack: " + Convert.ToBase64String(packetBuffer)); + byte[] rawBuf = new byte[len + 4]; + Buffer.BlockCopy(msBytes, (readLen - len - 4), rawBuf, 0, len + 4); + + Console.WriteLine("Ended abruptly while reading, processing: " + Convert.ToBase64String(rawBuf)); + break; + } + } + } + } +} diff --git a/AscNet.PcapParser/Properties/AssemblyInfo.cs b/AscNet.PcapParser/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..55d9368 --- /dev/null +++ b/AscNet.PcapParser/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AscNet.PcapParser")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AscNet.PcapParser")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e2c4ed9f-8360-4d6d-888c-ef31b551c064")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AscNet.PcapParser/packages.config b/AscNet.PcapParser/packages.config new file mode 100644 index 0000000..f34fff3 --- /dev/null +++ b/AscNet.PcapParser/packages.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AscNet.SDKServer/SDKServer.cs b/AscNet.SDKServer/SDKServer.cs index 115d8e3..b88cc93 100644 --- a/AscNet.SDKServer/SDKServer.cs +++ b/AscNet.SDKServer/SDKServer.cs @@ -41,7 +41,7 @@ namespace AscNet.SDKServer private class RequestLoggingMiddleware { private readonly RequestDelegate _next; - private static readonly string[] SurpressedRoutes = new string[] { "/feedback" }; + private static readonly string[] surpressedRoutes = ["/feedback"]; public RequestLoggingMiddleware(RequestDelegate next) { diff --git a/AscNet.sln b/AscNet.sln index 6d968e3..a8db29d 100644 --- a/AscNet.sln +++ b/AscNet.sln @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AscNet.Logging", "AscNet.Lo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AscNet.Table", "AscNet.Table\AscNet.Table.csproj", "{7B6F3E2A-69D6-429B-9D5C-52339093663C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AscNet.PcapParser", "AscNet.PcapParser\AscNet.PcapParser.csproj", "{E2C4ED9F-8360-4D6D-888C-EF31B551C064}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,10 @@ Global {7B6F3E2A-69D6-429B-9D5C-52339093663C}.Debug|Any CPU.Build.0 = Debug|Any CPU {7B6F3E2A-69D6-429B-9D5C-52339093663C}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B6F3E2A-69D6-429B-9D5C-52339093663C}.Release|Any CPU.Build.0 = Release|Any CPU + {E2C4ED9F-8360-4D6D-888C-EF31B551C064}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2C4ED9F-8360-4D6D-888C-EF31B551C064}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2C4ED9F-8360-4D6D-888C-EF31B551C064}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2C4ED9F-8360-4D6D-888C-EF31B551C064}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE