LoginSignup
6
6

More than 5 years have passed since last update.

バイナリ解析した際に利用したコードの備忘録

Posted at

はじめに

特定のデータファイルなどを改変したい際、その構造を操作する方法が提供されていない場合、最終的にはバイナリ解析して改変することになるかと思います。
チェックサムなどがある場合、その方法も解析が必要になってくるのだと思いますが、ここでは単純なデータ構造のバイナリファイルを解析した際に使用した、C#のコードについての備忘録です。
なお、コードは実際のものではなく簡略化した内容としました。

解析について

きわめて原始的な方法で実施していました。
一部を変更し、出力されたファイルと変更前のファイルとをWinMargeで比較し、変わった部分の前後から判断して行くだけです。
今回扱ったデータはそのデータが何であるのかのID、そのデータの長さ、データといった構造になっていました。
単純な構造ですので、データの見方がわかってしまえば簡単です。

バイト配列の操作

バイト配列を操作する簡単なクラスを作成します。
このままだとあまりBinaryReaderと変わりませんが、必要に応じて改変していきます。

public class MyBytes
{
    private List<byte> Bytes { get; set; } = new List<byte>();
    private int Pos { get; set; }
    public MyBytes(string filePath)
    {
        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        using (BinaryReader br = new BinaryReader(fs))
        {
            Bytes.AddRange(br.ReadBytes((int)fs.Length));
        }
        Pos = 0;
    }
    public byte[] ReadBytes(int count, int offsetPos = 0, bool readNext = true)
    {
        List<byte> result = Bytes.GetRange(Pos + offsetPos, count);
        if (next)
            readNext += offsetPos + count;

        return result.ToArray();
    }
    public bool EOF { get { return Bytes.Count <= Pos; } }
}

よく使う型のクラス

例えば、データの長さを示す形式がint型なら、int型を操作するクラスを作成します。

[System.Diagnostics.DebuggerDisplay("{Value} ({System.BitConverter.ToString(ToBytes()).Replace(\"-\", \" \")})")]
public class MyInt : IEnumerable<byte>
{
    public int Value { get; set; }
    public MyInt(MyBytes bytes)
    {
        Value = BitConverter.ToInt32(bytes.ReadBytes(4), 0);
    }
    public MyInt(int value)
    {
        Value = value;
    }
    public byte[] ToBytes()
    {
        return BitConverter.GetBytes(Value);
    }
    public static implicit operator int(MyInt obj)
    {
        return obj.Value;
    }
    public static implicit operator byte[](MyInt obj)
    {
        return obj.ToBytes();
    }
    public static implicit operator MyInt(MyBytes bytes)
    {
        return new MyInt(bytes);
    }
    public static implicit operator MyInt(int value)
    {
        return new MyInt(value);
    }
    public IEnumerator<byte> GetEnumerator()
    {
        return ToBytes().ToList<byte>().GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return ToBytes().GetEnumerator();
    }
}

ポイント

  • デバッグ時にウォッチで見やすくするように DebuggerDisplay を定義
  • 要求する型に合わせて動作するように implicit を定義
  • 繰り返しの要求用に IEnumerable を定義
  • MyBytesからデータを読み進めるため、MyBytesでの使用前提のクラス

解析用のクラス

解析した結果データ構造が判明した場合、各データ項目を操作しやすくするクラスを作成します。

public class MyData
{
    public MyBytes_Int DataID { get; set; }
    public MyBytes_Int Length { get; set; }
    public byte[] Data { get; set; }
    public EspB_Short DataSize { get; set; }

    public MyData(MyBytes bytes)
    {
        DataID = bytes;
        Length = bytes;
        Data = bytes.ReadBytes(Length);
    }
    public byte[] ToBytes()
    {
        List<byte> result = new List<byte>();
        b.AddRange(DataID);
        b.AddRange(Length);
        b.AddRange(Data);
        return result.ToArray();
    }
}

後は上記のクラスを基準にデータ構造に合わせたクラスを順次作成して行きます。

データの読み込みと書き出し

一通りデータの解析が済みましたら、データを読込み、必要な操作を行い、ファイル出力します。

MyBytes bytes = new MyBytes(@"C:\Hoge.xxx");
List<MyData> list = new List<MyData>();
while (bytes.EOF)
{
    list.Add(new MyData(bytes));
}

/* (何らかの改変) */

using (FileStream fs = new FileStream(@"C:\Hoge2.xxx", FileMode.Create))
using (BinaryWriter bw = new BinaryWriter(fs))
{
    foreach (var item in list)
    {
        bw.Write(item.ToBytes());
    }
}

最後に

実際にはもう少し複雑ですが、このような形で進めていました。
やってみれば意外にバイナリ解析は簡単なもので、後は地道な作業です。
クラス設計的に、MyIntMyBytesからデータを読み出すこと前提で作っていたりなど、設計思想的には好ましくない部分が多いのかもしれませんが、公式に公開するものではないので、とりあえずやりやすいように作ったものです。

以上

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6