Protecting a Qt Application or a Device Against Hacking, Part 2
Why would anyone want to hack an application or a device? And how would they go about their attack? And, most importantly, how can you protect your software against malicious attacks?
This post is a follow-up to part 1 of the topic. This time we take a fairly detailed look at attackers’ motivations, methods and the ways to deter most attacks from succeeding. This blog post is aimed primarily at anyone who have specific reverse engineering or tampering related risks in their systems, for example related to processing licenses, valuable content or media, proprietary algorithms or financial transactions.
Why would anyone want to hack my software?
“We’re not Facebook or handle ultra-sensitive data - why would anyone want to hack us?” It is easy to think that our application does not attract attackers to take the effort to reverse engineer the software.
However, there are several reasons why attackers might want to target any application. Here are the most common motivations.
Circumventing a licensing check
This is one of the most common targets. The attacker tries to disable the logic that checks for a valid license in order to be able to use the application without limitations. The usual way to achieve this is by modifying the application binary.
Extracting secret keys or passwords
Applications often contain embedded information which is used to encrypt the data stored in the application, enable protected communication for example with a cloud backend, or enable the application to use other resources that require authentication. By carefully analysing the application binary, it is easy to locate and extract the secret keys or passwords, if they are not protected.
Understanding how a proprietary algorithm works
Many applications contain implementations of algorithms that have intellectual property value. These may be developed by the company developing the application, or parts of the application that are licensed from other developers. If the algorithm code is not obfuscated in any way, the pseudo-code of the algorithm can be easily extracted from the application binary.
Finding open vulnerabilities that can be exploited to attack running systems
Many applications use third party modules (code, libraries, other assets) either directly or indirectly. Any such modules can have vulnerabilities. Further, updating such modules, especially in embedded applications, may not be straightforward. Obtaining knowledge of such vulnerabilities can be used to attack other systems that use the same modules.
Changing the application’s operation
Sometimes the attackers aim to change the behavior of the application, but only very slightly, so that the changed application is virtually not distinguishable from the original application. For example, consider an application used for financial transactions, used by consumers or businesses. If an attacker is able to distribute a slightly altered version that is able to manipulate a recipient’s bank account number, it is possible to gain significant monetary benefit in a short time before anyone notices the attack.
Changing the device operation or configuration
Physical devices depend more and more on the device software, such as firmware or special applications. Because of this, the configuration of devices is often done purely with software. For example, a high power variant of an engine may be just a locked configuration in the engine controller software. In this kind of scenario, the attacker aims to perform “feature unlock attack” by targeting to modify the controller software.
Making and distributing an application version that crashes or jams
Sometimes the attacker’s target is not to directly benefit from modifying the application. Instead, the target may be simply to cause harm to the services related to the application. For example, an application that crashes every time a certain operation is performed may cause significant monetary losses or bad reputation for the related services.
How attackers think - Popular hacking methods explained
To protect your application against various kinds of attacks, it is useful to first think like an attacker. Try to think of the obvious ways to attack specifically against your application and the different motivations an attacker might have.
Even more importantly, think out of the box: what would be the unlikely but still possible attack against your application? Also, put yourself in different user roles and try to identify the ways to attack from the viewpoint of each responsibility actor of your application. The actors typically operate over trust boundaries and that may then reveal execution paths or data flow which initially do not seem likely or possible.
Static binary analysis
Probably the most common way to attack an application is static binary analysis. It is often easy to obtain the actual binary. Tools to analyse the binary are available either free or with a reasonable cost. Tools allow parsing and extracting the various parts of the binary with a single click. There is no need for the attacker to write custom code for every target platform or architecture.
A typical application binary contains multiple types of sections. They can be roughly categorized as sections containing code, sections containing static data such as strings and sections containing runtime data. Out of these, the code and static data sections are the most interesting for the static analysis context.
Static data sections, such as a string section, usually contain the data that can be considered as application assets. It is deceptively easy to think that data embedded as strings is protected from attackers. That is not the case. In the worst case, the string data may contain even passwords or application’s secrets in plain format. If they are un-encrypted or un-obfuscated, extracting the string values is as easy as reading a book.
The code section contains all of the application logic. Disassembler tools can easily resolve the pseudo code structure, call hierarchy, local and global variable references and other similar information. But even an average-sized application will contain lots of binary code. So, the aim of the attacker is not to resolve every line of code. Instead, the aim is to identify the relevant parts of code to be used as the target for the attack.
For example, a few hundred instructions containing a license check logic is an attractive target for an attack. It does not require very special knowledge to break the license check. At the very minimum, modifying one single instruction may be enough.
Capture from Hopper disassembler tool demonstrating the control flow capabilities
Dynamic binary analysis
If an application is using the typical first level of protection, the code obfuscation, static binary analysis may not be enough to reveal the secrets. The typical next step is dynamic binary analysis. This means basically analysing the application operation in runtime: collecting all the possible data from code execution and memory usage and analysing the execution paths that can’t be revealed with static analysis.
There is a wide range of tools available for dynamic analysis. The free or low-cost tools provide the basic capability for disassembling and debugging an application. But there are also high-end tools, such as IDA Pro, which are built with loads of tools for analysing the target binaries.
A crucial element of dynamic binary analysis is to also understand the attack context. The typical attack context is so-called “whitebox attack context”. It means that the attacker has full access to the target application, to the application binary and to the execution environment. In practice this means that the attacker is able to control every aspect of application execution when trying to break it or steal its secrets.
Why is the attack context so important? Because it drives the assumptions what the developer can do about possible attacks and how they affect your application. Once again, it is easy to assume that “as my application is running in environment X, attack type Y is not applicable”. But very often, usually due to the resourcefulness of the attacker, the attack context ends up being the whitebox attack which means that the assumptions done in the design phase are the ones which eventually leave the application vulnerable. For example, developer may think that if an application is running in an embedded device, an attacker has no meaningful access to it. But in reality, attacker could gain access to device firmware, remove the software protection in the firmware, and then attack the application directly.
Another important concept is so-called “code lifting”. Code lifting refers to the scenario where the target application can be lifted for example to a simulator environment where the attacker has full capability to control the code execution or monitor the memory usage - even kernel memory. This is often paired with the white-box attack context. First the attacker gain access to the target application binary, and then moves it to another execution environment.
Methods to protect applications and devices against hacking
In the first blog post, we presented some basic methods for protection of your application, such as using static linking instead of dynamic linking, and making sure that the symbol stripping is enabled when generating the final application binary. But when there is a need for more comprehensive protection, there are additional methods for protecting applications against various kinds of attacks.
These methods can be roughly divided to four categories:
- Whitebox cryptography
The purpose of code obfuscation is to mainly prevent static analysis. Typical ways to perform code obfuscation is adding obfuscation of the strings used in the application, encrypting the static data sections of the binary so that they are decrypted when the application starts, and adding artificial code structures or execution paths which cannot be resolved with static analysis.
An example of code obfuscation would be a case where a code block is paired with a parallel “copy block” and the execution of the two blocks is controlled with a conditional branch with specific characteristics. The branch logic should be deterministic so that the developer can trust that the code is executed correctly, but static analysis can’t deduce the runtime behavior.
Just like static analysis is the first way to perform an attack, it is also the first level of defense. For example, a high-quality disassembler may be able to see partially through the obfuscation patterns. Also, the quality of modern compilers poses a problem. Fake code blocks may be wiped away by code optimization and hence decrease the quality of obfuscation.
The purpose of anti-tampering is to protect the application against dynamic analysis. While there are various approaches on how to implement anti-tampering, the basic idea is that the original application binary is modified to include additional code that does runtime checking of the binary. Such code snippets monitor specific areas of the binary to check if the area is modified and perform counteraction if a modification is detected.
Applying anti-tampering is very much application and execution environment specific. For example, there is a tradeoff between how much code modification checks can be used vs. the performance of the application. Also, the target architecture plays a major role: an approach that works well in certain environment may not protect the application in a different environment.
This also means that adding anti-tamper protection is much more tedious that adding basic code obfuscation. While code obfuscation can be usually applied at the compiler frontend level, anti-tampering has a dependency to compiler backend and architecture-specific parts.
The purpose of anti-debugging is simply trying to prevent the attacker from performing code lifting and running the application in a simulator or a similar environment with a debugger. Being able to use a debugger is often a big aid for the attacker as it enables the attacker to stop code execution and monitor the application memory space and registers at any point of time.
The usual approach for implementing anti-debugging is trying to detect the debugger process and preventing it from hooking the target application. The big challenge is that the attacker may try to attach the debugger at any point of application execution. It is not enough to only perform such check when the application is starting, for example. Resolving this challenge is not trivial. Detailed knowledge about the debugger behavior is required, and the anti-debugging logic must be built to protect from all possible ways to utilize the debugger.
Whereas code or string obfuscation tries to obfuscate the static application data, it does not distinguish security critical data from other static data. Hence, any application containing data such as cryptographic keys needs to have a solution for protecting these critical assets. It may prove to be very difficult to protect the keys only, but there is an alternative solution: whitebox cryptography.
The target of whitebox cryptography is to be able to protect the cryptographic keys even in an environment subjected to a whitebox type of an attack. The usual approach to implement whitebox cryptography is to integrate the key for a cryptographic algorithm as part of the algorithm implementation itself. That means, the key becomes an inseparable part of the algorithm implementation.
The benefit here is that instead of using the cryptographic key in an application, it is possible to use whitebox implementation of the algorithm directly, and no keys are exposed to the attacker. For example, one of the most commonly used cryptographic algorithms is AES algorithm. It has been a target for wide academic research from whitebox cryptography perspective, and it is also an essential algorithm to have in the product feature set for whitebox cryptography vendors.
Example case: INSIDE Secure tools
INSIDE Secure is an example of a state-of-the-art code protection tool vendor. Their Code Protection toolset can be run on a Qt application to both obfuscate the binary and static data including string, and make the Qt application self-protecting against tampering, irrespective of the integrity of the environment.
This makes it very hard to circumvent licensing checks, find out sensitive IPR, or breach the application integrity when processing financially valuable transactions.
A successful protection of an application is always a sum of its parts. As a developer, if you neglect one area, you will leave your application vulnerable to certain kind of attacks. Consequently, serious application protection is something that a normal developer should not try to implement by himself. There are a number of high quality tools available for just this purpose. Just go and pick the right one for you - and remember that a commercial license of Qt allows using these means of protecting your application or a device.
Can we help you?
Intopalo is a Qt Service Partner that specializes in software security. If you are not sure where to start, we are more than happy to give you a hand and guide you to the right direction. Visit Intopalo website for more information.
Subscribe to our newsletter
Try Qt 6.1 Now!
Download the latest release here: www.qt.io/download.
Qt 6 was created to be the productivity platform for the future, with next-gen 2D & 3D UX and limitless scalability.
Explore Qt World
Check our Qt demos and case studies in the virtual Qt World
Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.
Näytä tämä julkaisu Instagramissa.
Want to build something for tomorrow, join #QtPeople today! We have loads of cool jobs you don’t want to miss! http://qt.io/careers #builtwithQt #software #developers #coding #framework #tool #tooling #C++ #QML #engineers #sales #tech #technology #UI #UX #CX #Qt #Qtdev #global #openpositions #careers #job
Henkilön Qt (@theqtcompany) jakama julkaisu