Obfuscation is the language of Gods.

Obfuscar 2.0 errors with loading types from assembly

This post was most recently updated on March 27th, 2024.

5 min read.

I plugged Obfuscar into my build pipeline (the easiest configuration ever, by the way) because I needed to obfuscate a DLL I was going to push as a NuGet package. The DLL was obfuscated without changing any of the public APIs, Classes or Parameters – only internal stuff was scrambled.

Simple stuff. Presumably.

AI-powered summary: Secure DLL Deployment with Obfuscar for NuGet

Obfuscar integration into build pipelines offers a straightforward solution for obfuscating DLLs without altering public APIs. Despite successful obfuscation and functionality in projects, PowerShell encounters errors when loading types from the obfuscated assembly. This issue, often resulting in a generic error message or an OutOfMemoryException, stems from PowerShell’s confusion with obfuscated DLLs. The remedy lies in specific configurations within the obfuscar.xml file. Setting ‘HideStrings’ to false and ‘KeepPublicApi’ to true, or using ObfuscationAttribute to exclude items, ensures PowerShell compatibility.

Essentially, the article linked below describes what I was working towards:

However, I wasn’t quite having the success I hoped for.

Problem

Whilst the obfuscation was successful, and I could even reference the obfuscated DLL and use it in my other project just fine, when importing the DLL to PowerShell our whole team would run into different flavors of this error:

“Could not load type from assembly”

Or

“Cannot find type [typename]: Verify that the assembly containing this type is loaded.”

In PowerShell, running this:

$assembly = [Reflection.Assembly]::LoadFile([pathtodll])
$myObj = New-Object -TypeName "[MyNamespace].[PublicType]"

Resulted in this:

PowerShell throwing error "Cannot find type [typename]: Verify that the assembly containing this type is loaded." when we're trying to load our obfuscated assembly.
PowerShell throwing error “Cannot find type [typename]: Verify that the assembly containing this type is loaded.” when we’re trying to load our obfuscated assembly.

Even though the same type was instantiable just fine when referencing the DLL from another project! So what’s wrong with PowerShell, then??

Reason

Well, it turns out PowerShell just gets confused by obfuscated DLLs and there’s a fair chance of an OutOfMemoryException being thrown internally, then swallowed, and a more general “whoops we failed to perform whatever you requested” to be returned. This time being loading types, because that’s required to instantiate a new object.

A discussion about this can be found here: https://github.com/obfuscar/obfuscar/issues/171

Solution

Luckily, this can be solved with the following configuration (most of the time):

Open obfuscar.xml (or whatever file you use to configure Obfuscar in your project) and make sure you have these switches there:

How to configure Obfuscar to ignore Public API and some stringvalues when obfuscating your assemblies:
<?xml version="1.0" encoding="utf-8" ?>
<Obfuscator>
  <!-- These are just general configuration: -->
  <Var name="InPath" value="." />
  <Var name="OutPath" value=".\Obfuscator_Output" />

  <!-- Then your actual configuration: -->
  
  <!-- This needs to be false, otherwise PowerShell will freak out: -->
  <Var name="HideStrings" value="false" />

  <!-- This either needs to be true, or you need to decorate some of your types - more about that later! -->
  <Var name="KeepPublicApi" value="true" />
</Obfuscator>

HideStrings being false in combination of the types you’re using being non-obfuscated is the key here. In my case, I was happy just not obfuscating any of the Public members and methods, but you can also add this parameter to the ones you want to keep and set KeepPublicApi as false:

[ObfuscationAttribute(Exclude=true, ApplyToMembers=false)]

You can also add exclusion parameters to obfuscar.xml file, to skip obfuscating namespaces, types, methods, or properties – see below for an example (source):

How to configure Obfuscar to ignore certain namespaces, types, methods or properties:
<Module file="$(InPath)\AssemblyX.exe">
  <!-- skip a namespace -->
  <SkipNamespace name="Company.PublicBits" />

  <!-- to skip a namespace recursively, just put * on the end -->
  <SkipNamespace name="Company.PublicBits*" />

  <!-- skip field by name -->
  <SkipField type="Full.Namespace.And.TypeName"
    attrib="public" name="Fieldname" />

  <!-- skip field by regex -->
  <SkipField type="Full.Namespace.And.TypeName"
    attrib="public" rx="Pub.*" />

  <!-- skip type...will still obfuscate its methods -->
  <SkipType name="Full.Namespace.And.TypeName2" />

  <!-- skip type...will skip its methods next -->
  <SkipType name="Full.Namespace.And.TypeName3" />
  <!-- skip TypeName3's public methods -->
  <SkipMethod type="Full.Namespace.And.TypeName3"
    attrib="public" rx=".*" />
  <!-- skip TypeName3's protected methods -->
  <SkipMethod type="Full.Namespace.And.TypeName3"
    attrib="family" rx=".*" />

  <!-- skip type and its methods -->
  <SkipType name="Full.Namespace.And.TypeName4" skipMethods="true" />
  <!-- skip type and its fields -->
  <SkipType name="Full.Namespace.And.TypeName4" skipFields="true" />
  <!-- skip type and its properties -->
  <SkipType name="Full.Namespace.And.TypeName4" skipProperties="true" />
  <!-- skip type and its events -->
  <SkipType name="Full.Namespace.And.TypeName4" skipEvents="true" />
  <!-- skip attributes can be combined (this will skip the methods and fields) -->
  <SkipType name="Full.Namespace.And.TypeName4" skipMethods="true" skipFields="true" />
  <!-- skip the hiding of strings in this type's methods -->
  <SkipType name="Full.Namespace.And.TypeName4" skipStringHiding="true" />

  <!-- skip a property in TypeName5 by name -->
  <SkipProperty type="Full.Namespace.And.TypeName5"
    name="Property2" />
  <!-- skip a property in TypeName5 by regex -->
  <SkipProperty type="Full.Namespace.And.TypeName5"
    attrib="public" rx="Something\d" />

  <!-- skip an event in TypeName5 by name -->
  <SkipProperty type="Full.Namespace.And.TypeName5"
    name="Event2" />
  <!-- skip an event in TypeName5 by regex -->
  <SkipProperty type="Full.Namespace.And.TypeName5"
    rx="Any.*" />

  <!-- avoid the hiding of strings in TypeName6 on all methods -->
  <SkipStringHiding type="Full.Namespace.And.TypeName6" name="*" />
</Module>

Alternatively, you can just use the ObfuscationAttribute to exclude certain items from the obfuscation – a lengthy, thorough example below:

How to configure any decent obfuscator to ignore certain particular namespaces, types, methods or properties:
using System;
using System.Reflection;

// Mark this public assembly for obfuscation.
[assembly:ObfuscateAssemblyAttribute(false)]

// This class is marked for obfuscation, because the assembly
// is marked.
public class Type1
{

    // Exclude this method from obfuscation. The default value
    // of the Exclude property is true, so it is not necessary
    // to specify Exclude=True, although spelling it out makes
    // your intent clearer.
    [ObfuscationAttribute(Exclude=true)]
    public void MethodA() {}

    // This member is marked for obfuscation because the class
    // is marked.
    public void MethodB() {}
}

// Exclude this type from obfuscation, but do not apply that
// exclusion to members. The default value of the Exclude 
// property is true, so it is not necessary to specify 
// Exclude=true, although spelling it out makes your intent 
// clearer.
[ObfuscationAttribute(Exclude=true, ApplyToMembers=false)]
public class Type2
{

    // The exclusion of the type is not applied to its members,
    // however in order to mark the member with the "default" 
    // feature it is necessary to specify Exclude=false,
    // because the default value of Exclude is true. The tool
    // should not strip this attribute after obfuscation.
    [ObfuscationAttribute(Exclude=false, Feature="default", 
        StripAfterObfuscation=false)]
    public void MethodA() {}

    // This member is marked for obfuscation, because the 
    // exclusion of the type is not applied to its members.
    public void MethodB() {}

}

// This class only exists to provide an entry point for the
// assembly and to display the attribute values.
internal class Test
{

    public static void Main()
    {

        // Display the ObfuscateAssemblyAttribute properties
        // for the assembly.        
        Assembly assem = typeof(Test).Assembly;
        object[] assemAttrs = assem.GetCustomAttributes(
            typeof(ObfuscateAssemblyAttribute), false);

        foreach( Attribute a in assemAttrs )
        {
            ShowObfuscateAssembly((ObfuscateAssemblyAttribute) a);
        }

        // Display the ObfuscationAttribute properties for each
        // type that is visible to users of the assembly.
        foreach( Type t in assem.GetTypes() )
        {
            if (t.IsVisible)
            {
                object[] tAttrs = t.GetCustomAttributes(
                    typeof(ObfuscationAttribute), false);

                foreach( Attribute a in tAttrs )
                {
                    ShowObfuscation(((ObfuscationAttribute) a),
                        t.Name);
                }

                // Display the ObfuscationAttribute properties
                // for each member in the type.
                foreach( MemberInfo m in t.GetMembers() )
                {
                    object[] mAttrs = m.GetCustomAttributes(
                        typeof(ObfuscationAttribute), false);

                    foreach( Attribute a in mAttrs )
                    {
                        ShowObfuscation(((ObfuscationAttribute) a), 
                            t.Name + "." + m.Name);
                    }
                }
            }
        }
    }

    private static void ShowObfuscateAssembly(
        ObfuscateAssemblyAttribute ob)
    {
        Console.WriteLine("\r\nObfuscateAssemblyAttribute properties:");
        Console.WriteLine("   AssemblyIsPrivate: {0}", 
            ob.AssemblyIsPrivate);
        Console.WriteLine("   StripAfterObfuscation: {0}",
            ob.StripAfterObfuscation);
    }

    private static void ShowObfuscation(
        ObfuscationAttribute ob, string target)
    {
        Console.WriteLine("\r\nObfuscationAttribute properties for: {0}",
            target);
        Console.WriteLine("   Exclude: {0}", ob.Exclude);
        Console.WriteLine("   Feature: {0}", ob.Feature);
        Console.WriteLine("   StripAfterObfuscation: {0}",
            ob.StripAfterObfuscation);
        Console.WriteLine("   ApplyToMembers: {0}", ob.ApplyToMembers);
    }
}

/* This code example produces the following output:

ObfuscateAssemblyAttribute properties:
   AssemblyIsPrivate: False
   StripAfterObfuscation: True

ObfuscationAttribute properties for: Type1.MethodA
   Exclude: True
   Feature: all
   StripAfterObfuscation: True
   ApplyToMembers: True

ObfuscationAttribute properties for: Type2
   Exclude: True
   Feature: all
   StripAfterObfuscation: True
   ApplyToMembers: False

ObfuscationAttribute properties for: Type2.MethodA
   Exclude: False
   Feature: default
   StripAfterObfuscation: False
   ApplyToMembers: True
 */

That’s it – with the configuration to exclude whatever confuses your PowerShell in place, you should be good!

mm
5 1 vote
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments