Overview
This guide provides a detailed walkthrough of analyzing theControlFlowFlattening.ipa sample, which demonstrates control flow flattening - a common obfuscation technique that transforms normal program flow into a state machine to make reverse engineering more difficult.
What is Control Flow Flattening?
Control flow flattening is an obfuscation technique that converts straightforward program logic into a complex state machine. Instead of normalif-else statements and loops, the code is restructured around:
- A dispatcher loop (typically a
whileorforloop) - A state variable that controls which code block executes
- Switch statements that route execution based on the state variable
- State transitions that update the state variable
Normal Code vs. Flattened Code
Normal Code:Getting Started
Analysis Approach
Step 1: Identify Entry Points
Locate Main Functions
Start by identifying common iOS entry points:
_mainapplicationDidFinishLaunching- View controller lifecycle methods
Step 2: Recognize Flattening Patterns
Look for these telltale signs of control flow flattening:-
Dispatcher Loop
-
State Variable
- Often initialized at the start of the function
- Updated in each case block
- Controls which case executes next
-
Large Switch Statements
- Many case blocks (10+)
- Each case ends with updating the state variable
- Cases may be in non-sequential order
-
Obfuscated Control Flow Graph
- In Ghidra’s function graph view, you’ll see a complex web instead of clear hierarchical structure
- Many edges converging to a central switch block
Step 3: Trace State Transitions
Identify the State Variable
Find the variable that controls the switch statement:
- Usually an integer variable
- Modified in each case block
- Check what Ghidra names it (e.g.,
iVar1,local_28)
Map State Flow
Create a table of state transitions:
| Case | Action | Next State |
|---|---|---|
| 0 | Initialize variables | 1 |
| 1 | Perform calculation | 2 or 3 |
| 2 | Print result | -1 |
| 3 | Error handling | -1 |
Step 4: Deobfuscate the Logic
Once you understand the state transitions, you can mentally (or programmatically) reconstruct the original logic:Remove State Machinery
Eliminate the state variable assignments and focus on the actual operations performed in each case.
Restore Control Structures
Identify where conditional branches occur:
- Cases with conditional state assignments → if-else statements
- Cases that loop back to earlier states → loops
Example Analysis
Here’s what you might find when analyzing a flattened function:In Ghidra Decompiler
Reconstructed Logic
Key Indicators
When analyzing ControlFlowFlattening.ipa, look for:- Large functions with disproportionate complexity
- Single large switch statement dominating the function
- Complex CFG that doesn’t match the apparent simplicity of operations
- State variable being assigned in every case block
- Non-sequential case values (e.g., 0x1a3f, 0x2b1c instead of 0, 1, 2)
Tools and Techniques
Ghidra Function Graph
Use the function graph view (Window → Function Graph) to visualize the control flow:- Flattened functions show a “star” pattern with many blocks connecting to the central switch
- Normal functions show hierarchical flow
Manual Analysis
- Export decompiled code to text file
- Manually trace state transitions
- Create a flowchart of actual execution order
- Rewrite the function in pseudocode
Script-Based Deobfuscation
For advanced users, consider writing a Ghidra script to:- Identify the state variable
- Extract state transitions
- Automatically reorder case blocks
- Generate readable pseudocode
Practice Exercises
Basic Identification
Load the binary and identify which functions are using control flow flattening. Count how many flattened functions exist.
Common Challenges
Non-Sequential Case Values
Case values may be randomized (0x1a3f instead of 0) to make manual analysis harder. Focus on the transitions, not the actual values.Opaque Predicates
Some transitions may use complex expressions that always evaluate to the same value. These are “opaque predicates” designed to confuse analysis tools.Nested Switches
Some sophisticated obfuscators use nested switch statements. Handle these by analyzing the inner switch first, then the outer dispatcher.Tips
Start by identifying the simplest flattened function first. Once you understand the pattern, more complex examples become easier to analyze.
- Use Ghidra’s “Rename Variable” feature to give the state variable a meaningful name
- Add comments for each case block describing what it does
- Use colored highlights in the function graph to mark different logical sections
- Take notes externally - create a flowchart or state diagram
Next Steps
After mastering control flow flattening analysis:- Analyze more complex obfuscation combinations
- Learn about automated deobfuscation tools
- Study other obfuscation techniques (string encryption, opaque predicates)
- Compare with other sample applications
See Also
- Sample IPA Files - Overview of all example applications
- Swizzling Example - Analysis of method swizzling