Develop your master boot record and debug it with IDA Pro and the Bochs debugger plugin

Writing boot code is useful for many reasons, whether you are:

  • Developing your own operating system
  • Developing disk encryption systems
  • Experimenting and researching
  • Or even writing a bootkit


While developing the IDA Bochs plugin at Hex-Rays, we had to write a small MBR and we needed a nice and fast way to compile and debug our code.
In the beginning, we were using bochsdbg.exe to debug our code and little by little once we coded the “Bochs Disk Image loader” part we could debug the MBR with IDA and Bochs plugin.
Now you may be wondering: How can I use IDA Bochs plugin to debug my MBR?
For a quick answer, here are the needed steps:

  1. Prepare a Bochs disk image
  2. Prepare a bochsrc file
  3. Insert your MBR into the disk image
  4. Open bochsrc file with IDA
  5. Start debugging

In case you did not know, bochsrc files (though they are text files) are handled by the bochsrc.ldw (IDA Loader). The loader parses the bochsrc file looking for the first “ata” keyword then it locates its “path” attribute.
In the following example, bochsrc loader will detect “c.img”.

romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
megs: 16
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, path="c.img", mode=flat, cylinders=20, heads=16, spt=63
boot: disk
...

After finding the disk image file, the loader will simply create a new segment at 0x7C00 containing the first sector of that file only and then it selects the Bochs debugger (in Disk Image loader mode).
Once the loader is finished you can press F9 and start debugging.
As simple as this sounds, this process is really limited:

  • What if the MBR loads more code from different sectors? (MBR with 2 or more sectors of code)
  • What about symbol names?
  • What if we want to customize and control the MBR loading process?

Fortunately, IDA Pro provides a rich API (with the SDK or scripting) that will allow us to tackle all these issues.

Preparing a Bochs disk image

If you don’t have a Bochs image ready, please use the bximage.exe tool to create a disk image.
mbr_dskimg.jpg

Preparing bochsrc file

Edit your bochsrc file and add the ata0 (generated by bximage tool) line to it, and finally run bochsdbg.exe to verify that you can run Bochs properly (outside of IDA).
mbr_testbochs.jpg
If you see the Bochs debugger prompt, you can press “c” to continue execution but Bochs will complain because our disk image is not bootable.
(As a new disk image, It lacks the 55AA signature at the end of the first sector)

Inserting the MBR into the disk image

For your convenience, we included a sample mbr.asm file ready for you to compile.
nasmw -f bin mbr.asm
To insert the mbr into the disk image, we can write a small Python function:

def UpdateImage(imgfile, mbrfile):
  """
  Write the MBR code into the disk image
  """
  # open image file
  f = open(imgfile, "r+b")
  if not f:
    print "Could not open image file!"
    return False
  # open MBR file
  f2 = open(mbrfile, "rb")
  if not f2:
    print "Could not open mbr file!"
    return False
  # read whole MBR file
  mbr = f2.read()
  f2.close()
  # update image file
  f.write(mbr)
  f.close()
  return True

Loading bochsrc with IDA

As discussed previously, loading the bochsrc file into IDA is not enough (see above) so we need to write another script that acts like a loader:

def MbrLoader():
    """
    This small routine loads the MBR into IDA
    It acts as a custom file loader (written with a script)
    """
    import idaapi;
    import idc;
    global SECTOR_SIZE, BOOT_START, BOOT_SIZE, BOOT_END, SECTOR2, MBRNAME
    # wait till end of analysis
    idc.Wait()
    # adjust segment
    idc.SetSegBounds(BOOT_START, BOOT_START, BOOT_START + BOOT_SIZE, idaapi.SEGMOD_KEEP)
    # load the rest of the MBR
    idc.loadfile(MBRNAME, SECTOR_SIZE, SECTOR2, SECTOR_SIZE)
    # Make code
    idc.AnalyzeArea(BOOT_START, BOOT_END)

What we did is simply extend the segment from 512 to 1024 (our sample MBR is 1024 bytes long) and load into IDA the rest of the MBR code from the compiled mbr.asm binary.

Importing symbols into IDA

When we assemble mbr.asm, a map file will also be generated. We will write a simple parser to extract the addresses and names from the map file and copy them to IDA:

def ParseMap(map_file):
    """
    Opens and parses a map file
    Returns a list of tuples (addr, addr_name) or an empty list on failure
    """
    ret = []
    f = open(map_file)
    if not f:
        return ret
    # look for the beginning of symbols
    for line in f:
        if line.startswith("Real"):
            break
    else:
        return ret
    # Prepare RE for the line of the following form:
    # 7C1F              7C1F  io_error
    r = re.compile('\s*(\w+)\s*(\w+)\s*(\w*)')
    for line in f:
        m = r.match(line.strip())
        if not m:
            continue
        ret.append((int(m.group(2), 16), m.group(3)))
    return ret
def ApplySymbols():
    """
    This function tries to apply the symbol names in the database
    If it succeeds it prints how many symbol names were applied
    """
    global MBRNAME
    map_file = MBRNAME + ".map"
    if not os.path.exists(map_file):
        return
    syms = ParseMap(map_file)
    if not len(syms):
        return
    for sym in syms:
        MakeNameEx(sym[0], sym[1], SN_CHECK|SN_NOWARN)
    print "Applied %d symbol(s)" % len(syms)

Putting it all together

Now that we addressed all of the issues previously mentioned, let us glue everything with a batch file:

  rem Assemble the MBR
  if exist mbr del mbr
  nasmw -f bin mbr.asm
  if not exist mbr goto end
  rem Update the image file
  python mbr.py update
  if not errorlevel 0 goto end
  rem Run IDA to load the file
  idaw -c -A -OIDAPython:mbr.py bochsrc
  rem database was not created
  if not exist bochsrc.idb goto end
  if exist mbr del mbr
  if exist mbr.map del mbr.map
  rem delete old database
  if exist mbr.idb del mbr.idb
  rem rename to mbr
  ren bochsrc.idb mbr.idb
  rem Start idag (without debugger)
  rem start idag mbr
  rem Start IDAG with debugger directly
  start idag -rbochs mbr
  echo Ready to debug with IDA Bochs
:end

If you noticed, we run IDA twice: the first time we run it and pass our script name to IDAPython; the script will continue the custom loading process and symbol propagation for us.
The second time we run IDA with the “-rbochs” switch telling IDA to open the database and directly run the debugger.
You can still run IDA just once: “start idag -c -A -OIDAPython:mbr.py bochsrc” however you do not call Exit() and you turn off batch mode (with Batch()).
mbr_final.jpg

And last but not least, how do you debug your MBR code?

Please download the files from here. Comments and suggestions are welcome.