Compiler-Based Mobile App Security vs. Mobile App Wrappers
Mobile application protection puts the necessary lock on the application package you release. It prevents brand damage and revenue loss from threats such as back-end abuse, app cloning, piracy, IP theft, reverse engineering, and more. While there are multiple well known tools available to protect your mobile app, they are, of course, not all created equally.
Selecting an application protection tool should not be a checklist exercise with obscure feature names. Rather, it should start with choosing a proper foundation! In this article we explain the two groups of mobile app protection solutions available in the market. We’ll describe how they fundamentally work and why the group of compiler-based solutions is uniquely qualified to provide that foundation.
One group, mobile app security wrappers, will add a protected library with security primitives to your application. Typically, these solutions will instrument your original code with the required calls into the new library. The other group is compiler-based solutions which, on the other hand, will rebuild your original code completely and weave in multiple layers of protections in addition to hardening your application code.
So which of these groups fits your use-case? In the sections below, we’ll first paint a more complete picture of both groups, afterwards we discuss benefits, challenges, and appropriate use cases of each type.
Mobile app security with wrappers
Conceptually, wrappers are typically combinations of a regular Android/iOS SDK and some application package level post-processing. The SDK serves two main purposes:
- Providing the application developer with some security controls, e.g. root detection
- Undoing transformations at runtime, which post-processing logic puts in place, e.g. code or asset encryption.
An important point to highlight here is that, just like any other regular SDK, there is the clear 1-to-many distribution approach. A single version or build of the SDK will be used to protect all customers.
A second SDK property that’s inherently shared by these types of solutions is the ‘separation of concerns.’ All library-provided logic is cleanly separated from your main application code. The communication between the two happens through a limited, by design, very visible set of channels. This separation exists on two abstraction levels: on the package file content level as a clearly separate library, and on the code level as a set of clearly separate functions. Though a mitigation for the first one could be static linking, this is typically not possible due to technical constraints.
Some wrappers go a small step beyond combining a library with package level post-processing and include some rudimentary code patching. Sometimes called ‘hybrid solutions’, these efforts are taken in an attempt to unlock some of the typical advantages of compiler solutions (described below). Some available examples include:
- ‘unbreaking’ application code from the included library for tighter coupling,
- automatically calling into the additional library from the application code to try and remove some of the security bottleneck.
These patches are basic pattern matching-based replacements and should not be confused with proper recompilation-based solutions. The need to maintain the wrapper approach advantages prevents the required depth needed to make the code patching effective.
Compiler-based mobile app security solutions
Solutions in the compiler-based group will replace/repeat and enhance a step in the app compilation pipeline. Different solutions use different abstraction levels to apply their transformations (e.g. source-to-source, AST, IR, machine code). Though we won’t address the technical nuances of each abstraction level in this post, it is worth noting that there is more depth here to consider as it relates to leveraging a compiler-based solution.
Moving on, regardless of the chosen abstraction, the compiler-based approach enables analysis and code manipulation techniques fundamentally out of reach with any library-centric approach as seen in wrappers. These building blocks, in turn, unlock many of the advanced protection features that make up a modern software protection scheme. This is crucial because, in this way, compiler solutions level the playing field between attacker tools and the security solution, ensuring compiler solutions are not at a conceptual disadvantage vs. the attacker.
The major steps taken by compiler solutions are the following:
- Parse and model the code and data, application package, and metadata
- Reconstruct and derive high-level abstractions, e.g. control flow
- Add to and transform the instructions
- Rebuild and repackage the application
Several of these steps dictate some of the inherent properties not found in wrappers. First of all, having to parse, model, reason and rebuild will always take more time than adding an additional library to your application. This results in comparatively longer build times than library-based approaches. Second, having to parse and model your application and its metadata means that this approach is more sensitive to changes in your tech stack, like switching languages or upgrading versions.
Wrappers vs. compilers: The key differences
Now that we understand both types of solutions, let’s explore some of the pros and cons of the earlier discussed characteristics.
One-to-many approach
As stated above, wrappers in general need no or very little information about the application they are protecting. They work 1-to-many, essentially just ‘packing’ all apps with the same protection library over and over again. This enables some quick wins, though they come at a great cost. The table below summarizes the trade-offs of this approach, the benefits of being a “pro” for app security wrapping solutions, and the disadvantages of being a “pro” for compiler-based solutions.
Wrappers
|
Compiler-based |
Depending on the solution, there is a potential initial integration advantage for your team since the build times are generally faster than compiler solutions. | Need for proper modeling of your app and recompilation leads to, on average, longer build times. |
It’s impossible for a library-based solution to really tie the protection to the application internals, even though some hybrid approaches try. This leads to a glaring security issue, i.e. attacks on these solutions are inherently easy to scale. By using the same lock for all customers, an attacker only needs to learn how to break one to unlock them all. |
Compilers inherently recreate all your application code, which presents the opportunity to interweave the security controls. With minimal effort, these additions are randomized in semantics, locations, and structure. This property enables two crucial aspects of application security:
|
Need for dynamic configuration. Since it’s infeasible to ship or generate a new SDK for every possible combination of the configurable security controls, wrappers have to resort to runtime configuration. Technically, this means your protected application will be packaged with a protected piece of data used to decide whether the SDK should, for example, execute root detection or not. Tampering with this critical bottleneck offers attackers a straightforward way to disable all configurable controls. These issues have already popped up at several security conferences. |
No one-size-fits-all prebuilt artifacts are required, allowing configuration to be interpreted at protection-time to construct and apply bespoke security controls. Effectively, the discussed configuration bottleneck is not available in the protected application. |
Skipping application & code modeling
Compiler solutions, depending on the exact approach, have to reconstruct and model a lot of information about the application they protect. This differentiator again leads to several (dis)advantages across our two groups. In the table below, benefits of the modeling are a “pro” for compiler solutions.
Wrappers
|
Compiler-based |
More likely to support new technologies without requiring an update due to the library approach and the absence of insight into your application. | Requires R&D and updates from the mobile security solution provider to maintain proper modeling of your app when using new technologies. |
Generally, no configuration needed (or even available) beyond expressing your intent, making it more simple to apply. This is possible because the superficial security controls do not have to care or know about the actual application internals. In a sense, this is similar to the no-code app development concept due to necessity; a lack of depth in understanding of the application structure requires any security control to be very broad and generic. |
Configuration is typically necessary to augment the solution’s understanding of the application it needs to protect. This ties back directly to the required deeper analysis and modeling. |
By not going to the same level of depth as a compiler-based solution, attackers are not forced to either! Example: The concept of ‘code obfuscation’ is a set of techniques used to harden other controls or prevent IP theft in itself. Library solutions claiming this capability have to resort to blanket code encryption; they wrap the application code in a thin encryption layer. The packer here can be unpacked. |
Conceptual availability of the same abstractions, tools and techniques as reverse engineers. Example: Code obfuscation fundamentally removes or changes semantic information in the code, changes and shuffles instructions, control and data flow, etc. The transformations can often not be reversed or force the use of complex tools and techniques to approximate the original input. |
When to use a wrappers vs. compiler-based solution
Overall, we’ve learned that app wrappers can potentially be a bit easier to set up and would probably have a smaller impact on your development flow. In addition, the reduced complexity and vendor R&D requirements should make them the cheaper solution. However, after a deeper look, it’s clear that these wins are only possible because of the many shortcuts taken with the library approach. While this would often be a net positive, this reasoning doesn’t apply to mobile application security at all!
So when can you use this approach to protect your mobile application? It’s a solid first step if your app is built with technology that no compiler-based solution supports yet. In addition, if your app is temporary (used for events, or various one-off instances) and the threat model allows for it, you could consider wrapping your application.
To summarize: Though it may seem counterintuitive, some complexity is actually a good thing for mobile application security. Avoiding it by design puts you at a fundamental disadvantage because it leaves your defense inherently lacking the depth it needs in a fast evolving cat and mouse game.