What is Obfuscation?
Refractor transforms compiled Dart kernel bytecode (.dill files) to make reverse engineering harder. Unlike source-level obfuscation, Refractor operates on the kernel AST after Dart’s frontend compilation, giving you precise control over what gets transformed.
The Pass System
Refractor uses a multi-pass architecture where each pass performs a specific transformation on the kernel Component:Compile to Kernel
Your Dart source code is compiled to kernel bytecode (
.dill format) using the Dart frontend compiler.Apply Obfuscation Passes
Each enabled pass walks the kernel AST and applies transformations in sequence. Passes mutate the Component in place.
Pass Execution Order
Passes run in this order:- Rename — rewrites identifiers first so other passes work with obfuscated names
- String Encryption — replaces string literals with encrypted byte arrays
- Dead Code Injection — inserts unreachable branches to confuse decompilers
The order matters: renaming identifiers before string encryption ensures that even the decoder function has an obfuscated name.
Base Pass Architecture
All passes inherit from thePass abstract class (lib/src/engine/runner/pass.dart:4):
PassContext
Every pass receives aPassContext with shared state:
- SymbolTable — tracks original → obfuscated name mappings for the symbol map
- NameGenerator — generates sequential obfuscated names like
_$0,_$1 - PassOptions — configuration from
refractor.yaml - shouldObfuscateLibrary() — determines if a library is in scope for obfuscation
Visitor Pattern
Passes use two helper base classes:- PassVisitor (lib/src/engine/passes/pass_visitor.dart:4) — extends
RecursiveVisitorfor read-only tree walking - PassTransformer (lib/src/engine/passes/pass_transformer.dart:4) — extends
Transformerfor mutating transformations
Available Passes
Rename Pass
Rewrites class, method, and field names to short meaningless identifiers
String Encryption
Replaces string literals with XOR-encrypted byte arrays decoded at runtime
Dead Code Injection
Inserts unreachable branches to hinder static analysis tools
Obfuscation Scope
Refractor only obfuscates your project code:- Libraries matching your
pubspec.yamlname - Files under the current working directory
- Dart SDK libraries (
dart:core,dart:io, etc.) - External dependencies from pub.dev
- Files matching patterns in the
excludelist
Example Configuration
Performance Considerations
Runtime Impact
- Rename: Zero runtime cost — only changes identifiers
- String Encryption: Small overhead for decoding strings at first use
- Dead Code: Zero runtime cost — the Dart VM optimizer eliminates
if (false)branches
Binary Size
- Rename: Reduces binary size (shorter identifiers)
- String Encryption: Increases size slightly (decoder function + encoded bytes)
- Dead Code: Increases size (unreachable code preserved in binary)
Symbol Map
Thesymbol_map option generates a JSON file mapping obfuscated names back to originals:
- Debugging obfuscated crash reports
- Understanding decompiled code during security audits
- Verifying obfuscation effectiveness
How Passes Work Together
Passes are designed to compose safely:- Rename creates a mapping of all identifiers and applies them globally
- String Encryption injects a decoder function (which gets renamed if Rename runs first)
- Dead Code inserts unreachable branches throughout procedure bodies
Next Steps
Learn About Rename
Dive into identifier obfuscation mechanics
Explore String Encryption
Understand XOR encoding and runtime decoding