Reverse engineering is the processes of extracting knowledge or design information from anything man-made and re-producing it or re-producing anything based on the extracted information. The process often involves disassembling something (a mechanical device, electronic component, computer program, or biological, chemical, or organic matter) and analyzing its components and workings in detail.
The reason for employing such techniques might vary from penetration testing to criminal actions. When developing an application for some business, developers and testers should be aware of what reverse engineering actually entails, what techniques are used and what tools are employed.
This document is aimed at shedding light at some of the techniques that hackers use in order to break into your application and steal data. Please note that these are also techniques used for penetration testing to determine the vulnerability of an application. So, without further ado, let’s just dive in.
What do hackers look for in your application to start reverse-engineering it?
- Exposed Method Signatures:
Objective-C and Java programs contain rich information about themselves. Both language compilers will embed definitions of the class interfaces and the relationships among the classes in the binaries. Such information is one of the first things an attacker will seek when attacking an app.
In the example below, an attacker extracts class interfaces from the binary using the class-dump-z tool. The tool is specifically built for reverse-engineering. Below is a class interface extracted from a real-world iOS banking app:
In this particular interface, which describes the underlying architecture and design of the application, an attacker is going to immediately identify the jailbreak status method as a particularly attractive target for modification. If the attacker can successfully disable this method, an attacker will force the app to run in a particularly insecure perform that allows for subsequent attacks.
- Deploy method-scrambling to reassign methods to other methods at the binary level. Method scrambling techniques can only by applied to methods with the same number of parameters and parameter types.
- Remove any extraneous methods from the symbol table that are not required at runtime to be exposed within a production build.
- Rename any remaining exposed methods to values that do not reflect the semantics of the underlying functionality. Otherwise, an attacker will be unable to successfully hone in on attractive targets for modification or further analysis.
2. API Monitoring:
The Objective-C runtime lets an application modify its mapping from a selector (method name) to an implementation. In doing so, an application can patch a method and execute additional methods each time the original method is invoked by the runtime engine. This is typically done when an organization cannot inspect or modify the original method.
An attacker can take advantage of this feature to create a log of method calls invoked by the application. An attacker will be able to understand the control of flow of an application without decrypting the binary and analyzing it through the use of tools like IDA Pro.
- If Objective-C has to be used for such methods, treat this as a sensitive method that must be hidden within the exposed metadata. It will not stop swizzling from occurring but will lower the likelihood that an attacker will discover this method.
- Avoid making any direct method calls to system libraries. Instead, invoke the corresponding system call using inlined-assembly code. This will prevent an attacker from directly intercepting the method or its parameters through this technique.
- Native apps contain program symbols that reveal the locations and semantics of their data. These symbols provide helpful information that facilitates reverse engineering. Hackers can easily extract the symbols and analyze their associated data using tools such as IDA Pro.
As an illustration of the amount of information these symbols can reveal, below is a partial list of the symbols found on a real-world iOS banking app (the list was produced by nm, a symbol-dumping command-line tool):
In this example, the application declares sensitive data fields (about authentication and credit card information) and accurately describes what they will contain at runtime. Symbol names and locations reveal the internal assets of the application.
- Change compiler settings and source code to ensure that the application’s compiler will not expose any extraneous data symbols within production releases of code
- If the data symbol being protected is static in nature, encrypt the contents of the field while it is on disc and in memory at the appropriate times. This will prevent an attacker from modifying the contents of the field at runtime
- If the data symbol being protected is not static in nature, obfuscate the value of the field within the application’s memory at runtime to prevent runtime modification of the data’s value.
3. Cryptographic Key Interception:
Consider an application that uses cryptographic operations provided by system libraries. The application must pass appropriate keys to these libraries in order to decrypt the data. At runtime, attackers may choose to monitor the system library interface and intercept calls to decryption methods. The application will pass the appropriate key as a parameter to these methods and the attacker will successfully grab the key.
As another example, imagine an application that tries to mitigate this risk by implementing its own cryptography or statically links to a third-party library. In response, the attacker will examine the application’s symbol table and look for cryptography-related method names (including the words “key”, “crypto”, “encrypt”, “sign”, “AES”, “MD5”, and so on).
- Consider using a dedicated whitebox cryptography technology to handle all cryptographic operations. Such technologies should prevent an attacker from identifying the encryption algorithm through binary analysis or symbol exploration.
- If the application must dynamically call third-party libraries, treat this risk in the same way as other risks related to swizzling prevention.
- If secret keys must be hardcoded in the app, treat this risk in the same way as other sensitive strings that reside within the binary.
4. Algorithm decompilation and analysis:
Algorithms encoded in intermediate languages such as Objective-C or Java are particularly vulnerable. Decompilers have made it simple to turn low-level assembly code to a high-level source- like pseudocode. The Hex Rays Decompiler, a plugin to IDA Pro, is an excellent example. Widely used by adversaries and security researchers, it can decompile almost any ARM or x86 code into its original form with startling accuracy.
- Avoid coding sensitive algorithms in intermediate languages such as Java or Objective-C. Algorithms written in such languages will contain a large volume of metadata. If an attacker gets access to the binary, the metadata will allow an attacker to reproduce the original source code with remarkable accuracy
- If it is not possible to avoid specifying such algorithms in Objective C or Java, apply obfuscation techniques to sensitive methods contained within the application. Obfuscation is often confused with simple method-renaming techniques. Obfuscation techniques go far beyond simple method renaming. Apply the following techniques to sensitive code: instruction block chopping, instruction substitution, symbol chopping, method inlining, and dummy-code insertion.
5. Application Decryption:
To reverse-engineer an iDevice application, the attacker must attach a debugger to the relevant process and capture an unencrypted form of the application to disk.
Typically, attackers use tools like clutchmod to decrypt an application and store it to disk. This tool starts the application and attaches a debugger to it while it is running. At this point, iOS has decrypted the application and is executing it in memory.
- Insert defensive code into the app that can detect if the application’s signature has been removed. The inserted code should check for the presence of the SC_Info folder. If the folder does not exist, this is a strong indicator that the application has been successfully decrypted. The application should respond appropriately by failing in subtle and unpredictable ways
- Invoke additional code within the application that will detect the presence of debuggers. Invocation of said code should occur just before sensitive code is executed or assets are decrypted in memory. There are a diverse number of techniques appropriate for the detection of debuggers.
When applied appropriately, these self-defenses can make a mobile application highly resilient against attacks, even on rooted or jailbroken (compromised) devices. For example, self-checksum and self-repair code equips your app with self-awareness, self-repair, and tamper-response capabilities.
It is important to layer these defenses and implement them in a networked fashion such that the protection does not cover just the vulnerable application code, but also the protection mechanisms themselves. This is a multi-layered, defense-in-depth approach that has been proven highly effective against hacking attempts.