Obfuscating an RCP Application

In this article I’m going to take you on a tour of the process I use to build a large-scale RCP application. Obfuscating an RCP application can seem like a big challenge, but it’s really not so bad.

 

Selecting an obfuscator

The first challenge is selecting the right tool for the job. The most important feature of an RCP application obfuscator (assuming it does a good job at obfuscating!) is that it can obfuscate multiple jars at the same time. Some tools expect you to obfuscate one jar at a time, and unless your RCP application contains only one jar, these tools will not work.

In my case, I chose a tool called Zelix Klassmaster. This obfuscator handles multiple jars, and also integrates nicely into a build process managed by Ant scripts. The process I describe is based on Klassmaster. I’ve also heard that the open-source obfuscator ProGuard can handle multiple jars at the same time, but I haven’t tried this tool myself. If anyone manages to get a similar process working with ProGuard, I’d appreciate it if you’d let me know.

The big picture

The big picture for an obfuscation process includes all of the jars that are distributed with your application. These jars can then be divided into two categories: those that will be obfuscated and those that will not.

  • Obfuscated – Jars that make up your own proprietary plugins
  • Non-Obfuscated – All third-party plugins and libraries, including the RCP plugins. This category may also include your own plugins that cannot be obfuscated for one reason or another.

Zelix Klassmaster operates on these two sets of jars, loading them all into memory at the same time and then obfuscating only those in the first category.

The build process

An obfuscation process begins after an RCP product has been exported either by the Product Export Wizard or by an automated build process. The exported application is then obfuscated using an Ant script and a related Zelix script. The Ant script controls the process, and it has three functions:

  1. Create lists of jars and other parameters which are then substituted into the Zelix script.
  2. Execute the obfuscate Ant task provided by the Zelix obfuscator.
  3. Replace the original plugin jars with the new obfuscated jars.

In the obfuscate Ant task, Zelix loads all of the plugin jars into memory and then obfuscates those in the list to be obfuscated. These jars are placed into a new folder (in my case a folder called “obfuscated”). These jars are then merged back into the plugin directory of the application.

Of course, if your plugins are not exported as jars, then this process will be a little more involved. Another good argument for migrating to the new packaging structure.

Excluding package names – choosing a naming convention

Exclusions are one of the trickiest part of obfuscating any application, and in particular an RCP application. A good general rule of thumb is that any class referenced in an extension or extension point should be excluded. This doesn’t mean that the code in the class won’t be obfuscated, but that both the package and class names associated with the class need to be excluded. I’ll deal with class names in a bit, but first I’d like to address the issue of package exclusion.

The important thing about package exclusion is that when you exclude a package name that is deep in your package hierarchy, you are required to exclude all other packages above it. This can make it difficult to exclude packages deep in your hierarchy without abandoning package name obfuscation entirely.

The solution I’ve come up with is to make a clear distinction between obfuscated and non-obfuscated packages in the package naming conventions I use. It turns out that this convention fits nicely with another naming convention used in the Eclipse code itself that separates code making up the public API from internal code. Basically, package names that can be obfuscated are always considered to be “internal” packages and will never contain classes referenced by extensions or extension points. Public packages are excluded from package name obfuscation and may contain excluded classes.

To make this convention as useful as possible (to allow as many packages to be obfuscated as possible), this distinction between public and internal needs to be made very high in the package structure. My convention differs from the Eclipse convention in that I make this branch even before the plugin name portion of the package structure. Here is an example of the convention I use.

  • Public package – com.marketcontours.plugin-name.subpackage
  • Obfuscated package – com.marketcontours.internal.plugin-name.subpackage

Of course, only public packages can be included in the “Exported Packages” list for a plugin. If you specify a package that will be obfuscated, the plugin will not be able resolve the package name at runtime. This is just one more reason to align the concepts of public/internal packages and non-obfuscated/obfuscated packages. And finally, by maximizing the amount of classes in internal packages (which is a good thing to do for many reasons), we can keep the amount of unobfuscated package names to a minimum.

Excluding class names – a new properties file

The second challenge is to exclude the names of classes and interfaces referenced by extensions and extension points. I’ve found that the easiest way to do this is to track the exclusions at the plugin level with a new properties file called obfuscation.properties. Whenever I need to add a new exclusion, I put it into this property file, and then at build time these exclusions are combined by Zelix into a master exclusion list. The exclusion format I use is particular to Zelix, and is meant to exclude both the package and class names of the classes in question. To see the details of what the exclusions look like, check out the sample properties file included in the download below.

Remember that you need to include any class referenced in either your plugin.xml or manifest.mf files. This includes the Plugin (or Activator) class, if you use one.

Also, for now, each obfuscation.properties file must be added by hand to the the Zelix script. So every time you create a new plugin, you’ll need to edit the script. One area for improvement would be to have the Ant script create the list of properties files at build time and substitute them into the Zelix script.

Obfuscation and localization

There are a few extra gotchas that need to be handled when obfuscating an RCP application. The main one is that your localization classes (if you are using the new NLS class) need to be excluded from obfuscation. Fortunately, this can be done with one exclusion in the Zelix script:

exclude *.* + extends org.eclipse.osgi.util.NLS;

This line causes any class that extends the NLS class to be excluded from obfuscation.

A second issue also relates to NLS classes. When Zelix does flow-control obfuscation (for more information on this feature, see the Zelix site), it adds a static variable to various classes. This is usually not a problem, but with NLS classes it is. The NLS static initializer attempts to resolve the these static variables which it thinks are localized strings, and this initialization fails for these new variables added by Zelix. This doesn’t cause visible runtime problems, but it does pollute the logs. But again, this problem can be solved by adding a single exclude to the Zelix properties file:

obfuscateFlowExclude *.* extends org.eclipse.osgi.util.NLS *;

Try it for yourself

That ends this brief summary of my obfuscation process. If you would like to try this out for yourself, you can download the scripts that I use. Applying them to your own build process should be pretty straightforward, but if you have any questions, feel free to ask me questions.

Download obfuscation scripts

Advertisements

30 Responses to Obfuscating an RCP Application

  1. Tonny Madsen says:

    Interesting, but I feel it really should be easier 🙂 /tonny

  2. Jeff McAffer says:

    Cool. We played with this sometime ago and found a number of the same challenges. Did you see much space savings? Much of the challenge comes from identifying things that should not be obfuscated. API of course is one example. Here the authoritative answer comes from the manifest.mf. The presence of an Export-Package entry for a package that has the x-internal:=true directive indicates that the package is NOT API. Similarly, the absence of an Export-Package entry indicates that the package is not API. While the Eclipse team still uses “internal” in the package name, the manifest rules.

    It would be cool to look at an integration of your approach with the information that is available in PDE to drive the creation of the obfuscation.properties. This would likely capture most of the cases.

  3. […] and file sizes In response to my last post on obfuscation, Jeff McAffer asked if I had seen any space savings in the obfuscated code. Well l thought it would […]

  4. pjpaulin says:

    I’ve just posted a response to Jeff’s space savings question.

    https://rcpquickstart.wordpress.com/2007/07/13/obfuscation-and-file-sizes/

    Also, I agree it would be cool to add PDE tooling for this. It would be fairly trivial to create an ANT task that would spin through the PDE metadata and spit out a list of exclusions. The only issue would be that every obfuscator has it’s own exclusion syntax.

  5. garylai says:

    I have tried the way that this page introduced to obfuscate an rcp
    application. But failed. My rcp application is composed by several
    plugins, and one of them is used to run junit test. But after obfuscating,
    these junit test cannot pass mostly. Anyone can help or gives an example?
    Thanks

  6. pjpaulin says:

    Hi Gary,

    Can you be more descriptive about what’s happening? Are you getting specific exceptions when you run the tests?

  7. garylai says:

    Hi pjpaulin,
    When I run the tests, most of them said that it couldn’t find the classes definded in another plugins. But I am sure the classes are where they should be. Here are my steps to do the obfuscating:
    1.use proguard to obfuscate the rcp (most are directories), and store the new outputs in the new path(Can I store the new outputs in the same way?).
    2.use the new outputs to replace the old ones.
    3.execute the junit test.
    I also want to print you the map of the my rcp next time, thanks for your help!

  8. pjpaulin says:

    Hi Gary,

    One thing to check is that you are *not* obfuscating package names (classes are ok) which are being exported by a plug-in. The plug-in manifest is not updated to match the obfuscated package names and this can cause the type of cross-plug-in classloader issues you’re seeing.

  9. garylai says:

    Hi Pjpaulin,
    Oh, you are rigth. I have obfuscated package names. But it is our purpose to do it. Is there any way to solve this problem. And I can’t find how to forbidden obfuscating package names in proguard 4.0.

  10. pjpaulin says:

    Hi Gary,

    As I say in the article, it’s really important to break your plug-ins into an API section in which package names are exposed, and an implementation section in which package names are obfuscated. I use “internal” to indicate implementation packages, and I put that keyword as high in the package name structure as I can, ideally right after your domain (e.g. com.mydomain.internal.pluginname.etc). This allows you to obfuscate as many package names as possible. It also allows you to set up a simple rule that obfuscates all package names starting with com.mydomain.internal.*.

    JUnits sometimes present special problems, though. Are your tests in a separate plug-in? Are they in a fragment? And what class is trying to refer to the package containing the unit tests? Do you have a master test suite that runs tests in other plug-ins? In that case, one solution is to have a plug-in level test suite that runs the tests in a specific plug-in. This test suite lives in a non-obfuscated package, and it then refers to all the plug-in tests in obfuscated packages. The master test suite should then be able to see the plug-in level test suites and run them.

    As for excluding package names with ProGuard, I’m sorry to say I haven’t worked with the tool. I’m sure there must be a way to accomplish this, though.

  11. garylai says:

    Hi Pjpaulin,
    Thanks for your help! The Junits tests run correctly before obfuscating. And I have found out where the problem is: I use hibernate in the project, and after obfuscating, the mapping relation can’t be resolved. I am working with it!

  12. garylai says:

    Hi Pjpaulin,
    I hava obfuscated our project sucessfully. Thanks for your help! But I am facing another problem, which is in the junit test or java exception after obfuscating. If the test can’t pass, or classes throw exception, the original names will appear in the report. For example:
    org.eclipse.core.runtime.CoreException[1]: java.lang.NullPointerException
    at com.tigerknows.c.a.a.d(GUIHardwareKeys.java:31)
    at com.tigerknows.c.a.a.b(GUIHardwareKeys.java:76)
    at com.tigerknows.gui.f.(GMApplication.java:33)
    The name in the parenthesis is the original name. I don’t want to exposure the original name to the users. Do you hava any suggestions? Thanks!

  13. pjpaulin says:

    Hi Gary,

    There is a property in the your build.properties file called javacDebugInfo. If you set this to false, the extra debug information should no longer appear in stack traces.

  14. garylai says:

    It is unuseful, because I don’t want to exposure the original names. If somebody changes this file, he/she can easily see the original names. I think obfuscation is to change the name of the classes, fields, methods. Why do the orginal names appear in the exception after obfuscation. Shouldn’t the orginal names in the classes change to the obfuscating names?

  15. pjpaulin says:

    Hi Gary,

    Debug info is added at compile time and before obfuscation occurs. An obfuscator has to explicitly obfuscate the debug info, and it looks like ProGuard may not be doing this. Zelix Klassmaster obfuscates debug info. Others, such as Allatori, allow you to specify whether debug info is obfuscated.

    You’ll need to investigate whether ProGuard can be set up to obfuscate debug info or you may need to switch obfuscation tools.

  16. garylai says:

    Hi Pjpaulin,
    I found something in the proguard manual: The program remains functionally equivalent, except for the class names, method names, and line numbers given in exception stack traces. What a pity, proguard cannot solve the problem what I meet.

  17. garylai says:

    Hi Pjpaulin,
    Sorry for troubling you without investigating seriousliy. I have found the option in proguard that can be set up to obfuscat debug info.

  18. Ed says:

    Hi Patrick,

    Thank you for the useful help on obsfucation of RCP apps. I have tried to obsfuate an RCP app using Proguard and here is what I found. I was able to finally get Proguard to report a successful run, however, i was able to decompile the code using JarObserver very easily. This made me wonder what is really being obsfucated or not. So, I tried using Zelix Klassmaster and had issues with memory size, I am trying again with -Xmx1024M option for the Java VM. However, I am using Zelix from the GUI and not having luck (does not recognize the dependent jars on my classpath). When I did a test with both Proguard and Zelix on a simple one class project, Zelix looks like it did actually obsfucate (class names are renamed to aa,ab, etc), however not the case with Proguard. I have played around with the excludes on both obsfucators and can not seem to get Proguard to work. So I am giving up on Proguard and using Zelix. ** However, i was not able to download the scripts you posted for Ant tasks with Zelix, the link seems to be dead? Any suggestions or comments?

    Thanks
    Ed

  19. pjpaulin says:

    Hi Ed,

    I’m not too familiar with Proguard, so I can’t help much there. I did fix the link, though (thanks for catching it!). Once you get the scripts, feel free to ask questions about how to get them working.

    — Patrick

  20. Ed says:

    Hi Patrick,
    Thanks for the obfuscation scripts. I am trying to get things to work, however, there is fundamentally something I am doing incorrectly. When I create a product, I choose the option to package the product as a jar file. In this jar file is my lib file with all the 3rd party jars and one of my own jars (that I want to obfuscate). The trouble seems to be that I can not obfuscate a “jar inside a jar”. In other words, I can obfuscate my “utility jar” and the internal code inside my RCP app, but when I then use these two obfuscated jars in place of the lib/myUtility.jar and my RCP jar, the code no longer runs. The reason seems to be that when I obfuscate the obfuscation is using the jars inside of lib (and that gets deployed with the product as is). Should I not be building my product jarred up and instead create the product in packages? Any suggestions. I am trying to get Zelix up and running now, however, it looks to me like this has the same issue — the deployed product has a lib/*.jar embedded inside of it. I must not be using the “binary build” correctly — that is to deploy the 3rd party jars external to the product.jar? Any help is appreciated greatly !

    Thanks

    Ed

  21. pjpaulin says:

    Hi Ed,

    It’s best to avoid embedded jars if you can, and I can see how Zelix could have issues with this. As a general rule, I separate out third party jars into their own plug-in(s). I also prefer to have a single plug-in per jar, though I break this rule for complex multi-jar libraries such as Hibernate. And if I combine multiple jars in a plug-in, then I distribute the plug-in as a folder instead of a jar.

    This may not be the answer you wanted to hear! But I can tell you that separating out 3rd party libraries from your own code and packaging them as discrete plug-ins will make your life much easier.

    Hope this helps.

    — Patrick

  22. Cool comment.
    I think you’ll read our site..
    Thanks

  23. Dmitry says:

    Would you use a native compiler instead of an obfuscator?

  24. Patrick says:

    Hi Dmitry,

    I’ve never used a native compiler, but I’d be willing to consider it as an alternative to obfuscation. I’m not using obfuscation in my work at the moment, but I’m going to try out native compilation if the need arises again.

    — Patrick

  25. Dmitry says:

    Hi Patrick,

    Thanks for your reply. Check out the link under my name – I’d be most interested in your opinion on our efforts.

    Dmitry

  26. Ramapriya says:

    Hi Patrick,

    I was able to obfuscate, but the the obfuscated code didn’t work in RCP.

    Thanks
    Ramapriya

  27. Patrick says:

    Hi Ramapriya,

    From the email you sent me, it appears that your classpath is not complete. There are actually two paths that need to be set. One is for the set of plug-ins that are needed for compilation but which should *not* be obfuscated. In the build script these are referenced in the “non.obfuscated.jars” path.

    The other path refers to those jars which should be obfuscated. In the build script these are referenced in the “obfuscated.jars” path.

    You can download the scripts using the link at the very bottom of the original post. Hopefully these scripts will help you get things working.

    — Patrick

  28. Justin says:

    Hi Patrick,

    I’m currently evaluating Klassmaster for my RCP app and curiously it appears to be rewriting the manifest and plugin.xml with obfuscated package names (I’ve just told it to obfuscate everything as a first ‘see what happens’ test!). Presumably you didn’t encounter this? I’ve emailed Zelix as I’m not sure if this is a new feature, however the Require-Bundle entries now also has unnecessarily mangled entries, due to bundles commonly sharing the root package name of the contents I guess. This could prove to be a very useful feature if it can be controlled/tweaked though!

    Regards,
    Justin

  29. Patrick says:

    Hi Justin,

    I haven’t worked with Zelix in a while now, and perhaps they’ve made changes that allow for modifications of non-Java resources. If so, it would certainly make things easier. I would definitely be interested to hear how this works for you. Good luck!

    — Patrick

  30. Dmitry says:

    Now it’s official:

    Excelsior JET 6.5 Protects Eclipse RCP Applications From Reverse Engineering and Tampering

    http://www.excelsior-usa.com/blog/excelsior-jet/java-and-osgi-runtimes-blended-together/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: