Chapter 03
バイト列を読む基本 — エンディアン・整数・文字・マジック
この章のゴール.
16 進ダンプを見て、そこにある整数・ポインタ・文字列・マジックナンバー・タイムスタンプ・チェックサムを見分けられるようになること。 エンディアンとアラインメントという 2 つの落とし穴を理解すること。
1. 16 進ダンプの読み方
00008000: 0004 0020 c581 0008 8180 0008 8380 0008 ... ............
00008010: 8580 0008 8780 0008 8980 0008 0000 0000 ................- 左端の
00008000はオフセット(ファイル先頭からのバイト位置。16 進) - 中央がバイトの値(16 進、2 桁 = 1 バイト)
- 右端がASCII 表示(0x20〜0x7E の印字可能文字だけ。それ以外は
.)
この 32 バイトは、後で見るように Cortex-M のベクタテーブルの先頭で、20000400(スタックの初期値)と 080081c5(リセット後に飛ぶアドレス)などが並んでいる。 なぜ 0004 0020 が 20000400 なのか——それがエンディアンである。
2. エンディアン — バイトの並び順
エンディアン(endianness)は、2 バイト以上の整数をメモリにどの順で置くかの規則である。
- リトルエンディアン(little-endian、LE): 下位バイトを先に置く。0x12345678 は
78 56 34 12。Arm Cortex-M、x86、RISC-V の通常設定 - ビッグエンディアン(big-endian、BE): 上位バイトを先に置く。0x12345678 は
12 34 56 78。ネットワークプロトコル、一部の古い MIPS / PowerPC、多くのファイルフォーマットのヘッダ
組み込みで出会うのはほぼリトルエンディアンだが、次の場所ではビッグエンディアンが混ざる。
- ネットワークから来たデータ(IP アドレス、ポート番号、TCP/UDP ヘッダ)は「ネットワークバイトオーダー」= ビッグエンディアン
- 多くのファイル形式のマジックとヘッダ(PNG、ELF の一部、TLS レコード)
- 一部のセンサ・EEPROM のレジスタ
見分け方: 小さな正の整数(長さ、カウンタ、バージョン)を探す。 04 00 00 00 はリトルなら 4、ビッグなら 0x04000000(67108864)。4 のほうがありそうなら、その領域はリトルである。 xxd -e -g4(リトル)と xxd -g4(バイト順のまま)を切り替えて、意味の通るほうを選ぶ。
3. 整数を読む
| 見えるバイト(LE) | u8 | u16 | u32 |
|---|---|---|---|
2a | 42 | — | — |
2a 00 | — | 42 | — |
00 04 00 20 | — | — | 0x20000400 = 536871936 |
ff ff ff ff | 255 | 65535 | 4294967295(または −1 の i32、または「消去済み」) |
符号の判断: 最上位ビットが 1 の 32 ビット値(0x80000000 以上)は、文脈によって「大きな正の数」「負の数」「アドレス」「フラグの集まり」のどれか。 温度・カウンタなど小さいはずの量が 0xFFFFFF80 なら、それは i32 の −128 である可能性が高い。
0xFF の連続は「消去済みフラッシュ」であることが多い。NOR フラッシュは消去すると全ビットが 1(0xFF)になり、書き込みは 1→0 の方向にしかできない(10 章)。だから FF FF FF FF は「まだ何も書いていない u32」を意味することが多く、数値の 4294967295 ではない。
4. アラインメント — 値は境界に揃う
コンパイラは、32 ビット値を4 の倍数のアドレスに、16 ビット値を 2 の倍数に置く(アラインメント)。 だから構造体には詰め物(パディング)が入る。
struct rec {
uint8_t type; // オフセット 0
// 3 バイトのパディング(コンパイラが挿入)
uint32_t value; // オフセット 4(0 ではない!)
uint16_t len; // オフセット 8
// 2 バイトのパディング
}; // 合計 12 バイト(10 ではない)構造体を読むとき、「メンバのバイト数を足しただけ」ではズレる。 パディングを考慮するか、struct の書式に詰め物を明示する(Python なら <B3xIH2x。x が 1 バイトの詰め物)。 逆に、詰め物のない構造体(__attribute__((packed)) で作られたもの、通信フレーム、フラッシュに詰めた記録)もある。どちらかは実物で確かめる。
5. 文字列を読む
| 種類 | 特徴 | 例 |
|---|---|---|
| ASCII(NUL 終端) | 1 バイト 1 文字、末尾に 00 | 48 65 6c 6c 6f 00 = "Hello" |
| 長さ前置 | 先頭に長さ、その後に文字 | 05 48 65 6c 6c 6f = 長さ 5 の "Hello" |
| 固定長 | 決まった幅に詰め、余りは 00 か空白 | "CFG1" を 8 バイト枠に |
| UTF-16 LE | 1 文字 2 バイト、英字は xx 00 | 48 00 65 00 = "He"。Windows・BLE の一部 |
| UTF-8 | 1〜4 バイト。ASCII 互換。日本語は 3 バイト | e6 97 a5 = "日" |
strings は既定で ASCII のみ。UTF-16 は strings -e l、UTF-8 の日本語は端末が UTF-8 なら xxd の右側や grep -a で拾える。
6. マジックナンバー — 形式の目印
マジックナンバー(magic number)は、ファイルやデータ構造の先頭に置かれた固定のバイト列で、「これは○○形式だ」と示す目印である。 解析ではこれを探すのが最初の一歩になる。
| マジック(16 進) | ASCII | 形式 |
|---|---|---|
7F 45 4C 46 | .ELF | ELF 実行ファイル(05 章) |
1F 8B | — | gzip |
42 5A 68 | BZh | bzip2 |
28 B5 2F FD | — | zstd |
FD 37 7A 58 5A | — | xz |
89 50 4E 47 | .PNG | PNG |
55 AA / AA 55 | — | ブートセクタ末尾、多くのヘッダの目印 |
E9 / EB | — | x86 ブートセクタ、FAT の先頭ジャンプ |
hsqs / sqsh | — | squashfs(15 章) |
85 19 | — | JFFS2 ノード |
39 6E 30 3B | 9n0; | UBI |
50 4B 03 04 | PK.. | ZIP / JAR / APK |
D0 0D FE ED(0xd00dfeed BE) | — | デバイスツリー blob(DTB。15 章) |
EF BE AD DE(0xdeadbeef) | — | よくあるデバッグ用の番兵値 |
MCUboot(0x96f3b83d、08 章)や NVM3(10 章)のように、マジックがファイル先頭ではなく、各構造の頭にある形式も多い。その場合は全域を検索する。
# MCUboot のマジック 0x96f3b83d をリトルエンディアンのバイト列で検索
grep -a -b -o -P '\x3d\xb8\xf3\x96' work.bin
# Python で全出現位置を得る
python3 -c "d=open('work.bin','rb').read(); i=-1;
import sys
while (i:=d.find(bytes.fromhex('3db8f396'),i+1))>=0: print(hex(i))"7. タイムスタンプを読む
日時が埋まっていると、ログの解析(12 章)や差分(18 章)で役立つ。
| 形式 | 見え方 | 変換 |
|---|---|---|
| Unix 時刻(秒) | 2020 年代なら 0x6? 台の u32(例 0x65B00000) | 1970-01-01 からの秒。date -d @1706... |
| Unix 時刻(ミリ秒 / マイクロ秒) | u64 | 1000 / 1000000 で割る |
| 経過時間(tick) | 小さめの u32 が単調増加 | 起動からの ms など。絶対時刻ではない |
| FAT 日時 | 2 バイト日付 + 2 バイト時刻(ビット詰め) | 09 章 |
「2020 年代の Unix 秒」は 0x63000000〜0x70000000 の範囲(2023〜2029 年ごろ)に入る u32 を探すと当たりをつけやすい。
8. チェックサムと CRC を見分ける
構造の末尾にある、内容と相関しない 1〜4 バイトは、チェックサム(誤り検出の値)のことが多い。
- 単純な和: 全バイトを足した下位 8/16 ビット
- CRC(巡回冗長検査): よく使われる。CRC-16(CCITT、Modbus)、CRC-32(zlib、Ethernet と同じ多項式)
- 見分け方: 内容を 1 バイト変えると末尾も変わるはず。
crc32(zlib)で計算して末尾と一致するか試す。合わなければ CRC-16 や、初期値・ビット反転の違いを疑う(crcmodで総当たり)
CRC の照合は 18 章(壊れたダンプの検証)で使う。
9. 手を動かす
バイト列を各型として解釈する
この章のポイント
- 組み込みはほぼリトルエンディアン。ネットワークとファイルヘッダはビッグが混ざる。小さな整数で見分ける
xxd -e -g4で 32 ビット値を読む。FF FF FF FFは数値ではなく消去済みのことが多い- 構造体にはアラインメントの詰め物が入る。バイト数の足し算はズレる
- 文字列は NUL 終端・長さ前置・固定長・UTF-16 を見分ける
- マジックナンバーを全域検索して形式を見つける。末尾の相関しない数は CRC を疑う