In our last article we took a look at implementing an example tamper proofing solution in code. This has now been implemented as an NCloak task; that is, NCloak now automatically packages your assemblies and generates a bootstrapper to execute your program. In this article, we take a look at hair pulling "obvious now that I know the answer" problems I had during this seemingly simple implementation!
Quick recap
In the first tamper proofing implementation article we took a look at "half" of this automated implementation. Essentially we encrypted each input assembly, and placed it as an obfuscated resource in the output assembly along with a "hash" confirming the original assembly. We used a specific naming convention within our obfuscated resource section to help identification:
- A resource ending with ".v0" is a validation hash.
- A resource ending with ".e" is a Unicode encoded resource that details the entry point to the bootstrapper assembly (for "quick" lookup).
- Every other resource is an encrypted assembly.
In the next article we wrote a sample bootstrapper in C# that unpackaged these embedded resources and ran our assembly.
Implementation
To be honest, the code for automating generation of a bootstrapper is boring. It is a bunch of type building with IL statements riddled through it, basically defining the same bootstrapper we looked at last time. The "interesting" code was inside the encryption of the assemblies, and the sample implementation of the bootstrapper. The rest is just a bunch of IL (i.e a bunch of il.Append, and il.Create statements as we build the instructions to run the bootstrapper). And lets be honest: IL is boring to read (well.... it's boring to explain...).
Therefore, rather than pumping out sections of this code which will mean nothing to the majority I'll simply point you to a full copy of the source (take a look at the NCloak source, or more specifically the tamper proof task within NCloak).
Instead, we'll take a look at a problem I encountered while debugging this which I'm sure would've decreased my life expectancy by a few months.
Something which drove me nuts
While I was testing there was one part of the code which drove me completely nuts trying to find the issue. In fact, I wasted a whole evening trying out different debugging techniques, generation block formats etc to no avail... It wasn't until after a much needed sleep that I saw the problem in one of those "duh" moments...
What is this problem I'm speaking of?
I was getting two different errors (which made it confusing) upon running the bootstrapper: One was a "BadImageFormatException", and the other was a "MissingMethodException".
It drove me nuts...
To attempt to solve this I added debugging information everywhere trying to identify what on earth was going wrong. Everything appeared to be happening when it should be except for assembly resolution - it never seemed to enter that method at all. Hmmm... I thought. Perhaps it can't find it because it is never loaded? I decided to follow this line of thought...
To "solve" this I added a forced load upon bootstrapper initialisation (I've kept it there in the release code) thinking that this would solve the issue. Well, I could see from my debugging that everything was loading correctly - but the error still occurred. At this point I could swear I started growing my first grey hairs. I tried a handful of other different debugging techniques before throwing my hands up in the air and deciding to sleep on it.
The next day I tried running the source on a different computer - it worked perfectly! Feeling happy that I'd slept on it and somehow fixed it while I was asleep I happily continued on with my day (the idea was to write my blog the next day).
So, Friday morning I switched on my development PC and decided to test it once more. Shock horror - the same issue occurred. I considered throwing the laptop out the door, but before my emotions got in the way I decided to think logically about it.
After some careful thinking - I had my "Eureka!" moment. In fact, I felt like a complete idiot, so much so, that I thought what better way to celebrate this feeling than by telling the world of my "duh" moment.
Stop teasing... what was the problem?
Well, on my development machine I have default command line arguments stored against the NCloak console runner project to make testing nice and easy. A CTRL-F5 and my "clean files" get obfuscated and pumped into an "obfuscated" directory. What could go wrong?
Well, after a while this testing directory became clogged - particular with failed DLL's due to a mistype when writing IL (i.e. using ldloc.1 instead of ldloc.2). The problem should have been obvious when the assembly resolution never occurred, but the issue was because of the "dead" dll's lying in my directory.
Assembly resolution is a funny thing. I would've thought that assembly resolution would've occurred within the AppDomain context first. It appears this isn't so (well, not all of the time). Assembly resolution first looks within the current search directory and if found uses that assembly if found. This is what was happening on my development machine: the "BadImageFormat" exception was from some encrypted DLL's lying in the bootstrapper directory, while the "MissingMethod" exception was from some plain old unobfuscated assemblies in the bootstrapper directory.
Duh!
Anyway - I thought I'd share this story just in case any of you come across this problem!
Working again
Proejct Milestone
The NCloak tamper proof implementation is by no means the best it could be. It is fairly easily bypassed, simply due to the way it stores assemblies in an unsecure environment. It can be improved, to some extent, in future versions of NCloak. The main milestone of this implementation however is that we now have a base to work from and improve, rather than nothing at all.
The main issue with tamper proofing is that we are working with an unknown unsecured environment. This means that no matter what we do, someone can pull it apart and put it back together without the tamper proofing mechanisms used. We will look at methods to make this as hard as possible in the coming weeks without the use of a secure hardware; but please be aware that this is an issue with ANY implementation without the use of secure hardware.
On another note, please feel free to take the NCloak source and modify it for use with any hardware dongle if that's how you'd like to use the system. While not necessary, I would ask that you submit any source you write so that we can improve the end product for all users.
Conclusion
Implementing an automated tamper proofing was made extremely interesting, not by the challenges of outputting IL (although that was extremely time consuming), but by problems post implementation caused by assembly resolution.
Assembly resolution is an interesting thing, which I had always taken for granted and assumed it working a specific way. The main lesson is to be wary of redundant files in your bin directories. If I had kept the publish directory clean I would have had this implementation out half a week ago!
Coming Soon
A big thanks for all of you with future article suggestions. The current list of topics I will be covering in the coming weeks are (in no particular order):
- Using Mono.Cecil to automate string decryption decompilation
- Code flow transformations
- Dead code removal
- Tamper proofing improvements
- Overcoming the switch statement in IL
- Dispatcher driven tamperproofing
- The .NET hacking toolkit
- Anti-debugging techniques
- Watermarking
- Creating a managed IL parser
If you have any further topic suggestions then please feel free to let me know. Also, if you find any bugs in NCloak then please enter them in the Issue Tracker for the project. I know there are bugs; identifying them is half the challenge!
Labels: duh, ncloak, protecting your precious code, tamper proofing
In the last article we took a look at a partial implementation of tamper proofing: encrypting an assembly and placing it in the resource section of an assembly. This article takes a look at writing a bootstrapper for decrypting these embedded assemblies and running them dynamically on the fly (of course avoiding writing them to disk).
Let's get straight into it!
Implementation
To demonstrate this bootstrapper, I am using a sample console application which is essentially just a command line emulator. To save some time, I've already encrypted this applications two assemblies (SimpleProgram.exe and SimpleLibrary.dll) and included them manually into the articles example code as embedded resources. When it comes to the NCloak implementation, we will of course automate this process - however this manual process is more for the purpose of demonstration!
Our ideal scenario is for the bootstrapper to start the program running within the same console window, with no perceived difference whatsoever. I also want it to be running in a separate AppDomain than the bootstrapper - for no real reason except for cleanliness in the runtime (the bootstrapper and the application are completely different).
So lets kick this off...
Starting it off
For separation of concerns, I'm going to separate the code for bootstrapping into a class called "ProgramRunner". This means that we can spawn the ProgramRunner into a separate AppDomain right from the word go:
AppDomain domain = AppDomain.CreateDomain("App");
Assembly executingAssembly = Assembly.GetExecutingAssembly();
ProgramRunner runner = (ProgramRunner)domain.CreateInstanceAndUnwrap(executingAssembly.FullName, "TestBootstrapper.ProgramRunner");
AppDomain.CurrentDomain.AssemblyResolve += runner.ResolveAssembly;
//Load the entry point
runner.Start();
Let's just pretend that I haven't set a delegate for resolving assemblies just yet (we do take a look at that further down). This is pretty basic code; it's main purpose really to separate AppDomain's.
Now the purpose of the ProgramRunner is to load up the assemblies into memory, and then execute them. So let's take a look at the Start routine to see how the general process works:
/// <summary>
/// Starts this program.
/// </summary>
public void Start()
{
//Get the entry point from the file
string entryAssemblyResource = null;
string entryType = null;
string entryMethod = null;
foreach (string resourceName in executingAssembly.GetManifestResourceNames())
{
if (resourceName.EndsWith(".e"))
{
Stream s = executingAssembly.GetManifestResourceStream(resourceName);
if (s == null)
continue;
using (StreamReader sr = new StreamReader(s, Encoding.Unicode))
{
entryAssemblyResource = "TestBootstrapper.Resources." + sr.ReadLine();
entryType = sr.ReadLine();
entryMethod = sr.ReadLine();
}
//If we have it all, break out - otherwise keep searching
if (!String.IsNullOrEmpty(entryAssemblyResource) && !String.IsNullOrEmpty(entryType) && !String.IsNullOrEmpty(entryMethod))
break;
}
}
//Get out of here if we never found an entry assembly/type
if (String.IsNullOrEmpty(entryAssemblyResource) || String.IsNullOrEmpty(entryType) || String.IsNullOrEmpty(entryMethod))
return;
//Find the type
Type type = LoadType(entryAssemblyResource, entryType);
if (type == null)
return;
//Find the method
MethodInfo method = type.GetMethod(entryMethod, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (method == null)
return;
//Execute
method.Invoke(null, null);
}
As you can see, the Start routine does the main "processing" work. The first thing we are doing is finding the entry point to the embedded application. As a quick reference, we created a "lookup" resource which ended in ".e". As you may recall from last time, this is a Unicode encoded text file which contains the resource name, the type name and the method name each on separate names. The reason this file is created is really just to save time when searching obfuscated assemblies. After we've extracted these, we simply try to load the entry point type and invoke the entry method. We're done!
Loading an assembly
The process of loading an assembly needs to perform the following steps:
- Check the cache and return the required assembly if present
- Get the assembly hash from the bootstrapper resources
- Get the assembly data from the bootstrapper resources
- Decrypt the assembly
- Check the hash of the assembly is a match
- Load the assembly and add to our cache
Implementing this in code is fairly trivial:
private Assembly LoadAssembly(string resourceName)
{
//Check if we've loaded it already
if (loadedAssemblies.ContainsKey(resourceName))
return loadedAssemblies[resourceName];
//Get the hash first
string hash;
using (Stream s = executingAssembly.GetManifestResourceStream(resourceName + ".v0"))
{
if (s == null)
return null; //Exit without dinner
byte[] hashData = new byte[s.Length];
s.Read(hashData, 0, hashData.Length);
hash = Convert.ToBase64String(hashData);
}
//Get the data
byte[] data;
using (Stream s = executingAssembly.GetManifestResourceStream(resourceName))
{
if (s == null)
return null; //Exit without dinner
data = new byte[s.Length];
s.Read(data, 0, data.Length);
}
//Now decrypt the data
Rfc2898DeriveBytes password = new Rfc2898DeriveBytes("ee7ad9b5-1462-47f6-849e-37190a7751ee", Convert.FromBase64String("d3rImlxQskaHWUy80gSCjg=="), 2);
//Get the key bytes and initialisation vector
byte[] keyBytes = password.GetBytes(32);
byte[] initVector = password.GetBytes(16);
//Get the assembly
byte[] assembly = DecryptData(data, keyBytes, initVector);
//Check the hash
if (hash != HashData(assembly))
return null;
//Load it into our domain
Assembly asm = Assembly.Load(assembly);
if (asm == null)
return null;
//Add to our loaded list
loadedAssemblies.Add(resourceName, asm);
return asm;
}
A couple of points to note in this implementation:
- I've hardcoded the password and salt for decrypting the assembly for simplicity. In future implementations we might be a bit smarter about this, but remember that the bootstrapper will be dynamically generated so we have this luxury.
- Hashing uses the SHA256 algorithm. The implementation can be seen within the source code.
- Decryption is using the Rijndael algorithm. The implementation can be seen within the source code.
- We've used Assembly.Load to load the assembly - this loads it into the currently running AppDomain. The currently running AppDomain in this case is the new one we've spawned however thanks to the ProgramRunner class.
Resolving unknown types
As it happens, we're actually resolving the assemblies semi-dynamically in an arguably inefficient manner. I say "arguably" as you could say that loading each assembly on program load is efficient... I'll leave that for you to decide. Anyway, the reason it is done this way is because I decided to obfuscate the resource names, removing any relevance to runtime naming. Of course we could have included a mapping file to use, however this does give away more information to would be hackers than perhaps is required (although it may be something for future implementations?). Anyway, so how are we resolving assemblies?
/// <summary>
/// Resolves the assembly.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="args">The <see cref="System.ResolveEventArgs"/> instance containing the event data.</param>
/// <returns></returns>
public Assembly ResolveAssembly(object sender, ResolveEventArgs args)
{
//Load the dynamic assemblies
lock (assemblyLock)
{
if (!assembliesLoaded)
{
assembliesLoaded = true;
LoadAssemblies();
}
}
//Find the assembly we're looking for
Assembly[] currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in currentAssemblies)
{
if (a.FullName == args.Name)
return a;
}
return null;
}
Of course we link up this event in the ProgramRunner constructor:
AppDomain.Current.AssemblyResolve += ResolveAssembly;
Actually, we don't do much during resolution. We simply load all the encrypted assemblies on first request, and then resolve from what is loaded inside the current AppDomain.
The proof is in the pudding
The real question is: does it all work? Well, yes - it does. Quite effectively:
You wouldn't know the difference
Of course the main pitfall of this implementation (as mentioned by rickrat in the last article) is that because we are storing the assemblies in the assembly's resources, it is easy to extract them to disk. Once they are on disk it is just a matter of working out the decryption algorithm and running them directly from there. To add an extra step, we'll take a look at different places to store the images in a later article.
In fact overall this tamper proofing implementation is relatively easy to circumvent (unfortunately most implementations are!). The main advantages about this implementation is that:
- It is intended to be automated costing you a minimal amount of time for extra protection (of course automated injection = easy automated removal...)
- It is another block in the way of getting at your code.
- It is relatively fast
Conclusion
We did it! We've got a working implementation of a bootstrapper; next step is to rip this apart and automate it within an NCloak task. There isn't really anything special per se about this implementation; the main purpose is to get an idea about what a bootstrapper does, so that we can later see how much effort it is to remove.
Overall, we've come to the age old quandary once again - no matter what we do it can be circumvented. Unfortunately that is ALWAYS going to be the case - I've yet to see a fool proof implementation and I doubt I ever will. The main reason is that the computer has got to be able to run your program and understand it - so why can't a hacker? Our best bet is simply to make it as hard as possible to understand, yet try to maintain the benefits of using the .NET framework as our platform of choice!
Coming soon
Well, next week we'll automate this by generating a new assembly in Mono.Cecil. I won't go into IL specifics (I'll be writing many many parts to this mini series otherwise!), however I will go into the process of generating an assembly on the fly with Mono.Cecil.
In later weeks we'll look at:
- Improving the tamper proofing solution (Storing images in different locations, Hiding the decryption details a bit better).
- Blindly hacking our implementation (depressing for some but I think it's important to know the true strength of an implementation)
- Anything else that comes to mind - had a few good suggestions from comments so keep it up!
I'll also be improving the obfuscator a bit more in the next few weeks for those interested, introducing namespace obfuscation among other things (I forgot to write it in!). In the meantime, does anyone have any suggestion upon a suitable user interface: GUI or command line? I swing towards command line each and every time (automation), but how does everyone else like to use obfuscators?
Keep up the questions/suggestions - it's great to hear feedback from the community!
Last week we took a look at various methods of tamper proofing .NET assemblies, breaking it down into three primary methods:
- Hash checking - checking your assemblies hash to ensure unchanged
- Result checking - checking results of calculations throughout your assembly
- Encryption - encrypting your assemblies before deployment
Over the next few weeks I'll discuss an implementation of injecting tamper proof techniques into your assemblies using NCloak. To make things simple, we'll simply be using encryption and hash checking to detect any tampering of assemblies.
This actually requires a bit of code to be written, therefore I'm splitting this over three articles. The articles will cover (in order):
- A skeleton implementation of what we are doing within NCloak.
- An "ideal" bootstrapper implementation.
- Completing the circle; an implementation of injecting tamper proofing code
The overall plan...
Ideally, what I'm looking at implementing is for NCloak to do the following:
- Perform obfuscation as per normal
- Instead of outputting the assemblies, define a brand new "bootstrapper" executable.
- Encrypt each of the obfuscated assemblies, and insert them as embedded resources in our bootstrapper
- Define code within the bootstrapper to unpack and load these assemblies dynamically
Seems sorta complex... (hence splitting it into a few articles). Today we'll look at defining a new bootstrapper executable, and populating it's resources with encrypted versions of our obfuscated assemblies.
Encryption/Hashing Algorithms
To make things super easy, I'm going to simply use symmetric encryption, and SHA256 as a hashing algorithm. The implementation of these I've kept fairly trivial:
/// <summary>
/// Hashes the specified data using the SHA256 algorithm.
/// </summary>
/// <param name="rawData">The raw data.</param>
/// <returns>A byte array pertaining to the hash of the raw data</returns>
private static byte[] HashData(byte[] rawData)
{
SHA256 sha = SHA256.Create();
return sha.ComputeHash(rawData);
}
/// <summary>
/// Encrypts the specified data using Rijndael symmetric encryption.
/// </summary>
/// <param name="rawData">The raw data.</param>
/// <param name="keyBytes">The key bytes.</param>
/// <param name="initVector">The init vector.</param>
/// <returns>A byte array of the encrypted data</returns>
private static byte[] EncryptData(byte[] rawData, byte[] keyBytes, byte[] initVector)
{
//Use Rijndael encryption
Rijndael symmetricAlgorithm = Rijndael.Create();
symmetricAlgorithm.Mode = CipherMode.CBC;
//Generate an encryptor from the existing key bytes
using (ICryptoTransform encryptor = symmetricAlgorithm.CreateEncryptor(keyBytes, initVector))
{
//Do the encryption
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
//Write the data into the crypto stream
cryptoStream.Write(rawData, 0, rawData.Length);
//Make sure that the final block is flushed
cryptoStream.FlushFinalBlock();
//Return the cipher bytes
return memoryStream.ToArray();
}
}
}
}
As you can see, the encryption method requires the key and initialisation vector to be passed directly to the method. We'll let the framework create these for us using a random password and salt:
//Lets generate a password string passwordKey = Guid.NewGuid().ToString(); Guid salt = Guid.NewGuid(); Rfc2898DeriveBytes password = new Rfc2898DeriveBytes(passwordKey, salt.ToByteArray(), PasswordIterations); //Get the key bytes and initialisation vector byte[] keyBytes = password.GetBytes(KeySize/8); byte[] initVector = password.GetBytes(InitVectorSize);
Parsing the assemblies
The next step is to go through and encrypt/hash each of our obfuscated assemblies. This is a fairly trivial task now we've defined our methods:
//Go through each assembly, calculate the hash and encrypt
Dictionary<string, byte[]> encryptedAssemblies = new Dictionary<string, byte[]>();
Dictionary<string, byte[]> hashedAssemblies = new Dictionary<string, byte[]>();
Dictionary<string, AssemblyDefinition> assemblies = context.GetAssemblyDefinitions();
foreach (string assembly in assemblies.Keys)
{
//Get the raw data of the assembly
byte[] assemblyRawData;
AssemblyFactory.SaveAssembly(assemblies[assembly], out assemblyRawData);
//Calculate the hash
hashedAssemblies.Add(assembly, HashData(assemblyRawData));
//Now encrypt it
encryptedAssemblies.Add(assembly, EncryptData(assemblyRawData, keyBytes, initVector));
#if TEMP_OUTPUT_RAW
//Output
File.WriteAllBytes(Path.Combine(context.Settings.OutputDirectory, Path.GetFileName(assembly)), encryptedAssemblies[assembly]);
File.WriteAllBytes(Path.Combine(context.Settings.OutputDirectory, Path.GetFileName(assembly) + ".v0"), hashedAssemblies[assembly]);
#endif
}
#if TEMP_OUTPUT_RAW
File.WriteAllText(Path.Combine(context.Settings.OutputDirectory, "password.txt"),
passwordKey + Environment.NewLine +
Convert.ToBase64String(salt.ToByteArray()));
#endif
As you'll see in the above code, I've included some code wrapped with a conditional compilation constant: TEMP_OUTPUT_RAW. The reason I've included this code is to provide a stepping stone towards part two of this article series. That is, writing a bootstrapper for dynamically loading these encrypted assemblies and running them on the fly. In the "release" code of NCloak, we'll remove these constants... but for the meantime we'll leave them in there until we've got the bootstrapper working.
Defining the bootstrapper assembly
The final step for today is to define a new assembly to include the bootstrapper code in. Luckily, Mono.Cecil does more than pull apart and manipulation of assemblies - it can also create new assemblies! What we are aiming to do here is:
- Create a new assembly
- Embed our encrypted assembly
- Embed the hash of the original assembly
- If we have an entry point, embed the details of that also
Obviously in the final version we'll do a bit more, but for the meantime, we'll do just the above as this will give us a great starting point to write a bootstrapper.
You'll notice that in the steps above, I've mentioned storing entry point information. This is so that we can begin execution of the program without loading all embedded assemblies first. It also adds in yet another check which they have to get right in order for the system to run.
//Now we've got that information - it's up to us to generate a bootstrapper assembly
//We'll do this by starting from scratch
AssemblyDefinition bootstrapperAssembly = AssemblyFactory.DefineAssembly(
context.Settings.TamperProofAssemblyName,
TargetRuntime.NET_2_0,
AssemblyKind.Console);
//Add some resources - encrypted assemblies
foreach (string assembly in encryptedAssemblies.Keys)
{
//We'll randomise the names using the type table
string resourceName = context.NameManager.GenerateName(NamingTable.Type);
string hashName = resourceName + ".v0";
bootstrapperAssembly.MainModule.Resources.Add(
new EmbeddedResource(resourceName,
ManifestResourceAttributes.Private,
encryptedAssemblies[assembly]));
bootstrapperAssembly.MainModule.Resources.Add(
new EmbeddedResource(hashName,
ManifestResourceAttributes.Private,
hashedAssemblies[assembly]));
//If it has an entry point then save this as well
AssemblyDefinition def = assemblies[assembly];
if (def.EntryPoint != null)
{
StringBuilder entryPointHelper = new StringBuilder();
#if TEMP_OUTPUT_RAW
entryPointHelper.AppendLine(Path.GetFileName(assembly));
#else
entryPointHelper.AppendLine(resourceName);
#endif
entryPointHelper.AppendLine(def.EntryPoint.DeclaringType.Namespace +
"." + def.EntryPoint.DeclaringType.Name);
entryPointHelper.AppendLine(def.EntryPoint.Name);
bootstrapperAssembly.MainModule.Resources.Add(
new EmbeddedResource(resourceName + ".e",
ManifestResourceAttributes.Private,
Encoding.Unicode.GetBytes(entryPointHelper.ToString())));
#if TEMP_OUTPUT_RAW
File.WriteAllBytes(
Path.Combine(context.Settings.OutputDirectory, "entry.e"),
Encoding.Unicode.GetBytes(entryPointHelper.ToString()));
#endif
}
}
There are a number of things of note here:
- The new assembly being defined is created as an executable (either windows or console).
- The resource name is obfuscated. I haven't decided whether to keep this for the release version yet or not due to the requirement of having to load all assemblies during the resolution of an unknown assembly. What are your thoughts?
- The hash is identified as the same as the resource name with ".v0" on the end. This is for quick lookup of the hash without too much overhead.
- The resources are added to the Main Module using the EmbeddedResource class.
- If an entry point is detected then a Unicode resource is embedded containing the assembly resource name, the entry point type and the entry point method name. Remember that the type and method name will be obfuscated names at this point.
- The entry point definition is defined by ending with ".e"
Once again, I'm pumping the relevant information to disk so that we can use it to write a bootstrapper next article.
That's it!
Well, that's it for today. It sort of leaves things in an "anticipating" state, however it is important background information that will start to make more sense next time when we write the bootstrapper for this. Take away points from this article:
- We are implementing a mixture of encryption and hash checking as a tamper proofing mechanism. NCloak will automatically prepare your solution in a tamper proof manner.
- We're using a symmetric Rijndael algorithm for encryption of assemblies, and using SHA256 to compute the hash value.
- We're storing all of the encrypted assemblies and hash values as an embedded resource of the bootstrapper. The bootstrapper will dynamically load these on the fly.
- We're also storing the entry point as an embedded resource for quick entry into our program.
- To begin with, this implementation is pumping all information to disk. This is so we can use this data in our bootstrapper implementation next week!
Coming soon
In coming articles, hopefully things we start to make a bit more sense as to how this works. We'll take a look at the following:
- I'll provide the code for the bootstrapper that dynamically decrypts and loads assemblies on demand,
- We'll make NCloak automatically inject this tamper proofing code into your assemblies
- I'll probably also take a look at blindly hacking (i.e. assuming I didn't know how it was put together) this implementation
Until then, please let me know if you have any suggestions or questions.