Skip to main content

Function Call Obfuscation

Function call obfuscation disguises how functions (static, member, or virtual) are called in code. After decompilation, it's impossible to directly know which function is being executed, effectively protecting code security.

Implementation Principle

For each function call xxx Foo(T1 p1, T2 p2, ...):

  • Group called functions by their signatures, assigning each function a unique index within its group.
  • Use EncryptionService<Scope>::Encrypt(int value, int ops, int salt) to calculate an encryptedIndex from the index.
  • At the function call site, first call EncryptionService<Scope>::Decrypt(int value, int ops, int salt) to decrypt and get the original index.
  • Replace the original function call with xxx $Dispatch(T1 p1, T2 p2, ..., int index)

The encryptedIndex is decrypted only when executing that code location. After decompilation, it's impossible to directly know which function is called, effectively increasing the difficulty of code cracking!

Settings

ObfuzSettings.CallEncryptSettings contains settings related to constant encryption. See Configuration for details.

Proxy Mode

There are multiple methods to obfuscate function calls.

ModeDescription
DispatchConverts function calls to indirect calls through another dispatch function. The index of the dispatch function determines the actual function called. The index value is encrypted and only decrypted at runtime, effectively preventing attackers from analyzing call relationships.
DelegateConverts function calls to precomputed delegates. Delegate objects are bound to elements at specific indexes in a delegate[] array at runtime. The index value is encrypted and only decrypted at runtime, effectively preventing attackers from analyzing call relationships.

There's no significant difference in obfuscation level or runtime performance between Delegate and Dispatch modes. The advantage of Delegate over Dispatch is that the generated obfuscated code is more stable, with smaller differences when code changes.

Dispatch mode is recommended for most projects. For HybridCLR premium users, reducing code changes can reduce the number of functions switched to interpreted execution, improving DHE performance, so Delegate mode is recommended.

Encryption Level

The encryption level affects the ops parameter passed to EncryptionService<Scope>::Encrypt. See Encryption for detailed information about the ops parameter.

The encryption level ranges from [1-4]. During encryption, the number of ops generated equals the encryption level value. As long as constant encryption is enabled, it effectively prevents cracking. Higher encryption levels provide minimal additional protection against cracking. The default value of 1 is recommended.

The CallEncryptSettings.EncryptionLevel field can set the global default encryption level.

Rule Files

By default, Obfuz encrypts all function calls, but it also supports rule files to precisely control the scope and effect of constant encryption. The CallEncryptSettings.RuleFiles option can configure 0-N rule files. Rule files are relative to the project directory. Valid rule file paths look like: Assets/XXX/YYY.xml.

Configuration example:

<?xml version="1.0" encoding="UTF-8"?>

<obfuz>
<whitelist>
<assembly name="mscorlib" obfuscate="0"/>
<assembly name="UnityEngine.*" obfuscate="0"/>

<assembly name="Obfus2">
<type name="Banana" obfuscate="0"/>
<type name="*.TestTypeAllMethodNotObfus" obfuscate="0"/>
<type name="*.TestTypeSomeMethodNotObfus">
<method name="NotObfus*"/>
</type>
</assembly>
</whitelist>

<global obfuscationLevel="Basic">

</global>

<assembly name="Obfus2">
<type name="*.TestNotObfusTypeAllMethods" obfuscationLevel="None"/>
<type name="*.TestNotObfusTypeSomeMethods">
<method name="NotObfus*" obfuscationLevel="None"/>
</type>
<type name="Aaaa.TopClass/SubClass" obfuscationLevel="Advanced">
</type>
</assembly>
</obfuz>
  • The top-level tag must be obfuz
  • Secondary tags can be whitelist, global, and assembly

whitelist

The whitelist configures functions that won't be encrypted when called. This setting applies to all assemblies.

assembly

AttributeOptionalDefaultDescription
nameYesNoneName as a wildcard expression. If empty, matches all assemblies.
obfuscateYes1Whether to obfuscate calls to functions within this assembly.

The only child element of assembly is type.

type

AttributeOptionalDefaultDescription
nameYesNoneName as a wildcard expression. If empty, matches all types. Nested types use / to separate the containing type and nested type, e.g., test.ClassA/ClassB.
obfuscateYesInherits from assembly's同名 fieldWhether to obfuscate calls to functions within this type.

The only child element of type is method.

method

AttributeOptionalDefaultDescription
nameYesNoneName as a wildcard expression. If empty, matches all methods.
obfuscateYesInherits from type's同名 fieldWhether to obfuscate calls to this method.

global

The global tag defines global default encryption parameters.

AttributeOptionalDefaultDescription
obfuscationLevelYesNoneObfuscation level, which can be None, Basic, Advanced, or MostAdvanced.

assembly

AttributeOptionalDefaultDescription
nameNoAssembly name, which must be in the list of obfuscated assemblies.
OthersYesInherits from global同名 optionsAll options from global can be used. If not defined, inherits values from global.

The only child element of assembly is type.

type

AttributeOptionalDefaultDescription
nameNoWildcard string for type names. If empty, matches all types.
OthersYesInherits from assembly同名 optionsAll options from global can be used. If not defined, inherits values from assembly.

Since function calls can only appear in function code, the only child element of type is method.

method

AttributeOptionalDefaultDescription
nameNoWildcard string for method names. If empty, matches all methods.
OthersYesInherits from assembly同名 optionsAll options from global can be used. If not defined, inherits values from assembly.