Tools don’t hack apps, hackers do: Securing Android apps against Frida
Search for lockpicking and you’ll see that there’s no shortage of suppliers ready to serve locksmiths and hobbyists, each community having a perfectly legitimate need. Is there any reason to believe that burglars don’t shop the same sites?
Hackers are developers and they have a long history of enthusiastically embracing and adapting development (and DevOps) innovations to speed their work, extend their reach, and ship software – the only difference is that they’re more likely to use those development tools and platforms on YOUR software rather than on their own. Static code analysis tools, debuggers, and even public bug tracking databases are all go-to hacker resources.
Any comprehensive app-security strategy must include controls to prevent or at list significantly reduce the unauthorized use of development tools on (or against) applications. If you’re looking to tie this requirement back to regulations and compliance obligations, these topics typically fall squarely inside unauthorized privilege escalation or non-privileged user access to privileged utilities and tools.
Let’s take a closer look at an archetypal example, Frida, and drill into the controls that have proven to be effective in limiting its unauthorized use.
According to the Frida website, Frida is a “Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers.” A dynamic (binary) instrumentation toolkit (such as Frida) injects instrumentation code at runtime that can reveal and change the behavior and state of a targeted application throughout its execution.
The injected code executes as part of the normal instruction stream after being injected, but is transparent to the application that it's been injected into – making it both a legitimate alternative to debuggers and a powerful hacker tool.
Further, to encourage adoption and maximize its legitimate value, Frida has published an open API itself and built a suite of productivity tools all based in large part on this core technology. These include:
- frida-ps: a command line tool for listing processes.
- frida-trace: a command line tool to dynamically trace function calls.
- fridump: a universal memory dumper tool. Aimed to dump accessible memory regions from any platform supported by Frida.
- frida-discover: a tool for discovering internal functions in a program. It dynamically instruments every thread in a given process and stalks every called function during process execution, trying to discover internal functions like statically linked functions.
This is so invasive! Why not just ban these tools? These are three of the legitimate use cases (offered on the Frida website) where Frida is especially effective versus alternative approaches (thus justifying their existence).
- You’re building a desktop app which has been deployed at a customer’s site. There’s a problem but the built-in logging code just isn’t enough. You need to send your customer a custom build with lots of expensive logging code. Then you realize you could just use Frida and build an application-specific tool that will add all the diagnostics you need, and in just a few lines of Python. No need to send the customer a new custom build - you just send the tool which will work on many versions of your app.
- You’d like to build a Wireshark on steroids with support for sniffing encrypted protocols. It could even manipulate function calls to fake network conditions that would otherwise require you to set up a test lab.
- Your in-house app could use some black-box tests without polluting your production code with logic only required for exotic testing.
Frida is free, it’s useful, and, with the help of its extended development community, it’s getting easier and easier to use.
In-app Protection: What a development organization can do to secure their work against unauthorized monitoring and modification
Let’s begin by looking at a specific use case. Hail Frida!! The Universal SSL pinning bypass for Android applications offers a step-by-step guide (using Frida) to bypass an Android application’s implementation of SSL pinning (which is a technique used to ensure that an app is communicating securely with a verified service/server) for penetration testing. The use case is legitimate, but the identical steps can also be used to implement a “man in the middle” attack (an attack where the attacker secretly relays and possibly alters the communications between two parties who believe they are directly communicating with each other).
Effective risk management strategies combine preventative controls, detective controls, and responsive controls (also referred to as defensive or corrective controls) to present a layered approach to minimize the likelihood of an actual loss event (successful exploit) without incurring excessive cost or complexity in the process. In-app protection is no exception to this “golden rule.”
In order to run the injection script, it must be run on a rooted mobile device (or an emulator).
One way to prevent the injection of instrumentation is to prevent your application from running on a rooted device or inside an emulator – but developers often need to run their app in an emulator (or on a rooted device) during development and testing.
The requirement is that the app should not be run on an unauthorized rooted device or an emulator after deployment.
PreEmptive Protection DashO for Android & Java Root Check and Response & Emulator Check and Response
PreEmptive Protection products combine obfuscation transformations with “Checks.” A Check is a piece of code that is injected into application methods post-code but prior to signing and publication. At runtime, they "check" whether a particular condition is true and, if so, trigger various responses.
Unlike the dynamic form of code injection employed by Frida, this injection is entirely controlled by the original development organization and fully integrated into its build chain. The injected code is a combination of code included as a part of PreEmptive Protection (the “check” component and the default “response” code) and, optionally, customized response code written by the development organization.
Each component of the PreEmptive Protection suite of products includes a collection of relevant checks. PreEmptive Protection™ DashO™ for Android & Java (DashO) options include:
- Emulator Check and Response – (is the application running on an emulator?) and
- Root Check and Response – (is the application running on a rooted device?).
Configuring DashO to inject these two controls dramatically reduces the exposure stemming from Frida and similar dynamic instrumentation and hooking frameworks. However, no preventative control is perfect. New rooting and hooking kits are constantly being developed and some will inevitably find a way to avoid detection. …and because of this reality, the layered approach of prevention, detection, and response is essential.
Continuing with the Frida Android tutorial, the Frida server is moved to the device (or emulator), the script is run, and your application is now “hooked.” The SSL Pinning control you had painstakingly included in your app has been defeated.
Fortunately, PreEmptive Protection - DashO also includes a Hooking Check. The Hooking Check runs through a number of heuristics and attempts to determine if your app is somehow being run after being hooked (note, for a complete list of the Checks currently: See Runtime Checks inside PreEmptive Protection for Android).
What is the appropriate response when your app detects that it is being run on a rooted device or inside an emulator? Certainly, there are numerous scenarios other than a threat actor beginning the process of hooking your app with Frida. What if your app is a consumer banking app or an app helping to monitor a patient’s insulin level? It is reasonable to expect that different responses would be required in these two scenarios – and neither are likely to be a default behavior conceived and implemented by a third-party software vendor. This is why the ability to include custom responses can be so valuable (having said that, default responses are ideal for simple responses, minimizing custom development effort).
Any Check can provide its own custom response, so an application can defend itself based on many different conditions, and at many different points in its lifecycle.
The problem of preventing unauthorized use of development tools in the wild is complex and constantly evolving. While development teams are sometimes tempted to develop their own runtime defenses, this is almost certainly a losing battle in the long run. Effective detection of debuggers, tampering, rooted devices, etc. requires constant evolution in step with the changing toolkits and frameworks. We can say this with some measure of confidence because this is our full-time job – and it is a full-time job. Learn more about mobile in-app protection.