外部記憶装置

外付け記憶装置

MBR を雑にダンプしてみた

動機

MBRを触ったことがあまりなかったので、勉強がてらに雑に読み出してダンプしてみました。

MBRの構造

MBRはディスクの先頭セクタ(512byte)に存在します。

Wikipediaによると、 構造は以下のようになっています。

ja.wikipedia.org

オフセット サイズ(byte) 内容
0x000 446 ブートストラップローダ
0x1BE 16 第1パーティションテーブル
0x1CE 16 第2パーティションテーブル
0x1DE 16 第3パーティションテーブル
0x1FE 16 第4パーティションテーブル
0x1FE 2 ブートシグネチャ(0xAA55)

今回は、ブートシグネチャパーティションテーブルを読み取ることにしました。 ブートシグネチャはリトルエンディアンで 0xAA55 で、パーティションテーブルは以下のようになっています。

オフセット サイズ(byte) 内容
0x0 1 ブートグラグ(0x80:ブート可 0x00:ブート不可)
0x1 3 パーティション
最初のセクタ
(CHS方式)
ヘッド
シリンダの上位2ビットと
セクタ
シリンダの下位8ビット
0x4 1 パーティション識別子
0x5 3 パーティション
最後のセクタ
(CHS方式)
ヘッド
シリンダの上位2ビットと
セクタ
シリンダの下位8ビット
0x8 4 パーティションの最初のセクタ(LBA方式)
0xC 4 パーティションの全セクタ数

CHS方式は、シリンダ・ヘッド・セクタを利用した表現で、LBA方式はセクタ(512byte)を単位とした表現です。 CHS方式は最大24bit, LBA方式は最大32bit のフィールドしか割り当てられていないため、 扱えるディスクサイズには限界があります。 それが 528MB, 8.4GB, 137GB の壁として問題になりました。 詳細はこの方のページが非常に参考になりました。 caspar.hazymoon.jp

MBRをダンプする

初めて触るGo言語でダンプツールを作成しました。 まだ触り始めなので、もっといい書き方があるに違いないのですが、この程度でも十分に機能してくれます。

処理の流れとしては、

だけです。それぞれのデータのオフセットは固定なので簡単にダンプすることが出来ます。 さらに、Linuxではブロックデバイスファイルを open して read することでディスクの中身を簡単に読み出すことが出来るので楽です。

実際のコードがこんな感じです。

package main

import (
    "fmt"
    "os"
    "encoding/binary"
)

func printMBRPartitionInfo(partitionName string, buf []byte) {
    fmt.Println(partitionName)
    fmt.Printf("- Type: %02X\n", buf[0x04])
    fmt.Printf("- BootFlag: %t\n", buf[0x00] == 0x80)

    fmt.Printf("- Sector Num: %d\n", binary.LittleEndian.Uint32(buf[0x0C:]))
    fmt.Printf("- First Sector(CHS): %02X %02X %02X\n", buf[0x01], buf[0x02], buf[0x03])
    fmt.Printf("- Last  Sector(CHS): %02X %02X %02X\n", buf[0x05], buf[0x06], buf[0x07])
    fmt.Printf("- First Sector(LBA): 0x%08X\n", binary.LittleEndian.Uint32(buf[0x08:]))
}

func main(){
    // ブロックデバイスを開く
    file, err := os.Open("/dev/sdb")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // LBA 0 を読み込む
    buf := make([]byte, 512)
    file.ReadAt(buf, 0)

    // ブートシグネチャの読み取り
    if (buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA){
        return
    }
    printMBRPartitionInfo("1st Partition", buf[0x1BE:])
    printMBRPartitionInfo("2nd Partition", buf[0x1CE:])
    printMBRPartitionInfo("3rd Partition", buf[0x1DE:])
    printMBRPartitionInfo("4th Partition", buf[0x1EE:])
}

Lubuntu のインストラーが入ったUSBメモリに対して実行したときの結果は以下のようになりました。

1st Partition
- Type: 00
- BootFlag: true
- Sector Num: 3475360
- First Sector(CHS): 00 01 00
- Last  Sector(CHS): 6A E0 F6
- First Sector(LBA): 0x00000000
2nd Partition
- Type: EF
- BootFlag: false
- Sector Num: 8000
- First Sector(CHS): FE FF FF
- Last  Sector(CHS): FE FF FF
- First Sector(LBA): 0x00349820
3rd Partition
- Type: 83
- BootFlag: false
- Sector Num: 12140544
- First Sector(CHS): 76 1F D8
- Last  Sector(CHS): 2D E1 CC
- First Sector(LBA): 0x00351000
4th Partition
- Type: 00
- BootFlag: false
- Sector Num: 0
- First Sector(CHS): 00 00 00
- Last  Sector(CHS): 00 00 00
- First Sector(LBA): 0x00000000

第1パーティションは Type 00 の空のパーティションですが、パーティションが確保されていることから何らかの専用パーティションと考えられます。 ブートフラグが立っているので、ここからブートします。

第2パーティションは Type EF の EFIパーティションです。UEFI用の bootx64.efi などが配置されています。

第3パーティションは Type 83 の Linux ファイルシステムです。今回はwritableな領域として、ログの保存等に利用されています。

第4パーティションは存在しません。

第1パーティションについて

さすがに気になるので詳細を調べてみたところ、 Lubuntuのディスクイメージは Mac を含むあらゆるシステムで動かすために、CDのファイルシステムである ISO 9660 と Apple Partition Map を含むようです。

askubuntu.com

実際に parted で調べたところ、 Apple フォーマットのパーティションとして認識されました。

$ sudo parted /dev/sdb p
警告: ドライバは物理ブロックサイズが 2048 バイトであると言っていますが、Linux は 512 バイトだと言っています。
無視(I)/Ignore/取消(C)/Cancel? I                                          
モデル: BUFFALO USB Flash Disk (scsi)
ディスク /dev/sdb: 32.0GB
セクタサイズ (論理/物理): 2048B/512B
パーティションテーブル: mac
ディスクフラグ: 

番号  開始    終了    サイズ  ファイルシステム  名前   フラグ
 1    2048B   6143B   4096B                     Apple
 2    1765MB  1769MB  4096kB                    EFI

GPT なディスクを読んでみた

GPT 形式のディスクも読み込んでみました。

1st Partition
- Type: EE
- BootFlag: false
- Sector Num: 1000215215
- First Sector(CHS): 00 01 00
- Last  Sector(CHS): FE FF FF
- First Sector(LBA): 0x00000001
2nd Partition
- Type: 00
- BootFlag: false
- Sector Num: 0
- First Sector(CHS): 00 00 00
- Last  Sector(CHS): 00 00 00
- First Sector(LBA): 0x00000000
3rd Partition
- Type: 00
- BootFlag: false
- Sector Num: 0
- First Sector(CHS): 00 00 00
- Last  Sector(CHS): 00 00 00
- First Sector(LBA): 0x00000000
4th Partition
- Type: 00
- BootFlag: false
- Sector Num: 0
- First Sector(CHS): 00 00 00
- Last  Sector(CHS): 00 00 00
- First Sector(LBA): 0x00000000

第1パーティションは Type EE となり、GPT形式のディスクであることを示しています。

このように、GPT形式のディスクでも LBA 0 にMBRが存在し、GPT形式であることを示しています。

まとめ

MBR のダンプ自体は非常に簡単で、ディスクに対する知見を得ることが出来ました。 せっかくなので、GPT形式のディスクについてもダンプを取りたいと思っています。

RustでMBRダンプをしてみた(2020/03/28 追記)

Rustにも興味があったのでRustを使ってダンプしてみました。

use std::io::prelude::*;
use std::fs::File;

// ここらへんもう少しなんとかならんかな
fn convert_bytes_to_u32_le(bytes:&[u8]) -> Result<u32, ()>{
    // サイズが違うときはエラーとする
    if bytes.len() != 4 {
        return Err(());
    }

    let mut u32_raw:[u8; 4] = [0; 4];
    u32_raw.clone_from_slice(&bytes[0..4]);
    let _u32 = unsafe { std::mem::transmute::<[u8; 4], u32>(u32_raw) }.to_le();
    return Ok(_u32);
}

fn print_mbr_partition_info(part_name:&str, buffer:&[u8]){
    // サイズが違うときは何も表示しない
    if buffer.len() != 0x10 {
        return;
    }

    println!("{}", part_name);
    println!("- Type: 0x{:X}", buffer[0x04]);
    println!("- BootFlag: {}", buffer[0x00] == 0x80);
    println!("- Sector Num: {}", convert_bytes_to_u32_le(&buffer[0x0C..0x10]).unwrap());
    println!("- First Sector(CHS): {:02X} {:02X} {:02X}", buffer[0x1], buffer[0x02], buffer[0x03]);
    println!("- Last  Sector(CHS): {:02X} {:02X} {:02X}", buffer[0x5], buffer[0x06], buffer[0x07]);
    println!("- First Sector(LBA): 0x{:08X}", convert_bytes_to_u32_le(&buffer[0x08..0x0C]).unwrap());
}

fn main() {
    let mut f = File::open("/dev/sdb").unwrap();

    let mut buffer:[u8; 512] = [0; 512];
    f.read(&mut buffer).unwrap();

    // シグネチャがマッチしなければ終了する
    if (buffer[0x1FE] != 0x55) || (buffer[0x1FF] != 0xAA){
        return;
    }

    print_mbr_partition_info("1st Partition", &buffer[0x1BE..0x1CE]);
    print_mbr_partition_info("2nd Partition", &buffer[0x1CE..0x1DE]);
    print_mbr_partition_info("3rd Partition", &buffer[0x1DE..0x1EE]);
    print_mbr_partition_info("4th Partition", &buffer[0x1EE..0x1FE]);
}