2025-05-21 09:23:29 +08:00
using dnlib.DotNet ;
using Obfuz.Data ;
using Obfuz.Emit ;
using Obfuz.EncryptionVM ;
using Obfuz.ObfusPasses.CleanUp ;
2025-06-09 22:57:40 +08:00
using Obfuz.ObfusPasses.Instinct ;
2025-05-21 09:23:29 +08:00
using Obfuz.ObfusPasses.SymbolObfus ;
2025-05-21 20:41:01 +08:00
using Obfuz.Unity ;
2025-05-21 09:23:29 +08:00
using Obfuz.Utils ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using UnityEngine ;
namespace Obfuz
{
public class Obfuscator
{
2025-05-23 12:47:57 +08:00
private readonly CoreSettingsFacade _coreSettings ;
private readonly List < string > _allObfuscationRelativeAssemblyNames ;
private readonly HashSet < string > _assembliesUsingDynamicSecretKeys ;
2025-05-21 20:41:01 +08:00
private readonly CombinedAssemblyResolver _assemblyResolver ;
2025-05-21 09:23:29 +08:00
private readonly ConfigurablePassPolicy _passPolicy ;
private readonly Pipeline _pipeline1 = new Pipeline ( ) ;
private readonly Pipeline _pipeline2 = new Pipeline ( ) ;
private ObfuscationPassContext _ctx ;
public Obfuscator ( ObfuscatorBuilder builder )
{
2025-05-23 19:47:43 +08:00
CheckSettings ( builder . CoreSettingsFacade ) ;
2025-05-23 12:47:57 +08:00
_coreSettings = builder . CoreSettingsFacade ;
2025-05-23 19:47:43 +08:00
2025-05-23 12:47:57 +08:00
_allObfuscationRelativeAssemblyNames = _coreSettings . assembliesToObfuscate
. Concat ( _coreSettings . nonObfuscatedButReferencingObfuscatedAssemblies )
. ToList ( ) ;
_assembliesUsingDynamicSecretKeys = new HashSet < string > ( _coreSettings . assembliesUsingDynamicSecretKeys ) ;
2025-05-21 09:23:29 +08:00
2025-05-23 12:47:57 +08:00
_assemblyResolver = new CombinedAssemblyResolver ( new PathAssemblyResolver ( _coreSettings . assemblySearchPaths . ToArray ( ) ) , new UnityProjectManagedAssemblyResolver ( _coreSettings . buildTarget ) ) ;
_passPolicy = new ConfigurablePassPolicy ( _coreSettings . assembliesToObfuscate , _coreSettings . enabledObfuscationPasses , _coreSettings . obfuscationPassRuleConfigFiles ) ;
2025-05-21 09:23:29 +08:00
2025-06-09 22:57:40 +08:00
_pipeline1 . AddPass ( new InstinctPass ( ) ) ;
2025-05-23 12:47:57 +08:00
foreach ( var pass in _coreSettings . obfuscationPasses )
2025-05-21 09:23:29 +08:00
{
if ( pass is SymbolObfusPass symbolObfusPass )
{
_pipeline2 . AddPass ( pass ) ;
}
else
{
_pipeline1 . AddPass ( pass ) ;
}
}
_pipeline1 . AddPass ( new CleanUpInstructionPass ( ) ) ;
_pipeline2 . AddPass ( new RemoveObfuzAttributesPass ( ) ) ;
}
2025-05-23 19:47:43 +08:00
private void CheckSettings ( CoreSettingsFacade settings )
{
var totalAssemblies = new HashSet < string > ( ) ;
foreach ( var assName in settings . assembliesToObfuscate )
{
if ( string . IsNullOrWhiteSpace ( assName ) )
{
throw new Exception ( $"the name of some assembly in assembliesToObfuscate is empty! Please check your settings." ) ;
}
if ( ! totalAssemblies . Add ( assName ) )
{
throw new Exception ( $"the name of assembly `{assName}` in assembliesToObfuscate is duplicated! Please check your settings." ) ;
}
}
foreach ( var assName in settings . nonObfuscatedButReferencingObfuscatedAssemblies )
{
if ( string . IsNullOrWhiteSpace ( assName ) )
{
throw new Exception ( $"the name of some assembly in nonObfuscatedButReferencingObfuscatedAssemblies is empty! Please check your settings." ) ;
}
if ( ! totalAssemblies . Add ( assName ) )
{
throw new Exception ( $"the name of assembly `{assName}` in nonObfuscatedButReferencingObfuscatedAssemblies is duplicated! Please check your settings." ) ;
}
}
}
2025-05-21 09:23:29 +08:00
public void Run ( )
{
2025-05-30 09:51:57 +08:00
Debug . Log ( $"Obfuscator begin" ) ;
var sw = new System . Diagnostics . Stopwatch ( ) ;
sw . Start ( ) ;
2025-05-23 12:47:57 +08:00
FileUtil . RecreateDir ( _coreSettings . obfuscatedAssemblyOutputPath ) ;
FileUtil . RecreateDir ( _coreSettings . obfuscatedAssemblyTempOutputPath ) ;
2025-05-21 09:23:29 +08:00
RunPipeline ( _pipeline1 ) ;
2025-05-23 12:47:57 +08:00
_assemblyResolver . InsertFirst ( new PathAssemblyResolver ( _coreSettings . obfuscatedAssemblyTempOutputPath ) ) ;
2025-05-21 09:23:29 +08:00
RunPipeline ( _pipeline2 ) ;
2025-05-23 12:47:57 +08:00
FileUtil . CopyDir ( _coreSettings . obfuscatedAssemblyTempOutputPath , _coreSettings . obfuscatedAssemblyOutputPath , true ) ;
2025-05-30 09:51:57 +08:00
sw . Stop ( ) ;
Debug . Log ( $"Obfuscator end. cost time: {sw.ElapsedMilliseconds} ms" ) ;
2025-05-21 09:23:29 +08:00
}
private void RunPipeline ( Pipeline pipeline )
{
if ( pipeline . Empty )
{
return ;
}
OnPreObfuscation ( pipeline ) ;
DoObfuscation ( pipeline ) ;
OnPostObfuscation ( pipeline ) ;
}
private IEncryptor CreateEncryptionVirtualMachine ( byte [ ] secretKey )
{
2025-05-23 12:47:57 +08:00
var vmCreator = new VirtualMachineCreator ( _coreSettings . encryptionVmGenerationSecretKey ) ;
var vm = vmCreator . CreateVirtualMachine ( _coreSettings . encryptionVmOpCodeCount ) ;
2025-05-21 09:23:29 +08:00
var vmGenerator = new VirtualMachineCodeGenerator ( vm ) ;
2025-05-23 12:47:57 +08:00
string encryptionVmCodeFile = _coreSettings . encryptionVmCodeFile ;
if ( ! File . Exists ( encryptionVmCodeFile ) )
2025-05-21 09:23:29 +08:00
{
2025-05-23 12:47:57 +08:00
throw new Exception ( $"EncryptionVm CodeFile:`{encryptionVmCodeFile}` not exists! Please run `Obfuz/GenerateVm` to generate it!" ) ;
2025-05-21 09:23:29 +08:00
}
2025-05-23 12:47:57 +08:00
if ( ! vmGenerator . ValidateMatch ( encryptionVmCodeFile ) )
2025-05-21 09:23:29 +08:00
{
2025-05-23 12:47:57 +08:00
throw new Exception ( $"EncryptionVm CodeFile:`{encryptionVmCodeFile}` not match with encryptionVM settings! Please run `Obfuz/GenerateVm` to update it!" ) ;
2025-05-21 09:23:29 +08:00
}
var vms = new VirtualMachineSimulator ( vm , secretKey ) ;
2025-05-28 09:25:09 +08:00
var generatedVmTypes = ReflectionUtil . FindTypesInCurrentAppDomain ( "Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine" ) ;
2025-05-21 09:23:29 +08:00
if ( generatedVmTypes . Count = = 0 )
{
throw new Exception ( $"class Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine not found in any assembly! Please run `Obfuz/GenerateVm` to generate it!" ) ;
}
if ( generatedVmTypes . Count > 1 )
{
throw new Exception ( $"class Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine found in multiple assemblies! Please retain only one!" ) ;
}
2025-05-30 13:32:29 +08:00
var gvmInstance = ( IEncryptor ) Activator . CreateInstance ( generatedVmTypes [ 0 ] , new object [ ] { secretKey } ) ;
2025-05-21 09:23:29 +08:00
VerifyVm ( vm , vms , gvmInstance ) ;
return vms ;
}
private void VerifyVm ( VirtualMachine vm , VirtualMachineSimulator vms , IEncryptor gvm )
{
int testInt = 11223344 ;
long testLong = 1122334455667788L ;
float testFloat = 1234f ;
double testDouble = 1122334455.0 ;
string testString = "hello,world" ;
for ( int i = 0 ; i < vm . opCodes . Length ; i + + )
{
int ops = i * vm . opCodes . Length + i ;
//int salt = i;
//int ops = -1135538782;
int salt = - 879409147 ;
{
int encryptedIntOfVms = vms . Encrypt ( testInt , ops , salt ) ;
int decryptedIntOfVms = vms . Decrypt ( encryptedIntOfVms , ops , salt ) ;
if ( decryptedIntOfVms ! = testInt )
{
throw new Exception ( $"VirtualMachineSimulator decrypt failed! opCode:{i}, originalValue:{testInt} decryptedValue:{decryptedIntOfVms}" ) ;
}
int encryptedValueOfGvm = gvm . Encrypt ( testInt , ops , salt ) ;
int decryptedValueOfGvm = gvm . Decrypt ( encryptedValueOfGvm , ops , salt ) ;
if ( encryptedValueOfGvm ! = encryptedIntOfVms )
{
throw new Exception ( $"encryptedValue not match! opCode:{i}, originalValue:{testInt} encryptedValue VirtualMachineSimulator:{encryptedIntOfVms} GeneratedEncryptionVirtualMachine:{encryptedValueOfGvm}" ) ;
}
if ( decryptedValueOfGvm ! = testInt )
{
throw new Exception ( $"GeneratedEncryptionVirtualMachine decrypt failed! opCode:{i}, originalValue:{testInt} decryptedValue:{decryptedValueOfGvm}" ) ;
}
}
{
long encryptedLongOfVms = vms . Encrypt ( testLong , ops , salt ) ;
long decryptedLongOfVms = vms . Decrypt ( encryptedLongOfVms , ops , salt ) ;
if ( decryptedLongOfVms ! = testLong )
{
throw new Exception ( $"VirtualMachineSimulator decrypt long failed! opCode:{i}, originalValue:{testLong} decryptedValue:{decryptedLongOfVms}" ) ;
}
long encryptedValueOfGvm = gvm . Encrypt ( testLong , ops , salt ) ;
long decryptedValueOfGvm = gvm . Decrypt ( encryptedValueOfGvm , ops , salt ) ;
if ( encryptedValueOfGvm ! = encryptedLongOfVms )
{
throw new Exception ( $"encryptedValue not match! opCode:{i}, originalValue:{testLong} encryptedValue VirtualMachineSimulator:{encryptedLongOfVms} GeneratedEncryptionVirtualMachine:{encryptedValueOfGvm}" ) ;
}
if ( decryptedValueOfGvm ! = testLong )
{
throw new Exception ( $"GeneratedEncryptionVirtualMachine decrypt long failed! opCode:{i}, originalValue:{testLong} decryptedValue:{decryptedValueOfGvm}" ) ;
}
}
{
float encryptedFloatOfVms = vms . Encrypt ( testFloat , ops , salt ) ;
float decryptedFloatOfVms = vms . Decrypt ( encryptedFloatOfVms , ops , salt ) ;
if ( decryptedFloatOfVms ! = testFloat )
{
throw new Exception ( "encryptedFloat not match" ) ;
}
float encryptedValueOfGvm = gvm . Encrypt ( testFloat , ops , salt ) ;
float decryptedValueOfGvm = gvm . Decrypt ( encryptedFloatOfVms , ops , salt ) ;
if ( encryptedFloatOfVms ! = encryptedValueOfGvm )
{
throw new Exception ( $"encryptedValue not match! opCode:{i}, originalValue:{testFloat} encryptedValue" ) ;
}
if ( decryptedValueOfGvm ! = testFloat )
{
throw new Exception ( $"GeneratedEncryptionVirtualMachine decrypt float failed! opCode:{i}, originalValue:{testFloat}" ) ;
}
}
{
double encryptedFloatOfVms = vms . Encrypt ( testDouble , ops , salt ) ;
double decryptedFloatOfVms = vms . Decrypt ( encryptedFloatOfVms , ops , salt ) ;
if ( decryptedFloatOfVms ! = testDouble )
{
throw new Exception ( "encryptedFloat not match" ) ;
}
double encryptedValueOfGvm = gvm . Encrypt ( testDouble , ops , salt ) ;
double decryptedValueOfGvm = gvm . Decrypt ( encryptedFloatOfVms , ops , salt ) ;
if ( encryptedFloatOfVms ! = encryptedValueOfGvm )
{
throw new Exception ( $"encryptedValue not match! opCode:{i}, originalValue:{testDouble} encryptedValue" ) ;
}
if ( decryptedValueOfGvm ! = testDouble )
{
throw new Exception ( $"GeneratedEncryptionVirtualMachine decrypt float failed! opCode:{i}, originalValue:{testDouble}" ) ;
}
}
{
byte [ ] encryptedStrOfVms = vms . Encrypt ( testString , ops , salt ) ;
string decryptedStrOfVms = vms . DecryptString ( encryptedStrOfVms , 0 , encryptedStrOfVms . Length , ops , salt ) ;
if ( decryptedStrOfVms ! = testString )
{
throw new Exception ( $"VirtualMachineSimulator decrypt string failed! opCode:{i}, originalValue:{testString} decryptedValue:{decryptedStrOfVms}" ) ;
}
byte [ ] encryptedStrOfGvm = gvm . Encrypt ( testString , ops , salt ) ;
string decryptedStrOfGvm = gvm . DecryptString ( encryptedStrOfGvm , 0 , encryptedStrOfGvm . Length , ops , salt ) ;
if ( ! encryptedStrOfGvm . SequenceEqual ( encryptedStrOfVms ) )
{
throw new Exception ( $"encryptedValue not match! opCode:{i}, originalValue:{testString} encryptedValue VirtualMachineSimulator:{encryptedStrOfVms} GeneratedEncryptionVirtualMachine:{encryptedStrOfGvm}" ) ;
}
if ( decryptedStrOfGvm ! = testString )
{
throw new Exception ( $"GeneratedEncryptionVirtualMachine decrypt string failed! opCode:{i}, originalValue:{testString} decryptedValue:{decryptedStrOfGvm}" ) ;
}
}
}
}
private EncryptionScopeInfo CreateEncryptionScope ( byte [ ] byteSecret )
{
int [ ] intSecretKey = KeyGenerator . ConvertToIntKey ( byteSecret ) ;
IEncryptor encryption = CreateEncryptionVirtualMachine ( byteSecret ) ;
2025-05-23 12:47:57 +08:00
RandomCreator localRandomCreator = ( seed ) = > new RandomWithKey ( intSecretKey , _coreSettings . randomSeed ^ seed ) ;
2025-05-21 09:23:29 +08:00
return new EncryptionScopeInfo ( encryption , localRandomCreator ) ;
}
private EncryptionScopeProvider CreateEncryptionScopeProvider ( )
{
2025-05-23 12:47:57 +08:00
var defaultStaticScope = CreateEncryptionScope ( _coreSettings . defaultStaticSecretKey ) ;
var defaultDynamicScope = CreateEncryptionScope ( _coreSettings . defaultDynamicSecretKey ) ;
2025-05-21 09:23:29 +08:00
foreach ( string dynamicAssName in _assembliesUsingDynamicSecretKeys )
{
2025-05-23 12:47:57 +08:00
if ( ! _coreSettings . assembliesToObfuscate . Contains ( dynamicAssName ) )
2025-05-21 09:23:29 +08:00
{
2025-05-23 12:47:57 +08:00
throw new Exception ( $"Dynamic secret assembly `{dynamicAssName}` should be in the assembliesToObfuscate list!" ) ;
2025-05-21 09:23:29 +08:00
}
}
return new EncryptionScopeProvider ( defaultStaticScope , defaultDynamicScope , _assembliesUsingDynamicSecretKeys ) ;
}
private void OnPreObfuscation ( Pipeline pipeline )
{
2025-05-21 20:41:01 +08:00
AssemblyCache assemblyCache = new AssemblyCache ( _assemblyResolver ) ;
2025-05-21 09:23:29 +08:00
List < ModuleDef > modulesToObfuscate = new List < ModuleDef > ( ) ;
List < ModuleDef > allObfuscationRelativeModules = new List < ModuleDef > ( ) ;
LoadAssemblies ( assemblyCache , modulesToObfuscate , allObfuscationRelativeModules ) ;
EncryptionScopeProvider encryptionScopeProvider = CreateEncryptionScopeProvider ( ) ;
2025-07-02 18:57:53 +08:00
var moduleEntityManager = new GroupByModuleEntityManager ( )
{
EncryptionScopeProvider = encryptionScopeProvider ,
} ;
2025-05-29 22:20:27 +08:00
var obfuzIgnoreScopeComputeCache = new ObfuzIgnoreScopeComputeCache ( ) ;
2025-05-21 09:23:29 +08:00
_ctx = new ObfuscationPassContext
{
2025-05-23 12:47:57 +08:00
coreSettings = _coreSettings ,
2025-05-21 09:23:29 +08:00
assemblyCache = assemblyCache ,
modulesToObfuscate = modulesToObfuscate ,
allObfuscationRelativeModules = allObfuscationRelativeModules ,
2025-07-02 18:57:53 +08:00
2025-05-21 09:23:29 +08:00
moduleEntityManager = moduleEntityManager ,
2025-05-29 22:20:27 +08:00
obfuzIgnoreScopeComputeCache = obfuzIgnoreScopeComputeCache ,
2025-05-21 09:23:29 +08:00
2025-05-29 22:20:27 +08:00
whiteList = new ObfuscationMethodWhitelist ( obfuzIgnoreScopeComputeCache ) ,
2025-05-21 09:23:29 +08:00
passPolicy = _passPolicy ,
} ;
ObfuscationPassContext . Current = _ctx ;
pipeline . Start ( ) ;
}
private void LoadAssemblies ( AssemblyCache assemblyCache , List < ModuleDef > modulesToObfuscate , List < ModuleDef > allObfuscationRelativeModules )
{
2025-05-23 12:47:57 +08:00
foreach ( string assName in _allObfuscationRelativeAssemblyNames )
2025-05-21 09:23:29 +08:00
{
ModuleDefMD mod = assemblyCache . TryLoadModule ( assName ) ;
if ( mod = = null )
{
Debug . Log ( $"assembly: {assName} not found! ignore." ) ;
continue ;
}
2025-05-23 12:47:57 +08:00
if ( _coreSettings . assembliesToObfuscate . Contains ( assName ) )
2025-05-21 09:23:29 +08:00
{
modulesToObfuscate . Add ( mod ) ;
}
allObfuscationRelativeModules . Add ( mod ) ;
}
}
private void WriteAssemblies ( )
{
foreach ( ModuleDef mod in _ctx . allObfuscationRelativeModules )
{
string assNameWithExt = mod . Name ;
2025-05-23 12:47:57 +08:00
string outputFile = $"{_coreSettings.obfuscatedAssemblyTempOutputPath}/{assNameWithExt}" ;
2025-05-21 09:23:29 +08:00
mod . Write ( outputFile ) ;
Debug . Log ( $"save module. name:{mod.Assembly.Name} output:{outputFile}" ) ;
}
}
private void DoObfuscation ( Pipeline pipeline )
{
pipeline . Run ( ) ;
}
private void OnPostObfuscation ( Pipeline pipeline )
{
pipeline . Stop ( ) ;
2025-07-02 18:57:53 +08:00
_ctx . moduleEntityManager . Done < ConstFieldAllocator > ( ) ;
_ctx . moduleEntityManager . Done < RvaDataAllocator > ( ) ;
2025-05-21 09:23:29 +08:00
WriteAssemblies ( ) ;
}
}
}