Disabling RSA & RC4

hackademiC

Member
Hi all,

I've been playing around with RABCDasm for the last week or so and I'm attempting to disable the RSA and RC4 in the Habbo.swf so that I'm able to use it without having to encrypt and decrypt. I know that I can go and get another SWF that is already patched with headers/structures and use that, but I want to do this myself manually so that I can learn it and do it again for any future releases!

The client version: PRODUCTION-202206221407-523648813

Anyhow, I've been able to remove the host protection quite easily by looking over the methods and returning true and false where needed, however disabling the RC4 and RSA has been the most difficult so far and I'm not making much progress.

Bit of a long shot, but if anyone has any knowledge of walking over AS3 byte instructions and can help, it would be awesome!

So far, like I said, I've patched the host protection stuff (that was easy!) and the client connects and sends the <policy-file-request/> packet, disconnects and then sends the ClientHello and InitCrypto packets just like normal. All is well, I'm also able to other miscellaneous send packets back (alert windows, and broadcast message windows) and the message box/alert boxes appear like normal too.

The issue is, no matter what I'm doing, the client never, ever will send the SSOTicketMessage packet. I've tried removing the line of code that calls the InitCrypto to the server, and replace it with the function that calls the SSO function, however even though with the line present that calls the SSO function, it doesn't send it.

I'm pretty sure what is occuring is this. The function that is calling the SSOTicket packet, is calling it and then calling SocketConnection.send() which encodes the packet (with header) and checks if the RC4 variable is null or not, and then if it's null, the function returns back and doesn't send. The problem is, I've tried editing the .send() function so that instead of .writeBytes(encryptedMessage), I am setting the variable that is returned from .encode() to a local7 for example instead of the original local4, and then passing that same variable through to .writeBytes(). I've removed the null check, by passing ifeq rather than ifne to make it skip the null check. However, once I do that, it either just doesn't send the packet, or I'm getting a stack depth inbalance.

All of these functions are in the same class, thus, in theory, should be able to call any of the methods I like from any of the other methods, without getting any reference errors.

This is what I've found so far:
JavaScript:
public class IncomingMessages
    {

        private var _SafeStr_14849:_SafeStr_1526; // Client or connection?
        private var _SafeStr_14855:_SafeStr_261;
        private var _SafeStr_20035:_SafeStr_2336;
        private var _SafeStr_15408:String;
        private var _SafeStr_20036:Boolean;
        private var _SafeStr_19965:Boolean;
        private var _SafeStr_16574:Vector.<_SafeStr_2275> = new Vector.<_SafeStr_2275>(0);
        private var _SafeStr_20037:_SafeStr_4288;

        public function IncomingMessages(k:_SafeStr_1526, _arg_2:_SafeStr_261)
        {
            this._SafeStr_14849 = k;
            this._SafeStr_14855 = _arg_2;
            var _local_3:_SafeStr_2282 = this._SafeStr_14855.connection;
            if (_local_3 == null)
            {
                throw (new Error("Connection is required to initialize!"));
            };
            _local_3.addEventListener(Event.CONNECT, this._SafeStr_20023);
            _local_3.addEventListener(Event.CLOSE, this._SafeStr_20024);
            this.addHabboConnectionMessageEvent(new _SafeStr_3630(this._SafeStr_20025));//3542 IdentityAccountsEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_3469(this._SafeStr_16556));//1874 LoginFailedHotelClosedMessageEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_3811(this._SafeStr_20026));//1312 UniqueMachineIDEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_3777(this._SafeStr_20027));//2942 MaintenanceStatusMessageEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_2812(this._SafeStr_20028));//1922 AuthenticationOKMessageEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_3428(this._SafeStr_20029));//1552 CompleteDiffieHandshakeEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_3291(this._SafeStr_20030));//3943 InitDiffieHandshakeEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_3458(this._SafeStr_20031));//2788 GenericErrorEvent
            this.addHabboConnectionMessageEvent(new DisconnectReasonEvent(this._SafeStr_20032));// DisconnectReasonEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_3374(this._SafeStr_20033));//1221 ErrorReportEvent
            this.addHabboConnectionMessageEvent(new _SafeStr_3828(this._SafeStr_20034));//2285 PingMessageEvent
            this._SafeStr_14849.context.events.addEventListener(Event.UNLOAD, this.unloading);
        }

This is obviously listening for those incoming messages, and then calling the method that the event is linked too. From this, I know that, the function which should kick off the SSOTicket is in the function _SafeStr_20029().

This is function _SafeStr_20029()
JavaScript:
        private function _SafeStr_20029(k:_SafeStr_2275):void
        {
            var _local_9:get;
            var _local_2:_SafeStr_2282 = k.connection; // Gets the socket/connection?
            var _local_3:_SafeStr_3428 = (k as _SafeStr_3428); // Casts the event to an incoming message to read the contents?
            var _local_4:ByteArray = new ByteArray();
            var _local_5:ByteArray = new ByteArray();
            _local_4.writeBytes(CryptoTools._SafeStr_9526(_local_3._SafeStr_9531)); // String from CompleteDiffie
            this._SafeStr_20037.verify(_local_4, _local_5, _local_4.length);
            this._SafeStr_20037.dispose();
            this._SafeStr_20035._SafeStr_15416(_local_5.toString(), 10);
            var _local_6:String = this._SafeStr_20035._SafeStr_7527(16).toUpperCase();
            if (!this._SafeStr_20035._SafeStr_15418())
            {
                return;
            };
            var _local_7:ByteArray = CryptoTools._SafeStr_9526(_local_6);
            _local_7.position = 0;
            var _local_8:get = this._SafeStr_14855._SafeStr_20041();
            _local_8.init(_local_7);
            if (_local_3._SafeStr_9532) // Checks the boolean from CompleteDiffie.
            {
                _local_9 = this._SafeStr_14855._SafeStr_20041();
                _local_9.init(_local_7);
            };
            _local_2._SafeStr_16234(_local_8, _local_9); // Most likely sets the keys on the client/socket object to be used to encrypt/decrypt.
            this._SafeStr_20036 = false; // Sets handshake failed to false?
            this._SafeStr_14849._SafeStr_6469(_SafeStr_2270._SafeStr_6516); // This calls the event HANDSHAKE_OK.
            this._SafeStr_14849._SafeStr_9533(_local_2); // This function sends the SSO.
        }

From this, I know I can either send a packet which will fire the method _SafeStr_20029() or copy the last line which calls the SSO event and move it to another function.

I then went on and found where the ClientHello and InitCrypto are called from. They are called when a client connects (surprise, surprise)

Based on the first snippet, I know that function _SafeStr_20023() is called when the socket connects.

This is _SafeStr_20023()
JavaScript:
private function _SafeStr_20023(k:Event=null):void
        {
            var _local_2:_SafeStr_2282 = this._SafeStr_14855.connection;
            if (_local_2 != null)
            {
                this._SafeStr_9540(); // Decoding a Base64 DH or RSA related and setting values, most likely n and e? - Removed.
                this._SafeStr_14849._SafeStr_6469(_SafeStr_2270._SafeStr_6514); // Event tracking - HABBO_CONNECTION_EVENT_ESTABLISHED
                this._SafeStr_19965 = false;
                this._SafeStr_20036 = true;
                this._SafeStr_14849._SafeStr_6469(_SafeStr_2270._SafeStr_6515); // Event tracking - HABBO_CONNECTION_EVENT_HANDSHAKING - Removed.
                _local_2._SafeStr_16233(new _SafeStr_3392()); // Sends ClientHelloMessageComposer
                _local_2._SafeStr_16233(new _SafeStr_3623()); // Sends InitDiffieHandshakeMessageComposer. Change to send SSOTicket instead? - Removed.
            };
        }

I then went ahead in the byte code, and replaced the InitDiffieHandshakeMessage and put the this._SafeStr_14849._SafeStr_9533(_local_2); in it's place. I've confirmed this by assembling and checking the AS3 representation and the function is valid at least. The lines with - Removed, are lines I've removed and also left there both times to see if it makes a difference. I've also tried setting _SafeStr_19965 to true and _SafeStr_20036 to false and seems to make no difference.

Basically, I just can't work out why the SSOTicketMessage is never sent, and I'm pretty confident it has something to do with the .send() function which is what is really sending the SSO message as seen below:
JavaScript:
public function _SafeStr_9533(k:_SafeStr_2282):void
        {
            var _local_5:_SafeStr_2804;
            k.send(new _SafeStr_2802(401, this._SafeStr_19969, this._SafeStr_19966));
            var _local_2:String = CommunicationUtils._SafeStr_6164(CommunicationUtils._SafeStr_6487);
            var _local_3:String = CommunicationUtils._SafeStr_6488();
            var _local_4:Array = Capabilities.version.split(" ");
            k.send(new _SafeStr_2803(_local_2, _local_3, _local_4.join("/")));
            if (((this._SafeStr_19967) && (this._SafeStr_19967.length > 0)))
            {
                _local_5 = new _SafeStr_2804(this._SafeStr_19967);
                k.send(_local_5);
            };
        }

k is the socket object, and it's calling the .send() function which is the same function calling the encrypt function. Where as the ClientHello and InitCrypto is being called by another function _SafeStr_16233(), which is doing almost the same thing, but instead it's not checking for null on the RC4 object or passing the message into any other methods, other than .writeBytes().

And lastly, this is the .send() function and the .16233() function which does the encoding and then sending the packet/message.
JavaScript:
        public function send(k:_SafeStr_2273):Boolean
        {
            if (disposed)
            {
                return (false);
            };
            if (((this._SafeStr_16252) && (!(this._SafeStr_16253))))
            {
                if (this._SafeStr_16254 == null)
                {
                    this._SafeStr_16254 = new Vector.<_SafeStr_2273>(0);
                };
                this._SafeStr_16254.push(k);
                return (false);
            };
            var _local_2:int = this._SafeStr_16251._SafeStr_7077(k);
            if (_local_2 < 0)
            {
                return (false);
            };
            var _local_3:Array = k._SafeStr_15106();
            var _local_4:ByteArray = this._SafeStr_16248.encode(_local_2, _local_3);
            if (this._SafeStr_7079)
            {
                this._SafeStr_7079._SafeStr_16241(String(_local_2));
            };
            if (this._SafeStr_16249 == null)
            {
                return (false);
            };
            if (this._SafeStr_16245.connected)
            {
                this._SafeStr_16249._SafeStr_7524(_local_4);
                this._SafeStr_16245.writeBytes(_local_4); // This writes bytes to the socket.
                this._SafeStr_16245.flush();
            }
            else
            {
                return (false);
            };
            return (true);
        }

        public function _SafeStr_16233(k:_SafeStr_2273):Boolean
        {
            if (disposed)
            {
                return (false);
            };
            var _local_2:int = this._SafeStr_16251._SafeStr_7077(k);
            if (_local_2 < 0)
            {
                return (false);
            };
            var _local_3:Array = k._SafeStr_15106();
            var _local_4:ByteArray = this._SafeStr_16248.encode(_local_2, _local_3);
            var _local_5:String = getQualifiedClassName(k);
            var _local_6:Class = (getDefinitionByName(_local_5) as Class);
            if (!ClassUtils.implementsInterface(_local_6, _SafeStr_3047))
            {
                return (false);
            };
            if (this._SafeStr_7079)
            {
                this._SafeStr_7079._SafeStr_16241(String(_local_2));
            };
            if (this._SafeStr_16245.connected)
            {
                this._SafeStr_16245.writeBytes(_local_4);
                this._SafeStr_16245.flush();
            }
            else
            {
                return (false);
            };
            return (true);
        }

Hopefully, this makes sense, and someone can help. Otherwise, when I find out, I'll post my results! :)

The SWF is an original with only host protection patched. RC4 and RSA keys are as original and haven't been touched.

SWF Version: PRODUCTION-202206221407-523648813
The SWF link: https://mega.nz/file/mogCFYrQ#zyme8fCsZtVc8Yx8scxol9uCRSfOMKNZXdz0SB0bCQs
 
Update: Finally figured it out and disabled all encryption/handshaking.

My initial thought of removing the InitCrypto function call and replacing it with the SSO function call was correct. Instead of sending InitCrypto, it now sends the SSOTicketMessage instead.

Before:
JavaScript:
private function _SafeStr_20023(k:Event=null):void
{
    var _local_2:_SafeStr_2282 = this._SafeStr_14855.connection;
    if (_local_2 != null)
    {
        this._SafeStr_9540(); // DH or RSA
        this._SafeStr_14849._SafeStr_6469(_SafeStr_2270._SafeStr_6514); // Event tracking - HABBO_CONNECTION_EVENT_ESTABLISHED
        this._SafeStr_19965 = false;
        this._SafeStr_20036 = true;
        this._SafeStr_14849._SafeStr_6469(_SafeStr_2270._SafeStr_6515); // Event tracking - HABBO_CONNECTION_EVENT_HANDSHAKING
        _local_2._SafeStr_16233(new _SafeStr_3392()); // Sends ClientHelloMessageComposer
        _local_2._SafeStr_16233(new _SafeStr_3623()); // Sends InitDiffieHandshakeMessageComposer.
    };
}

After:
JavaScript:
private function _SafeStr_18944(k:Event=null):void
{
    var _local_2:_SafeStr_2282 = this._SafeStr_11890.connection;
    if (_local_2 != null)
    {
        this._SafeStr_9540();
        this._SafeStr_11882._SafeStr_6469(_SafeStr_2270._SafeStr_6514);
        this._SafeStr_18886 = false;
        this._SafeStr_18957 = true;
        this._SafeStr_11882._SafeStr_6469(_SafeStr_2270._SafeStr_6515);
        _local_2._SafeStr_14009(new _SafeStr_3392());
        this._SafeStr_11882._SafeStr_9533(_local_2); // Calls the SSO function.
    };
}

All that was left to do, was to make sure any outgoing messages handled by the .send() function weren't being passed through to ARC4 in anyway. The function doing that is:

Before:
JavaScript:
public function send(k:_SafeStr_2273):Boolean
{
    if (disposed)
    {
        return (false);
    };
    if (((this._SafeStr_16252) && (!(this._SafeStr_16253))))
    {
        if (this._SafeStr_16254 == null)
        {
            this._SafeStr_16254 = new Vector.<_SafeStr_2273>(0);
        };
        this._SafeStr_16254.push(k);
        return (false);
    };
    var _local_2:int = this._SafeStr_16251._SafeStr_7077(k);
    if (_local_2 < 0)
    {
        return (false);
    };
    var _local_3:Array = k._SafeStr_15106();
    var _local_4:ByteArray = this._SafeStr_16248.encode(_local_2, _local_3);
    if (this._SafeStr_7079)
    {
        this._SafeStr_7079._SafeStr_16241(String(_local_2));
    };
    if (this._SafeStr_16249 == null)
    {
        return (false);
    };
    if (this._SafeStr_16245.connected)
    {
        this._SafeStr_16249._SafeStr_7524(_local_4); // Bad function, it's call the ARC4 function.
        this._SafeStr_16245.writeBytes(_local_4); // This writes bytes to the socket.
        this._SafeStr_16245.flush();
    }
    else
    {
        return (false);
    };
    return (true);
}

After:
JavaScript:
public function send(k:_SafeStr_2273):Boolean
{
    if (disposed)
    {
        return (false);
    };
    if (((this._SafeStr_14028) && (!(this._SafeStr_14029))))
    {
        if (this._SafeStr_14030 == null)
        {
            this._SafeStr_14030 = new Vector.<_SafeStr_2273>(0);
        };
        this._SafeStr_14030.push(k);
        return (false);
    };
    var _local_2:int = this._SafeStr_14027._SafeStr_7077(k);
    if (_local_2 < 0)
    {
        return (false);
    };
    var _local_3:Array = k._SafeStr_12322();
    var _local_4:ByteArray = this._SafeStr_14024.encode(_local_2, _local_3);
    if (this._SafeStr_7079)
    {
        this._SafeStr_7079._SafeStr_14017(String(_local_2));
    };
    if (this._SafeStr_14025 == true)
    {
        return (false);
    };
    if (this._SafeStr_14021.connected)
    {
        this._SafeStr_14021.writeBytes(_local_4);
        this._SafeStr_14021.flush();
    }
    else
    {
        return (false);
    };
    return (true);
}

Now, I knew I had to remove that line of code however, removing the actual call was messing up the stack depth, somewhere else. So, instead of actually removing it in the byte code, there was a nice little jump instruction that I took advantage off to skip over the block of code, and essentially remove it when the file was re-assembled. In the same function call in byte code, it looked like this.

Before:
JavaScript:
L184:
      getproperty         QName(PrivateNamespace("_-53p"), "_-423")
      getproperty         QName(PackageNamespace("", "#0"), "connected")
      iffalse             L206

      debugline           284
      getlocal            6 // Replace with pushfalse to go to L197 and skip over L190 & L192.
      iffalse             L197

L190:
      getlocal0
      getproperty         QName(PrivateNamespace("_-53p"), "_-5SK")
L192:
      getlocal            4
      callpropvoid        QName(Namespace("_-6G-"), "_-4RR"), 1

      getlocal            5
      iftrue              L201

      debugline           285
L197:
      getlocal0
      getproperty         QName(PrivateNamespace("_-53p"), "_-423")
      getlocal            4
      callpropvoid        QName(PackageNamespace("", "#0"), "writeBytes"), 1

After:
JavaScript:
L184:
      getproperty         QName(PrivateNamespace("_-53p"), "_-423")
      getproperty         QName(PackageNamespace("", "#0"), "connected")
      iffalse             L206

      debugline           284
      pushfalse // Now goes L197 instead, skipping over L190 and L192 and straight to writing bytes to the socket.
      iffalse             L197

L190:
      getlocal0
      getproperty         QName(PrivateNamespace("_-53p"), "_-5SK")
L192:
      getlocal            4
      callpropvoid        QName(Namespace("_-6G-"), "_-4RR"), 1

      getlocal            5
      iftrue              L201

      debugline           285
L197:
      getlocal0
      getproperty         QName(PrivateNamespace("_-53p"), "_-423")
      getlocal            4
      callpropvoid        QName(PackageNamespace("", "#0"), "writeBytes"), 1

Basically, pushing false onto the stack, and then having the next iffalse instruction check it, took it immediately over the function call happening on L190 and L192 and skip directly onto the write bytes block. Once assembled the calls are now gone.

The last and final thing was to make sure the check null was skipped over, as since RC4 wasn't init'd, it would just return and not send. All we did was instead of pushing null onto the stack, we are pushing true where it checks if it's null. Therefore, if and only if RC4 was init'd, it would return instead.

Before:
JavaScript:
pushnull
ifne                L182

After:
JavaScript:
pushtrue
ifne                L182

Hopefully, if anyone else wants to do the same or has the same issue, can see my solution! :)
 
Last edited:
Personally I would have made the encipher/decipher RC4 methods just return the argument that gets passed so it doesn't actually get enciphered and send back packets that the client would've liked regardless if encryption never actually gets used.

Much like how V7 RC4 was bypassed back in the day by editing empty.cct and hooking into the RC4 class of fuse_client.cct 😁
 
Personally I would have made the encipher/decipher RC4 methods just return the argument that gets passed so it doesn't actually get enciphered and send back packets that the client would've liked regardless if encryption never actually gets used.

Much like how V7 RC4 was bypassed back in the day by editing empty.cct and hooking into the RC4 class of fuse_client.cct 😁
True, that's a good point, however the function that is called that does the enciphering doesn't return anything, it's a void. I'm assuming the RC4 class replaces the byte array somewhere else in the function call, but I thought instead of trying to change a function further up in another class, it's just better to not call it at all. Also, and since the RC4 class is never fully init'd, if I left the call to encipher in there, when the function is called, I get a null reference, most likely as the RC4 class was never init'd to begin with.

I looked at some older releases and the RC4 function call has changed over the years, it used to return the encrypted byte array and set a local variable, and then the .writeBytes() would be called with the local variable passed as a parameter. As you could imagine, you could very easily fix that by either changing the variable that gets set, or just pass the un-encrypted byte array rather than the one that was encrypted.

It's pretty funny how hard they attempt to prevent you from manipulating the methods, and most of the time, it just a matter of swapping checks around and skipping over certain function calls. :p
 
True, that's a good point, however the function that is called that does the enciphering doesn't return anything, it's a void. I'm assuming the RC4 class replaces the byte array somewhere else in the function call, but I thought instead of trying to change a function further up in another class, it's just better to not call it at all. Also, and since the RC4 class is never fully init'd, if I left the call to encipher in there, when the function is called, I get a null reference, most likely as the RC4 class was never init'd to begin with.

I looked at some older releases and the RC4 function call has changed over the years, it used to return the encrypted byte array and set a local variable, and then the .writeBytes() would be called with the local variable passed as a parameter. As you could imagine, you could very easily fix that by either changing the variable that gets set, or just pass the un-encrypted byte array rather than the one that was encrypted.

It's pretty funny how hard they attempt to prevent you from manipulating the methods, and most of the time, it just a matter of swapping checks around and skipping over certain function calls. :p

It's a void because you were probably looking at the base class/interface and not the class that is used for the dependency injection.

And yeah it is pretty funny, when I've custom removed the hosts protection for example I'd just search for "domain" because that was an AS3 variable and thus couldn't be obfuscated - so those were easy to find.
 
It's a void because you were probably looking at the base class/interface and not the class that is used for the dependency injection.
Yep most likely.

And yeah it is pretty funny, when I've custom removed the hosts protection for example I'd just search for "domain" because that was an AS3 variable and thus couldn't be obfuscated - so those were easy to find.
Haha yep! And there's also the .connect() method for the socket too, nice and easy to find also! :p
 
Back
Top