[dotnet-Sec]初探反序列化
参考Github上y4✌的开源笔记,狠狠学!
环境搭建
.NET:5.0
IDE:Rider(JB家族)
新建项目
选择.NET Core
(支持跨平台)下的控制台应用程序,然后创建
这是接触到的关于dotnet的第一个反序列化demo,使用的是BinaryFormatter生成二进制流
// Disable the warning.
#pragma warning disable SYSLIB0011
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace dotnet_sec_101
{
[Serializable]
public class DemoObject
{
public int n1;
[NonSerialized] public int n2;
public string str;
}
class Tester
{
public static void BinaryFomatterSerialize(string file, object o)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
FileStream fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
binaryFormatter.Serialize(fileStream, o);
fileStream.Close();
Console.WriteLine($"serialize object {o} to file {file}.");
}
public static object BinaryFomatterDeserialFromFile(string file)
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
object o = formatter.Deserialize(stream);
stream.Close();
return o;
}
static void Main(string[] args)
{
try
{
DemoObject demoObject = new DemoObject();
demoObject.n1 = 1;
demoObject.n2 = 2;
demoObject.str = "hack";
BinaryFomatterSerialize("ser.bin", demoObject);
DemoObject _demoObject = (DemoObject)BinaryFomatterDeserialFromFile("ser.bin");
Console.WriteLine($"n1: {_demoObject.n1}");
Console.WriteLine($"NonSer n2: {_demoObject.n2}");
Console.WriteLine($"str: {_demoObject.str}");
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
Console.ReadKey();
}
}
}
// Re-enable the warning.
#pragma warning restore SYSLIB0011
由于.net
的高版本问题,需要在csproj项目文件中添加如下条目
运行,可以发现加了[NonSerialized]
特性的变量n2未参与序列化,于是在反序列化后也就没有对应值,默认为0
我们查看使用BinaryFormatter生成的二进制文件ser.bin,开头为00 01 00 00
IFormatter接口
我们查看底层BinaryFormatter的实现,实现了IFormatter接口
(后面测试在.net framework
平台下还继承了另一个接口IRemotingFormatter
:
IRemotingFormatter
是用于远程调用的RPC接口
)
最终的IFormatter接口如下:
public interface IFormatter
{
object Deserialize(Stream serializationStream);
void Serialize(Stream serializationStream, object graph);
ISurrogateSelector SurrogateSelector { get; set; }
SerializationBinder Binder { get; set; }
StreamingContext Context { get; set; }
}
IFormatter定义了序列化和反序列化的两个方法,以及三个字段,其中每个字段含义如下:
类 字段名 | 含义用途 |
---|---|
ISurrogateSelector SurrogateSelector | 序列化代理选择器 接管formatter的序列化或反序列化处理 |
SerializationBinder Binder | 用于控制在序列化和反序列化期间使用的实际类型 |
StreamingContext Context | 序列化流上下文 其中states字段包含了序列化的来源和目的地 |
通过这三个字段,我们可以控制序列化和反序列化时数据的类型、值以及其他信息。
序列化和反序列化的生命周期
生命周期如下:
- 首先确定formatter是否有代理选择器,如果有则检查代理选择器要处理的对象类型是否和给定的对象类型一致,如果一致,代理选择器会调用
ISerializable.GetObjectData()
。- 如果没有代理选择器,或者代理选择器不处理该对象类型,则检查对象是否有
[Serializable]
特性。如果不能序列化则抛出异常。- 检查该对象是否实现
ISerializable
接口,如果实现就调用其GetObjectData
方法。- 如果没实现
ISerializable
接口就使用默认的序列化策略,序列化所有没标记[NonSerialized]
的字段。
ISerializationSurrogate代理器模式
首先看下它的接口定义:
需要在继承接口中实现如下函数:GetObjectData
,SetObjectData
using System.Runtime.InteropServices;
using System.Security;
#nullable disable
namespace System.Runtime.Serialization
{
[ComVisible(true)]
public interface ISerializationSurrogate
{
[SecurityCritical] // 确保只有具有足够权限的代码才能调用或访问被标记的代码。
void GetObjectData(object obj, SerializationInfo info, StreamingContext context);
// 序列化的时候被调用(压缩对象前需要get data)
[SecurityCritical]
object SetObjectData( // 反序列化时调用(恢复对象时要set data)
object obj,
SerializationInfo info,
StreamingContext context,
ISurrogateSelector selector);
}
}
非代理器 + 继承ISerializable
接口定义如下:
#nullable disable
namespace System.Runtime.Serialization
{
[ComVisible(true)]
public interface ISerializable
{
[SecurityCritical]
void GetObjectData(SerializationInfo info, StreamingContext context);
}
}
注释掉BinaryFormatter
设置代理器的语句即可
然后运行可以发现
注解模式(使用四个回调事件)
这个没啥好说的
特性 | 调用关联的方法时 | 典型用法 |
---|---|---|
OnDeserializingAttribute | 反序列化之前 | 初始化可选字段的默认值。 |
OnDeserializedAttribute | 反序列化之后 | 根据其他字段的内容修改可选字段值。 |
OnSerializingAttribute | 序列化之前 | 准备序列化。 例如,创建可选数据结构。 |
OnSerializedAttribute | 序列化之后 | 记录序列化事件。 |
演示demo
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;
namespace NetSerializer
{
[Serializable]
public class MyObject : ISerializable
{
public string str { get; set; }
public MyObject()
{
}
// 实现Iserializable接口的类必须包含有序列化构造函数, 否则会出错
protected MyObject(SerializationInfo info, StreamingContext context)
{
Console.WriteLine("MyObject(SerializationInfo info, StreamingContext context)");
str = info.GetString("str");
}
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
Console.WriteLine("GetObjectData(SerializationInfo info, StreamingContext context)");
info.AddValue("str", str, typeof(string));
}
// 四个回调事件 以及对应的特性
[OnDeserializing] // 反序列化之前
private void TestForOnDeserializing(StreamingContext sc)
{
// 打印回调函数调用时的上下文
//Console.WriteLine(sc.ToString());
Console.WriteLine("OnDeserializing Test");
}
[OnDeserialized]
private void TestForOnDeserialized(StreamingContext sc)
{
//Console.WriteLine(sc.ToString());
Console.WriteLine("OnDeserialized Test");
}
[OnSerializing]
private void TestForOnSerializing(StreamingContext sc)
{
//Console.WriteLine(sc.ToString());
Console.WriteLine("OnSerializing Test");
}
[OnSerialized]
private void TestForOnSerialized(StreamingContext sc)
{
//Console.WriteLine(sc.ToString());
Console.WriteLine("OnSerialized Test");
}
}
// 声明代理选择器
class MySerializationSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
Console.WriteLine("GetObjectData of IserializationSurrogate");
info.AddValue("str", ((MyObject)obj).str);
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context,
ISurrogateSelector selector)
{
Console.WriteLine("SetObjectData of IserializationSurrogate");
MyObject m = new MyObject();
m.str = (string)info.GetValue("str", typeof(string));
return m;
}
}
class Tester
{
static void Main(string[] args)
{
try
{
MyObject myObject = new MyObject();
myObject.str = "hello";
using (MemoryStream memoryStream = new MemoryStream())
{
// 构建formatter
BinaryFormatter binaryFormatter = new BinaryFormatter();
// 设置序列化代理选择器
SurrogateSelector ss = new SurrogateSelector();
ss.AddSurrogate(typeof(MyObject), binaryFormatter.Context, new MySerializationSurrogate());
// 将序列化代理选择器赋给formatter
binaryFormatter.SurrogateSelector = ss;
// 序列化
binaryFormatter.Serialize(memoryStream, myObject);
// 重置stream
memoryStream.Position = 0;
myObject = null;
// 反序列化
myObject = (MyObject)binaryFormatter.Deserialize(memoryStream);
Console.WriteLine(myObject.str);
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
}
生命周期小结
SerializationInfo
在上面生命周期的探讨中,使用到了SerializationInfo作为参数
存储序列化过程中的信息
其中上述的AddValue
函数的底层实现:
使用m_members
、m_data
、m_types
存储变量名、值、类型
internal void AddValueInternal(string name, object value, Type type)
{
if (this.m_nameToIndex.ContainsKey(name))
throw new SerializationException(Environment.GetResourceString("Serialization_SameNameTwice"));
this.m_nameToIndex.Add(name, this.m_currMember);
if (this.m_currMember >= this.m_members.Length)
this.ExpandArrays();
this.m_members[this.m_currMember] = name;
this.m_data[this.m_currMember] = value;
this.m_types[this.m_currMember] = type;
++this.m_currMember;
}
在不使用代理器模式的情况且在反序列化的过程中,通过调试可以发现,在CompleteISerializableObject()
中调用了runtimeConstructorInfo.SerializationInvoke(obj, info, context)
,这也能说明了SerializationInfo
的作用
然后进入到自定义的构造函数,完整栈帧如下
Main Thread
ObjectManager.CompleteISerializableObject()
ObjectManager.FixupSpecialObject()
ObjectManager.DoFixups()
ObjectReader.Deserialize()
BinaryFormatter.Deserialize()
BinaryFormatter.Deserialize()
Tester.Main()
internal void CompleteISerializableObject(
object obj,
SerializationInfo info,
StreamingContext context)
{
if (obj == null)
throw new ArgumentNullException(nameof (obj));
RuntimeType t = obj is ISerializable ? (RuntimeType) obj.GetType() : throw new ArgumentException(Environment.GetResourceString("Serialization_NotISer"));
RuntimeConstructorInfo runtimeConstructorInfo;
try
{
runtimeConstructorInfo = !(t == ObjectManager.TypeOfWindowsIdentity) || !this.m_isCrossAppDomain ? ObjectManager.GetConstructor(t) : WindowsIdentity.GetSpecialSerializationCtor();
}
catch (Exception ex)
{
throw new SerializationException(Environment.GetResourceString("Serialization_ConstructorNotFound", (object) t), ex);
}
// 调用构造函数
runtimeConstructorInfo.SerializationInvoke(obj, info, context);
}
相应的在序列化过程中可以找到调用GetObjectData的时机:
标签:info,Console,Sec,WriteLine,new,dotnet,StreamingContext,序列化 From: https://www.cnblogs.com/icfh/p/18027206Main Thread
WriteObjectInfo.InitSerialize()
WriteObjectInfo.Serialize()
ObjectWriter.Serialize()
BinaryFormatter.Serialize()
BinaryFormatter.Serialize()
Tester.Main()