Ghidra needs to understand which memory locations are RAM, ROM, I/O space, etc. for proper analysis.
Canon firmware loads some binary parts from ROM into a various memory locations. Those needs to be properly defined / loaded into a project before reverse engineering starts.
The goal of this article is to combine those two together into a memory map in Ghidra
Because Ghidra “overlay” function is meant mostly for memory banking, this means we cannot use it in “logical way”: first define that RAM is from `x to y`, then “overlay” ROMCOPY regions over that.
Usual workflow is to first add all rom copies into a memory map, then define all the memory map regions, working around those already occupied by ROMCOPY regions.
Visual explanation may make more sense. Image below contains a complete memory map for EOS R 1.8.0 (7.3.9) ROM.
Uncacheable RAM mirror on this model is available between 0x40000000
and 0xBFFFFFFF
.
Because DryOS loads some chunks into locations inside that region (see 0x40100000
and 40700000
), those were added to a memory map first.
Later all remaining “undefined” area between 0x40000000
and 0xBFFFFFFF
was defined as uninitialized RAM to complete that part of a memory map.
There are two possible sources: QEMU and static analysis.
Ideally you want to do both, as each method has it pros and cons.
Running firmware in QEMU with -d romcpy
option set will produce romcpy.sh
in a camera model subdirectory.
File is generated using naïve approach - QEMU watches all the memory transfers and tries to write down any bulk transfer from ROM location to somewhere else. This means that file will contain false positives, as well as may be missing some data.
Example file from SX740.102:
dd if=ROM0.BIN of=SX740.0xDF001000.bin bs=1 skip=$((0x88F0)) count=$((0x400)) dd if=ROM0.BIN of=SX740.0xDF000000.bin bs=1 skip=$((0x8CF0)) count=$((0x100)) dd if=ROM0.BIN of=SX740.0x40100000.bin bs=1 skip=$((0x8E0C)) count=$((0x12EA0)) dd if=ROM0.BIN of=SX740.0x40700000.bin bs=1 skip=$((0x0)) count=$((0x4900)) dd if=ROM0.BIN of=SX740.0x1F1740.bin bs=1 skip=$((0x40000)) count=$((0x230)) dd if=ROM0.BIN of=SX740.0x4000.bin bs=1 skip=$((0x101CED8)) count=$((0x55F14)) dd if=ROM0.BIN of=SX740.0xDF002800.bin bs=1 skip=$((0x1072DEC)) count=$((0xB9C))
If you are not familiar with dd
command syntax, example based on 1st row:
if=
is input (source) file, in that case ROM0.bin
, which (SX740 is DIGIC 8) means 0xE0000000
rom baseof=
contains output file name. After a dot .
we add destination memory address (0xDF001000
)skip=
contains offset inside input file 0x88F0
count=
is the length of copied data 0x400
Thus in this example, 0x400
data bytes from 0xE00088F0
(0xE0000000 + 0x88F0
) was copied to 0xDF001000
You can run this script in terminal (it has no executable flag and shebang, thus you may want to do bash ./romcpy.sh
) to produce new files containing only the regions that were copied to RAM.
This is not necessarily needed, as ROMCPY regions can be defined in Ghidra without extracting files.
2nd way is to make a temporary Ghidra project, but load only code ROM image. This is enough to disassemble / decompile DryOS bootloader and find all the important regions simply by looking at the decompiled code.
While qemu -d romcpy
is (arguably) the easiest way to obtain list of all chunks moved from ROM to RAM, as described in previous section - it is imperfect.
First of all, it includes all things - including bootloader FROMUTIL that is not needed and may mess up analysis ( it is not available after DryOS boots anyway). 2nd, it just tries to detect bulk memory moves - so a lot of “small regions” are false positives, and sometimes it misses other bits.
QEMU result might be slightly off as compared to code, as reads/writes are often aligned to bigger chunks.
Navigate to firmware_entry
(see Create a Ghidra project). Look in the decompiled code for for
loops. Some will just write zeroes (we are not interested in those), other read from one address (in code rom range) and write to another address.
Read the code, note down each source address, destination start address and destination end address. Calculate region sizes.
In disassembled code those may look like:
// src holds address of data source in rom src = &DAT_e101ced8; // loop reads src, writes to dst and increases both pointers by 1 // as long as "end" destination address is reached for (dst = &DAT_00004000; dst < &DAT_00023770; dst = dst + 1) { *dst = *src; src = src + 1; }
Example, from SX740.102:
source | to:start | to:end | size |
---|---|---|---|
0xe101ced8 | 0x4000 | 0x23770 | 0x1F770 |
0xe103c648 | 0x23770 | 0x59f14 | 0x367A4 |
- | 0x59f14 | 0xddd1c | |
0xe1072dec | 0xdf002800 | 0xdf00339c | 0xB9C |
- | 0xdf00339c | 0xdf0033a8 | |
As mentioned before, there are two methods to add a ROMCOPY region to memory map.
Both methods are equally good, so select which one is easier for you.
You will need to repeat this step for each ROMCOPY region.
In Window → Memory Map
click green “+” symbol. This will open Add Memory Block
panel.
0x
prefix
Go to File → Add to program
. Select file extracted from ROM (eg with romcpy.sh
). Proceed like in case of adding 2nd ROM.
As explained before, ther regions (notably: RAM) will overlap with ROMCOPY regions defined in previous steps.
As a rule of thumb:
0x40000000
0x00000000
0xCxxxxxxx
and 0xDxxxxxxx
are ranges where most devices liveAs a reminder: Regions (mostly RAM) will overlap with ROMCOPY regions defined earlier. Unfortunately that is not possible (“overlay” option does not apply for that case), so you will need to split continuous blocks to fill around already defined ROMCOPY chunks.
In Window → Memory Map
click green ”+“ symbol. This will open Add Memory Block
panel.
0x
prefix