I serialized an instance of my class into a file (with BinaryFormatter
)
After, in another project, I wanted to deserialize this file, but it did not work because my new project does not have the description of my old class. The .Deserialize()
gets an exception
Unable to find assembly '*MyAssembly, Version=1.9.0.0, Culture=neutral, PublicKeyToken=null'.*".
But I have the .DLL of the assembly containing a description of the old class which I want to deserialize.
I don't want to add a reference a this DLL in the project (I want be able to deserialize a class of any kind of assembly...)
How can I inform the Serializer/Deserializer to use my dynamically loaded assembly?
Xml Serializer serializes only public member of object but Binary Serializer serializes all member whether public or private. In Xml Serialization, some of object state is only saved but in Binary Serialization, entire object state is saved.
The BinaryFormatter type is dangerous and is not recommended for data processing. Applications should stop using BinaryFormatter as soon as possible, even if they believe the data they're processing to be trustworthy.
Binary serialization allows modifying private members inside an object and therefore changing the state of it. Because of this, other serialization frameworks, like System. Text. Json, that operate on the public API surface are recommended.
.Net Serialization (C#/VB.Net) Serialization can be defined as the process of converting the state of an object instance to a stream of data, so that it can be transported across the network or can be persisted in the storage location.
Assuming you are loading your assembly via Assembly.Load()
or Assembly.LoadFrom()
, then as explained in this answer to SerializationException for dynamically loaded Type by Chris Shain, you can use the AppDomain.AssemblyResolve
event to load your dynamic assembly during deserialization. However, for security reasons you will want to prevent loading of entirely unexpected assemblies.
One possible implementation would be to introduce the following:
public class AssemblyResolver
{
readonly string assemblyFullPath;
readonly AssemblyName assemblyName;
public AssemblyResolver(string assemblyName, string assemblyFullPath)
{
// You might want to validate here that assemblyPath really is an absolute not relative path.
// See e.g. https://stackoverflow.com/questions/5565029/check-if-full-path-given
this.assemblyFullPath = assemblyFullPath;
this.assemblyName = new AssemblyName(assemblyName);
}
public ResolveEventHandler AssemblyResolve
{
get
{
return (o, a) =>
{
var name = new AssemblyName(a.Name);
if (name.Name == assemblyName.Name) // Check only the name if you want to ignore version. Otherwise you can just check string equality.
return Assembly.LoadFrom(assemblyFullPath);
return null;
};
}
}
}
Then, somewhere in startup, add an appropriate ResolveEventHandler
to AppDomain.CurrentDomain.AssemblyResolve
e.g. as follows:
class Program
{
const string assemblyFullPath = @"C:\Full-path-to-my-assembly\MyAssembly.dll";
const string assemblyName = @"MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
static Program()
{
AppDomain.CurrentDomain.AssemblyResolve += new AssemblyResolver(assemblyName, assemblyFullPath).AssemblyResolve;
}
This ResolveEventHandler
checks to see whether the requested assembly has your dynamic assembly's name, and if so, loads the current version from the expected full path.
An alternative would be to write a custom SerializationBinder
and attach it to BinaryFormatter.Binder
. In BindToType (string assemblyName, string typeName)
the binder would need to check for types belonging to your dynamic assembly, and bind to them appropriately. The trick here is dealing with situations in which your dynamically loaded types are nested in a generic from another assembly, e.g. a List<MyClass>
. In that case assemblyName
will be the name of the assembly of List<T>
not MyClass
. For details on how to do this see
In comments @sgnsajgon asked, I wonder why I cannot deserialize stream the same way I would do when signed assembly is explicitly referenced in project - just formatter.Deserialize(stream)
and nothing else.
While I don't know what Microsoft employees were thinking when they designed these classes (back in .Net 1.1), it might be because:
In the words of Eric Lippert, no one ever designed, specified, implemented, tested, documented and shipped that feature.
BinaryFormatter
security is already somewhat of a dumpster fire, but automatically calling Assembly.Load()
on any unexpected assembly name in a BinaryFormatter
stream might make things even worse.
By "dumpster fire" I mean that, out-of-the-box, BinaryFormatter
will instantiate and populate the types specified in the input stream which might not be the types you are expecting. Thus you might do
var instance = (MyClass)new BinaryFormatter().Deserialize(stream);
But if the stream actually contains a serialized attack gadget such as TempFileCollection
then the gadget will be created and populated and the attack will be effected.
(For details on this sort of attack, see TypeNameHandling caution in Newtonsoft Json, External json vulnerable because of Json.Net TypeNameHandling auto?, How to configure Json.NET to create a vulnerable web API and Alvaro Muñoz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf. Those links specify how to modify Json.NET's configuration to enable such attacks; BinaryFormatter
is vulnerable to them in default configuration.)
Now if BinaryFormatter
were automatically calling Assembly.Load()
on unrecognized assembly names, apps using it might additionally become vulnerable to a DLL planting attack where attack types from attack DLLs would get unexpectedly loaded from some unexpected location rather than a secure location, further exacerbating the attack risk.
(Incidentally, if you do choose to write your own SerializationBinder
you can filter out unexpected types and/or known attack types, thus reducing the risk of attack gadget injection. This also can be harder than expected since BinaryFormatter
streams often include serialized private or internal classes that you may not know to allow.)
As an aside, What are the deficiencies of the built-in BinaryFormatter based .Net serialization? gives a useful overview of other problems you might encounter using BinaryFormatter
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With