【问题标题】:Casting a byte array to a managed structure将字节数组转换为托管结构
【发布时间】:2011-09-14 04:14:48
【问题描述】:

更新:这个问题的答案帮助我编写了开源项目AlicanC's Modern Warfare 2 Tool on GitHub。你可以看到我是如何在MW2Packets.cs 中读取这些数据包的,以及我在Extensions.cs 中编写的用于读取大端数据的扩展。

我正在 C# 应用程序中使用 Pcap.Net 捕获 Call of Duty: Modern Warfare 2 的 UDP 数据包。我从图书馆收到byte[]。我试图像字符串一样解析它,但效果不佳。

byte[] 我有一个通用数据包标头,然后是另一个特定于数据包类型的标头,然后是大厅中每个玩家的信息。

一位乐于助人的人帮我检查了一些数据包并提出了以下结构:

// Fields are big endian unless specified otherwise.
struct packet_header
{
    uint16_t magic;
    uint16_t packet_size;
    uint32_t unknown1;
    uint32_t unknown2;
    uint32_t unknown3;
    uint32_t unknown4;
    uint16_t unknown5;
    uint16_t unknown6;
    uint32_t unknown7;
    uint32_t unknown8;
    cstring_t packet_type; // \0 terminated string
};

// Fields are little endian unless specified otherwise.
struct header_partystate //Header for the "partystate" packet type
{
    uint32_t unknown1;
    uint8_t unknown2;
    uint8_t player_entry_count;
    uint32_t unknown4;
    uint32_t unknown5;
    uint32_t unknown6;
    uint32_t unknown7;
    uint8_t unknown8;
    uint32_t unknown9;
    uint16_t unknown10;
    uint8_t unknown11;
    uint8_t unknown12[9];
    uint32_t unknown13;
    uint32_t unknown14;
    uint16_t unknown15;
    uint16_t unknown16;
    uint32_t unknown17[10];
    uint32_t unknown18;
    uint32_t unknown19;
    uint8_t unknown20;
    uint32_t unknown21;
    uint32_t unknown22;
    uint32_t unknown23;
};

// Fields are little endian unless specified otherwise.
struct player_entry
{
    uint8_t player_id;

    // The following fields may not actually exist in the data if it's an empty entry.
    uint8_t unknown1[3];
    cstring_t player_name;
    uint32_t unknown2;
    uint64_t steam_id;
    uint32_t internal_ip;
    uint32_t external_ip;
    uint16_t unknown3;
    uint16_t unknown4;
    uint32_t unknown5;
    uint32_t unknown6;
    uint32_t unknown7;
    uint32_t unknown8;
    uint32_t unknown9;
    uint32_t unknown10;
    uint32_t unknown11;
    uint32_t unknown12;
    uint16_t unknown13;
    uint8_t unknown14[???];     // Appears to be a bit mask, sometimes the length is zero, sometimes it's one. (First entry is always zero?)
    uint8_t unknown15;
    uint32_t unknown16;
    uint16_t unknown17;
    uint8_t unknown18[???];     // Most of the time this is 4 bytes, other times it is 3 bytes.
};

我在我的 C# 应用程序中重新创建了包头结构,如下所示:

[StructLayout(LayoutKind.Sequential, Pack=1)]
struct PacketHeader
{
    public UInt16 magic;
    public UInt16 packetSize;
    public UInt32 unknown1;
    public UInt32 unknown2;
    public UInt32 unknown3;
    public UInt32 unknown4;
    public UInt16 unknown5;
    public UInt16 unknown6;
    public UInt32 unknown7;
    public UInt32 unknown8;
    public String packetType;
}

然后我尝试为“partystate”标题创建一个结构,但我收到错误消息说fixed 关键字不安全:

[StructLayout(LayoutKind.Sequential, Pack=1)]
struct PartyStateHeader
{
    UInt32 unknown1;
    Byte unknown2;
    Byte playerEntryCount;
    UInt32 unknown4;
    UInt32 unknown5;
    UInt32 unknown6;
    UInt32 unknown7;
    Byte unknown8;
    UInt32 unknown9;
    UInt16 unknown10;
    Byte unknown11;
    fixed Byte unknown12[9];
    UInt32 unknown13;
    UInt32 unknown14;
    UInt16 unknown15;
    UInt16 unknown16;
    fixed UInt32 unknown17[10];
    UInt32 unknown18;
    UInt32 unknown19;
    Byte unknown20;
    UInt32 unknown21;
    UInt32 unknown22;
    UInt32 unknown23;
}

由于unknown14unknown18 的大小不同,我无法为玩家条目做任何事情。 (玩家条目是最重要的。)

现在,不知何故,我必须将byte[] 我必须转换为这些PacketHeader 结构。可悲的是,这并不像(PacketHeader)bytes 那样容易。我尝试了我在互联网上找到的这种方法,但它抛出了AccessViolationException

GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
PacketHeader packetHeader = (PacketHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PacketHeader));

我怎样才能做到这一点?

【问题讨论】:

  • My answer here 不直接对结构进行编组,而是采用从您从网络读取的字节数组中提取字段值的方法。不过,您可能必须自己翻转大端位; .NET 基类都是 little-endian AFAIK。

标签: c# bytearray structure pcap.net


【解决方案1】:

//我在:http://code.cheesydesign.com/?p=572 找到了这个(我还没有测试过,但是 // 乍一看,它会很好用。)

    /// <summary>
    /// Reads in a block from a file and converts it to the struct
    /// type specified by the template parameter
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="reader"></param>
    /// <returns></returns>
    private static T FromBinaryReader<T>(BinaryReader reader)
    {

        // Read in a byte array
        byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T)));

        // Pin the managed memory while, copy it out the data, then unpin it
        GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
        T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();

        return theStructure;
    }

【讨论】:

  • 这个答案对我帮助很大。它可以用作重新解释演员表。
  • 这个函数如何处理c字符串?
【解决方案2】:

我会将字节数组转换为内存流。然后在该流上实例化一个二进制读取器。然后定义使用二进制读取器并解析单个类的辅助函数。

内置的BinaryReader 类总是使用小端序。

我会在这里使用类而不是结构。

class PacketHeader 
{
    uint16_t magic;
    uint16_t packet_size;
    uint32_t unknown1;
    uint32_t unknown2;
    uint32_t unknown3;
    uint32_t unknown4;
    uint16_t unknown5;
    uint16_t unknown6;
    uint32_t unknown7;
    uint32_t unknown8;
    string packet_type; // replaced with a real string
};

PacketHeader ReadPacketHeader(BinaryReader reader)
{
  var result=new PacketHeader();
  result.magic = reader.ReadInt16();
  ...
  result.packet_type=ReadCString();//Some helper function you might need to define yourself
  return result;
}

【讨论】:

  • 这不是我的问题的答案,而是我真正需要的。结构(现在是类)具有动态大小。播放器条目在某些情况下没有最后一个字节,并且它们具有动态大小的字符串。最好的解决方案是一一阅读,而不是直接投射。
【解决方案3】:

我就是这样做的:

using System;
using System.Runtime.InteropServices;
public static object GetObjectFromBytes(byte[] buffer, Type objType)
{
    object obj = null;
    if ((buffer != null) && (buffer.Length > 0))
    {
        IntPtr ptrObj = IntPtr.Zero;
        try
        {
            int objSize = Marshal.SizeOf(objType);
            if (objSize > 0)
            {
                if (buffer.Length < objSize)
                    throw new Exception(String.Format("Buffer smaller than needed for creation of object of type {0}", objType));
                ptrObj = Marshal.AllocHGlobal(objSize);
                if (ptrObj != IntPtr.Zero)
                {
                    Marshal.Copy(buffer, 0, ptrObj, objSize);
                    obj = Marshal.PtrToStructure(ptrObj, objType);
                }
                else
                    throw new Exception(String.Format("Couldn't allocate memory to create object of type {0}", objType));
            }
        }
        finally
        {
            if (ptrObj != IntPtr.Zero)
                Marshal.FreeHGlobal(ptrObj);
        }
    }
    return obj;
}

在结构定义中,我没有使用任何fixed 区域,而是在标准编组不起作用时使用MarshalAs 属性。这就是你可能需要的字符串。

你可以这样使用这个函数:

PacketHeader ph = (PacketHeader)GetObjectFromBytes(buffer, typeof(PacketHeader));

编辑: 我在代码示例中没有看到您的 BigEndian“限制”。此解决方案仅在字节为 LittleEndian 时才有效。

编辑 2: 在您的示例字符串中,您将使用以下内容装饰它:

[MarshalAs(UnmanagedType.LPStr)]

在数组中,我会为一个 n 大小的数组使用类似的东西:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = n)]

【讨论】:

    【解决方案4】:

    对于那些可以访问 C# 7.3 功能的人,我使用这段不安全的代码来“序列化”为字节:

    public static class Serializer
    {
        public static unsafe byte[] Serialize<T>(T value) where T : unmanaged
        {
            byte[] buffer = new byte[sizeof(T)];
    
            fixed (byte* bufferPtr = buffer)
            {
                Buffer.MemoryCopy(&value, bufferPtr, sizeof(T), sizeof(T));
            }
    
            return buffer;
        }
    
        public static unsafe T Deserialize<T>(byte[] buffer) where T : unmanaged
        {
            T result = new T();
    
            fixed (byte* bufferPtr = buffer)
            {
                Buffer.MemoryCopy(bufferPtr, &result, sizeof(T), sizeof(T));
            }
    
            return result;
        }
    }
    

    unmanaged 类型可以是结构(没有引用类型的简单结构,被视为托管结构)或本机类型,例如 intshort 等。

    【讨论】:

      【解决方案5】:

      如果您想要快速的代码而不需要复制,这就是解决方案。我们在这里处理原始byte[],只需将指针转换为unsafe 代码,就像在本机C / C++ 中一样。因此没有开销调用昂贵的框架方法、制作副本等。

      对非托管 struct 的任何更改都将反映在托管 byte[] 中,反之亦然。

      //FOR DEBUG/TEST ONLY
      using System.Runtime.InteropServices;
      namespace ByteStructCast1
      {
          class Program
          {
              [StructLayout(LayoutKind.Sequential, Pack = 1)]
              unsafe struct StructTest//4B
              {
                  [MarshalAs(UnmanagedType.U2)]
                  public ushort item1; //2B
                  public fixed byte item2[2]; //2B =2x 1B
              }
              static void Main(string[] args)
              {
                  //managed byte array
                  byte[] DB1 = new byte[7]; //7B more than we need. byte buffer usually is greater.
                  DB1[0] = 2;//test data |> LITTLE ENDIAN
                  DB1[1] = 0;//test data |
                  DB1[2] = 3;//test data
                  DB1[3] = 4;//test data
                  unsafe //we'll now pin unmanaged struct over managed byte array
                  {
                      fixed(byte* db1 = DB1) //db1 is pinned pointer to DB1 byte[] array
                      {
                          //StructTest t1 = *(StructTest*)db1;    //does not change DB1/db1
                          //t1.item1 = 11;                        //does not change DB1/db1
                          db1[0] = 22;                            //does CHANGE DB1/db1
                          DB1[0] = 33;                            //does CHANGE DB1/db1
                          StructTest* ptest = (StructTest*)db1;   //does CHANGE DB1/db1
                          ptest->item1 = 44;                      //does CHANGE DB1/db1
                          ptest->item2[0]++;                      //does CHANGE DB1/db1
                          ptest->item2[1]--;                      //does CHANGE DB1/db1
                      }
                  }
              }
          }
      }
      

      这也可以在您使用原始类型的fixed-size 缓冲区时使用,并且需要使用成员作为structs 处理其中的元素,例如ulongMyStruct,均为 64 位长。

      【讨论】:

        【解决方案6】:

        嗯,你真的有两个任务。首先是将 byte[] 本质上解释为结构,其次是处理可能的不同字节序。

        因此,它们有些分歧。如果您想使用封送处理,AFAIK - 它只会将字节解释为托管结构。因此,从一个字节序转换为另一个字节序由您决定。做起来不难,但不会是自动的。

        因此,要将 byte[] 解释为结构,您必须具有类似的内容:

        [StructLayout(LayoutKind.Sequential)]
        internal struct X
        {
            public int IntValue;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U1)] 
            public byte[] Array;
        }
        
        static void Main(string[] args)
        {
            byte[] data = {1, 0, 0, 0, 9, 8, 7}; // IntValue = 1, Array = {9,8,7}
            IntPtr ptPoit = Marshal.AllocHGlobal(data.Length);
            Marshal.Copy(data, 0, ptPoit, data.Length);
            var x = (X) Marshal.PtrToStructure(ptPoit, typeof (X));
            Marshal.FreeHGlobal(ptPoit);
        
            Console.WriteLine("x.IntValue = {0}", x.IntValue);
            Console.WriteLine("x.Array = ({0}, {1}, {2})", x.Array[0], x.Array[1], x.Array[2]);
        }
        

        所以前 4 个字节转到 IntValue (1,0,0,0) -> [little endian] -> 1 接下来的 3 个字节直接进入数组。

        如果你想要 BigEndian,你应该自己做:

        int LittleToBigEndian(int littleEndian)
        {
            byte[] buf = BitConverter.GetBytes(littleEndian).Reverse().ToArray();
            return BitConverter.ToInt32(buf, 0);
        }
        

        这样有点乱,所以可能你最好坚持使用自定义编写的解析器,它从源字节 [] 中一个接一个地获取字节,并在没有 StructLayout 和其他本机互操作的情况下填充你的数据类。

        【讨论】:

        • 很高兴指出,如果非托管结构打包到一个字节,最好将结构声明为 [StructLayout(LayoutKind.Sequential), Pack = 1]
        【解决方案7】:

        要将字节数组转换为字符串,请执行此操作;

        byte [] dBytes = ...
        string str;
        System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
        str = enc.GetString(dBytes);
        

        并将字符串转换回字节数组

        public static byte[] StrToByteArray(string str)
        {
            System.Text.UTF8Encoding  encoding=new System.Text.UTF8Encoding();
            return encoding.GetBytes(str);
        }
        

        现在读取你的字符串,看看你的数据是什么。

        【讨论】:

        • 我可以将其转换为字符串。效果不好,因为它不应该这样做。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-11
        相关资源
        最近更新 更多