Writing boot code is useful for many reasons, whether you are:
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:
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:
Fortunately, IDA Pro provides a rich API (with the SDK or scripting) that will allow us to tackle all these issues.
If you don’t have a Bochs image ready, please use the bximage.exe tool to create a disk image.
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).
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)
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
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.
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)
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()).
And last but not least, how do you debug your MBR code?
Please download the files from here. Comments and suggestions are welcome.