2025-04-05 19:02:50 +08:00
using dnlib.DotNet ;
2025-05-10 11:25:07 +08:00
using Obfuz.Data ;
2025-05-01 10:45:31 +08:00
using Obfuz.Emit ;
2025-05-11 12:53:24 +08:00
using Obfuz.EncryptionVM ;
2025-05-04 19:55:10 +08:00
using Obfuz.ObfusPasses ;
2025-05-10 18:25:43 +08:00
using Obfuz.ObfusPasses.CleanUp ;
2025-05-12 17:21:20 +08:00
using Obfuz.ObfusPasses.SymbolObfus ;
2025-05-04 19:24:14 +08:00
using Obfuz.Utils ;
2025-04-05 19:02:50 +08:00
using System ;
using System.Collections.Generic ;
2025-04-05 21:47:28 +08:00
using System.IO ;
2025-04-05 19:02:50 +08:00
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
2025-04-05 21:47:28 +08:00
using UnityEngine ;
2025-04-05 19:02:50 +08:00
namespace Obfuz
{
public class Obfuscator
{
2025-05-03 21:43:50 +08:00
private readonly string _obfuscatedAssemblyOutputDir ;
2025-04-05 19:02:50 +08:00
2025-05-03 21:43:50 +08:00
private readonly List < string > _toObfuscatedAssemblyNames ;
private readonly List < string > _notObfuscatedAssemblyNamesReferencingObfuscated ;
2025-05-12 17:21:20 +08:00
private readonly List < string > _assemblySearchDirs ;
2025-04-21 09:57:34 +08:00
2025-05-12 22:01:35 +08:00
private readonly ConfigurablePassPolicy _passPolicy ;
2025-05-12 17:21:20 +08:00
private readonly Pipeline _pipeline1 = new Pipeline ( ) ;
private readonly Pipeline _pipeline2 = new Pipeline ( ) ;
2025-05-16 11:33:03 +08:00
private readonly byte [ ] _defaultStaticByteSecret ;
private readonly int [ ] _defaultStaticIntSecret ;
private readonly byte [ ] _defaultDynamicByteSecret ;
private readonly int [ ] _defaultDynamicIntSecret ;
private readonly HashSet < string > _dynamicSecretAssemblyNames ;
2025-05-12 08:13:01 +08:00
private readonly int _randomSeed ;
private readonly string _encryptionVmGenerationSecret ;
2025-05-11 17:36:58 +08:00
private readonly int _encryptionVmOpCodeCount ;
2025-05-11 19:28:19 +08:00
private readonly string _encryptionVmCodeFile ;
2025-04-21 09:57:34 +08:00
2025-05-04 19:24:14 +08:00
private ObfuscationPassContext _ctx ;
2025-04-17 22:02:48 +08:00
2025-05-11 17:36:58 +08:00
public Obfuscator ( ObfuscatorBuilder builder )
2025-04-05 19:02:50 +08:00
{
2025-05-16 11:33:03 +08:00
_defaultStaticByteSecret = KeyGenerator . GenerateKey ( builder . DefaultStaticSecret , VirtualMachine . SecretKeyLength ) ;
_defaultStaticIntSecret = KeyGenerator . ConvertToIntKey ( _defaultStaticByteSecret ) ;
_defaultDynamicByteSecret = KeyGenerator . GenerateKey ( builder . DefaultDynamicSecret , VirtualMachine . SecretKeyLength ) ;
_defaultDynamicIntSecret = KeyGenerator . ConvertToIntKey ( _defaultDynamicByteSecret ) ;
_dynamicSecretAssemblyNames = new HashSet < string > ( builder . DynamicSecretAssemblyNames ) ;
2025-05-12 08:13:01 +08:00
_randomSeed = builder . RandomSeed ;
_encryptionVmGenerationSecret = builder . EncryptionVmGenerationSecretKey ;
2025-05-11 17:36:58 +08:00
_encryptionVmOpCodeCount = builder . EncryptionVmOpCodeCount ;
2025-05-11 19:28:19 +08:00
_encryptionVmCodeFile = builder . EncryptionVmCodeFile ;
2025-05-11 09:17:04 +08:00
2025-05-11 17:36:58 +08:00
_toObfuscatedAssemblyNames = builder . ToObfuscatedAssemblyNames ;
_notObfuscatedAssemblyNamesReferencingObfuscated = builder . NotObfuscatedAssemblyNamesReferencingObfuscated ;
_obfuscatedAssemblyOutputDir = builder . ObfuscatedAssemblyOutputDir ;
2025-05-12 17:21:20 +08:00
_assemblySearchDirs = builder . AssemblySearchDirs ;
2025-05-03 21:43:50 +08:00
2025-05-12 22:01:35 +08:00
_passPolicy = new ConfigurablePassPolicy ( _toObfuscatedAssemblyNames , builder . EnableObfuscationPasses , builder . ObfuscationPassConfigFiles ) ;
2025-05-11 17:36:58 +08:00
foreach ( var pass in builder . ObfuscationPasses )
2025-05-03 21:43:50 +08:00
{
2025-05-12 17:21:20 +08:00
if ( pass is SymbolObfusPass symbolObfusPass )
{
_pipeline2 . AddPass ( pass ) ;
}
else
{
_pipeline1 . AddPass ( pass ) ;
}
2025-05-03 21:43:50 +08:00
}
2025-05-12 17:21:20 +08:00
_pipeline1 . AddPass ( new CleanUpInstructionPass ( ) ) ;
2025-05-16 10:06:19 +08:00
_pipeline2 . AddPass ( new RemoveObfuzAttributesPass ( ) ) ;
2025-04-05 19:02:50 +08:00
}
2025-04-16 23:03:41 +08:00
public void Run ( )
2025-05-03 21:43:50 +08:00
{
2025-05-12 17:21:20 +08:00
FileUtil . RecreateDir ( _obfuscatedAssemblyOutputDir ) ;
RunPipeline ( _pipeline1 ) ;
_assemblySearchDirs . Insert ( 0 , _obfuscatedAssemblyOutputDir ) ;
RunPipeline ( _pipeline2 ) ;
}
private void RunPipeline ( Pipeline pipeline )
{
if ( pipeline . Empty )
{
return ;
}
OnPreObfuscation ( pipeline ) ;
DoObfuscation ( pipeline ) ;
OnPostObfuscation ( pipeline ) ;
2025-05-03 21:43:50 +08:00
}
2025-05-16 11:33:03 +08:00
private IEncryptor CreateEncryptionVirtualMachine ( byte [ ] secret )
2025-05-11 12:48:53 +08:00
{
2025-05-12 08:13:01 +08:00
var vmCreator = new VirtualMachineCreator ( _encryptionVmGenerationSecret ) ;
2025-05-11 17:36:58 +08:00
var vm = vmCreator . CreateVirtualMachine ( _encryptionVmOpCodeCount ) ;
2025-05-11 19:28:19 +08:00
var vmGenerator = new VirtualMachineCodeGenerator ( vm ) ;
if ( ! File . Exists ( _encryptionVmCodeFile ) )
{
throw new Exception ( $"EncryptionVm CodeFile:`{_encryptionVmCodeFile}` not exists! Please run `Obfuz/GenerateVm` to generate it!" ) ;
}
if ( ! vmGenerator . ValidateMatch ( _encryptionVmCodeFile ) )
{
throw new Exception ( $"EncryptionVm CodeFile:`{_encryptionVmCodeFile}` not match with encryptionVM settings! Please run `Obfuz/GenerateVm` to update it!" ) ;
}
2025-05-16 11:33:03 +08:00
var vms = new VirtualMachineSimulator ( vm , secret ) ;
2025-05-11 19:28:19 +08:00
var generatedVmTypes = AppDomain . CurrentDomain . GetAssemblies ( )
. Select ( assembly = > assembly . GetType ( "Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine" ) )
. Where ( type = > type ! = null )
. ToList ( ) ;
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-16 11:33:03 +08:00
var gvmInstance = ( IEncryptor ) Activator . CreateInstance ( generatedVmTypes [ 0 ] , new object [ ] { secret } ) ;
2025-05-11 19:28:19 +08:00
2025-05-14 14:36:25 +08:00
VerifyVm ( vm , vms , gvmInstance ) ;
return vms ;
}
private void VerifyVm ( VirtualMachine vm , VirtualMachineSimulator vms , IEncryptor gvmInstance )
{
int testInt = 11223344 ;
long testLong = 1122334455667788L ;
float testFloat = 1234f ;
double testDouble = 1122334455.0 ;
2025-05-14 10:46:42 +08:00
string testString = "hello,world" ;
2025-05-11 19:28:19 +08:00
for ( int i = 0 ; i < vm . opCodes . Length ; i + + )
{
2025-05-14 10:46:42 +08:00
int ops = i * vm . opCodes . Length + i ;
2025-05-14 12:21:25 +08:00
//int salt = i;
//int ops = -1135538782;
int salt = - 879409147 ;
2025-05-11 19:28:19 +08:00
{
2025-05-14 14:36:25 +08:00
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 = gvmInstance . Encrypt ( testInt , ops , salt ) ;
int decryptedValueOfGvm = gvmInstance . 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}" ) ;
}
2025-05-11 19:28:19 +08:00
}
{
2025-05-14 14:36:25 +08:00
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 = gvmInstance . Encrypt ( testLong , ops , salt ) ;
long decryptedValueOfGvm = gvmInstance . 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}" ) ;
}
2025-05-11 19:28:19 +08:00
}
{
2025-05-14 14:36:25 +08:00
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 = gvmInstance . Encrypt ( testFloat , ops , salt ) ;
float decryptedValueOfGvm = gvmInstance . 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}" ) ;
}
2025-05-11 19:28:19 +08:00
}
2025-05-14 10:46:42 +08:00
{
2025-05-14 14:36:25 +08:00
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 = gvmInstance . Encrypt ( testDouble , ops , salt ) ;
double decryptedValueOfGvm = gvmInstance . 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}" ) ;
}
2025-05-14 10:46:42 +08:00
}
2025-05-14 14:36:25 +08:00
2025-05-14 10:46:42 +08:00
{
2025-05-14 14:36:25 +08:00
byte [ ] encryptedStrOfVms = vms . Encrypt ( testString , ops , salt ) ;
string descryptedStrOfVms = vms . DecryptString ( encryptedStrOfVms , 0 , encryptedStrOfVms . Length , ops , salt ) ;
if ( descryptedStrOfVms ! = testString )
{
throw new Exception ( $"VirtualMachineSimulator decrypt string failed! opCode:{i}, originalValue:{testString} decryptedValue:{descryptedStrOfVms}" ) ;
}
byte [ ] encryptedStrOfGvm = gvmInstance . Encrypt ( testString , ops , salt ) ;
string descryptedStrOfGvm = gvmInstance . 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 ( descryptedStrOfGvm ! = testString )
{
throw new Exception ( $"GeneratedEncryptionVirtualMachine decrypt string failed! opCode:{i}, originalValue:{testString} decryptedValue:{descryptedStrOfGvm}" ) ;
}
2025-05-14 10:46:42 +08:00
}
2025-05-11 19:28:19 +08:00
}
2025-05-11 12:48:53 +08:00
}
2025-05-16 11:33:03 +08:00
private EncryptionScopeInfo CreateEncryptionScope ( byte [ ] byteSecret , int [ ] intSecret )
{
IEncryptor encryption = CreateEncryptionVirtualMachine ( byteSecret ) ;
RandomCreator localRandomCreator = ( seed ) = > new RandomWithKey ( intSecret , _randomSeed ^ seed ) ;
return new EncryptionScopeInfo ( byteSecret , intSecret , encryption , localRandomCreator ) ;
}
private EncryptionScopeProvider CreateEncryptionScopeProvider ( )
{
var defaultStaticScope = CreateEncryptionScope ( _defaultStaticByteSecret , _defaultStaticIntSecret ) ;
var defaultDynamicScope = CreateEncryptionScope ( _defaultDynamicByteSecret , _defaultDynamicIntSecret ) ;
2025-05-16 12:33:37 +08:00
foreach ( string dynamicAssName in _dynamicSecretAssemblyNames )
{
if ( ! _toObfuscatedAssemblyNames . Contains ( dynamicAssName ) )
{
throw new Exception ( $"Dynamic secret assembly `{dynamicAssName}` should be in the toObfuscatedAssemblyNames list!" ) ;
}
}
2025-05-16 11:33:03 +08:00
return new EncryptionScopeProvider ( defaultStaticScope , defaultDynamicScope , _dynamicSecretAssemblyNames ) ;
}
2025-05-12 17:21:20 +08:00
private void OnPreObfuscation ( Pipeline pipeline )
2025-04-05 19:02:50 +08:00
{
2025-05-12 17:21:20 +08:00
AssemblyCache assemblyCache = new AssemblyCache ( new PathAssemblyResolver ( _assemblySearchDirs . ToArray ( ) ) ) ;
List < ModuleDef > toObfuscatedModules = new List < ModuleDef > ( ) ;
List < ModuleDef > obfuscatedAndNotObfuscatedModules = new List < ModuleDef > ( ) ;
LoadAssemblies ( assemblyCache , toObfuscatedModules , obfuscatedAndNotObfuscatedModules ) ;
2025-05-03 22:32:18 +08:00
2025-05-16 11:33:03 +08:00
EncryptionScopeProvider encryptionScopeProvider = CreateEncryptionScopeProvider ( ) ;
2025-05-13 08:49:57 +08:00
var moduleEntityManager = new GroupByModuleEntityManager ( ) ;
2025-05-16 11:33:03 +08:00
var rvaDataAllocator = new RvaDataAllocator ( encryptionScopeProvider , moduleEntityManager ) ;
var constFieldAllocator = new ConstFieldAllocator ( encryptionScopeProvider , rvaDataAllocator , moduleEntityManager ) ;
2025-05-04 19:24:14 +08:00
_ctx = new ObfuscationPassContext
2025-05-03 22:32:18 +08:00
{
2025-05-12 17:21:20 +08:00
assemblyCache = assemblyCache ,
toObfuscatedModules = toObfuscatedModules ,
obfuscatedAndNotObfuscatedModules = obfuscatedAndNotObfuscatedModules ,
2025-05-03 22:32:18 +08:00
toObfuscatedAssemblyNames = _toObfuscatedAssemblyNames ,
notObfuscatedAssemblyNamesReferencingObfuscated = _notObfuscatedAssemblyNamesReferencingObfuscated ,
obfuscatedAssemblyOutputDir = _obfuscatedAssemblyOutputDir ,
2025-05-13 08:49:57 +08:00
moduleEntityManager = moduleEntityManager ,
2025-05-10 11:25:07 +08:00
2025-05-16 11:33:03 +08:00
encryptionScopeProvider = encryptionScopeProvider ,
2025-05-10 11:25:07 +08:00
rvaDataAllocator = rvaDataAllocator ,
constFieldAllocator = constFieldAllocator ,
2025-05-12 18:03:39 +08:00
whiteList = new NotObfuscatedMethodWhiteList ( ) ,
2025-05-12 22:01:35 +08:00
passPolicy = _passPolicy ,
2025-05-03 22:32:18 +08:00
} ;
2025-05-13 08:49:57 +08:00
ObfuscationPassContext . Current = _ctx ;
pipeline . Start ( ) ;
2025-04-05 19:02:50 +08:00
}
2025-05-12 17:21:20 +08:00
private void LoadAssemblies ( AssemblyCache assemblyCache , List < ModuleDef > toObfuscatedModules , List < ModuleDef > obfuscatedAndNotObfuscatedModules )
2025-04-05 19:02:50 +08:00
{
2025-05-03 23:23:16 +08:00
foreach ( string assName in _toObfuscatedAssemblyNames . Concat ( _notObfuscatedAssemblyNamesReferencingObfuscated ) )
2025-04-05 19:02:50 +08:00
{
2025-05-12 17:21:20 +08:00
ModuleDefMD mod = assemblyCache . TryLoadModule ( assName ) ;
2025-04-17 22:02:48 +08:00
if ( mod = = null )
{
Debug . Log ( $"assembly: {assName} not found! ignore." ) ;
continue ;
}
2025-05-03 23:23:16 +08:00
if ( _toObfuscatedAssemblyNames . Contains ( assName ) )
2025-04-05 19:02:50 +08:00
{
2025-05-12 17:21:20 +08:00
toObfuscatedModules . Add ( mod ) ;
2025-04-05 19:02:50 +08:00
}
2025-05-12 17:21:20 +08:00
obfuscatedAndNotObfuscatedModules . Add ( mod ) ;
2025-04-05 19:02:50 +08:00
}
}
2025-05-12 17:21:20 +08:00
private void WriteAssemblies ( )
2025-04-05 19:02:50 +08:00
{
2025-05-12 17:21:20 +08:00
foreach ( ModuleDef mod in _ctx . obfuscatedAndNotObfuscatedModules )
2025-04-05 21:47:28 +08:00
{
2025-05-03 23:23:16 +08:00
string assNameWithExt = mod . Name ;
string outputFile = $"{_obfuscatedAssemblyOutputDir}/{assNameWithExt}" ;
mod . Write ( outputFile ) ;
Debug . Log ( $"save module. name:{mod.Assembly.Name} output:{outputFile}" ) ;
2025-04-05 21:47:28 +08:00
}
}
2025-05-12 17:21:20 +08:00
private void DoObfuscation ( Pipeline pipeline )
{
2025-05-13 08:49:57 +08:00
pipeline . Run ( ) ;
2025-05-12 17:21:20 +08:00
}
private void OnPostObfuscation ( Pipeline pipeline )
{
2025-05-13 08:49:57 +08:00
pipeline . Stop ( ) ;
2025-05-14 10:46:42 +08:00
_ctx . constFieldAllocator . Done ( ) ;
_ctx . rvaDataAllocator . Done ( ) ;
2025-05-12 17:21:20 +08:00
WriteAssemblies ( ) ;
}
2025-04-05 19:02:50 +08:00
}
}