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

11
Spirited wrote: Fri May 14, 2021 11:06 pm 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

Code: Select all

 
         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:

Code: Select all

        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.

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

12
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.
Interested in my work?

If you wanna learn more about me and my projects: visit my portfolio website. There, you can find my free, open-source work and articles about game development. Due to contractual restrictions: I am not available for job requests or volunteer work.

About Me | GitLab Profile | Website

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

13
Spirited wrote: Sat May 15, 2021 12:52 am 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:

Code: Select all

 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:

Code: Select all

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

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

14
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:
Interested in my work?

If you wanna learn more about me and my projects: visit my portfolio website. There, you can find my free, open-source work and articles about game development. Due to contractual restrictions: I am not available for job requests or volunteer work.

About Me | GitLab Profile | Website

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

15
Spirited wrote: Sat May 15, 2021 7:36 am 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:

Code: Select all

        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:
Image
The results when doing it in the SSCCE:
Image

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:
Image

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.

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

16
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.
Interested in my work?

If you wanna learn more about me and my projects: visit my portfolio website. There, you can find my free, open-source work and articles about game development. Due to contractual restrictions: I am not available for job requests or volunteer work.

About Me | GitLab Profile | Website

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

17
Spirited wrote: Sat May 15, 2021 7:01 pm 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.

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

18
All good. It's often a simple mistake like that. Glad you're making progress again.
Interested in my work?

If you wanna learn more about me and my projects: visit my portfolio website. There, you can find my free, open-source work and articles about game development. Due to contractual restrictions: I am not available for job requests or volunteer work.

About Me | GitLab Profile | Website

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

19
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

Code: Select all

        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:

Code: Select all

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

Code: Select all

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

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

20
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/-/blo ... ake.cs#L65
Interested in my work?

If you wanna learn more about me and my projects: visit my portfolio website. There, you can find my free, open-source work and articles about game development. Due to contractual restrictions: I am not available for job requests or volunteer work.

About Me | GitLab Profile | Website