当前位置: 首页 > article >正文

C#中深度解析BinaryFormatter序列化生成的二进制文件

C#中深度解析BinaryFormatter序列化生成的二进制文件

BinaryFormatter序列化时,对象必须有 可序列化特性[Serializable]

一.新建窗体测试程序BinaryDeepAnalysisDemo,将默认的Form1重命名为FormBinaryDeepAnalysis

二.新建测试类Test

Test.cs源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BinaryDeepAnalysisDemo
{
    /// <summary>
    /// 一定要标记该类是可序列化的
    /// </summary>
    [Serializable]
    public class Test
    {
        public byte ByteData { get; set; }
        public short Int16Data { get; set; }
        public int Id { get; set; }
        public long Int64Data { get; set; }
        public float FloatData { get; set; }
        public double DoubleData { get; set; }
        public string TestName { get; set; }
    }
}

三.新建字典与类相互转化类ConversionUtil:

ConversionUtil.cs源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
 
namespace BinaryDeepAnalysisDemo
{
    /// <summary>
    /// 字典Dictionary与自定义类CustomClass属性之间的转化
    /// 斯内科 2024-05-10
    /// </summary>
    public class ConversionUtil
    {
        /// <summary>
        /// 将一个实体类的属性转化为键值对集合,键为属性名PropertyName,值为对应的值
        /// </summary>
        /// <typeparam name="T">一种自定义类,该类必须已实例化</typeparam>
        /// <param name="obj">实例化后的类对象</param>
        /// <returns>返回转化后的字典</returns>
        public static Dictionary<string, object> CustomClassToDictionary<T>(T obj) where T : class, new()
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            Type type = typeof(T);
            PropertyInfo[] propertyInfos = type.GetProperties();
            for (int i = 0; i < propertyInfos.Length; i++)
            {
                string key = propertyInfos[i].Name;
                object val = propertyInfos[i].GetValue(obj);
                dict.Add(key, val);
            }
            return dict;
        }

        /// <summary>
        /// 将字典转化为实例化类,如果类的属性名称在字典中存在该键key,就使用该键对应的值为类的属性key赋值【注意,转化可能因值的类型、范围等不同而导致失败】
        /// 如果类的属性名称在字典中不存在该键key,则不进行赋值,只是按属性对应类型的默认值处理
        /// </summary>
        /// <typeparam name="T">要转化的目标类型</typeparam>
        /// <param name="dict">键值对字典</param>
        /// <returns>要获取的目标实例化类对象,如果字典为空,则按类的默认值处理</returns>
        public static T DictionaryToCustomClass<T>(Dictionary<string, object> dict) where T : class
        {
            T obj = default(T);
            if (dict == null || dict.Count == 0)
            {
                return obj;
            }
            Type type = typeof(T);
            obj = (T)Activator.CreateInstance(type);
            PropertyInfo[] propertyInfos = type.GetProperties();
            for (int i = 0; i < propertyInfos.Length; i++)
            {
                string key = propertyInfos[i].Name;
                if (dict.ContainsKey(key))
                {
                    propertyInfos[i].SetValue(obj, dict[key]);
                }
            }
            return obj;
        }
    }
}

四.测试窗体设计器相关代码如下

文件FormBinaryDeepAnalysis.Designer.cs


namespace BinaryDeepAnalysisDemo
{
    partial class FormBinaryDeepAnalysis
    {
        /// <summary>
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 清理所有正在使用的资源。
        /// </summary>
        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows 窗体设计器生成的代码

        /// <summary>
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.panel1 = new System.Windows.Forms.Panel();
            this.rtxtMessage = new System.Windows.Forms.RichTextBox();
            this.btnSerial = new System.Windows.Forms.Button();
            this.btnDeserial = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // panel1
            // 
            this.panel1.Location = new System.Drawing.Point(12, 12);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(300, 532);
            this.panel1.TabIndex = 0;
            // 
            // rtxtMessage
            // 
            this.rtxtMessage.Location = new System.Drawing.Point(511, 12);
            this.rtxtMessage.Name = "rtxtMessage";
            this.rtxtMessage.ReadOnly = true;
            this.rtxtMessage.Size = new System.Drawing.Size(482, 532);
            this.rtxtMessage.TabIndex = 1;
            this.rtxtMessage.Text = "";
            // 
            // btnSerial
            // 
            this.btnSerial.Location = new System.Drawing.Point(360, 78);
            this.btnSerial.Name = "btnSerial";
            this.btnSerial.Size = new System.Drawing.Size(63, 23);
            this.btnSerial.TabIndex = 2;
            this.btnSerial.Text = "序列化";
            this.btnSerial.UseVisualStyleBackColor = true;
            this.btnSerial.Click += new System.EventHandler(this.btnSerial_Click);
            // 
            // btnDeserial
            // 
            this.btnDeserial.Location = new System.Drawing.Point(360, 155);
            this.btnDeserial.Name = "btnDeserial";
            this.btnDeserial.Size = new System.Drawing.Size(63, 23);
            this.btnDeserial.TabIndex = 3;
            this.btnDeserial.Text = "反序列化";
            this.btnDeserial.UseVisualStyleBackColor = true;
            this.btnDeserial.Click += new System.EventHandler(this.btnDeserial_Click);
            // 
            // FormBinaryDeepAnalysis
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1001, 556);
            this.Controls.Add(this.btnDeserial);
            this.Controls.Add(this.btnSerial);
            this.Controls.Add(this.rtxtMessage);
            this.Controls.Add(this.panel1);
            this.Name = "FormBinaryDeepAnalysis";
            this.Text = "序列化生成的二进制进行深度解析";
            this.Load += new System.EventHandler(this.FormBinaryDeepAnalysis_Load);
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Panel panel1;
        private System.Windows.Forms.RichTextBox rtxtMessage;
        private System.Windows.Forms.Button btnSerial;
        private System.Windows.Forms.Button btnDeserial;
    }
}

五.窗体FormBinaryDeepAnalysis相关示例代码如下

文件FormBinaryDeepAnalysis.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BinaryDeepAnalysisDemo
{
    public partial class FormBinaryDeepAnalysis : Form
    {
        public FormBinaryDeepAnalysis()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 显示文本
        /// </summary>
        /// <param name="contents"></param>
        private void DisplayMessage(string contents) 
        {
            if (!IsHandleCreated)
                return;
            this.BeginInvoke(new Action(() =>
            {
                if (rtxtMessage.TextLength >= 20480) 
                {
                    rtxtMessage.Clear();
                }
                rtxtMessage.AppendText($"{contents}\n");
                rtxtMessage.ScrollToCaret();
            }));
        }

        private void FormBinaryDeepAnalysis_Load(object sender, EventArgs e)
        {
            Test test = new Test()
            {
                ByteData = 234,
                Int16Data = 23456,
                Id = 12345678,
                Int64Data = 9876543210123L,
                FloatData = 60.25F,
                DoubleData = -87654321.3125,
                TestName = "ABC123abc"
            };
            Dictionary<string, object> dict = ConversionUtil.CustomClassToDictionary(test);
            for (int i = 0; i < dict.Count; i++)
            {
                KeyValuePair<string, object> keyValuePair = dict.ElementAt(i);
                Label label = new Label();
                label.Name = $"lbl{keyValuePair.Key}";
                label.Text = keyValuePair.Key;
                label.Location = new Point(3, 3 + 30 * i);

                TextBox textBox = new TextBox();
                textBox.Name = $"txb{keyValuePair.Key}";
                textBox.Text = Convert.ToString(keyValuePair.Value);
                textBox.Location = new Point(100, 3 + 30 * i);
                textBox.Size = new Size(160, 25);
                textBox.Tag = keyValuePair.Key;

                panel1.Controls.AddRange(new Control[] { label, textBox });
            }
        }

        private void btnSerial_Click(object sender, EventArgs e)
        {
            try
            {
                Dictionary<string, object> dict = new Dictionary<string, object>();
                for (int i = 0; i < panel1.Controls.Count; i++)
                {
                    Control control = panel1.Controls[i];
                    if (control is TextBox)
                    {
                        object val = control.Text;
                        switch (Convert.ToString(control.Tag))
                        {
                            case "ByteData":
                                val = byte.Parse(control.Text);
                                break;
                            case "Int16Data":
                                val = short.Parse(control.Text);
                                break;
                            case "Id":
                                val = int.Parse(control.Text);
                                break;
                            case "Int64Data":
                                val = long.Parse(control.Text);
                                break;
                            case "FloatData":
                                val = float.Parse(control.Text);
                                break;
                            case "DoubleData":
                                val = double.Parse(control.Text);
                                break;
                        }
                        dict.Add(Convert.ToString(control.Tag), val);
                    }
                }
                Test test = ConversionUtil.DictionaryToCustomClass<Test>(dict);
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                string path = $"{AppDomain.CurrentDomain.BaseDirectory}test.bin";
                using (System.IO.FileStream stream = new System.IO.FileStream(path, System.IO.FileMode.Create))
                {
                    binaryFormatter.Serialize(stream, test);
                }
                byte[] buffer = System.IO.File.ReadAllBytes(path);
                DisplayMessage($"长度:{buffer.Length}\n字节流:\n{string.Join(",", buffer)}");
                DisplayMessage("------打印每个属性的字节流编码,以及查找序列化的二进制数据------");
                SeekData(TypeCode.Byte, "ByteData", test.ByteData, buffer);
                SeekData(TypeCode.Int16, "Int16Data", test.Int16Data, buffer);
                SeekData(TypeCode.Int32, "Id", test.Id, buffer);
                SeekData(TypeCode.Int64, "Int64Data", test.Int64Data, buffer);
                SeekData(TypeCode.Single, "FloatData", test.FloatData, buffer);
                SeekData(TypeCode.Double, "DoubleData", test.DoubleData, buffer);
                SeekData(TypeCode.String, "TestName", test.TestName, buffer);

                DisplayMessage("------打印可转换为ASCII的字符,如果是看不到的特殊字符,显示源字节------");
                string ascii = "";
                for (int i = 0; i < buffer.Length; i++)
                {
                    if (buffer[i] >= 32 && buffer[i] <= 126)
                    {
                        ascii += (char)buffer[i];
                    }
                    else
                    {
                        ascii += $"【{buffer[i]}】";
                    }
                }
                DisplayMessage(ascii);
            }
            catch (Exception ex) 
            {
                DisplayMessage($"序列化时出现异常:【{ex.Message}】");
            }
        }

        /// <summary>
        /// 从字节流中查找数据
        /// </summary>
        /// <param name="typeCode"></param>
        /// <param name="propertyName"></param>
        /// <param name="val"></param>
        /// <param name="buffer"></param>
        private void SeekData(TypeCode typeCode, string propertyName, object val, byte[] buffer) 
        {
            int byteCount = 0;//类型所占用的字节个数
            byte[] dataBuffer = new byte[0];
            switch (typeCode)
            {
                case TypeCode.Byte:
                    byteCount = sizeof(byte);//类型所占用的字节个数
                    dataBuffer = new byte[] { (byte)val };
                    break;
                case TypeCode.Int16:
                    byteCount = sizeof(short);
                    dataBuffer = BitConverter.GetBytes((short)val);
                    break;
                case TypeCode.Int32:
                    byteCount = sizeof(int);
                    dataBuffer = BitConverter.GetBytes((int)val);
                    break;
                case TypeCode.Int64:
                    byteCount = sizeof(long);
                    dataBuffer = BitConverter.GetBytes((long)val);
                    break;
                case TypeCode.Single:
                    byteCount = sizeof(float);
                    dataBuffer = BitConverter.GetBytes((float)val);
                    break;
                case TypeCode.Double:
                    byteCount = sizeof(double);
                    dataBuffer = BitConverter.GetBytes((double)val);
                    break;
                default:
                    dataBuffer = Encoding.ASCII.GetBytes(Convert.ToString(val));
                    byteCount = dataBuffer.Length;
                    break;
            }
            DisplayMessage($"{propertyName}属性的值为【{val}】,字节流【{string.Join(",", dataBuffer)}】");
            List<int> listIndex = new List<int>();
            for (int i = 0; i < buffer.Length - byteCount; i++)
            {
                bool[] bArray = new bool[byteCount];
                for (int cnt = 0; cnt < byteCount; cnt++)
                {
                    bArray[cnt] = buffer[i + cnt] == dataBuffer[cnt];
                }
                //查找所有元素是否都为true
                if (Array.FindAll(bArray, x => x).Length == byteCount) 
                {
                    listIndex.Add(i);
                }
            }
            DisplayMessage($"\x20\x20\x20【{propertyName}】数据的地址查找到的起始索引有【{string.Join(",", listIndex)}】");
        }

        private void btnDeserial_Click(object sender, EventArgs e)
        {
            Test test = null;
            string path = $"{AppDomain.CurrentDomain.BaseDirectory}test.bin";
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            using (System.IO.FileStream stream = new System.IO.FileStream(path, System.IO.FileMode.Open))
            {
                test = binaryFormatter.Deserialize(stream) as Test;
            }
            if(test == null) 
            {
                return;
            }
            Dictionary<string, object> dict = ConversionUtil.CustomClassToDictionary(test);
            for (int i = 0; i < panel1.Controls.Count; i++)
            {
                Control control = panel1.Controls[i];
                if (control is TextBox)
                {
                    control.Text = "";
                    switch (Convert.ToString(control.Tag))
                    {
                        case "ByteData":
                            control.Text = Convert.ToString(test.ByteData);
                            break;
                        case "Int16Data":
                            control.Text = Convert.ToString(test.Int16Data);
                            break;
                        case "Id":
                            control.Text = Convert.ToString(test.Id);
                            break;
                        case "Int64Data":
                            control.Text = Convert.ToString(test.Int64Data);
                            break;
                        case "FloatData":
                            control.Text = Convert.ToString(test.FloatData);
                            break;
                        case "DoubleData":
                            control.Text = Convert.ToString(test.DoubleData);
                            break;
                        default:
                            control.Text = test.TestName;
                            break;
                    }
                }
            }
        }
    }
}

六.窗体运行如图:

我们可以发现 生成的字节码中 含有 程序集和类信息,以及属性名和对应的值

程序集和版本信息

类和属性字段以及相关数据类型信息

<TestName> 这里的TestName就是属性字段,如果需要获取该字段的类型,可以使用System.Reflection反射来获取具体的数据类型

具体数值区域

这里的数据大多都是使用BitConverter.GetBytes(数据值)进行字节流转化

七.BinaryFormatter序列化的不安全性

 如果将二进制文件读取出来,然后篡改指定的字节码,生成新的二进制文件,然后重新读取,结果就会发现数据被篡改了.很多单机游戏的存档文件修改就是这么干的.

这也是不提倡使用BinaryFormatter类进行序列化的原因,可能被劫持篡改二进制文件,因此在最新的net 5.0中会被标记为不安全的和过时的原因


http://www.kler.cn/a/537434.html

相关文章:

  • C# Mutex 锁 使用详解
  • 使用令牌桶算法通过redis实现限流
  • springboot配置https
  • 大学资产管理系统中的下载功能设计与实现
  • 50个常用的DeepSeek提示词
  • 学习日记-250207
  • WebSocket connection failed 解决
  • 2024中国行政区划多边形矢量数据(含有十段线)仅供学习
  • 活动预告 |【Part 1】Microsoft 安全在线技术公开课:通过扩展检测和响应抵御威胁
  • 即梦(Dreamina)技术浅析(六):多模态生成模型
  • golang使用sqlite3,开启wal模式,并发读写
  • AD域控粗略了解
  • DeepSeek+AnythingLLM生成攻防演练方案
  • [权限提升] Linux 提权 维持 — 系统错误配置提权 - Sudo 滥用提权
  • 微信小程序案例1——制作猫眼电影底部标签导航栏
  • 网络安全ITP是什么 网络安全产品ips
  • C++轻量级桌面GUI库FLTK
  • 图文并茂-jvm内存模型
  • GaussDB对象权限的注意事项
  • 【再谈设计模式】状态模式~对象行为的状态驱动者
  • 计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas)
  • 【算法】动态规划专题⑨ —— 二维费用背包问题 python
  • 如何衡量您的文化
  • LeetCode:503.下一个更大元素II
  • 正则表达式进阶(二)——零宽断言详解:\b \B \K \z \A
  • 半导体行业跨网文件交换系统