HIDARI日記(右)

そのときどき興味ある技術を中心にだらだら書いてます。内容は個人の見解であり、所属する企業を代表するものではありません。

シリアライズされたバイナリを解析する

バイト列を例えば数値にするとか

ネットワークから流れてくるバイナリを一定の長さに区切って数値やらに変換する方法を色々教わりながら考えました.

例えば 0x0000000F000A61 のようなバイナリがあった時,これを整数の 1510 そして文字(ASCIIコード)の a として取り出したいときにどうすればいいのか,ということです.

結論からいうと,今回は共用体(union)を使って行うことにしました.

なおWin32APIを使っていますのでunsigned longやunsigned charなどはDWORDやBYTEに読み替えてもいいかも.

サンプルコード

はこんな感じ.説明は下のほうです.

#include "stdafx.h"
#include <Windows.h>
#include <iostream>

using namespace std;

//**************************************************
// Class Definition
//***************************************************
class FooBar
{
public:
    FooBar(char *src);
    ~FooBar();

    unsigned long FetchLong(unsigned long startIndex);
private:
    enum ByteLength
    {
        uchar=1,
        ulong=4,
    };

    typedef union _Foo
    {
        unsigned long ulong;
        unsigned char ch[4];
    } Foo;

    void CopyULong(char src);

    char *targetSource;
    Foo foo;
    Foo *fooPointer;
};

//**************************************************
// Public Function Definition
//***************************************************
FooBar::FooBar(char *src)
{
    targetSource = src;
    foo.ulong = 0;
    fooPointer = &foo;
}

FooBar::~FooBar()
{
}

unsigned long FooBar::FetchLong(unsigned long startIndex)
{
    CopyULong(&targetSource[startIndex]);
    return htonl(foo.ulong);
}

//***************************************************
// Private Function Definition
//***************************************************
void FooBar::CopyULong(char *src)
{
    for (int i = 0; i < ulong; i++)
    {
        fooPointer->ch[i] = src[i];
    }
}

//***************************************************
// main 
//***************************************************
int _tmain(int argc, _TCHAR* argv)
{
    char ch[] = {0x00,0x00,0x00,0x0C,   // unsigned long(DWORD)
                 0x61,                  // unsigned char(BYTE)
                 0x00,0x00,0x00,0x06,   // unsigned long(DWORD)
                 0x00,0x02,             // unsigned short(WORD)
                 0x62,                  // delimiter的な
    };

    FooBar myFooBar(ch);
    unsigned long num = myFooBar.FetchLong(5);

    cout << num << endl;    // 出力:6
}

簡単にいうと

まず取得したい型のサイズ分の長さを持った2種類のメンバ(unsigned charの配列と実際の型)を定義した共用体を用意しておきます.

    typedef union _Foo
    {
        unsigned long ulong;   // 今回は例としてunsigned long
        unsigned char ch[4];
    } Foo;

そしてバイナリから必要な範囲を配列の方のメンバにコピーし,それをunsigned longとして取り出すことで値を得ています.

サンプルでは以下の2つの関数でこの処理を行なっています.

unsigned long FooBar::FetchLong(unsigned long startIndex)
{
    CopyULong(&targetSource[startIndex]);
    return htonl(foo.ulong);    // htonl関数はバイトオーダーを変換します
}

void FooBar::CopyULong(char *src)
{
    for (int i = 0; i < ulong; i++)
    {
        fooPointer->ch[i] = src[i];
    }
}

コメントにあるバイトオーダーやhtonl関数についてはこちら辺りを参考にするといいかもです.

おわりに,あるいは他の方法について

たぶんもっと頭いい方法あるんのでしょうが,今の僕には限界でした…

他にもBoostを使って解析す方法や, #pragma pack でメモリのアライメントを気にせずに,予め用意しておいた構造体に流しこむ方法など有りました.

せっかく教えて貰ったのでboostを使う方法,pragma pack使う方法ともに素振りしておきたいとおもいます.

ある程度まとまったらまた別の記事にできるかも.

謝辞

多くのアドバイスを頂いた,@datsunsさん,@ktz_aliasさん,@HappyLuckyAkiraさん,@Posauneさん,ありがとうございました!!