Jump to content
Returning Members: Password Reset Required ×

Trying to understand a bit more about about the RC5 5187 Acc-Client password decryption process


Recommended Posts

Posted

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.

Posted

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!

Posted

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

Posted

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

Ah 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

Posted
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.
Posted

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.

Posted
Yep, all good. And don't worry about asking questions. Asking questions and starting discussions is absolutely contributing.
Posted

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!

Posted
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).
Posted

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.

Posted
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.
Posted

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');
       }
       

slNZ7ek.png

Posted
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. :blush:
Posted

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. :blush:

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:

OIvLChy.png

The results when doing it in the SSCCE:

pMQnZUU.png

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:

B36wxoV.png

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.

Posted
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.
Posted

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.

Yikes

Thanks a ton for your patience though.

Posted

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();

Posted

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

Perhaps 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. :disappointed:

Posted

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);

Posted
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).
Posted

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 2021

So 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 bytes

2. Length of total packet (minus 11 junk bytes) - 4 bytes

3. Junk/Padding length - 4 bytes

4. Junk/Padding bytes - random 12-24

5. EncryptionIv length - 4 bytes

6. EncryptionIV - 8 bytes

7. DecryptionIVLength - 4 bytes

8. DecrpytionIV - 8 bytes

9 Primelength - 4 bytes

10. Prime - 128 bytes

11. GeneratorLength - 4 bytes

12. Generator - 2 bytes.

13. KeyLength - 4 bytes

14. 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 class

https://pastebin.com/nB5y2AdM

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...