MBR を雑にダンプしてみた
動機
MBRを触ったことがあまりなかったので、勉強がてらに雑に読み出してダンプしてみました。
MBRの構造
MBRはディスクの先頭セクタ(512byte)に存在します。
Wikipediaによると、 構造は以下のようになっています。
| オフセット | サイズ(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 を含むようです。
実際に 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]); }