Habbo R39 Encryption don't receive session params packet

Gabrielle

She/Her - Be who you wanna be
In the SWF is the following code which gets sent after you send @A:

Code:
 private function sendConnectionParameters(param1:IConnection) : void
      {
         var_882 = true;
         dispatchLoginStepEvent(HabboCommunicationEvent.const_11);
         param1.send(new VersionCheckMessageComposer(401,var_1073,var_1681));
         var _loc2_:String = "";
         var _loc3_:SharedObject = SharedObject.getLocal(const_1298,"/");
         if(_loc3_.data.machineid != null)
         {
            _loc2_ = _loc3_.data.machineid;
         }
         param1.send(new UniqueIDMessageComposer(_loc2_));
         param1.send(new GetSessionParametersMessageComposer());
      }

This is my message handler to handle CN (206) packet:

C#:
using Org.BouncyCastle.Math;
using Stargazer.Communication.Interfaces;
using Stargazer.Communication.Outgoing.Handshake;
using Stargazer.Networking.Game.Clients;
using Stargazer.Networking.Game.Packets;
using Stargazer.Security;

namespace Stargazer.Communication.Incoming.Handshake;

public class InitCryptoMessageEvent : IMessageHandler
{
    public MessageHandlerType Type => MessageHandlerType.Handshake;

    public async Task Handle(Client client, ClientPacket packet)
    {
        client.DiffieHellman =
            new DiffieHellman(new BigInteger(Stargazer.Prime, 16), new BigInteger(Stargazer.Generator, 16));

        var token = new BigInteger(DiffieHellman.GenerateRandomHexBytes(15)).ToString(16);
        
        client.PaddingGenerator = new PaddingGenerator(
            int.Parse(
                token.Substring(
                    token.Length - 4),
                System.Globalization.NumberStyles.HexNumber),
            65536);
        
        await client.SendComposer(new InitCryptoMessageComposer(token));
    }
}

This is my DiffieHellman class:

C#:
using System.Security.Cryptography;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;

namespace Stargazer.Security;

public class DiffieHellman
{
    private readonly BigInteger _prime;
    private readonly BigInteger _privateKey;
    
    public BigInteger PublicKey { get; private set; }

    public DiffieHellman(BigInteger prime, BigInteger generator)
    {
        _prime = prime;
        _privateKey = new BigInteger(GenerateRandomHexBytes(30));

        PublicKey = generator.ModPow(_privateKey, _prime);
    }

    public BigInteger GenerateSharedKey(BigInteger clientPublicKey)
    {
        return clientPublicKey.ModPow(_privateKey, _prime);
    }
    
    public static byte[] GenerateRandomHexBytes(int length)
    {
        if (length < 1)
        {
            throw new ArgumentException("Length must be positive.", nameof(length));
        }

        return RandomNumberGenerator.GetBytes(length / 2);
    }
}

This is my message handler to handle _R (2002) packet:

C#:
using Org.BouncyCastle.Math;
using Stargazer.Communication.Interfaces;
using Stargazer.Communication.Outgoing.Handshake;
using Stargazer.Networking.Game.Clients;
using Stargazer.Networking.Game.Packets;
using Stargazer.Security;

namespace Stargazer.Communication.Incoming.Handshake;

public class GenerateSecretKeyMessageEvent : IMessageHandler
{
    public MessageHandlerType Type => MessageHandlerType.Handshake;
    public async Task Handle(Client client, ClientPacket packet)
    {
        // TODO: Initialize RC4
        var clientPublicKey = new BigInteger(packet.ReadString(), 10);
        client.Cryptography =
            new MikeCrypto(HexEncoding.GetBytes(client.DiffieHellman.GenerateSharedKey(clientPublicKey).ToString(16)), client.PaddingGenerator);

        await client.SendComposer(new SecretKeyComposer(client.DiffieHellman.PublicKey.ToString(10)));
    }
}

The MikeCrypto and PaddingGenerator classes are taken from:


This is my Netty decoder class:

C#:
using System.Text;
using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Transport.Channels;
using HabboEncoding;
using Stargazer.Networking.Game.Clients;
using Stargazer.Networking.Game.Packets;

namespace Stargazer.Networking.Game.Codecs;

public class Decoder : ByteToMessageDecoder
{
    protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
    {
        if (!ClientManager.Instance.TryGetClient(context.Channel, out var client) || client == null)
            return;
        
        Console.WriteLine(Encoding.GetEncoding(0).GetString(input.Array));
        
        if (input.GetByte(0) == 60 && client.Cryptography == null)
        {
            context.WriteAndFlushAsync(Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes("<?xml version=\"1.0\"?>\r\n" +
                "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\r\n" +
                "<cross-domain-policy>\r\n" +
                "<allow-access-from domain=\"*\" to-ports=\"*\" />\r\n" +
                "</cross-domain-policy>\0")));
            context.CloseAsync();
        }
        else
        {
            input.MarkReaderIndex();

            if (client.Cryptography != null)
            {
                String tHeader;
                String tBody;

                int pMsgSize;

                while (input.ReadableBytes > 6)
                {
                    var tHeaderMsg = new byte[6];
                    input.ReadBytes(tHeaderMsg);

                    tHeader = Encoding.GetEncoding(0).GetString(tHeaderMsg);
                    tHeader = client.Cryptography.decipherHeader(tHeader);

                    var tByte1 = tHeader[2] & 63;
                    var tByte2 = tHeader[1] & 63;
                    var tByte3 = tHeader[0] & 63;
                    pMsgSize = (tByte2 * 64) | tByte1;
                    pMsgSize = (tByte3 * 64 * 64) | pMsgSize;

                    Console.WriteLine("Msg size: {0}", pMsgSize);

                    if (input.ReadableBytes < pMsgSize)
                    {
                        input.ResetReaderIndex();
                        return;
                    }

                    var tBodyMsg = new byte[pMsgSize];
                    input.ReadBytes(tBodyMsg);

                    tBody = Encoding.GetEncoding(0).GetString(tBodyMsg);
                    tBody = client.Cryptography.decipherBody(tBody);
                    
                    Console.WriteLine("Body: {0}", tBody);
                    output.Add(new ClientPacket(Unpooled.CopiedBuffer(Encoding.GetEncoding(0).GetBytes(tBody))));
                }
            }
            else
            {
                while (input.ReadableBytes >= 5)
                {
                    var length = Base64Encoding.DecodeInt32(input.ReadBytes(3).Array);

                    if (length <= input.ReadableBytes)
                    {
                        output.Add(new ClientPacket(input.ReadBytes(length)));
                    }
                }
            }
        }
    }
}

Now, this is my console output with debug (from *after* policy is sent):

Code:
info: Stargazer.Networking.Game.GameNetworkHandler[0]
      New connection from [::ffff:127.0.0.1]:58149
@@CCNH




@Il_RIh878705046860206082228499295989120092569386376737953360034297434798239396511301525397184936942257674577254017292080560909905595040891345475541399835917666311832048975680003865158764063941613836113270480167145010122484788654757
6432529193821420147405045557382108071151745483688124075801324754646388515525301655242359737682091101784265467763204996170093378504110337892185817627340413052419071163037130283267793308553464314187844509755836733351568415025413355825
033700028529298671415251252691997066651629905536188179535271963904615734735342047424541453177730170199727220998161285669058785106688251548431099088805156980653


R/0mcw87g6c8zO6v1SW2cGi53WJT+eNXMZmCKwMy5MbWSdjGKoDM6WvorDhk+b2gZUfard8MHX+QasTzcERPQIBi8tvANfMTdjLvXSVlmfB+ciyR2afEMSLh6l3zYYuwXbtjnLfHZkzLnboOLQbMhuXeTKzHXbiKKfY1YMM3rWup9d4yWxp/TZFNCUlj25Ory86d5xKlkSCh+GAoX1y+gA9A




Msg size: 210
Body: RRYdAA\http://127.0.0.1/gordon/RELEASE39-22643-22891-200911110035_07c3a2a30713fd5bea8a8caf07e33438/@uhttp://127.0.0.1/gamedata/vars.txt?version=1682505336
rtfO5wb73KnTxCAM227YEMVmXqBMfpAlk




Msg size: 27
Body: Lm@Obeepgoesthejeep
A0dPNghwxQ




Msg size: 4
Body: @L
6a2sQgFNEGXO/7YO9HBwvNgp+hMxA8dAXol8jXQrRjcYkTQl3Tbt5hbSZ3cS+o




Msg size: 8
Body: Cd
Msg size: 3
Body: @G
Msg size: 4
Body: @H
Msg size: 23
Body: @Z@Jhabbo_club
86+rZggvAw40Q2TDpwitgG7O90+AnM3Uj0FQul8j61j+kkkiIt2lOrHHdvQ7WPihNx7SzJjNbL06+lxqevUhetrlFU4027GHnH8uPiNkeFDxxgrfSbJt58h5MFZ+0zuERgbjGt4v3987w3W6d89UyKwPUwbdPO4kagNji6n9MZ7VR0hepnv7dmAdsCYh205pPH5py929/bgMON+IKplbsYNmw1SV7M018eCJtU+z
3WfPjN3gw



Msg size: 235
Body: FePOA~Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) MxNitro/1.0.1.3000 Chrome/35.0.1849.0 Safari/537.36@NWIN 14,0,0,177@IWindows 8@@H`JhEMQCSJH
JWPfuw4+hwpZqnYNMNmH27UXwHdCafRDXmWR1AMCbaxXal0L26ksoHrlN1VXY5HjWn9Yqk4ShkSf4VwmFJ7n01oXwlLA0cwTcJk50Nc7tV2xnht6J8TDoWUcPZKVBUHCVVwBhMletorGIxXYEys9WKY8Q+v0X48NVboJU3h+8JPZlBpG/mr6e8IOtMulPGO8EzaYtBNurpjl/8rj0S2sUZJboNVBJLB0rmXmPzfE
r5Fyup



Msg size: 232
Body: FeP^A~Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) MxNitro/1.0.1.3000 Chrome/35.0.1849.0 Safari/537.36@NWIN 14,0,0,177@IWindows 8@@H`KhEMHQJH
24/BsgdKl2XEIXKFKTNLShJG0H8Vdst9UP2VBSPvtklWv9UlXMWZOBYO5Xm6oIOSnSZ5Vj4i51jG5YGMt6+WnwWm51dSaaxbLW6RhC0qpIEHGeiy1ulvTjAahqBxHdKRcUgu6QMu7fTptMmTzJE3mBpLQJH4kfqNvu9wVU1OZHj0yN2UeSnzd+HG4zR7qO2IncbiHtX51bxSGGDYh0shrCnzIHMfTLMVGRmv9zUX
AlwQ8



Msg size: 231
Body: FePmA~Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) MxNitro/1.0.1.3000 Chrome/35.0.1849.0 Safari/537.36@NWIN 14,0,0,177@IWindows 8@@H`KhEMHQJH
suigTw5RJ40NrXp5hGETiAWUlGzByNhUi5YsK1af1hHfHIifu+OgJqBI7G+A+WZ8gr1HtQFVPHwdjhLcrtQn5zIMT/yQ+83ZiC5Q15mb1ax1aab8TuJ6G9Ozx39gYk2czOhcqJFql6HldGXwi58LELa07U0NBpqw3N0oxeFwLaOIEnpjeKkemnU2hNmMTOUoF/NYD0Ad4GFan6dcrv8CvG+EbWBqd502ZCPR9KlT
En



Msg size: 228
Body: FeP|A~Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) MxNitro/1.0.1.3000 Chrome/35.0.1849.0 Safari/537.36@NWIN 14,0,0,177@IWindows 8@@H`KhEMHQJH

The message size seems improper and for some reason I don't receive \Y (1817, GetSessionParametersMessageComposer) and IDK what I'm doing wrong. Maybe @Quackster has an idea somehow what I'm doing wrong.
 
Is the packet not padded with random garbage that you need to remove? I'd be double checking your implementation around how the padding is done.

For example, instead of 2, 1, 0 mine is 3, 2, 1 because the first byte is always trash.

Code:
                    var tByte1 = tHeader[2] & 63;
                    var tByte2 = tHeader[1] & 63;
                    var tByte3 = tHeader[0] & 63;

And then we iterate the padding thingy.

Code:
            player.getNetwork().setTx(
                    NettyPlayerNetwork.iterateRandom(player.getNetwork().getTx())
            );

And my NettyPlayerNetwork packets.

Code:
    public static String removePadding(String tBody, int i) {
        if (i >= tBody.length())
            return tBody;

        return tBody.substring(i);
    }

    public void setToken(String tToken) {
        this.pToken = tToken;
        String tSeedHex = pToken.substring(pToken.length() - 4);
        Map<String, Integer> tVals = new HashMap<>();
        tVals.put("0", 0);
        tVals.put("1", 1);
        tVals.put("2", 2);
        tVals.put("3", 3);
        tVals.put("4", 4);
        tVals.put("5", 5);
        tVals.put("6", 6);
        tVals.put("7", 7);
        tVals.put("8", 8);
        tVals.put("9", 9);
        tVals.put("a", 10);
        tVals.put("b", 11);
        tVals.put("c", 12);
        tVals.put("d", 13);
        tVals.put("e", 14);
        tVals.put("f", 15);
        tVals.put("A", 10);
        tVals.put("B", 11);
        tVals.put("C", 12);
        tVals.put("D", 13);
        tVals.put("E", 14);
        tVals.put("F", 15);

        this.pTx = 0;

        for (int i = 0; i <= 3; i++) {
            this.pTx += (int)(Math.pow(16, i) * tVals.get(Character.toString(tSeedHex.charAt(3 - i))));
        }

        this.pRx = 0;

        tSeedHex = pToken.substring(0, 4);

        for (int i = 0; i <= 3; i++) {
            this.pRx += (int)(Math.pow(16, i) * tVals.get(Character.toString(tSeedHex.charAt(3 - i))));
        }
    }

    public static int iterateRandom(int tSeed) {
     return ((19979 * tSeed) + 5) % 65536;
    }

And when I send the InitCrypto packet, our token determines the "Tx" variable.

Code:
public class INIT_CRYPTO implements MessageEvent {

    @Override
    public void handle(Player player, NettyRequest reader) throws Exception {
        if (player.isLoggedIn()) {
            return;
        }

        String pToken = DiffieHellman.generateRandomNumString(24);

        player.send(new CRYPTO_PARAMETERS(pToken));
        player.getNetwork().setToken(pToken);
    }
}

And then your remove the random garbage in the decoder:

Code:
tBody = NettyPlayerNetwork.removePadding(tBody, player.getNetwork().getTx() % 5);

Line 65: https://github.com/Quackster/ShockwaveDHKeyExchange/blob/master/EncryptionDecoder.java#L65

Just added the two-way encryption methods and the other pieces of code for full context: https://github.com/Quackster/ShockwaveDHKeyExchange/commit/0a88c79c2f9d6bb296f2c4e1330402b5e4d578d1
 
@Quackster yeah I tried to remove it earlier with the stuff I found in Lemon but might've done it wrong (psst.. I just realized I never put it back in my code.. silly silly me). I'll check tomorrow as soon as I wake up (since I'm going to watch a video then head to bed) and I'll report whether it works.
 
@Quackster yeah I tried to remove it earlier with the stuff I found in Lemon but might've done it wrong (psst.. I just realized I never put it back in my code.. silly silly me). I'll check tomorrow as soon as I wake up (since I'm going to watch a video then head to bed) and I'll report whether it works.

Alright, I'm eager to see your progress.
 
Alright, I'm eager to see your progress.

So, I did match my code to your repo, and there's 2 issues:

1. Sometimes, it doesn't work *at all*, meaning no data gets decoded properly
2. If it DOES work, this is what happens:

Code:
Invoked InitCryptoMessageEvent
Invoked GenerateSecretKeyMessageEvent

Input byte count: 214
Packet header 1170 RRYdAA\http://127.0.0.1/gordon/RELEASE39-22643-22891-200911110035_07c3a2a30713fd5bea8a8caf07e33438/@uhttp://127.0.0.1/gamedata/vars.txt?version=1682573478
Invoked VersionCheckMessageEvent

Input byte count: 36
Packet header 813 Lm@Obeepgoesthejeep
Invoked UniqueIDMessageEvent

Input byte count: 13
Packet header 12 @L

Input byte count: 9
Packet header 228 Cd

Input byte count: 55
Packet header 7 @G

Input byte count: 42
Packet header 8 @H

Input byte count: 29
Packet header 26 @Z@Jhabbo_club

The input byte count is from the moment the Decode function is called after initializing the encryption, so before any decode etc. (just so I could check that it isn't an issue with decoding itself which causes the packet not to be received), I did sent @C in UniqueIDMessageEvent, but you can clearly see even the bytes for the packet aren't received. The only packet bytes received are @L (messenger), Cd (badges), @G (info retrieve), @H (get credits) and @Z (get club). And of course VersionCheck and UniqueID which are implemented already cause I do get them always (if I can decode properly)


Here's my repo, I'm clueless as how to fix it so I'd appreciate a second pair of eyes (or a third...) looking at it
 
Last edited:
I did have an issue where mine wouldn't always work for the Shockwave client, the issue was that the getBytes() for the shared key (that initialises the Cryptography class) BigInteger would occassionally put a 0 at the very start of the array, and the Shockwave client didn't have this zero - so I had to remove it, this was a consequence of Java's BigInteger class by the way and I did a hacky workaround by removing it.

See here: https://github.com/Quackster/ShockwaveDHKeyExchange/blob/master/HugeInt15.java#L166

Perhaps it's the same issue as what you're experiencing?
 
I did have an issue where mine wouldn't always work for the Shockwave client, the issue was that the getBytes() for the shared key (that initialises the Cryptography class) BigInteger would occassionally put a 0 at the very start of the array, and the Shockwave client didn't have this zero - so I had to remove it, this was a consequence of Java's BigInteger class by the way and I did a hacky workaround by removing it.

See here: https://github.com/Quackster/ShockwaveDHKeyExchange/blob/master/HugeInt15.java#L166

Perhaps it's the same issue as what you're experiencing?
IDK if the BigInteger class I'm using has the same issue. I might just try it out, but last few times it worked for me so IDK, hard to debug tbh
 
Back
Top