Wednesday, April 21, 2010

Defeating Nitro-LM

I have been working on a flash project that calls for some basic intellectual property protection mechanisms, which led me to discover a neat little commercial encryption framework called Nitro-LM.

Before I go any further, I want to point out two things. First, I am all for open-source and sharing codes (which is the main reason why I started this blog), but there are definitely situations when you want to encrypt part of the code, for your own benefits as well as the protections of your users. For instance, let's say you have developed an awesome online game, you certainly do not want a cheater to reverse-engineer your code, exploit it, and ruin the fun for other players, right? Second, I admit that client-side code is never 100% secured. This is especially true for flash applications, which can easily be decompiled. Even if you apply all the encryption and obfuscation technologies in the world to protect your code, an overly zealous hacker can still, given enough time and effort, reconstruct the source code from bytecode.

Therefore, let's agree that the purpose of any intellectual property protection mechanism is not to prevent others from exploiting your code, but rather to make the process of reverse-engineering less cost-effective than actually playing by the rules. That's said, I set out to test if this Nitro-LM framework is enough for my task.


I found this post by Thomas Burleson (thank you, nice post by the way) demonstrating the use of Nitro-LM to encrypt a remote shared library. My goal is to see how easy (or difficult) to obtain the decryption key.

First, let's simply pass the SWF file through a decompiler. VoilĂ , the actionscripts, both of the custom preloader and of Nitro-LM, are all easily readable.


As you can see, the doorway to the decryption key lies in these three lines of codes.
licenseClient = factory.getInstance();
licenseClient.addEventListener(LicenseClientEvent.LICENSE_RESPONSE, onResponse_DecryptionKeyLoaded);
licenseClient.requestKey(PRODUCT_ID, PRODUCT_VERSION);

The values of PRODUCT_ID and PRODUCT_VERSION are shown in plain text, no secret here. But first, we must create the factory class, shown here.
ProductKeys.putPublicKey(PRODUCT_ID, new PRODUCT_KEY());
factory = new LicenseClientFactory(onInitializedNitroLM);

Getting the PRODUCT_KEY can be a bit tricky at first, if one depends solely on the output of the decompiler. As you can see, the actionscript block for the PRODUCT_KEY class contains no useful information.
package tb.preloader.nitrolm
{
    import mx.core.*;

    public class NitroDecryptor_PRODUCT_KEY extends ByteArrayAsset
    {

        public function NitroDecryptor_PRODUCT_KEY()
        {
            return;
        }// end function

    }
}

Luckily, anyone with a little bit of experience can recognize that the meat of the class is embedded as binary data. After combing through the exported assets, I found the product key (DefineBinaryData TagID = 7).

Next, I need to create the factory class. Looking into the LicenseClientFactory class, we can see similar binary embedding security measures.
public class LicenseClientFactory extends EventDispatcher
    {
        private var b64:Base64 = null;
        private var debug:Boolean;
        private var _licenseClient:ILicenseClient = null;
        private var blob1:Class;
        private var blob2:Class;
        private var initialized:Boolean = false;
        private static var z:String = null;

        public function LicenseClientFactory(param1:Function, param2:Boolean = false)
        {
            blob1 = LicenseClientFactory_blob1;
            blob2 = LicenseClientFactory_blob2;
            this.debug = param2;
            this.addEventListener(Event.COMPLETE, param1);
            var _loc_3:* = new blob1() as ByteArray;
            z = new String(Base64.decode(new String(new blob2())));
            var _loc_4:* = _loc_3.readInt();
            _loc_3.position = _loc_3.position + _loc_4;
            var _loc_5:* = new ByteArray();
            _loc_3.readBytes(_loc_5, 0, 8);
            _loc_3.position = 104;
            var _loc_6:* = new ByteArray();
            _loc_3.readBytes(_loc_6);
            var _loc_7:* = Crypto.getCipher(z.split("|")[1], _loc_5, Crypto.getPad(z.split("|")[2]));
            Crypto.getCipher(z.split("|")[1], _loc_5, Crypto.getPad(z.split("|")[2])).decrypt(_loc_6);
            var _loc_8:* = new Loader();
            var _loc_9:* = new LoaderContext(false, ApplicationDomain.currentDomain);
            if (new LoaderContext(false, ApplicationDomain.currentDomain).hasOwnProperty("allowLoadBytesCodeExecution"))
            {
                _loc_9.allowLoadBytesCodeExecution = true;
            }
            _loc_8.contentLoaderInfo.addEventListener(Event.COMPLETE, lsc);
            _loc_8.loadBytes(_loc_6, _loc_9);
            return;
        }// end function
}

Using similar methods as before, we can obtain blob1 (DefineBinaryData TagID = 5), and blob2 (DefineBinaryData TagID = 4). It turns out, blobl1 is an encrypted SWF file containing the implementation of the LicenseClient class and blob2 is a base64-encoded string contains the decryption key for blob1.

At this point, you can simply plug in blob1 and blob2 to the above code as embedded resource, then create a new LicenseClient from the factory, insert the PRODUCT_ID and call the requestKey method to obtain the decryption key.

For the sake of curiosity, here is what you get after base64-decoding blob2.
a3MqamtkajIjQCUoVygmQERzamY7bEAjJShTWktTKCNFS1NHc2pzMDllMzM
|blowfish-ecb
|pkcs5
|com.simplifiedlogic.nitrolicense.LicenseClient

You can also recreate the unencrypted LicenseClient SWF by running blob1 and blob2 through the above decryption function. Finally, decompile the LicenseClient SWF to look into the client logic.


All in all, it took me one evening to reverse-engineer the Nitro-LM encryption mechanism and to obtain the decryption key. It was fun and not without challenges, but ultimately I wouldn't depend on Nitro-LM to protect national secrets.

If you have any thoughts and ideas on intellectual property protection, feel free to join in. Hope you enjoy this.

2 comments:

  1. To be clear, you defeated the discontinued product Nitro-LM Lite. Contact me via e-mail if you're interested in taking a look under the hood of the upcoming LM Enterprise release.

    ReplyDelete
  2. Hello,

    Interesting article, I'm trying to secure a SWF file at the moment, that's how i got to your website. I'd like to ask is you used Sothink's SWF Decompiler to get the "DefineBinaryData", because when i check my SWF file and export the resources, i get a 28 byte SWF file (or a larger, but still empty FLA)

    Thanks,
    Z.

    ReplyDelete