Steganography banner.png/php for r34-r39 encryption

Gabrielle

She/Her - Be who you wanna be
Hello,

Habbo use(s/d) a banner (later supersecret) for their encryption. Essentially, the prime+generator is hidden in the image as data using steganography. I know capostrike made a script to read the banner from Habbo and get the right prime+generator.

The issue though is, Habbo's old banner obviously doesn't exist and even the link to their supersecret doesn't work anymore. And using the script and the banner.png I got from the SWF pack, I don't get correct data.

I assume the banner.png is basically useless, since the token that was used to save the banner.png is unknown (unless for some reason it's unused which I highly doubt).

I was looking at this:


Where the banner is decoded and stuff, but I'm not sure how to create a working image with a prime+generator encoded into it. I tried using capostrike's banner.php the client doesn't even work.

I hope somebody would be willing to help me get through the banner stuff so I don't have to modify the SWF to either change how the banner is decoded or change how the prime+generator are retrieved.
 
I was actually looking at this the other day to implement the DH key exchange in ION/Deltar for RELEASE 34.

Simply you just need to reverse the way it reads the banner.png pixels.

onHotelViewBanner loaded: https://github.com/ntuative/RELEASE...unication/demo/HabboCommunicationDemo.as#L510

decode function: https://github.com/ntuative/RELEASE...unication/demo/HabboCommunicationDemo.as#L140

It would be cool to debug the output that the flash client receives to ensure what is being ported has the exact same results, that's what I did when I successfully implemented encryption for Havana (release 28 and above for Shockwave) and used Director MX 2004.
 
My first idea was to read out the banner.png and use the correct prime+generator to make sure all the keys can be generated/calculated correctly. But I wasn't able to do this. I did vaguely remember being able to edit Habbo.swf so I can debug the banner decoding without ABC but I can't remember for the life of me how I did it
 
My first idea was to read out the banner.png and use the correct prime+generator to make sure all the keys can be generated/calculated correctly. But I wasn't able to do this. I did vaguely remember being able to edit Habbo.swf so I can debug the banner decoding without ABC but I can't remember for the life of me how I did it

RABCASM and then decompress with.

Code:
abcexport Habbo.swf
rabcdasm Habbo-0.abc
rabcdasm Habbo-1.abc
rabcdasm Habbo-2.abc
pause

Recompress with

Code:
abcreplace Habbo.swf 0 Habbo-0/Habbo-0.main.abc
abcreplace Habbo.swf 1 Habbo-1/Habbo-1.main.abc
abcreplace Habbo.swf 2 Habbo-2/Habbo-2.main.abc

rabcasm Habbo-0/Habbo-0.main.asasm
rabcasm Habbo-1/Habbo-1.main.asasm
rabcasm Habbo-2/Habbo-2.main.asasm

Also depending on the .swf the Habbo-2 line may not be needed for decompression andrecompression. I know for sure it wasn't required for release 39.

But in my honest opinion you're better off changing this line/method and edit the bytecode so to set var_75.mode to HabboConnectionType.const_1211 just before the switch case, or just replace the entire method as demonstrated in the last example.

So it could look like this:
(You'd need to know the bytecode to set the variable though. 😅)

In my opinion, this is the easiest method.

Code:
      private function onInitCrypto(param1:IMessageEvent) : void
      {
         var _loc6_:* = null;
         var _loc7_:* = null;
         var _loc8_:* = null;
         var _loc2_:IConnection = param1.connection;
         var _loc3_:InitCryptoMessageEvent = param1 as InitCryptoMessageEvent;
         var _loc4_:String = _loc3_.token;
         var _loc5_:Boolean = _loc3_.isServerEncrypted;
         var_1679 = new PseudoRandom(parseInt(_loc4_.substring(_loc4_.length - 4),16),65536);
         // THIS WAS CHANGED BELOW
         var_75.mode = HabboConnectionType.const_1211
         switch(var_75.mode)
         {
            case HabboConnectionType.const_382:
               _loc6_ = "";
               _loc6_ = _habboConfiguration.getKey("url.prefix",_loc6_);
               _loc6_ = _loc6_.replace("http://","");
               _loc6_ = _loc6_.replace("https://","");
               _loc7_ = _habboConfiguration.getKey("hotelview.banner.url","http:/\nsitename$/gamedata/banner");
               _loc7_ = _loc7_.replace("$sitename$",_loc6_);
               var_881 = _loc4_;
               var_165.loadBannerImage(_loc7_ + "?token=" + var_881,onHotelViewBannerLoaded);
               break;
            case HabboConnectionType.const_1140:
               sendConnectionParameters(_loc2_);
               break;
            case HabboConnectionType.const_1211:
               var_880 = generateRandomHexString(30);
               var_417 = new DiffieHellman(new BigInteger(_habboConfiguration.getKey("connection.development.prime"),16),new BigInteger(_habboConfiguration.getKey("connection.development.generator"),16));
               var_417.init(var_880);
               _loc8_ = var_417.getPublicKey(10);
               _loc2_.send(new GenerateSecretKeyMessageComposer(_loc8_.toUpperCase()));
               break;
            default:
               Logger.log("[HabboCommunicationDemo] Unknown Connection Mode: undefined");
         }
      }

Or this:

Code:
      private function onInitCrypto(param1:IMessageEvent) : void
      {
         var _loc6_:* = null;
         var _loc7_:* = null;
         var _loc8_:* = null;
         var _loc2_:IConnection = param1.connection;
         var _loc3_:InitCryptoMessageEvent = param1 as InitCryptoMessageEvent;
         var _loc4_:String = _loc3_.token;
         var _loc5_:Boolean = _loc3_.isServerEncrypted;
         var_1679 = new PseudoRandom(parseInt(_loc4_.substring(_loc4_.length - 4),16),65536);
         // THIS WAS CHANGED BELOW
         var_880 = generateRandomHexString(30);
         var_417 = new DiffieHellman(new BigInteger(_habboConfiguration.getKey("connection.development.prime"),16),new BigInteger(_habboConfiguration.getKey("connection.development.generator"),16));
         var_417.init(var_880);
         _loc8_ = var_417.getPublicKey(10);
         _loc2_.send(new GenerateSecretKeyMessageComposer(_loc8_.toUpperCase()));
      }
 
Last edited:
RABCASM and then decompress with.

Code:
abcexport Habbo.swf
rabcdasm Habbo-0.abc
rabcdasm Habbo-1.abc
rabcdasm Habbo-2.abc
pause

Recompress with

Code:
abcreplace Habbo.swf 0 Habbo-0/Habbo-0.main.abc
abcreplace Habbo.swf 1 Habbo-1/Habbo-1.main.abc
abcreplace Habbo.swf 2 Habbo-2/Habbo-2.main.abc

rabcasm Habbo-0/Habbo-0.main.asasm
rabcasm Habbo-1/Habbo-1.main.asasm
rabcasm Habbo-2/Habbo-2.main.asasm

Also depending on the .swf the Habbo-2 line may not be needed for decompression andrecompression. I know for sure it wasn't required for release 39.

But in my honest opinion you're better off changing this line/method and edit the bytecode so to set var_75.mode to HabboConnectionType.const_1211 just before the switch case, or just replace the entire method as demonstrated in the last example.

So it could look like this:
(You'd need to know the bytecode to set the variable though. 😅)

In my opinion, this is the easiest method.

Code:
      private function onInitCrypto(param1:IMessageEvent) : void
      {
         var _loc6_:* = null;
         var _loc7_:* = null;
         var _loc8_:* = null;
         var _loc2_:IConnection = param1.connection;
         var _loc3_:InitCryptoMessageEvent = param1 as InitCryptoMessageEvent;
         var _loc4_:String = _loc3_.token;
         var _loc5_:Boolean = _loc3_.isServerEncrypted;
         var_1679 = new PseudoRandom(parseInt(_loc4_.substring(_loc4_.length - 4),16),65536);
         // THIS WAS CHANGED BELOW
         var_75.mode = HabboConnectionType.const_1211
         switch(var_75.mode)
         {
            case HabboConnectionType.const_382:
               _loc6_ = "";
               _loc6_ = _habboConfiguration.getKey("url.prefix",_loc6_);
               _loc6_ = _loc6_.replace("http://","");
               _loc6_ = _loc6_.replace("https://","");
               _loc7_ = _habboConfiguration.getKey("hotelview.banner.url","http:/\nsitename$/gamedata/banner");
               _loc7_ = _loc7_.replace("$sitename$",_loc6_);
               var_881 = _loc4_;
               var_165.loadBannerImage(_loc7_ + "?token=" + var_881,onHotelViewBannerLoaded);
               break;
            case HabboConnectionType.const_1140:
               sendConnectionParameters(_loc2_);
               break;
            case HabboConnectionType.const_1211:
               var_880 = generateRandomHexString(30);
               var_417 = new DiffieHellman(new BigInteger(_habboConfiguration.getKey("connection.development.prime"),16),new BigInteger(_habboConfiguration.getKey("connection.development.generator"),16));
               var_417.init(var_880);
               _loc8_ = var_417.getPublicKey(10);
               _loc2_.send(new GenerateSecretKeyMessageComposer(_loc8_.toUpperCase()));
               break;
            default:
               Logger.log("[HabboCommunicationDemo] Unknown Connection Mode: undefined");
         }
      }

Or this:

Code:
      private function onInitCrypto(param1:IMessageEvent) : void
      {
         var _loc6_:* = null;
         var _loc7_:* = null;
         var _loc8_:* = null;
         var _loc2_:IConnection = param1.connection;
         var _loc3_:InitCryptoMessageEvent = param1 as InitCryptoMessageEvent;
         var _loc4_:String = _loc3_.token;
         var _loc5_:Boolean = _loc3_.isServerEncrypted;
         var_1679 = new PseudoRandom(parseInt(_loc4_.substring(_loc4_.length - 4),16),65536);
         // THIS WAS CHANGED BELOW
         var_880 = generateRandomHexString(30);
         var_417 = new DiffieHellman(new BigInteger(_habboConfiguration.getKey("connection.development.prime"),16),new BigInteger(_habboConfiguration.getKey("connection.development.generator"),16));
         var_417.init(var_880);
         _loc8_ = var_417.getPublicKey(10);
         _loc2_.send(new GenerateSecretKeyMessageComposer(_loc8_.toUpperCase()));
      }

I managed to get the first method working, here's the patched swf: https://www.mediafire.com/file/ntq1mkkx4sgevnn/Habbo_DH_PATCHED_RELEASE39.swf/file

This swf also has two other things patched as it was the one I used for Classic Habbo (removed the annoying flash plugin out of date alert popup when entering a room with a trax machine, and the forever loading news widget popup that happened on login).

Once it was decompressed I located the line below using JPEXS:

Code:
§_-MW§ = new §_-1u2§(parseInt(_loc4_.substring(_loc4_.length - 4),16),65536);

In this case, the bytecode when decompressed with RABCASM was:

Code:
      findproperty        QName(PrivateNamespace("_-15o", "com.sulake.habbo.communication.demo:HabboCommunicationDemo#0"), "_-MW")
      findpropstrict      QName(PackageNamespace("_-0RR"), "_-1u2")
      findpropstrict      QName(PackageNamespace(""), "parseInt")
      getlocal            4
      getlocal            4
      getproperty         QName(PackageNamespace(""), "length")
      pushbyte            4
      subtract
      callproperty        QName(Namespace("http://adobe.com/AS3/2006/builtin"), "substring"), 1
      pushbyte            16
      callproperty        QName(PackageNamespace(""), "parseInt"), 2
      pushint             65536
      constructprop       QName(PackageNamespace("_-0RR"), "_-1u2"), 2
      initproperty        QName(PrivateNamespace("_-15o", "com.sulake.habbo.communication.demo:HabboCommunicationDemo#0"), "_-MW")

And then I added this underneath it:

Code:
      getlocal0
      getproperty         QName(PrivateNamespace("_-15o", "com.sulake.habbo.communication.demo:HabboCommunicationDemo#0"),"_-1H4")
      getlex              QName(PackageNamespace("_-ie"), "_-0lk")
      setproperty         QName(PackageNamespace(""), "_-1qi")

Which translates to:

Code:
this.§_-1H4§.§_-1hr§ = §_-0lk§.§_-1qi§;

So now the function looks like this - and as you can see, the line I added now forces the SWF to use the last switch case, which you can see by the variable names, it was the development encryption toggle left in there by Sulake. 😂

Code:
      private function §_-0nT§(param1:IMessageEvent) : void
      {
         var _loc6_:String = null;
         var _loc7_:String = null;
         var _loc8_:String = null;
         var _loc2_:IConnection = param1.§_-bh§;
         var _loc3_:§_-KJ§ = param1 as §_-KJ§;
         var _loc4_:String = _loc3_.§_-0HR§;
         var _loc5_:Boolean = _loc3_.§_-1Rj§;
         §_-MW§ = new §_-1u2§(parseInt(_loc4_.substring(_loc4_.length - 4),16),65536);
         this.§_-1H4§.§_-1hr§ = §_-0lk§.§_-1qi§;
         switch(§_-1H4§.§_-1hr§)
         {
            case §_-0lk§.§_-18R§:
               _loc6_ = "";
               _loc6_ = _habboConfiguration.getKey("url.prefix",_loc6_);
               _loc6_ = _loc6_.replace("http://","");
               _loc6_ = _loc6_.replace("https://","");
               _loc7_ = _habboConfiguration.getKey("hotelview.banner.url","http:/\nsitename$/gamedata/banner");
               _loc7_ = _loc7_.replace("$sitename$",_loc6_);
               §_-0qb§ = _loc4_;
               §_-1Ex§.§_-0vb§(_loc7_ + "?token=" + §_-0qb§,§_-1D0§);
               break;
            case §_-0lk§.§_-0u3§:
               §_-0DA§(_loc2_);
               break;
            case §_-0lk§.§_-1qi§:
               §_-0LB§ = §_-1j0§(30);
               §_-1ut§ = new §_-07d§(new BigInteger(_habboConfiguration.getKey("connection.development.prime"),16),new BigInteger(_habboConfiguration.getKey("connection.development.generator"),16));
               §_-1ut§.init(§_-0LB§);
               _loc8_ = §_-1ut§.§_-y3§(10);
               _loc2_.send(new §_-1kW§(_loc8_.toUpperCase()));
               break;
            default:
               Logger.log("[HabboCommunicationDemo] Unknown Connection Mode: " + §_-1H4§.§_-1hr§);
         }
      }

There are slight differences between the bytecode output from JPEXES and RABCASM so I don't recommend copying pasting one from the other unless you're sure of what you're doing.

Luckily, you don't need to do the above because I already patched it. I'm just telling others so the knowledge doesn't get lost.

You need add this to your external variables (with your own calculated Diffie-Hellman prime and generator):

Code:
connection.development.prime=
connection.development.generator=

And then on the server when you receive INIT_CRYPTO (CN) you should send back the crypto parameters with your calculated public key and you should get back the server's calculated public key in response. 😄

Code:
2023-04-22T22:05:00.738 INFO  [Connection 0] - Received (INIT_CRYPTO): 206 / CNH
2023-04-22T22:05:00.739 INFO  [org.alexdev.havana.server.netty.codec.NetworkEncoder] - SENT: 277 / DU33647028824563241226977554417776[2]H[1]
2023-04-22T22:05:00.946 INFO  [Connection 0] - Received (GENERATEKEY): 2002 / _RIi19360252571018866670945585104226478459099373541397308172421011561208126335018052575175648756961133993352853994101761371127559023763230497537100066866887643849145106064696822494517525244891847795925923421308300687251458344216195058538598240282935094370790821456705965990837513550750024213582358381392746838916110267544301514497823371193543630640640173631173250755756186476113002208489257964334140874422017553584266991191206510543856870474758675604944427355024007810518617452977153855663600402945067078512484186310697568549062560629708458250256288658617948895219439391001373634986831124046187016499577197678275221258719
2023-04-22T22:05:00.948 INFO  [org.alexdev.havana.server.netty.codec.NetworkEncoder] - SENT: 1 / @A046031992269747801506766[1]
2023-04-22T22:06:00.743 INFO  [org.alexdev.havana.server.netty.codec.NetworkEncoder] - SENT: 50 / @r[1]
2023-04-22T22:07:00.744 INFO  [org.alexdev.havana.server.netty.codec.NetworkEncoder] - SENT: 287 / D_M[1]
 
Back
Top