What is Code Obfuscation?
Code Obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. While the process may modify actual method instructions or metadata, it does not alter the output of the program. To be clear, with enough time and effort, almost all code can be reverse engineered. However, on some platforms such as Java, Android, iOS, or .NET (e.g. Xamarin, C#, VB.NET, F#) free decompilers can easily reverse-engineer source code from an executable or library in virtually no time and with no effort. Automated code obfuscation makes reverse-engineering a program difficult and economically unfeasible.
Why use a Code Obfuscator?
By making an application much more difficult to reverse-engineer, you can protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.
How does Obfuscation Work?
Code obfuscation consists of many different techniques that can complement each other to create a layered defense. It is most effective for languages that create some form of intermediate level instructions such as Java or the .NET languages like C#, VB.NET, Managed C++, F#, etc. Some typical examples of obfuscation and application security techniques include:
Renaming alters the name of methods and variables. It makes the decompiled source harder for a human to understand but does not alter program execution. The new names can utilize different schemes like "a", "b", "c", or numbers, unprintable characters or invisible characters. And names can be overloaded as long they have different scope. Name obfuscation is a basic transform that is used by most .NET (C#, etc.), iOS, Java and Android obfuscators.
In a managed executable, all strings are clearly discoverable and readable. Even when methods and variables are renamed, strings can be used to locate critical code sections by looking for string references inside the binary. This includes messages (especially error messages) that are displayed to the user. To provide an effective barrier against this type of attack, string encryption hides strings in the executable and only restores their original value when needed. Decrypting strings at runtime typically incurs a slight runtime performance penalty.
Control Flow Obfuscation
Control flow obfuscation synthesizes conditional, branching, and iterative constructs that produce valid executable logic, but yield non-deterministic semantic results when decompiled. More simply stated, it makes decompiled code look like spaghetti logic which is very difficult for a hacker to comprehend. These techniques may affect the runtime performance of a method.
Instruction Pattern Transformation
Converts common instructions created by the compiler to other, less obvious constructs. These are perfectly legal machine language instructions that may not map cleanly to high level languages such as Java or C#. One example is transient variable caching which leverages the stack based nature of the Java and .NET runtimes.
Dummy Code Insertion
Inserting code into the executable that does not affect the logic of the program, but breaks decompilers or makes reverse engineered code much more difficult to analyze.
Unused Code and Metadata Removal
Removing debug information, non-essential metadata and used code from applications make them smaller and reduce the information available to an attacker. This procedure may slightly improve the runtime performance.
This transform combines multiple input executables/libraries into one or more output binaries. Linking can be used to make your application smaller, especially when used with renaming and pruning, It can simplify deployment scenarios and it may reduce information available to hackers.
Opaque Predicate Insertion
Obfuscates by adding conditional branches that always evaluate to known results—results that cannot easily be determined via static analysis. This is a way of introducing potentially incorrect code that will never actually be executed, but is confusing to attackers trying to understand decompiled output.
An obfuscator can inject application self protection into your code to verify your application has not been tampered with in any way. If tampering is detected, it can shut down the application, limit the functionality, invoke random crashes (to disguise the reason for the crash), or perform any other custom action. It might also send a message to a service to provide details about the tampering detected.
When a hacker is trying to pirate or counterfeit your app, steal your data, or alter the behavior of a critical piece of infrastructure software they will almost certainly begin with reverse engineering and stepping through your application with a debugger. An obfuscator can layer in application self-protection by injecting code to detect if your production application is executing within a debugger. If a debugger is used, it can corrupt sensitive data (protecting it from theft), invoke random crashes (to disguise that the crash was the result of a debug check), or perform any other custom action. It might also send a message to a service to provide a warning signal.
Should I obfuscate my application?
Strongly consider utilizing obfuscation and runtime app self-protection if you release software that runs in an untrusted environment and has intellectual property, provides access to sensitive information, or has gated functionality. Obfuscation makes it much more difﬁcult for attackers to review the code and analyze the application. It also makes it hard for hackers to debug and tamper with your application. The end goal is to add a layer of protection to make it difﬁcult to extract or discover useful information, such as trade secrets (IP), credentials, or security vulnerabilities from an application. It should also make it more difficult to modify application logic or repackage an application with malicious code.