- Overview
- Installation
- Project Setup
- 1: Enable Obfuscation
- 2: Configure Transforms
- 3: Configure Random Number Generator
- 4: Debug Transforms
- Configuration File Example
- Using PPiOS-ControlFlow with PPiOS-Rename
- Transforms
- Confirming Obfuscation
- 1: Analyzing the LLVM IR
- 2: Using LLVM to generate a call graph
- 3: Using a reverse assembler and call graph generator
- Sample Project
- Version Compatibility
- Troubleshooting
- Ensure Xcode integration is properly installed
- Clean DerivedData directory
- Reduce or disable optimizations
- Reducing obfuscation transforms
- Configuration file errors
- Performance problems
- Profiling obfuscated code
- Update Checks
- Evaluation Copy
Overview
PreEmptive Protection for iOS - Control Flow (or PPiOS-ControlFlow, for short) is a specialized compiler that compiles Objective-C code into obfuscated native code. It achieves this via a series of "control flow" transforms that alter the operations the computer performs without altering the result of those operations, which makes the resulting binary image much more difficult to reverse engineer or modify.
Another form of obfuscation, "renaming", works by changing the names of symbols (variables, methods, class names, etc.) so that their meaning is obscured. PreEmptive Solutions offers an open-source product, PreEmptive Protection for iOS - Rename. PPiOS-Rename is meant to work alongside PPiOS-ControlFlow; together they provide much better protection than either one alone can provide.
If you have both PPiOS-ControlFlow and PPiOS-Rename, follow the instructions in Using PPiOS-ControlFlow with PPiOS-Rename.
Supported Platforms
PreEmptive Protection for iOS - Control Flow supports apps developed for:
- iOS 8, 9, and 10
- iPhone, iPod Touch, and iPad
- ARM 32-bit, 64-bit, and x86 simulator
Using:
- Xcode 8
- OSX El Capitan and macOS Sierra
- Objective-C
Installation
To install the software, extract the archive and run the install.command
script either from the terminal, or by running it through Finder. This will install PPiOS-ControlFlow to the proper location and configure it to work with
the default installation of Xcode in /Applications/Xcode.app. Enter your license key when prompted by the installer. Access to the Internet is required to validate the license key.
Note: Installations using an Evaluation or a Subscription license will continue to require access to the Internet.
After installation, default installs of Xcode will automatically use the PPiOS-ControlFlow compiler whenever the appropriate arguments are configured (see below).
However, if you install another version of Xcode in another directory, or have modified or updated your Xcode installation and your obfuscated projects no longer build, then you need to reinstall PPiOS-ControlFlow into Xcode. You can do
this by using the install_xcode_integration.sh
shell script, e.g.:
#Assumes default Xcode installation is in the directory /Applications/Xcode.app
/usr/local/share/preemptive/PPiOS/bin/install_xcode_integration.sh
You can optionally pass an argument with the path to a non-default install of Xcode, e.g.:
/usr/local/share/preemptive/PPiOS/bin/install_xcode_integration.sh /Applications/Xcode2.app
You can also uninstall this integration by using the uninstall_xcode_integration.sh
script.
/usr/local/share/preemptive/PPiOS/bin/uninstall_xcode_integration.sh /Applications/Xcode2.app
Uninstallation
To uninstall PPiOS-ControlFlow, run the uninstall.command
program. This program is located in the installation directory of PPiOS-ControlFlow,
/usr/local/share/preemptive/PPiOS/uninstall.command
Note: If Xcode is installed in an alternate location, the PPiOS-ControlFlow integration in Xcode would need to be removed manually before uninstalling PPiOS-ControlFlow:
/usr/local/share/preemptive/PPiOS/bin/uninstall_xcode_integration.sh /Applications/Xcode2.app
Project Setup
To configure an app to be obfuscated by PPiOS-ControlFlow, you must pass additional arguments to the compiler to enable and configure the obfuscation.
For projects built from the command-line (e.g. Makefiles), it is easiest to set the CFLAGS
environment variable with these arguments, and they will be passed to the compiler.
For projects built from Xcode, you can configure Xcode to set the flags:
- Go to the Project Navigator, and select the project.
- Click the icon to
Select a project or target
(near the upper left corner of the main pane). - Choose the target to obfuscate.
- Select Build Setttings.
- Make sure to use the All, instead of Basic or Customized, filtering near the top of the properties window.
- Select the Combined view, and not the Levels view.
- Find the Apple LLVM 8.x - Custom Compiler Flags heading.
- Underneath it, double-click on the Other C Flags item.
- From there, use the
+
button to add the desired obfuscation arguments.
1: Enable Obfuscation
There are three different ways to enable obfuscation and to specify obfuscation options:
- The command line argument
-ppconfig=<file>
. This specifies the path to the configuration file. In Xcode, you can use the$SRCROOT
variable to fill in the project's path. - The command line argument
-control-flow=<level>
. There are four levels: high, medium, low, and off. Please see the table below for the meaning of each level. - The environment variable PPIOS_PPCONFIG. This specifies the path to the configuration file. If the command line argument
-ppconfig
is set, the environment's value will be ignored.
Control Flow Level | branch-injection | block-injection | opaque-predicates | switch-obfuscation |
---|---|---|---|---|
High | On | On | On | On |
Medium | On | On | On | - |
Low | On | On | - | - |
Off | - | - | - | - |
Note: If you specify a level on the command line, it overrides the enabling and disabling of transforms in a configuration file. If you specify multiple -ppconfig
or -control-flow
arguments on the command
line, all except the final ones will be ignored.
(See below for an overview of each transform.)
2: Configure Transforms
If more specific configuration is required, a config file may be used. The config file is a YAML-based document. The smallest allowed document is:
control-flow: <level>
You can enable individual transforms by specifying them:
control-flow:
level: <level> #Level is required
switch-obfuscation: <true|false>
branch-injection: <true|false>
opaque-predicates: <true|false>
block-injection: <true|false>
You can also set a level and override the setting for a given transform.
control-flow:
level: low
switch-obfuscation: true
By default, the enabled transforms will be applied to all possible functions.
If you wish to limit the transforms to specific functions, add an include with one or more expressions that match the function name(s):
control-flow:
level: <level>
defaults:
include: <Expression>
or
control-flow:
level: <level>
defaults:
includes:
- <Expression1>
- <Expression2>
- <Expression3>
Notice that the defaults
keyword can also be used in the singular form, default
. The same is true for include
and exclude
keywords as well. The singular and plural forms of these keywords are equivalent.
If you need to override that global setting, or just set an expression for an individual transform, you can specify an include under the transform.
control-flow:
level: <level>
switch-obfuscation:
include: <Expression>
When an include is used, only functions matching the regular expression will be altered.
Note: If the provided expression does not match any functions, nothing will be altered.
If you wish to exclude certain functions, add an exclude with one or more expressions that match the function name(s):
control-flow:
level: <level>
defaults:
exclude: <Expression>
or
control-flow:
level: <level>
defaults:
excludes:
- <Expression1>
- <Expression2>
- <Expression3>
Again, if you need to override that global setting, or just set an expression for an individual transform, you can specify an exclude under the transform.
control-flow:
level: <level>
defaults:
exclude: <Expression>
branch-injection:
exclude: <Expression>
With the exclude argument, any function that matches the expression will not be altered by that transform, even if it is also matched by an include argument.
The expressions can either be regular expressions or simple strings to be matched. To define a regular expression enclose it in '/'
characters (e.g. "/[Ss]ecure/"
). If you want case insensitive matching,
end the regular expression with'/i'
instead of '/'
(e.g. "/secure/i"
). Simple strings are always case insensitive.
For example, to obfuscate an app with Branch Injection, and only affect functions that have "Secure" in their name, but exclude any functions that have "UI" in their name, use this configuration:
control-flow:
level: off
branch-injection:
include: "Secure"
exclude: "UI"
To also apply Block Injection to all functions, extend the configuration as follows:
control-flow:
level: off
branch-injection:
include: "Secure"
exclude: "UI"
block-injection: true
To extend the exclusion so it covers "Test" as well as "UI", the argument list would be:
control-flow:
level: off
branch-injection:
include: "Secure"
exclude: "/UI|Test/i" #Using a regular expression
block-injection: true
or
control-flow:
level: off
branch-injection:
include: Secure
excludes:
- "UI"
- "Test"
block-injection: true
Note: To configure include or exclude for a specific transform, you must override the configured defaults set for include and exclude.
The expressions need to match the class and function names as the compiler sees them.
For example, functions in a class declaration like:
@interface FPPDataModel : NSObject
+ (NSString *) stringForIndex:(NSInteger) index; //Static method
- (NSInteger) currentIndex; //Instance method
- (void) recordMessage:(NSString *)msg authCode:(FPPAuthCode *)auth userName:(NSString *)user;
@end
would be seen by the compiler as:
+[FPPDataModel stringForIndex:]
-[FPPDataModel currentIndex]
-[FPPDataModel recordMessage:authCode:userName:]
Here are some sample expressions:
Expression | Matches |
---|---|
"Test"
|
All functions with "test" and all functions in classes which contain "test". e.g.
-[AAAAppModel isTest]
-[AAAAppModel testUIPassword]
-[AAAAppTestModel secureConnection:errorOnFail:]
Note the quotes. |
"/[Ss]ecure/"
|
An uppercase or lowercase 'S' followed by "ecure". e.g.
-[AAAAppModel isSecureConnection:]
-[AAAAppModel testSecureConnection:errorOnFail:]
-[AAAAppModel secureUIPassword]
Note the quotes. |
"[AAAAppModel "
|
All functions in the AAAAppModel class.
Note the quotes and space. |
"-[AAAAppModel "
|
Instance functions in the AAAAppModel class.
Note the quotes and space. |
"+[AAAAppModel "
|
Static functions in the AAAAppModel class.
Note the quotes and space. |
"/\\[AAAAppModel |\\[AAAAppDelegate /"
|
All functions in the AAAAppModel and AAAAppDelegate classes.
Note the quotes and spaces. |
"-[FPPDataModel recordMessage:authCode:userName:]"
|
A particular function in the FPPDataModel class. (String Match) |
"/-\\[FPPDataModel recordMessage:authCode:userName:\\]/"
|
A particular function in the FPPDataModel class. (Regular Expression Match)
Note the \\ .
|
This last pattern could, alternatively, be written without quotes as:
/-\[FPPDataModel recordMessage:authCode:userName:]/
Only one backslash ('\
') is required to escape the left bracket ('[
') to avoid its interpretation as a regular expression control character. The YAML parser will interpret a backslash within quotes to
be the start of an escape sequence. In the example above, the first backslash is required to escape the second when parsed by the YAML parser, and the second is required to escape the left bracket for the regular expression parser. The right
bracket (']
') need not be escaped.
Note: We recommend encapsulating expressions in quotes, so they are read properly by the YAML parser.
There is additional configuration that affects the behavior of the transforms, which you may wish to set:
block-percent: <percent>
Only apply Branch Injection or Switch Obfuscation to a percentage of blocks in a function. The blocks will be chosen at random. Default: 100
These setting will be located inside the particular transformation in the configuration file.
control-flow:
branch-injection:
block-percent: <percent>
switch-obfuscation:
block-percent: <percent>
Note: The <percent>
must be set to an integer value. (e.g. 76
for 76%)
3: Configure Random Number Generator
Some transforms use a pseudo random number generator to determine which functions or blocks should be changed and/or how they will be changed. By default, a random seed is used to initialize the generator. However, if you want better reproducibility between obfuscations of the same code, you can specify a 32-digit hexadecimal number (a 16-byte value) to be used as the seed, optionally prefixed by '0x'. It can be specified in three ways.
- Add it to the command line via:
-random-seed=<32-digit hexadecimal number>
- Set a
PPIOS_RANDOM_SEED
environment variable which specifies the number. - Add it in the configuration file:
random-seed: <32-digit hexadecimal number>
Note: The command line argument will override the environment variable. The environment variable will override a value in the configuration file. If the value you provide is not the correct size, an error will occur and compilation will fail.
Here are some examples of valid seeds:
- 0xDEADBEEFFEEDFACECAFEBABE12345678
- CAFEBABECAFEBABECAFEBABECAFEBABE
- 12345678901234567890123456789012
- 0123456789ABCDEF0123456789ABCDEF
- 0x00112233445566778899AABBCCDDEEFF
- 0x61ED36FC3F8F853EA85EB0F1BFC517B8
- 0x0A98599D58F805A80A16466920C39096
Note: If you specify multiple -random-seed
arguments on the command line, all except the final one will be ignored.
4: Debug Transforms
There is debug information which can be output to help you see what is being done to your source files.
-mllvm -debug-only=trace
Output the names of the functions and whether a transform is applied or skipped because of the provided rules. This also outputs the provided random seed.
Note: This is useful when configuring include and exclude.
The output is accessible in Xcode in the Report Navigator by selecting the appropriate build and expanding the compile command for the file of interest. Make sure to set the filter to All Messages.
Alternatively, the entire build log can be obtained by right-clicking on one of the messages, and selecting Copy Transcript for Shown Results (All, All Messages) as Text.
Configuration File Example
Here is a example of a full configuration file.
random-seed: 0x0A98599D58F805A80A16466920C39096
control-flow:
level: low
defaults:
includes:
- "[TLA"
- "[TLB"
exclude: "Test"
branch-injection:
block-percent: 55
block-injection: false
# include: "[TLAAppClass "
# exclude: "Test"
switch-obfuscation:
include: "[TLAAppClass "
exclude: "/Test|UI/i"
block-percent: 85
opaque-predicates: true
In this sample, the value 0x0A98599D58F805A80A16466920C39096
is used as the random seed. Switch Obfuscation and Opaque Predicates were turned on and Block Injection was turned off. The percent of blocks for Branch Injection was set
to 55% and the percent of blocks for Switch Obfuscation was set to 85%. Both Branch Injection and Opaque Predicates will use the default include and exclude settings, but Switch Obfuscation has its own. Notice that to turn off
Block Injection, the include and exclude were commented out.
Using PPiOS-ControlFlow with PPiOS-Rename
PPiOS-ControlFlow and PPiOS-Rename work together, each handling different aspects of obfuscation. Depending on the specifics of how your application works, configuration of the two programs may need to be tuned. For example, it may not be feasible to inject branches into tight loops of an algorithm, and PPiOS-ControlFlow would need to be configured to exclude such methods. Similarly, if the application depends on a non-open-source Cocoapod, PPiOS-Rename would need to be configured to exclude all of the classes in the Cocoapod's namespace. It is recommended, therefore, that you get your application to work with PPiOS-ControlFlow and PPiOS-Rename independently, before using the products together.
PPiOS-ControlFlow and PPiOS-Rename mostly work together without interfering with each other, except in the case that the configuration for PPiOS-ControlFlow references class or method names that have been changed by PPiOS-Rename. Thus, PPiOS-ControlFlow needs to know about the renamings to expect.
After obfuscating the sources with PPiOS-Rename, the application can be compiled with PPiOS-ControlFlow by adding the following options to the command-line:
-ppios-rename-map=<symbols-map-filename>
Alternatively, the rename map may be specified as a root-level configuration in the configuration file named rename-map
taking a single string argument:
rename-map: "symbols.map"
control-flow: ...
It may also be specified with the environment variable PPIOS_RENAME_MAP
.
We recommend that the rename map be specified either with the command-line option or with the environment variable, so that the same configuration file could be used to produce a build obfuscated only with PPiOS-ControlFlow.
Combined Build Process
The combined build process is essentially that used for PPiOS-Rename alone, except that the last step is compiled with PPiOS-ControlFlow:
- Ensure all local source code changes have been committed
- Build the program without obfuscation
- Analyze the program (PPiOS-Rename)
- Apply renaming to the sources (PPiOS-Rename)
- Build the program again (PPiOS-ControlFlow)
Step 5 is the same as when using PPiOS-ControlFlow alone, except that the rename map must be specified in one of the ways described above.
Integrating PPiOS-ControlFlow and PPiOS-Rename with Xcode
To configure your Xcode project to build the application with PPiOS-ControlFlow and PPiOS-Rename, first follow the instructions in the PPiOS-Rename documentation about integrating it into Xcode. Once that is complete:
- Duplicate the original target again, and rename it to
Unobfuscated <original-target-name>
.
Note: If applying these changes to a framework project, you may need to use underscores instead of spaces in the new target name. - Edit the scheme (or add one) for this new target, renaming the scheme to
Unobfuscated <original-scheme-name>
. - Now select the original target, select Build Settings, select All settings, select Combined view, and search for
cflags
. - In the Other C Flags edit box, add:
-ppconfig=<your-ppios-config>
or-control-flow=<high|medium|low>
-ppios-rename-map=<your-symbols-map>
Once this is complete, there should be four targets with corresponding schemes:
- YourApp
- Build and Analyze YourApp
- Apply Renaming to YourApp
- Unobfuscated YourApp
Building with both PPiOS-ControlFlow and PPiOS-Rename is accomplished by building targets #2, #3, and then #1. This will produce an app named YourApp.app
and will have both control-flow obfuscation and renaming obfuscation.
The app can also be built without either form of obfuscation by just building target #4. This will produce an app named Unobfuscated YourApp.app
.
The app can be tested with PPiOS-Rename alone by building targets #2, #3, and then #4. This will produce an app named Unobfuscated YourApp.app
, but it will have renaming obfuscation.
The app can be tested with PPiOS-ControlFlow alone by creating an empty rename map (echo '{}' > symbols.map
) and building target #1. This will produce an app named YourApp.app
which will only have control-flow
obfuscation.
Note: Always remember that target #3 will change your sources, and any change it makes will need to be reverted before doing any of these build sequences, and before continuing development.
Translating .dSYMs
PPiOS-ControlFlow now includes a utility named llvm-dsymutil
that can be used to de-obfuscate .dSYM
files produced by builds that use PPiOS-Rename. These .dSYM
files can subsequently be used by
analytics services to symbolicate crashdumps with the original names, rather than the obfuscated names. See the documentation for PPiOS-Rename for additional details.
Transforms
PPiOS-ControlFlow works by performing various transforms that modify the control flow of the code that is being compiled. The resulting binary is still functionally identical to an un-obfuscated binary, but it will be more difficult to reverse engineer.
The following transforms can be performed:
Switch Obfuscation
Switch Obfuscation works by altering the control flow of your program from a series of specialized constructs (if, switch, loops) and changes them to be just one type of control flow construct: A switch with a loop surrounding it.
An example function might be originally written like so:
int max(int x, int y) {
if (x >= y) {
return x;
} else {
return y;
}
}
After the Switch Obfuscation transform is completed however, this code would look something like this:
int max(int x, int y) {
int switchvar = 1942843;
while (true) {
switch (switchvar) {
case 598232:
return y;
case 1942843:
if (x >= y) {
switchvar = 8289314;
} else {
switchvar = 598232;
}
break;
case 8289314:
return x;
}
}
}
As you can see, this makes the control flow much more difficult to follow. With more complicated functions this of course becomes much more effective at obscuring the actual control flow used by the code.
Note: Switch Obfuscation may significantly increase the time functions take to execute. We recommend you use the include
configuration to only apply it to certain functions or the block-percent
option to limit
the application of the transform.
Branch Injection
Branch Injection works by inserting if statements and placing code that would form an infinite loop in the portion that is never reached.
An example function might be originally written like so:
int max(int x, int y) {
if (x >= y) {
return x;
} else {
return y;
}
}
After Branch Injection, the code becomes much more convoluted:
int max(int x, int y) {
top:
int i = 0;
if (1 == 1) {
othertop:
if (x >= y) {
int ret;
if (1000 != 20) {
ret = x;
} else {
ret = x;
i++;
goto othertop;
}
return ret;
} else {
return y;
}
} else {
int ret;
if(x >= y) {
ret = x;
} else {
ret = y;
}
i++;
goto top;
}
}
Opaque Predicates
Opaque Predicates work by adding mirrored constants standard mathematical operations. An example would be changing x + y
to x + 3283 + y - 3283
. This transform does not make the code significantly more difficult to understand,
but when combined with other transforms it can make the control flow significantly more complicated and difficult to reverse engineer.
An example function might be originally written like so:
bool isTwenty(int x, int y) {
if ( (x + y) == 20 ) {
return true;
}
return false;
}
After using Opaque Predicates, the mathematical calculation has changed:
bool isTwenty(int x) {
if ( (x + 42 + y - 42) == 20 ) {
return true;
}
return false;
}
Block Injection
Block Injection is another obfuscation transform that is weak by itself, but helps the other transforms to be more powerful. This can help the other transforms by providing more places for code injection and obfuscation.
An example function might be originally written like so:
int printsomething() {
printf("Hello");
printf("World!");
return 0;
}
After Block Injection is performed, the code would look something like this:
int printsomething() {
printf("Hello");
goto two;
two:
printf("World!");
goto three;
three:
return 0;
}
Confirming Obfuscation
There are multiple approaches you can use to see the effects of obfuscation:
1: Analyzing the LLVM IR
LLVM, and thus clang, uses its own intermediate language called IR. It is similar to assembly, but is processor agnostic. LLVM IR is also strongly-typed and has high-order constructs like switches, similar to .NET IL or Java bytecode. LLVM IR is the primary code that LLVM uses for optimizations, and it is what PPiOS-ControlFlow uses to perform its obfuscation transforms. After LLVM runs all of the "passes" for optimization and analysis, it eventually compiles it to actual machine code for a given processor.
Clang and LLVM can output the LLVM IR instead of compiling it to machine code. This can be useful for judging how well obfuscation is working. You can pass the command line arguments -S -emit-llvm
to clang to have it output LLVM IR
code instead of machine code. The build will fail at the linking stage, though, because the usual .o
files will have plain-text LLVM IR code instead of the usual machine-level code. You can view the .o
files
in a text editor to compare the un-obfuscated code to the obfuscated code.
You can see the path to the .o
files in the build output. Usually they will be under $HOME/Library/Developer/Xcode/DerivedData/
.
2: Using LLVM to generate a call graph
Once you have generated plain-text IR (see above), you can then generate a control-flow graph of individual functions. This can be done by running:
/usr/local/share/preemptive/PPiOS/bin/opt -dot-cfg path/to/plain_text_IR.o
Note that this will usually generate a warning about the output going to the screen. You can ignore the warning, or suppress it by adding -o <anything>
to the command line.
This generates a series of .dot
files, one per function in the .o
. file. These files will usually have control characters in their names, so it is often easiest to open them via the Finder. The most popular program that
can display .dot
files is Graphviz.
3: Using a reverse assembler and call graph generator
You can also use one of the many reverse assemblers to produce a call graph from the machine code that is output as the final output. Because these tools operate on the resulting machine code, there is much less information available and it will be harder to decipher the reverse-assembled code.
The most popular reverse assemblers with call graph support are:
Sample Project
A sample project demonstrating the process of obfuscating an iOS app is available on GitHub: PPiOS-Sample-Vie. This project uses both PPiOS-ControlFlow and PPiOS-Rename, but can be used to examine their effects independently. All of the steps required to integrate PPiOS into an existing Xcode project have already been applied. The configuration has also been adjusted to ensure a positive user experience with the obfuscated app. Documentation for the project discusses in detail how to build and use the sample, how the project was configured, and how to interpret the build output.
Version Compatibility
You will need to select the appropriate version of the PPiOS-ControlFlow based on the version of Xcode you use:
Xcode | PPiOS-ControlFlow |
---|---|
6 | 2.1 |
7 | 2.2, 2.3 |
8 | 2.4, 2.5 |
Troubleshooting
Ensure Xcode integration is properly installed
Open a new terminal and type clang --version
. You should see a response like:
Apple LLVM version ...
And when you type clang --version -control-flow=high
, you should see a response like:
PPiOS-ControlFlow LLVM version ...
If you get a different error running either of these commands, it may mean that the integration is not working properly. Try running the install_xcode_integration.sh
script again to reinstall the integration.
Clean DerivedData directory
In some cases, you may receive an error about the module cache being out of date. To force Xcode to rebuild its module cache and thus resolve this issue, just delete the directory Library/Developer/Xcode/DerivedData
in your home directory.
Alternatively, you can run this from a terminal:
rm -rf ~/Library/Developer/Xcode/DerivedData
At the same time as you do this, you may also want to clean your project's build folder completely. You can do this by using Command-Option-Shift-K. Xcode will ask for confirmation before cleaning the folder.
Reduce or disable optimizations
In some cases, certain optimizations can interact with the obfuscation transforms to either cause a compiler or runtime error. If you experience either of these problems, reducing or disabling optimizations may help. This setting can be changed in Xcode by going to the project properties and selecting "Fast [-O -O1]" or "None [-O0]".
Reducing obfuscation transforms
In some cases, reducing the number of obfuscation transforms and selecting only certain ones can help, especially if reducing or disabling optimizations does not help or is not ideal for the application. You can do this by only selecting certain obfuscation transforms rather than using all of them.
Configuration file errors
The configuration file is a YAML document which uses spaces to determine hierarchy. Make sure to use spaces and not tabs when indenting to prevent a common parsing error. Here are some other errors you may encounter:
Unexpected key: {key}
: Make sure {key} is one mentioned in the Configure Transforms section and that it is defined at the correct level.Invalid value: '[true|false]
[include|exclude]'. Expecting true or false.
: If you are defining include and/or exclude, the transform is automatically on. Remove thetrue
or comment out the include and/or exclude.Unexpected token. Expected Key or Block End
: Make sure you haven't specified a value and a child entry for a key. (e.g.control-flow: high
and thenswitch-obfuscation:
below it.)
Performance problems
In some cases, obfuscation can have a significant impact on the performance of an application. In order to ensure your application is as obfuscated as possible without performance impacts, you can try the following:
- Exclude the functions consuming the most amount of CPU time. These time consuming functions can be found by using a profiler, such as Instruments. Start by excluding them from Switch Obfuscation, then try additional transforms as needed.
- Reduce the Switch Obfuscation and/or Branch Injection
block-percent
. By default, these are both set to 100%. - Disable certain obfuscation transforms or use a lower level for control-flow.
Profiling obfuscated code
When using obfuscation, the stack trace and the amount of time spent in each function is correct. However, it should be noted that the line numbers which correspond to the most time taken in a function may be inaccurate. For instance you may see that a simple assignment line is identified as the most expensive part of a function. This can be caused by the obfuscation related code being injected near this line number in the machine code. It is recommended to not use obfuscation when profiling a function's code, and instead to only profile obfuscated code when determining which set of functions to exclude from obfuscation.
Update Checks
PPiOS-ControlFlow will automatically check for updates. It will not check more than once a day. If you want to disable this check, you have several options (in order of precedence):
- Add a command line argument
-enable-update-check=<true|false>
. Enable or disable the update check. - Set a
PPIOS_ENABLE_UPDATE_CHECK
environment variable which specifiestrue
orfalse
. - Add it to the system configuration file /etc/preemptive/ppios.config:
enable-update-check: <true|false>
.
Evaluation Copy
There are some limitations on the Evaluation version of PPiOS-ControlFlow:
- It must be used on a machine with an active connection to the Internet.
- A compiler warning is always issued indicating that it is an evaluation version. Note: This warning (as with all PPiOS-ControlFlow warnings) can be silenced with the
-Wno-ppios
option. - Attempting to compile for an iOS device will produce a compiler error. The evaluation version may not be used to create binaries for general release.