-
Notifications
You must be signed in to change notification settings - Fork 244
IDAPro
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.
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%
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.
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
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
.)
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:
-
IDA defines functions as beginning at even addresses, but the linker needs odd addresses.
-
Functions begin at odd address in Flash memory. Items in RAM can begin at any address.
-
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;
}
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.