Skip to content
Travis Goodspeed edited this page Jul 1, 2017 · 6 revisions

Reversing MD380 Firmware with IDA Pro

While Radare2 is our preferred tool for reverse engineering the Tytera firmware, IDA Pro is also handy. This short document will show you how to decrypt and load the firmware, how to identify functions, and how to import symbol names to avoid reproducing known work.

These notes might also be handy for use with Binary Ninja or other reverse engineering tools.

Decrypting the Firmware

Tytera encrypts their updates to prevent us from reading them, just as they lock the STM32F405 to prevent us from extracting its firmware. Luckily, we can easily bypass that with a handy tool from DD4CR, which is embedded in md380-fw.

Tytera has released many different versions of the firmware, but for simplicity's sake, we try to maintain patches against as few as possible. At the time of my writing, we target S013.020 for devices with GPS and D013.020 for devices without GPS. Let's fetch D013.020 from the Internet Archive and unpack it.

x270% cd md380tools/firmware 
x270% make unwrapped/D013.020.img
"make" -f Makefile_orig unwrapped/D013.020.img
make[1]: Entering directory '/home/travis/svn/md380tools/firmware'
https://archive.org/download/TYTMD380FW2/TYT-Vocoder-MD380-D13.20.bin => dl/D013.020.bin
cp dl/D013.020.bin bin/D013.020.bin
../md380-fw --unwrap bin/D013.020.bin unwrapped/D013.020.img
DEBUG: reading "bin/D013.020.bin"
INFO: base address 0x800c000
INFO: length 0xf3000
DEBUG: writing "unwrapped/D013.020.img"
make[1]: Leaving directory '/home/travis/svn/md380tools/firmware'
x270% 

This produces an unwrapped firmware image, which is both decrypted and stripped of its header. If we view it in hex, you can see that the image begins with an ARM interrupt vector table. The stack pointer is initialized to 0x2001f170 and the RESET vector is at 0x080f9245. (Odd addresses imply the 16-bit Thumb2 instruction set; none of this code uses 32-bit wide instructions.)

x270% hd unwrapped/D013.020.img|head   
00000000  70 f1 01 20 45 92 0f 08  f1 3e 09 08 f9 3e 09 08  |p.. E....>...>..|
00000010  01 3f 09 08 09 3f 09 08  11 3f 09 08 00 00 00 00  |.?...?...?......|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 19 3f 09 08  |.............?..|
00000030  1b 3f 09 08 00 00 00 00  1f 3e 04 08 1d 3f 09 08  |.?.......>...?..|
00000040  ad c5 0f 08 b1 c5 0f 08  b5 c5 0f 08 39 42 09 08  |............9B..|
00000050  bd c5 0f 08 c1 c5 0f 08  3f 40 09 08 23 40 09 08  |........?@..#@..|
00000060  9d 3f 09 08 89 3f 09 08  d5 c5 0f 08 d9 c5 0f 08  |.?...?..........|
00000070  dd c5 0f 08 a1 e9 04 08  e5 c5 0f 08 e9 c5 0f 08  |................|
00000080  a9 e9 04 08 f1 c5 0f 08  f5 c5 0f 08 f9 c5 0f 08  |................|
00000090  fd c5 0f 08 01 c6 0f 08  05 c6 0f 08 09 c6 0f 08  |................|
x270% 

Loading the Firmware

Now that we have a binary, we need to load it into memory. Unlike the virtual memory of a desktop application, embedded systems usually have memory of different types at fixed locations.

The STM32F405 used by the MD380 follows this pattern. One megabyte of Flash resides at 0x08000000, 128K of SRAM at 0x20000000, 64K of TCRAM at 0x10000000, and IO devices at 0x40000000. SRAM and TCRAM differ in that SRAM is executable, but TCRAM is slightly faster by being tied directly to the data bus.

You should also note that even though Flash begins at 0x08000000, a Flash bootloader takes the first little bit. Our application will be loaded to 0x0800C000.

First launch IDA with idaq D013.020.img, then configure the Processor Type to ARM Little-endian [ARM] and click the Set button to apply the change. Under Processor options/Edit ARM architecture options, set ARM instructions to No as our chip can only run Thumb2 instructions. Base architecture ought to be ARMv7-M, but you could get away with Any as the architecture doesn't change all that much.

Next, you need to tell IDA where to load the firmware. Create a ROM Section at 0x0800C000 and load the input file to that address.

If we were stopping here, it would also be a good idea to create a RAM section at 0x20000000, but we won't be doing that for two reasons. First, this chip has two RAM sections, and we wouldn't be able to include the second one at 0x10000000. Second, the RAM has actual contents, and it would be much more useful to have real data there from a running process rather than just a range of zeroes.

IDA will give you a warning about the ARM and Thumb mode switch. This warning is just telling you that there is a fake status register called T which you can edit by pressing Alt-G. Set T=0 for 32-bit ARM instructions and T=1 for 16-bit Thumb instructions. All instructions in this firmware a Thumb. After the binary has been loaded, select the CODE32 word at the start of Flash and use the Alt-G trick to set T=1; you will then see CODE16 at that address.

Next IDA will warn you that it cannot identify the entry point, as there is no standard entry location for a headerless binary. It tells you to press C at a valid entry location to begin the auto-analysis.

At this stage the binary is loaded properly. We'll bring some RAM items into place and then begin the auto-analysis.

Loading a Core Dump

Now that the code is properly loaded, it's time to load a coredump of RAM. Because very little of the TCRAM is used for globals, we'll just load the SRAM, and you can find a handy dump at md380tools/cores/d13020-core.img.

Click File/Load File/Additional Binary File and choose d13020-core.img. You should load it to a segment at 0x0, offset 0x20000000 and mark that it is not a code segment. (This region is executable, but Tytera happens to never place code in it.)

To keep everything tidy, you might also open the segment editor with Shift+F7 to give it the name of SRAM and assign it the right permissions. You might also load the TCRAM segment to 0x10000000 with an appropriate name.

Now that you have SRAM and Flash loaded, you can begin to explore the binary. First, let's find a few symbols on our own. Then I'll show you how to import symbols from the MD380Tools project

Beginning Analysis

Now that everything is in its proper place, we need to start identifying entry points and letting the auto-analyzer chase down their child functions.

In firmware like this, there are only two sources of entry points that have no parent functions: interrupt handlers and the targets of function pointers. We'll start with the interrupt handlers, can get to function pointers later.

At the beginning of the Flash Application (0x0800C000), you will find a number of 32-bit little endian words. Ignoring those that are zeroed, you'll find that all but the first are odd and in the Flash region. The first word is the start of the initial call stack, the second word is the address of the RESET handler, and subsequent words are other interrupts from the table. (Use the D key to change the width of each data word from one to four bytes.)

ROM:0800C000 ; Segment type: Pure code
ROM:0800C000                 AREA ROM, CODE, ALIGN=0
ROM:0800C000                 ; ORG 0x800C000
ROM:0800C000                 CODE16
ROM:0800C000                 DCD 0x2001F170          ; Top of initial stack.
ROM:0800C004                 DCD 0x80F9245           ; RESET handler
ROM:0800C008                 DCD 0x8093EF1
ROM:0800C00C                 DCD 0x8093EF9
ROM:0800C010                 DCD 0x8093F01
ROM:0800C014                 DCD 0x8093F09
ROM:0800C018                 DCD 0x8093F11
ROM:0800C01C                 DCD 0
ROM:0800C020                 DCD 0
ROM:0800C024                 DCD 0
ROM:0800C028                 DCD 0
ROM:0800C02C                 DCD 0x8093F19
ROM:0800C030                 DCD 0x8093F1B
ROM:0800C034                 DCD 0

Now, because we see the reset vector is 0x080F9245, we know that the Thumb2 handler for entering the application is at 0x080F9244. (This is because all Thumb2 code is 16-bit aligned, and the least significant bit is indicating the instruction set.)

Going to 0x080F9244 and hitting the C key auto-analyzes many functions of the binary. We might also hit P to make it a function, as Thumb2 allows even the RESET vector to be written as a C function. N can give it a name of RESET_handler so that we recognize it when seen elsewhere.

(For some reason, IDA gets confused if we hit P first, leaving off the return at the end of the function. So get in the habit of hitting C then P.)

Importing Symbols

So now you've got the firmware loaded, and you can begin identifying functions and wandering around the image. But there's a LOT of code in this firmware, and you've got better things to do than manually name symbols that are already known. Let's import them from the MD380Tools project and save the trouble.

The project maintains symbols for D02.032, D13.020, and S13.020 firmware revisions. (Symbols can be ported to new targets by the symgrate tool in symbols/.)

x270% ls applet/src/symbols_*
applet/src/symbols_d02.032  applet/src/symbols_s13.020
applet/src/symbols_d13.020
x270% 

Each of these files describes symbol names (functions and data) in the GNU LD format. We take care to avoid arithmetic in these files, so that they are easy to parse.

x270% cat applet/src/symbols_d13.020 | grep Get_Welcome
Get_Welcome_Line1_from_spi_flash         = 0x080226c1 ;
Get_Welcome_Line2_from_spi_flash         = 0x080226d3 ;
x270% 

For example, IDA ought to have given the default names sub_80226C0 and sub_80226D2 to the functions that grab the welcome lines from SPI Flash. From the loaded core dump, IDA already knows what strings have been loaded, but doesn't know the symbol names.

ROM:080226C0 sub_80226C0
ROM:080226C0                 PUSH    {R7,LR}
ROM:080226C2                 MOVS    R2, #0x14
ROM:080226C4                 MOV.W   R1, #0x2040
ROM:080226C8                 LDR.W   R0, =aKk4vcz3147092 ; "KK4VCZ    3147092   "
ROM:080226CC                 BL      sub_8031476
ROM:080226D0                 POP     {R0,PC}
ROM:080226D0 ; End of function sub_80226C0

ROM:080226D2
ROM:080226D2 sub_80226D2
ROM:080226D2                 PUSH    {R7,LR}
ROM:080226D4                 MOVS    R2, #0x14
ROM:080226D6                 MOVW    R1, #0x2054
ROM:080226DA                 LDR.W   R0, =(aKk4vcz3147092+0x14) ; "3147092   "
ROM:080226DE                 BL      sub_8031476
ROM:080226E2                 POP     {R0,PC}
ROM:080226E2 ; End of function sub_80226D2

To import the symbols, we'll simply convert them over to an IDA Python script that assigns the symbol names. A few rules to keep in mind:

  1. IDA defines functions as beginning at even addresses, but the linker needs odd addresses.

  2. Functions begin at odd address in Flash memory. Items in RAM can begin at any address.

  3. bool ida_name.set_name(ea, name) lets us set the name of any object, but not a tail byte.

This quick little python script produces an IDA Python script that'll take care of the import.

#!/usr/bin/python

# This is a janky little parser for converting symbol files to an
# IDA Python script.  It's very specific to the MD380Tools project,
# and you ought to rewrite it for use in your own projects.

import sys;

for l in sys.stdin:
    words=l.strip().split();

    try:
        name=words[0];
        adrstr=words[2];
        adr=int(adrstr,16);

        #Fix up addresses in Flash that look like functions.
        if(adr&0xFF000000==0x08000000):
            adr=adr&~1;

        print("ida_name.set_name(0x%x,\"%s\");" % (adr,name))
    except:
        # Print warnings when our janky parser goes awry.
        if len(words)>0:
            print("#Warning in: %s\n"%l.strip());

Conveniently the output has one symbol per line, making it easy to grep or search the results.

x270% ./symbols2idapy.py <symbols_d13.020 >loadsyms_d13.020.py      
x270% head loadsyms_d13.020.py
ida_name.set_name(0x800c188,"md380_create_main_menu_entry");
ida_name.set_name(0x800c72e,"md380_create_menu_entry");
ida_name.set_name(0x800ded8,"gfx_drawtext10");
ida_name.set_name(0x800def6,"gfx_drawtext");
ida_name.set_name(0x800df1a,"draw_datetime_row");
ida_name.set_name(0x800e538,"draw_zone_channel");
ida_name.set_name(0x800fc84,"md380_menu_entry_back");
ida_name.set_name(0x80134a0,"Create_Menu_Utilies");
ida_name.set_name(0x80136c0,"md380_menu_entry_programradio");
ida_name.set_name(0x80156a4,"Create_Menu_Entry_RX_QRG_shown");
x270% 

Running the script with File/Script file happily labels all the known functions and data, module a few objects that inconveniently overlap.

ROM:080226C0 Get_Welcome_Line1_from_spi_flash
ROM:080226C0                 PUSH    {R7,LR}
ROM:080226C2                 MOVS    R2, #0x14
ROM:080226C4                 MOV.W   R1, #0x2040
ROM:080226C8                 LDR.W   R0, =toplinetext ; "KK4VCZ    3147092   "
ROM:080226CC                 BL      md380_spiflash_read
ROM:080226D0                 POP     {R0,PC}
ROM:080226D0 ; End of function Get_Welcome_Line1_from_spi_flash

ROM:080226D2 Get_Welcome_Line2_from_spi_flash
ROM:080226D2                 PUSH    {R7,LR}
ROM:080226D4                 MOVS    R2, #0x14
ROM:080226D6                 MOVW    R1, #0x2054
ROM:080226DA                 LDR.W   R0, =(toplinetext+0x14) ; "3147092   "
ROM:080226DE                 BL      md380_spiflash_read
ROM:080226E2                 POP     {R0,PC}
ROM:080226E2 ; End of function Get_Welcome_Line2_from_spi_flash

If your license includes the HexRays decompiler for ARM32, you can also decompile the code.

int Get_Welcome_Line1_from_spi_flash()
{
  int v1; // [sp+0h] [bp-8h]@0

  md380_spiflash_read(toplinetext, 0x2040, 20);
  return v1;
}

Where to go from here?

Now that you have the firmware open in IDA, you can begin exploring and writing your own patches, which we write in C in the applet/ directory. Reverse engineering new pieces of code, you might create new features.

By using our symgrate tool to migrate symbols over from older versions, you ought to be able to create symbol files for new firmware revisions. This allows for new radio models to be supported.