At work I have a project that involves working with the Master Boot Record (MBR) and Volume Boot Record (VBR). I needed a quick and easy way to dump the contents of these sectors and see the bits they contained. After completing it I decided it was worth a quick post.

Introduction

For those that aren't familiar with hard drives, MBRs, and VBRs, here's a quick primer. For everyone else feel free to skip ahead.

Hard Drives

Your hard drive is composed of several spinning magnetic disks. Each disk can store data on both sides and thus has two read/write heads. The data is stored in concentric rings called cylindars. These cylindars can be subdivided into sections called sectors or blocks. For hard drives, sectors are usually 512 bytes long. When data is read or written to disk the smallest unit is a sector. So, even if you only want to read or write 10 bytes the computer will actually read or write 512. In order to locate any piece of data on a hard drive it is uniquely addressable by its cylindar, head, and sector. This addressing scheme is called CHS Addressing. This form, however, is being deprecated in favor of Logical Block Addressing (LBA). LBA maps the CHS addressing scheme into a sequential set of addresses. Thus the first sector of a hard drive becomes LBA=0, the second, LBA=1, and so on. This abstracts the physical geometry from the developer and only requires that they know the block size and total number of blocks.

MBR and VBR

The Master Boot Record (MBR) and Volume Boot Record (VBR) are used to bootstrap into the operating system on a Basic Input/Output? System (BIOS) based computer. The process and technologies are different, and beyond the scope of this post, for an Extensible Firmware Interface (EFI) based computer. When your computer boots it needs to execute code to load the operating system or whatever software you want. This first set of code lives in the BIOS. This code checks what hardware is present and does a few tests to make sure everything is ok to boot. Then, according to the boot order you have specified (or your computer manufacturer has specified) it begins loading the first sector of various disks. When it finds one that is marked as an MBR it proceeds to transfer execution into it. The MBR contains a few things to help boot into the system. The first is a very small chunk of code, usually only 440 bytes, to perform the steps required in this phase, a disk signature, to uniquely identify the disk, a partition table, to list the main partitions on a drive, and the MBR signature, to identify this sector as an MBR. A partition is a part of the hard drive that has been logically seperated to act as its own volume as far as an operating system is concerned and can have an independent file system structure. The MBR's job is to look through the partition table, find the first partition marked as bootable, load the first sector of the partition, and transfer execution to it. The first sector of a partition is called the volume boot record. The job of the VBR is usually to load the operating system's bootloader but many are more complex and can load other volume boot records to provide the user with a choice of operating system to load.

The App

I knew had to make an app that dumped the MBR and the VBR so I started looking into raw disk access in Windows. I realize that there are apps out there that will do this already, such as  WinHex, but where's the fun in that?

In Windows there is a special path namespace, called the Win32 Device Namespace, that allows file like direct access to devices. It starts with "//./" and can be used for logical volumes, physical drives, named pipes, and even DOS devices, such as COM1. The path "//./PhysicalDriveN" gives raw access to physical drives where you replace N with whichever drive you want to access. The drive number can be found in the Microsoft Management Console (mmc.exe) with the disk management snap in. The path "//./{Drive Letter}:" gives access to the logical volumes where you would replace {Drive Letter} with C, D, etc. In my case I used it to dump the Volume Boot Sector. And, since I mentioned it, "//./COMN" would give access to a COM device where you would replace N with the specific number.

I made a fairly simple interface for the app. All it takes is a path, the offset (bytes in hex) and the size (bytes in hex). And because it is used to directly access drives, it must be run as an administrator. To read the output you will need a hex editor such as  HxD or  WinHex. The offset specifies the number of bytes from the beginning of the drive to start reading. The size specifies the number of bytes to read.

The code works by first creating a file handle for the path supplied using CreateFile. Then, the SetFilePointer function is used to move the specified offset. Next, the ReadFile function is used to read the specified number of bytes. And, last, the .NET FileStream class is used to write out the bytes that were read.

Screenshot of the app

Screenshot of the app

Screenshot of HxD with a VBR

Screenshot of HxD with a VBR

The Code


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.IO;

namespace RawDump
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public enum EMoveMethod : uint
        {
            Begin = 0,
            Current = 1,
            End = 2
        }

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern uint SetFilePointer(
            [In] SafeFileHandle hFile,
            [In] int lDistanceToMove,
            [Out] out int lpDistanceToMoveHigh,
            [In] EMoveMethod dwMoveMethod);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
          uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
          uint dwFlagsAndAttributes, IntPtr hTemplateFile);

        [DllImport("kernel32", SetLastError = true)]
        internal extern static int ReadFile(SafeFileHandle handle, byte[] bytes,
           int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero);

        private void buttonDump_Click(object sender, EventArgs e)
        {
            short FILE_ATTRIBUTE_NORMAL = 0x80;
            short INVALID_HANDLE_VALUE = -1;
            uint GENERIC_READ = 0x80000000;
            uint GENERIC_WRITE = 0x40000000;
            uint CREATE_NEW = 1;
            uint CREATE_ALWAYS = 2;
            uint OPEN_EXISTING = 3;

            SaveFileDialog mySFD = new SaveFileDialog();
            mySFD.FileName = "dump.bin";
            mySFD.InitialDirectory = Path.GetDirectoryName(Application.StartupPath);
            if(mySFD.ShowDialog() == DialogResult.OK)
            {
                SafeFileHandle handleValue = CreateFile(textBoxPath.Text, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
                if (handleValue.IsInvalid)
                {
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                }

                int offset = int.Parse(textBoxOffset.Text, System.Globalization.NumberStyles.HexNumber);
                int size = int.Parse(textBoxSize.Text, System.Globalization.NumberStyles.HexNumber);
                byte[] buf = new byte[size];
                int read = 0;
                int moveToHigh;
                SetFilePointer(handleValue, offset, out moveToHigh, EMoveMethod.Begin);
                ReadFile(handleValue, buf, size, out read, IntPtr.Zero);
                FileStream myStream = File.OpenWrite(mySFD.FileName);
                myStream.Write(buf, 0, size);
                myStream.Flush();
                myStream.Close();
                handleValue.Close();
            }
        }
    }
}