Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Loading MD380 Firmware into Binary Ninja
Binary Ninja is the latest serious competitor among reverse engineering CAD tools. It might be more expensive than Radare2, but it's cheaper than IDA Pro and includes a very smooth UI and a clean, modern API. Unfortunately, Binary Ninja doesn't have dialogs for loading raw images to arbitrary addresses; fortunately, it's very easy to write a plugin that places things at the right addresses. It's also easy to write plugins for importing symbols.
The plugin described in this article was written in a weekend as a way
to learn Binary Ninja scripting. It can be found, perhaps with some
md380tools/playground/binaryninja in the repo.
git clone https://github.com/travisgoodspeed/md380tools
ARM and Thumb2
Before we get started, I need to remind you of a sore point in ARM reversing: there are multiple instruction sets! Even though the MD380's firmware only uses the 16-bit wide Thumb2 instruction set, Binary Ninja also supports the 32-bit wide ARM7 instruction set.
An ARM CPU uses the least significant bit of the program counter to
indicate the instruction set, but ignores it when doing a fetch. An
ARM function at
0x0800F000 would be called as
0x0800F000, but a
Thumb2 function at the same address would be called at
Binary Ninja does it both ways. In the GUI, you must hit the
which defines a function, on the byte after the start of the
function. So just like the CPU, to define a Thumb2 function at
0x0800F000, you must navigate to
0x0800F001 and hit
In the scripting environment, it does things the other way. There,
you must manually specify the Thumb2 instruction set and then give the
0x0800F000. If you provide the odd address that the
GUI expects, it will run off the rails and try to interpret unaligned
Unlike IDA, Hopper, and Radare2, the Binary Ninja GUI doesn't provide
a loader for raw binary files. At best, you can define functions in a
Raw view at address
0x00000000, but anything more sophisticated
requires a plugin. Luckily, the plugin API is very clean and we can
toss together a loader plugin from the examples in short order.
Loader plugins work by extending the
BinaryView class to present a
view of the executable. You can find an example of this in the
nds.py example loader for Nintendo DS ROM images, and it was this
example that I forked for an MD380 loader.
from binaryninja.binaryview import BinaryView from binaryninja.architecture import Architecture from binaryninja.enums import SegmentFlag from binaryninja.log import log_error from binaryninja.types import Symbol from binaryninja.enums import SymbolType, SegmentFlag from binaryninja import PluginCommand from binaryninja.interaction import get_open_filename_input import struct import traceback class MD380View(BinaryView): """This class implements a view of the loaded firmware, for any image that might be a firmware image for the MD380 or related radios loaded to 0x0800C000. """ def __init__(self, data): BinaryView.__init__(self, file_metadata = data.file, parent_view = data) self.raw = data @classmethod def is_valid_for_data(self, data): hdr = data.read(0, 0x160) if len(hdr) < 0x160 or len(hdr)>0x100000: return False if ord(hdr[0x3]) != 0x20: # First word is the initial stack pointer, must be in SRAM around 0x20000000. return False if ord(hdr[0x7]) != 0x08: # Second word is the reset vector, must be in Flash around 0x08000000. return False return True def init_common(self): self.platform = Architecture["thumb2"].standalone_platform self.hdr = self.raw.read(0, 0x100001) print "Loaded %d bytes." % len(self.hdr) def init_thumb2(self, adr=0x08000000): try: self.init_common() self.thumb2_offset = 0 self.arm_entry_addr = struct.unpack("<L", self.hdr[0x4:0x8]) self.thumb2_load_addr = adr #struct.unpack("<L", self.hdr[0x38:0x3C]) self.thumb2_size = len(self.hdr); # Add segment for SRAM, not backed by file contents self.add_auto_segment(0x20000000, 0x20000, #128K at address 0x20000000. 0, 0, SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentExecutable) # Add segment for TCRAM, not backed by file contents self.add_auto_segment(0x10000000, 0x10000, #64K at address 0x10000000. 0, 0, SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable) #Add a segment for this Flash application. self.add_auto_segment(self.thumb2_load_addr, self.thumb2_size, self.thumb2_offset, self.thumb2_size, SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) #Define the RESET vector entry point. self.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, self.arm_entry_addr&~1, "RESET")) self.add_entry_point(self.arm_entry_addr&~1) return True except: log_error(traceback.format_exc()) return False def perform_is_executable(self): return True def perform_get_entry_point(self): return self.arm_entry_addr class MD380AppView(MD380View): """MD380 Application loaded to 0x0800C000.""" name = "MD380" long_name = "MD380 Flash Application" def init(self): return self.init_thumb2(0x0800c000) MD380AppView.register()
You can load this plugin by dropping it into
in Linux or the equivalent location on your platform's plugin
directory. Then run
binaryninja D013.020.img to load the firmware.
You can select the viewer plugin from the bottom-right of the screen,
which will auto-analyze the RESET vector as it first loads.
Identifying Interrupt Handlers
Unfortunately, the RESET vector only reveals a dozen or so functions, so we'll continue with the interrupt handlers.
As I mentioned earlier, the raw firmware has no real header, but it
does begin with a series of 32-bit little-endian pointers. We can
init_thumb2() function of our viewing class to include
those interrupt handler addresses, and to give them names that
indicate their status.
#Define other entries of the Interrupt Vector Table (IVT) for ivtindex in range(8,0x184+4,4): ivector=struct.unpack("<L", self.hdr[ivtindex:ivtindex+4]) if ivector>0: #Create the symbol, then the entry point. self.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, ivector&~1, "vec_%x"%ivector)) self.add_function(ivector&~1);
This grows the identified functions to a few dozen, but many are useless infinite loops that just branch to themselves. We'll need to load symbols from the Radare2 annotations or the GNU LD linker scripts used in compiling the code.
Loading Symbols from Radare2
md380tools/annotations you will find annotations of the firmware
that were produced in Radare2, and designed for use in the same.
You'll note that each line is just an r2 command to define a function
at a given address.
x270% cat annotations/d13.020/flash.r | head f md380_menu_id @ 0x2001e915 f md380_menu_mem_base @ 0x2001b274 f md380_menu_memory @ 0x2001d5cc f mn_editbuffer_poi @ 0x200049fc f md380_menu_edit_buf @ 0x2001cb9a f md380_menu_0x2001d3f0 @ 0x2001e946 f md380_menu_depth @ 0x20004acc f md380_menu_0x2001d3ef @ 0x2001e945 f md380_menu_0x2001d3f1 @ 0x2001e947 x270%
Some have a slightly different format, defining both the function name and its size.
af+ 0x0809a4c0 4 gfx_font_small
Using them in Binary Ninja is no problem. In the same plugin file, we'll just define a plugin that opens a file dialog, then shotgun parses that file for all R2 definitions, importing them to the local database.
def importr2symbols(bv,filename): """Janky shotgun parser to import Radare2 symbols file to Binary Ninja.""" f=open(filename,"r"); for l in f: words=l.strip().split(); try: if words=="#": pass; elif words=="f" and words=="@": #f name @ 0xDEADBEEF name=words; adrstr=words; adr=int(adrstr.strip(";"),16); #Functions are in Flash if adr&0xF8000000==0x08000000: bv.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, adr&~1, name)); bv.add_function(adr&~1); print("Imported function symbol %s at 0x%x"%(name,adr)); #Data in SRAM or DRAM elif adr&0xFE000000==0x02000000: bv.define_auto_symbol(Symbol(SymbolType.DataSymbol, adr&~1, name)); print("Imported data symbol %s at 0x%x"%(name,adr)); elif (words=="af+" or words=="f") and int(words,16)>0: name=words; adrstr=words; adr=int(adrstr.strip(";"),16); #Functions are in Flash if adr&0xF8000000==0x08000000: bv.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, adr&~1, name)); bv.add_function(adr&~1); print("Imported function symbol %s at 0x%x"%(name,adr)); #Data in SRAM or DRAM elif adr&0xFE000000==0x02000000: bv.define_auto_symbol(Symbol(SymbolType.DataSymbol, adr&~1, name)); print("Imported data symbol %s at 0x%x"%(name,adr)); else: print "Ignoring: ",words; except: if len(words)>3: print("Ignoring: %s\n"%words); #log_error(traceback.format_exc()) def md380r2symbols(view): """This loads an MD380Tools symbols file in Radare2 format.""" filename=get_open_filename_input("Select GNU LD symbols file from MD380Tools.") if filename: print("Opening: %s"%filename); importr2symbols(view,filename); else: print("Aborting."); PluginCommand.register("Load MD380 R2 Symbols", "Load Radare2 symbols from MD380Tools", md380r2symbols);
The plugin is operating by right-clicking on the background.
And sure enough, we have our symbols.
Loading Symbols from GNU LD Scripts
Our Radare2 symbols are great for their completeness, but they require
manual identification. Luckily, MD380Tools includes a custom tool
symgrate that identifies identical Thumb2 functions between
Flash revisions. Let's load the symbols for firmware version D03.008,
to which MD380Tools has never been ported.
First, use the symbols migration tool to generate the symbols.
x270% cd md380tools/symbols x270% make 189 symbols_d02_034 189 symbols_d03_008 189 symbols_d03_020 189 symbols_d13_009 189 symbols_d13_014 189 symbols_d13_020 189 symbols_s03_012 189 symbols_s13_012 189 symbols_s13_020 1701 total These are temporary symbol files that will be overwritten. Move them elsewhere before editing. x270% head symbols_d03_008 /* Symbols for ../firmware/unwrapped/D003.008.img imported from ../firmware/unwrapped/D013.020.img. */ md380_create_main_menu_entry = 0x0800c189; /* 1024 byte match */ md380_create_menu_entry = 0x0800c72f; /* 86 byte match */ gfx_drawtext10 = 0x0800ded9; /* 26 byte match */ gfx_drawtext = 0x0800def7; /* 56 byte match */ draw_datetime_row = 0x0800df1b; /* 20 byte match */ draw_zone_channel = 0x0800e538; /* 10 byte match */ md380_menu_entry_back = 0x0800fc85; /* 40 byte match */ Create_Menu_Utilies = 0x080134a1; /* 520 byte match */ md380_menu_entry_programradio = 0x080136c1; /* 936 byte match */ x270%
Then extend the Binary Ninja plugin to handle the GNU LD format used by this file. We're shotgun parsing it, so arithmetic operations won't be handled.
def importldsymbols(bv,filename): """Janky parser to import a GNU LD symbols file to Binary Ninja.""" f=open(filename,"r"); for l in f: words=l.strip().split(); try: name=words; adrstr=words; adr=int(adrstr.strip(";"),16); #Function symbols are odd address in Flash. if adr&0xF8000001==0x08000001: bv.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, adr&~1, name)); bv.add_function(adr&~1); print("Imported function symbol %s at 0x%x"%(name,adr)); #Data symbols are in SRAM or TCRAM with unpredictable alignment. elif adr&0xC0000000==0: bv.define_auto_symbol(Symbol(SymbolType.DataSymbol, adr, name)); print("Imported data symbol %s at 0x%x"%(name,adr)); else: print "Uncategorized adr=0x%08x."%adr; except: # Print warnings when our janky parser goes awry. if len(words)>0 and words!="/*" and words!="*/": print("#Warning in: %s\n"%words); log_error(traceback.format_exc()) def md380ldsymbols(view): """This loads an MD380Tools symbols file in GNU LD format.""" filename=get_open_filename_input("Select GNU LD symbols file from MD380Tools.") if filename: print("Opening: %s"%filename); importldsymbols(view,filename); else: print("Aborting."); PluginCommand.register("Load MD380 LD Symbols", "Load GNU LD symbols from MD380Tools", md380ldsymbols);
Then unwrap that firmware revision and load it up in Binary Ninja.
x270% cd md380tools/firmware x270% make unwrapped/D003.008.img "make" -f Makefile_orig unwrapped/D003.008.img make: Entering directory '/home/travis/svn/md380tools/firmware' ../md380-fw --unwrap bin/D003.008.bin unwrapped/D003.008.img DEBUG: reading "bin/D003.008.bin" INFO: base address 0x800c000 INFO: length 0xf3000 DEBUG: writing "unwrapped/D003.008.img" make: Leaving directory '/home/travis/svn/md380tools/firmware' x270% binaryninja unwrapped/D003.008.img
The auto-analyzer will identify the interrupt table, and then you can
right click and select
Load MD380 LD Symbols to open
symbols_d03_008. We now have an annotated binary for a new firmware
revision without having manually reverse engineered a single function!
That's All, Folks!
By this point, you should see that the symbols from the MD380Tools project are easily accessible, and easy to use with other tools. You should also have learned that Binary Ninja is pretty damned easy to script; none of this code is particular advanced.
I took the lazy way out and included assumptions about the MD380's
architecture, such as that firmware is loaded to
0x0, or that the code is all Thumb2 with no
native ARM. With a bit of polish, you could add generic support for