Protection Features
  • 18 Feb 2025
  • 6 Minutes à lire
  • Sombre
    Lumière
  • PDF

Protection Features

  • Sombre
    Lumière
  • PDF

The content is currently unavailable in French. You are viewing the default English version.
Résumé de l’article

Jigsaw currently offers the following protection techniques:

Feature

Description

Section encryption

Encrypts code and data sections within the native binary to prevent static analysis.

Control flow abstraction

Diverts call instructions within the code sections to a central dispatch function that hides the links between code blocks. This means that all external calls are hidden, as is the control flow of the application. The reconstruction of the proper control flow depends on the valid state of the App Shielding library.

Block splitting (experimental)

Breaks binary symbols into smaller fragments and shuffles them throughout the code.

Integrity checking

Adds a checksum network to protect the code from unauthorized modification. With this feature enabled, modification causes the binary to crash in random ways.

Debug stripping

Removes debug information from the binary.

Section Encryption

Section encryption ensures that a binary cannot be statically analyzed (i.e., understood while on disk). Jigsaw encrypts much of the binary file by sections to prevent such analysis. Decryption begins shortly after start-up and after runtime integrity has been proven. Derivation of the keys used to decrypt each section depends on the App Shielding library being in a valid state.

The following sections of an ELF (Android/Linux) binary are typically encrypted:

Section Encryption

Section

Purpose

.text

Contains the executable code of the binary.

.plt

Handles runtime lookup of external functions.

.rodata

Read-only data. Typically, constants in the app.

On iOS, the __Text sections of a binary cannot be encrypted, because they are enforced as read-only and need to be codesigned. By default, the __cstrings section is placed inside __TEXT, but this can be moved to __DATA with a linker flag. See Moving Binary Sections for more details.

After moving __cstrings, the following sections can be encrypted on iOS:

Section Encryption on iOS

Section

Purpose

__Data, __data

Assorted data.

__Data, __thread_data

Data associated with thread initialization.

__Data, __cstrings

C-style strings used by C/C++ and Objective-C code.

__Data, __const

Constant values used in the code.

With these sections encrypted, the binary leaks very little information about its purpose or implementation. Sections can also be configured to leave them unprotected. See Configuring Protection Settings: Section Encryption for more details.

The performance impact of section encryption is minimal because of the fast decryption methods used. Typically, you can expect an increase in start-up time of around 100ms. There is no ongoing impact to runtime performance.

Control Flow Abstraction

Section encryption protects against static analysis, forcing an attacker to move to dynamic analysis where they try to understand the purpose of the library while it is running.

After it is loaded and decrypted, the binary is still protected by control flow abstraction. This control flow abstraction analyzes the code and redirects call instructions (i.e., call for Intel, bl for ARM architectures) between internal and external symbols to a single dispatch function. The dispatch function acts as a router between calls to ensure that callees are matched up with the right callers. It also calls into App Shielding to ensure proper runtime integrity.

With control flow abstraction, an attacker will not be able to see where a code jump goes to and whether it is to an external dependency or another internal symbol. Trying to extract a call graph from the code will be of limited use, because all calls go to the same place, and the graph is effectively flattened.

Because of the extra processing and indirection imposed by this technique, there is inevitably a performance overhead. This is typically around 90% (i.e., your code will be almost twice as slow as the unprotected version). Thus, control flow abstraction is not applied at a 100% level to all call instructions. The default is 30%, meaning the code should only be around 25% slower but still have many protections to overcome.

Control flow values can be entirely configured to suit your application with some symbols having no protection and others having 100% (i.e., maximum protection). See Configuring Protection Settings: Control Flow Abstraction for more details. The call instructions to redirect are picked at random (using a random seed value) but within the configured protectionLevel settings.

For Android, the mapping between caller and callee is held in a data table at runtime and requires many calls into App Shielding to fully populate. The target is to do this in 100 calls to ensure that there are many opportunities to detect runtime threats. This target can be changed in the configuration (see Configuring Protection Settings: Control Flow Abstraction).

Block Splitting (Experimental)

Control flow abstraction hides calls between symbols or calls to externals. If you have a large symbol with few or no dependencies, however, or if you want to increase the obfuscation of particular symbols, block splitting can be useful.

Block splitting takes the code blocks in one or more symbols/functions, splits them into smaller fragments, shuffles them with unrelated code, and inserts jumps to reconnect the control flow. Block splitting happens before control flow abstraction and consequently, if indirect links are chosen, this flow is hidden by control flow abstraction. The following diagram illustrates this effect:

Block splitting

You can control the size of the fragments (smaller being more obfuscated but slower) and specify the link type in the configuration (see Configuring Protection Settings: Block Splitting).

Note that block splitting currently does not handle code which throws or catches C++ exceptions. It might also cause crashes if code jumps directly inside the original function rather than via its entry point. For these reasons, block splitting is disabled by default but can be enabled in the configuration (see Configuring Protection Settings: Block Splitting).

Block splitting is considered "experimental" at this stage and should only be used if the target functions are extensively tested after protection.

Integrity Checking (Checksum)

To ensure that your code has not been tampered with (e.g., patched, hooked, or a breakpoint inserted), Jigsaw embeds a checksum network covering all the application code. This feature is tightly integrated with control flow abstraction, so to use integrity checking, you must have control flow abstraction enabled. By default, both features are enabled to a modest level.

A detected modification will disrupt the control flow of the app, inevitably causing a crash. This makes it difficult for an attacker to disable checksum processing, because the known good values are not available at runtime. Instead, computed values are used to direct control flow.

By default, integrity checking computes a low overhead checksum network that covers the whole app and randomly places checks throughout the app. In most cases, this provides strong protection with small performance overhead for no effort. To limit the performance impact, a level is used to determine the percentage of call instructions which are used for checksum validation. The default level is 50%, meaning that 50% of the call instructions redirected by the control flow are used for checksums. Because default value of control flow is 30%, 15% of the total call instructions (i.e., 50% of 30%) are used for checksum enforcement.

Due to randomness, there is a risk that key functions might be checked infrequently or not checked on main code paths, thereby leaving them exposed to modification. Similarly, there is a small risk that a checksum validation will be inserted in a key performance function and slow it down. See Configuring Protection Settings: Checksum for details on configuring around these occurrences.

Debug Stripping

Binary libraries contain a surprising amount of debug information, even on release builds and especially on debug builds. The Jigsaw native debug stripping removes those symbols and other debug information which is often left behind by standard strip commands. This protection technique has no impact on performance and actually reduces the size of the binaries.


Cet article vous a-t-il été utile ?

What's Next
Changing your password will log you out immediately. Use the new password to log back in.
First name must have atleast 2 characters. Numbers and special characters are not allowed.
Last name must have atleast 1 characters. Numbers and special characters are not allowed.
Enter a valid email
Enter a valid password
Your profile has been successfully updated.
ESC

Ozzy, facilitant la découverte de connaissances grâce à l’intelligence conversationnelle