Rezlind Posted May 13, 2021 Posted May 13, 2021 Hey all, I feel like I only come to this forum with questions, hopefully soon I can provide something of value back.Last few days I've been trying to wrap my head around the 5187 Login sequence. Cops v6 enhanced included the RC5 class to use and while I've used the 5187 Comet source Login sequence as a reference, the RC5 implementations are coded a bit different than what I am working with. I think I understand how the exchange works at the top level as the docs on Comet 5187 clearly lay it out. But one of the things I don't fully understand are:What role does the following set of bytes play in this exchange process: /// Generates a random key (Key) to use for the algorithm. /// CO2: { 0x3C, 0xDC, 0xFE, 0xE8, 0xC4, 0x54, 0xD6, 0x7E, 0x16, 0xA6, 0xF8, 0x1A, 0xE8, 0xD0, 0x38, 0xBE }I've tried converting the seed to a byte array and resizing it to fill the extra space required but that doesn't seem to satisfy the Cipher as the decrypted password is not anywhere close to what it should be.I'm not really sure if I'm in over my head here but I'm going to keep bashing my head against this until it works, any guidance - even if its getting me to ask the right questions would be appreciated. Quote
wickedliving Posted May 13, 2021 Posted May 13, 2021 I'm a little confused on what you're asking. Are you attempting to rewrite/port CptSky's RC5 version or just understand it more? Quote
Rezlind Posted May 13, 2021 Author Posted May 13, 2021 I'm a little confused on what you're asking. Are you attempting to rewrite/port CptSky's RC5 version or just understand it more?Sorry, it's evident I don't understand something when I can't even articulate the question well:I've been experimenting with bringing the enhanced version up a few versions. It was fairly simple to upgrade from 4330->5017. But I've kind of hit a roadblock upgrading from 5017-5187 due to not fully understanding how this RC5 cipher is structured. It would seem Cpt Sky included the RC5 implementation in the enhanced CopsV6 repo but when I try to implement it the decrypted password is always off. I'm positive I'm screwing up somewhere and I thought perhaps asking questions about certain aspects of the RC5 implementation would help me overcome whatever is messing up the decryption.Currently as I have it a 5187 client connects, the account server sends a MsgEncryptCode containing random UInt32 seed, then, when the client responds with the encrypted password in a MsgAccount packet I attempt to do the following: var seedBytes = BitConverter.GetBytes(aClient.Seed); Array.Resize(ref seedBytes, 16); RivestCipher5 cipher = new RivestCipher5(seedBytes); This doesn't seem correct but I do this because the RC5 implementation on CopsV6 enhanced demands a 16 byte seed. public unsafe RivestCipher5(Byte[] aSeed) { if (aSeed.Length != RC5_16) throw new ArgumentException("Length of the seed must be exactly " + RC5_16 + " bytes.", "aSeed"); One of the over-all questions I had is related to the summary notes above the RivestCipher5 function that say: /// /// Generates a random key (Key) to use for the algorithm. /// CO2: { 0x3C, 0xDC, 0xFE, 0xE8, 0xC4, 0x54, 0xD6, 0x7E, 0x16, 0xA6, 0xF8, 0x1A, 0xE8, 0xD0, 0x38, 0xBE } /// And I realized that despite reading through countless EPvP posts I didn't quite understand the purpose of those keys as I hadn't seen them in use anywhere in the RivestCipher5 class.So to directly answer your question: Both port it, and understand it better. Thanks for responding! Quote
wickedliving Posted May 13, 2021 Posted May 13, 2021 Okay, I see what you mean. I am not sure about 5187, but I'm pretty sure all clients that use RC5 use the same seed which is the byte array you provided earlier.The way I understand COP's RC5 library (using my very limited memory of C#), it's initialized using that seed.{ 0x3C, 0xDC, 0xFE, 0xE8, 0xC4, 0x54, 0xD6, 0x7E, 0x16, 0xA6, 0xF8, 0x1A, 0xE8, 0xD0, 0x38, 0xBE }Other than the comments, that isn't provided anywhere in his library. That has to be provided by you when using RC5. The link I've posted below shows where in his source he is using it.https://gitlab.com/conquer-online/servers/cops-v6-emulator-enhanced-edition/-/blob/master/AccServer/Network/MsgAccount.cs#L56 Quote
Rezlind Posted May 13, 2021 Author Posted May 13, 2021 Okay, I see what you mean. I am not sure about 5187, but I'm pretty sure all clients that use RC5 use the same seed which is the byte array you provided earlier.The way I understand COP's RC5 library (using my very limited memory of C#), it's initialized using that seed.{ 0x3C, 0xDC, 0xFE, 0xE8, 0xC4, 0x54, 0xD6, 0x7E, 0x16, 0xA6, 0xF8, 0x1A, 0xE8, 0xD0, 0x38, 0xBE }Other than the comments, that isn't provided anywhere in his library. That has to be provided by you when using RC5. The link I've posted below shows where in his source he is using it.https://gitlab.com/conquer-online/servers/cops-v6-emulator-enhanced-edition/-/blob/master/AccServer/Network/MsgAccount.cs#L56Ah right, I'm familiar with the constant he made COServer.Server.RC5_SEED to contain these bytes. If this constant is intended to be used when instantiating the cipher my confusion then lies in - where does the Uint32 key for decrypting the password come into play?With Spirited's source it's clear to me from looking at the RC5 class DecryptPassword method how the seed from earlier is utilized. But looking at Cpt Sky's implementation it isn't immediately apparent to me how that Uint32 key we sent to the client earlier in MsgEncryptCode is utilized. At a glance it doesn't seem to be used at all?Thanks Quote
Spirited Posted May 13, 2021 Posted May 13, 2021 So, the reason why CptSky implements it with a static key is because patches before 5174(ish) don't generate the key using a random seed. You can see this difference in Comet as well between the patches (link). In newer patches, the seed is a random number that gets passed to the client in this packet. Additionally, there's a new "scan code" cipher (just what I'm calling it) that translates virtual key scan codes into ASCII characters. You can use the cipher I reverse engineered by hand here (it handles numkey for you as part of the cipher). The full process can be found here. Quote
Rezlind Posted May 13, 2021 Author Posted May 13, 2021 So, the reason why CptSky implements it with a static key is because patches before 5174(ish) don't generate the key using a random seed. You can see this difference in Comet as well between the patches (link). In newer patches, the seed is a random number that gets passed to the client in this packet. Additionally, there's a new "scan code" cipher (just what I'm calling it) that translates virtual key scan codes into ASCII characters. You can use the cipher I reverse engineered by hand here (it handles numkey for you as part of the cipher). The full process can be found here.Ah there we go, that sorts out my confusion. I didn't realize that previous patches before 5174 which did utilize the RC5 password encryption used the static key versus a random seed.Thank you Spirited. Quote
Spirited Posted May 13, 2021 Posted May 13, 2021 Yep, all good. And don't worry about asking questions. Asking questions and starting discussions is absolutely contributing. Quote
Rezlind Posted May 14, 2021 Author Posted May 14, 2021 Spent quite a few hours on this but it seems somehow, somewhere I implemented this incorrectly.I've gone over Spirited's Comet source, Spirited's Phoenix source, the Exodus binary source, Cpt Sky's original implementation, and double checked World Conquer's git history on the RC5 files to ensure there was no modification necessary that may have been missed in the original implementation. I compared them line by line where applicable and the only differences I could find were:1. Phoenix Source 5187 seed byte generator uses a 10 byte initialization vector where as Cpt Sky and Comet are both using a 16 byte initialization vector 2. Phoenix Source 5187 When generating the substitution box the substitutionBuffer[index -1] adds + 0x9E3779B9 instead of subtracting 0x61C88647.I disregarded the 2nd discrepency because I later found a post where Spirited pointed out that RC5_QW32 needs to be changed to 0x61C88647 - Then it'll work on the 5180 - 5527 clients.At the moment, a password that should be decrypted as "bob" is instead being decrypted as "-Z\0\0\0\0\0Jf\0\0\0\0\0\05". For testing purposes, and as I had seen in one of the sources that implemented this I simply get the hash code of the user's IP address as the seed.My MsgEncryptCode class writes a Uint32 seed to offset 4 of the packet as indicated here (despite the version mismatch I couldn't find evidence that this packet structure was different for 5187 from 5635).When I then get a MsgAccount packet from the client I generate a new RivestCipher5 with the Uint32 seed from earlier. The seed passes through the code below to transform it into 16 bytes for use in the generating the key for the algorithm.I apologize for the messy code below as I've tried various things and in the process it's gotten a bit out of hand. byte[] theseSeeds = new byte[16]; for (int cnt = 0; cnt < 16; cnt++) { playerSeed *= 0x343fd; playerSeed += 0x269ec3; theseSeeds[cnt] = (byte)((playerSeed >> 16) & 0x7fff); } Afterwards, I create a new scanCodeCipher and decrypt the password, then run it through the scan code cipher. __Account = Program.Encoding.GetString(mBuf, 4, MAX_NAME_SIZE).TrimEnd('\0'); RivestCipher5 cipher = new RivestCipher5(aClient.Seed); var scanCodes = new ScanCodeCipher(__Account); Byte[] pwd_data = new Byte[MAX_NAME_SIZE]; Buffer.BlockCopy(mBuf, 132, pwd_data, 0, MAX_NAME_SIZE); cipher.Decrypt(ref pwd_data, MAX_NAME_SIZE); var result = new Byte[MAX_NAME_SIZE]; scanCodes.Decrypt(pwd_data, result); __Password = Encoding.ASCII.GetString(result).Trim('\0'); Possible points of failure I think could exist:1. converting Span types to byte[] as I don't think I have access to those in my current version.2. Offset wrong on MsgEncryptCode's Seed?As always I appreciate everyone's guidance. Thanks! Quote
Spirited Posted May 14, 2021 Posted May 14, 2021 Oh man, maybe don't listen to anything I said a decade ago. That was exactly when I started learning how to program... lol. Just looking at the code sample you provided, did you need to ref pwd_data when passing it into Decrypt for the scan codes? That's one of the reasons why I avoided using ref in a lot of those ciphers and opted for passing in a source and destination (even if the destination completely overlaps). Quote
Rezlind Posted May 14, 2021 Author Posted May 14, 2021 Oh man, maybe don't listen to anything I said a decade ago. That was exactly when I started learning how to program... lol. Just looking at the code sample you provided, did you need to ref pwd_data when passing it into Decrypt for the scan codes? That's one of the reasons why I avoided using ref in a lot of those ciphers and opted for passing in a source and destination (even if the destination completely overlaps).Heh yeah sorry that wasn't meant as a critique, I was just happy to find another reference source and something to compare it to.The decrypt function is Cpt Sky's decrypt function which I figured should still work, it has the ref keyword.Cpt Sky's Decrypt public unsafe void Decrypt(ref Byte[] aBuf, Int32 aLength) { if (aLength % 8 != 0) throw new ArgumentException("Length of the buffer must be a multiple of 64 bits.", "aLength"); fixed (Byte* buf = aBuf) { UInt32* data = (UInt32*)buf; for (Int32 k = 0, len = aLength / 8; k < len; ++k) { UInt32 lv = data[2 * k]; UInt32 rv = data[2 * k + 1]; // <--- +1 here. for (Int32 i = RC5_12; i >= 1; --i) { rv = rotr((rv - mSub[2 * i + 1]), lv) ^ lv; lv = rotr((lv - mSub[2 * i]), rv) ^ rv; } data[2 * k] = lv - mSub[0]; data[2 * k + 1] = rv - mSub[1]; } } } 5187 Comet RC5 Decrypt: public void Decrypt(Span<byte> src, Span<byte> dst) { // Pad the buffer var length = src.Length / 8; if (src.Length % 8 > 0) length = length + 1; src.CopyTo(dst); // Decrypt the buffer for (int word = 0; word < length; word++) { uint a = BitConverter.ToUInt32(dst.Slice(8 * word)); uint b = BitConverter.ToUInt32(dst.Slice((8 * word) + 4)); // <--- +4 here for (int round = RC5.Rounds; round > 0; round--) { b = (b - this.Sub[2 * round + 1]).RotateRight((int)a) ^ a; a = (a - this.Sub[2 * round]).RotateRight((int)b) ^ b; } BitConverter.GetBytes(a - this.Sub[0]).CopyTo(dst.Slice(8 * word)); BitConverter.GetBytes(b - this.Sub[1]).CopyTo(dst.Slice(8 * word + 4)); } } There is a slight difference between the two but not sure if at the end of the day its just two different ways to get to the same result. Quote
Spirited Posted May 15, 2021 Posted May 15, 2021 No worries, just letting you know that some of my older posts aren't super reliable or friendly. I was a really different person a decade ago. Anyways, I don't think I was as clear as I could have been. I was referring to the Decrypt in the scan code cipher, not RC5. I think your RC5 is probably fine if you're using either implementation. Quote
Rezlind Posted May 15, 2021 Author Posted May 15, 2021 No worries, just letting you know that some of my older posts aren't super reliable or friendly. I was a really different person a decade ago. Anyways, I don't think I was as clear as I could have been. I was referring to the Decrypt in the scan code cipher, not RC5. I think your RC5 is probably fine if you're using either implementation.I used your implementation from comet and from phoenix to double check myself. The ref in the code I pasted was from the RC5 decrypt method. The Scan Code decrypt method that I last tested was yours: public void Decrypt(byte[] src, byte[] dst) { for (int i = 0; i < src.Length; i++) { // Check for the end of the string if (src[i] == 0) { dst.Slice(i, dst.Length - i).Fill(0); return; } // Substitute the byte byte position = (byte)(this.Sub[src[i] * 2]); dst[i] = ScanCodeCipher.Key[position % ScanCodeCipher.Key.Length]; } } Just to test, I changed it to overwrite itself rather than a new empty byte array: __Account = Program.Encoding.GetString(mBuf, 4, MAX_NAME_SIZE).TrimEnd('\0'); RivestCipher5 cipher = new RivestCipher5(aClient.Seed); var scanCodes = new ScanCodeCipher(__Account); Byte[] pwd_data = new Byte[MAX_NAME_SIZE]; Buffer.BlockCopy(mBuf, 132, pwd_data, 0, MAX_NAME_SIZE); cipher.Decrypt(ref pwd_data, MAX_NAME_SIZE); scanCodes.Decrypt(pwd_data, pwd_data); __Password = Encoding.ASCII.GetString(pwd_data).Trim('\0'); __Server = Program.Encoding.GetString(mBuf, 260, MAX_NAME_SIZE).TrimEnd('\0'); } Quote
Spirited Posted May 15, 2021 Posted May 15, 2021 It's a bit tricky to make a recommendation at this point with the limited visibility. Have you tried using Comet's RC5 implementation and did it produce those same values? Maybe you can run them side-by-side and see what values get outputted by each, or use Comet's values to create a unit test for your project. If you can start ruling out the ciphers, then you might have an easier time with the MsgAccount packet as a whole. And just a quick side note - Phoenix was one of my first open source projects and was really experimental. I don't recommend using it as a reference. Quote
Rezlind Posted May 15, 2021 Author Posted May 15, 2021 It's a bit tricky to make a recommendation at this point with the limited visibility. Have you tried using Comet's RC5 implementation and did it produce those same values? Maybe you can run them side-by-side and see what values get outputted by each, or use Comet's values to create a unit test for your project. If you can start ruling out the ciphers, then you might have an easier time with the MsgAccount packet as a whole. And just a quick side note - Phoenix was one of my first open source projects and was really experimental. I don't recommend using it as a reference. Hehe understood, I definitely wasn't critiquing any of the code, plus I'm sure at the time it was one of the top releases in the private server community.So you were the second person to suggest that I do an SSCCE, I went ahead and copied your code for a quick console app, here is the code and it uses all the classes you have on Comet 5187 related to RC5: static void Main(string[] args) { string accName = "bob"; // Username is bob, password is also bob. // Copied the password bytes as I grab the 16 bytes on the server. string[] result = new string[] { "D0","1D", "BA", "D0", "E0", "A1", "8B", "94", "1B", "88", "E7", "75", "B0", "EC", "10", "10" }; byte[] passwordArray = result .Select(value => Convert.ToByte(value, 16)) .ToArray(); RC5 cipher = new RC5(2411687969); // The constant seed I was using for testing purposes. ScanCodeCipher scanCipher = new ScanCodeCipher(accName); Console.WriteLine("Before Encrypt {0}", PacketDump.Hex(passwordArray)); cipher.Decrypt(passwordArray, passwordArray); Console.WriteLine("After Encrypt {0}", PacketDump.Hex(passwordArray)); scanCipher.Decrypt(passwordArray, passwordArray); Console.WriteLine("After ScanCode Decrypt {0}", PacketDump.Hex(passwordArray)); } The results of this were exactly the same as on the server so now I can rule out there being something wrong with the RC5 cipher and the scan code cipher as I set them up on my end.The packet results when doing it on the server:The results when doing it in the SSCCE:At this point I think I need consider other points of failure:1. Sending the seed, surely I couldn't have messed this up - it's a straightforward packet.2. Grabbing the password bit from the packet.In the case of 2 -> MsgAccount Packet in it's entirety:The underlined portion is what I grab from the packet starting from 132, up to the max character length of 16. I'm not sure what the rest of the data is there or how it factors into handling the packet. Quote
Spirited Posted May 15, 2021 Posted May 15, 2021 Yeah, it might be worth looking into how you're sending MsgEncryptCode. Are you able to take a packet dump before encrypting and sending that? Maybe also double check that you're not re-generating keys after sending MsgEncryptCode. It all looks pretty reasonable to me just looking from the outside in. I think you meant to underline one byte sooner in your packet dump, right? Otherwise that's only 15 characters. Quote
Rezlind Posted May 15, 2021 Author Posted May 15, 2021 Yeah, it might be worth looking into how you're sending MsgEncryptCode. Are you able to take a packet dump before encrypting and sending that? Maybe also double check that you're not re-generating keys after sending MsgEncryptCode. It all looks pretty reasonable to me just looking from the outside in. I think you meant to underline one byte sooner in your packet dump, right? Otherwise that's only 15 characters.Yes you were right I intended to include one more. Also, you were right to suggest looking at MsgEncryptCode. I foolishly thought I was sending the value but I was assigning the Seed variable to a private __Seed variable rather than the public Get Setter. In the end the server was not getting a valid seed, the offset from 4-8 was zeroed out.Rookie mistake and I now know to checkover even the simple things.YikesThanks a ton for your patience though. Quote
Spirited Posted May 15, 2021 Posted May 15, 2021 All good. It's often a simple mistake like that. Glad you're making progress again. Quote
Rezlind Posted May 17, 2021 Author Posted May 17, 2021 Seems I became stuck on the DH exchange as well. The client instantly disconnects upon receiving this packet and I'm not really sure why.Building up the packet public MsgDH(string key, byte[] encryptionIV, byte[] decryptionIV, string aPrimeRoot, string aGenerator) : base((321 + 12) - 11) { //Padding = BitConverter.ToUInt32(new byte[11], 0); Buffer.BlockCopy(new byte[11], 0, mBuf, 0, 11); Length = (321+12)-11; JunkLength = 12; Buffer.BlockCopy(new byte[12], 0, mBuf, 20, 12); // Junk Packet //Junk = BitConverter.ToUInt32(new byte[12], 0); EncryptionIVLength = 8; EncryptionIV = BitConverter.ToUInt64(encryptionIV, 0); DecryptionIVLength = 8; DecryptionIV = BitConverter.ToUInt64(decryptionIV, 0); __StrPacker = new StringPacker(this, 56); __StrPacker.AddString(aPrimeRoot); __StrPacker.AddString(aGenerator); __StrPacker.AddString(key); } The offsets: //public UInt32 Padding //{ // get { return __Padding; } // set //{ // __Padding = value; WriteUInt32(0, value); // } // } public UInt32 Length { get { return __Length; } set { __Length = value; WriteUInt32(12, value); } } public UInt32 JunkLength { get { return __JunkLength; } set { __JunkLength = value; WriteUInt32(16, value); } } //public UInt32 Junk //{ // get { return __Junk; } // set // { // __Junk = value; WriteUInt32(20, value); // } //} public UInt32 EncryptionIVLength { get { return __EncryptionIVLength; } set { __EncryptionIVLength = value; WriteUInt32(32, value); } } public UInt64 EncryptionIV { get { return __EncryptionIV; } set { __EncryptionIV = value; WriteUInt64(36, value); } } public UInt32 DecryptionIVLength { get { return __DecryptionIVLength; } set { __DecryptionIVLength = value; WriteUInt32(44, value); } } public UInt64 DecryptionIV { get { return __DecryptionIV; } set { __DecryptionIV = value; WriteUInt64(48, value); } } I compared it to the Comet 5187 MsgHandshake example and I think I match it pretty much exactly with the exception of the length of the padding (Which I refer to as Junk in my snippet). In Spirited's it seems to be a variable length of anything between 24-48 bytes long. Perhaps I'm misunderstanding that though. writer.Write(this.Padding.AsSpan(0, 9)); writer.Write(messageLength - 11); writer.Write(this.Padding.Length - 11); writer.Write(this.Padding.AsSpan(9, this.Padding.Length - 11)); writer.Write(this.EncryptionIV.Length); writer.Write(this.EncryptionIV); writer.Write(this.DecryptionIV.Length); writer.Write(this.DecryptionIV); writer.Write(this.PrimeRoot.Length); writer.Write(this.PrimeRoot, this.PrimeRoot.Length); writer.Write(this.Generator.Length); writer.Write(this.Generator, this.Generator.Length); writer.Write(this.ServerKey.Length); writer.Write(this.ServerKey, this.ServerKey.Length); return writer.ToArray(); Quote
Spirited Posted May 17, 2021 Posted May 17, 2021 So when decoding the message, Comet only skips in 7 bytes before it reads in the length. So perhaps you're skipping that? See here: https://gitlab.com/spirited/comet/-/blob/5187/src/Comet.Game/Packets/MsgHandshake.cs#L65 Quote
Rezlind Posted May 17, 2021 Author Posted May 17, 2021 So when decoding the message, Comet only skips in 7 bytes before it reads in the length. So perhaps you're skipping that? See here: https://gitlab.com/spirited/comet/-/blob/5187/src/Comet.Game/Packets/MsgHandshake.cs#L65Perhaps I didn't articulate this well sorry - This is the start of the DH handshake. The client disconnects the moment it receives the first DH Exchange packet after initially connecting to the Game server. I copy pasted a sample from a revised 5165 COEmu source to ensure I wasn't doing something incorrectly. As far as I know nothing about the DH exchange changed between 5165 and 5187 -> even when performing the query with the packet structure below: var random = new System.Random(); int PAD_LEN = 11; int _junk_len = 12; string tqs = "TQServer"; MemoryStream ms = new MemoryStream(); byte[] pad = new byte[PAD_LEN]; random.NextBytes(pad); byte[] junk = new byte[_junk_len]; random.NextBytes(junk); int size = 47 + aPrimeRoot.Length + aGenerator.Length + key.Length + 12 + 8 + 8; BinaryWriter bw = new BinaryWriter(ms); bw.Write(pad); bw.Write(size - PAD_LEN); bw.Write((UInt32)_junk_len); bw.Write(junk); bw.Write((UInt32)encryptionIV.Length); bw.Write(encryptionIV); bw.Write((UInt32)decryptionIV.Length); bw.Write(decryptionIV); bw.Write((UInt32)aPrimeRoot.ToCharArray().Length); foreach (char fP in aPrimeRoot.ToCharArray()) { bw.BaseStream.WriteByte((byte)fP); } bw.Write((UInt32)aGenerator.ToCharArray().Length); foreach (char fG in aGenerator.ToCharArray()) { bw.BaseStream.WriteByte((byte)fG); } bw.Write((UInt32)key.ToCharArray().Length); foreach (char SPK in key.ToCharArray()) { bw.BaseStream.WriteByte((byte)SPK); } foreach (char tq in tqs.ToCharArray()) { bw.BaseStream.WriteByte((byte)tq); } byte[] Packet = new byte[ms.Length]; Packet = ms.ToArray(); Console.WriteLine(PacketDump.Hex(Packet)); Buffer.BlockCopy(Packet, 0, mBuf, 0, (int)ms.Length); // Copy it to this current Msg in preparation of sending it to the server. Console.WriteLine(PacketDump.Hex(mBuf)); The client instantly disconnected. I'm not really sure what could cause a conquer client to instantly disconnect from the game server other than a malformed packet? I'm thinking perhaps I need to step away from this for a few days and come back to it with a fresh pair of eyes. Quote
Spirited Posted May 17, 2021 Posted May 17, 2021 Usually it has to do with the packet length being incorrect. In my implementation, I have the following definition: https://gitlab.com/spirited/comet/-/blob/5187/src/Comet.Game/Packets/MsgHandshake.cs#L83. I'm at work right now so I can't really check your work very closely, but I'm not 100% following your length calculation. Quote
Rezlind Posted May 18, 2021 Author Posted May 18, 2021 Usually it has to do with the packet length being incorrect. In my implementation, I have the following definition: https://gitlab.com/spirited/comet/-/blob/5187/src/Comet.Game/Packets/MsgHandshake.cs#L83. I'm at work right now so I can't really check your work very closely, but I'm not 100% following your length calculation.For testing purposes I hard coded some things just to see what the value would be like, I went over this several times and with 11 bytes of padding at the start, and 12 bytes of junk length before the encryptionIV, the total length of the packet should be 326. I stepped through and ensured that all the data is being written correctly but sadly it still disconnects me. I may be a lost cause at this point. Random rnd = new Random(); var paddingBytes = new byte[11]; rnd.NextBytes(paddingBytes); Buffer.BlockCopy(paddingBytes, 0, mBuf, 0, 11); // 0-11 (11 bytes) Length = 326 - 11; // 12-15 (4 bytes) JunkLength = 12; // 16-19 (4 bytes) var junkBytes = new byte[12]; rnd.NextBytes(junkBytes); Buffer.BlockCopy(junkBytes, 0, mBuf, 20, 12); // 20-31 ( 12 bytes) EncryptionIVLength = 8; //32-35 ( 4 bytes) Buffer.BlockCopy(encryptionIV, 0, mBuf, 36, 8); // 36-43 ( 8 bytes) DecryptionIVLength = 8;// 44-47 ( 4 bytes) Buffer.BlockCopy(decryptionIV, 0, mBuf, 48, 8); // 48-55 (8 bytes) PrimeRootLength = 128; // 55-59 (4 bytes) Buffer.BlockCopy(Encoding.ASCII.GetBytes(aPrimeRoot), 0, mBuf, 60, 128); GeneratorLength = 2; // 188-191 (4 bytes) Buffer.BlockCopy(Encoding.ASCII.GetBytes(aGenerator), 0, mBuf, 192, 2); PrimaryKeyLength = 128; //194-197 (4 bytes) Buffer.BlockCopy(Encoding.ASCII.GetBytes(key), 0, mBuf, 198, 128); Quote
Spirited Posted May 18, 2021 Posted May 18, 2021 Ah, I don't think this is documented very well in Comet. Comet's client send method creates a copy of the packet with the server footer automatically appended to it and writes the packet length. Since the initial handshake packet is length 325 given the inputs you provided, +8 for the footer (TQServer) would give you the correct packet length of 333 (which is what it's expecting from you). Quote
Rezlind Posted May 23, 2021 Author Posted May 23, 2021 Ah, I don't think this is documented very well in Comet. Comet's client send method creates a copy of the packet with the server footer automatically appended to it and writes the packet length. Since the initial handshake packet is length 325 given the inputs you provided, +8 for the footer (TQServer) would give you the correct packet length of 333 (which is what it's expecting from you).Took a look again after a few days and found that I'm still stuck on this unfortunately. I did discover that the client produces debug logs:ERROR: CGameSocket::ReceiveMsg() OnShakeHand failed at ..\3DRole/Network/socket.h, 564 -- Sun May 23 16:35:41 2021So at-least I know I'm failing the handshake somehow. I've followed Comet pretty closely, and below is what I have done so far:I've done quite a bit of debugging and as far as I can tell I'm sending the packet in the correct structure:1. Header - 11 bytes2. Length of total packet (minus 11 junk bytes) - 4 bytes3. Junk/Padding length - 4 bytes4. Junk/Padding bytes - random 12-245. EncryptionIv length - 4 bytes6. EncryptionIV - 8 bytes7. DecryptionIVLength - 4 bytes8. DecrpytionIV - 8 bytes9 Primelength - 4 bytes10. Prime - 128 bytes11. GeneratorLength - 4 bytes12. Generator - 2 bytes.13. KeyLength - 4 bytes14. Key - 128 bytes.I am also appending the TQFooter (8 bytes but no length bytes before and not included in the total length part of the packet. ) at the end of the packet. Is there anything else you can think of that I may be fucking up in that initial DH handshake send?Here is function snippet of that classhttps://pastebin.com/nB5y2AdM Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.