While reverse-engineering the firmware on the Digoo DG-HOSA device which I have a couple of posts on already. I ran across some memory addresses that did not directly map to peripherals. I found the address ranges are called the Bit-band range and had special functionality allowing direct access to individual bits on peripherals. This post will give a quick summary of what these addresses are and how to unmap them to the normal peripheral addresses.
What is Bit Banding?
The Bit Banding is a feature of the ARM Cortex-M3/M4 allowing you to directly access specific bits within the Peripheral and SRAM regions. Normally you need to write a whole register at a time even if you want to change a single bit. There are memory regions allocated for this purpose they are named “Bit band alias” in this snip of the Processor memory map table.
From Cortex-M3 Technical Reference
To calculate the Bit band address for a specific bit on a port you use the following formula. This formula is also found in the Cortex-M3 Technical reference.
bit_word_addr = bit_band_base + ((byte_offset x 32) + (bit_number × 4))
bit_word_offset – position of the target bit in the bit-band memory region.
bit_word_addr – address of the word in the alias memory region that maps to the targeted bit.
bit_band_base – starting address of the alias region.
byte_offset – number of the byte in the bit-band region that contains the targeted bit.
bit_number – bit position (0-7) of the targeted bit.
This was a quick introduction to bit banding if you are looking for more information check out his post which goes into great detail on this feature.
Bit-banding Explained: A Key Feature of ARM Cortex-M3/M4
Now to reverse a bit mapped address
Now that we how to forward map an address I wrote up some python code that takes in the address, unmaps it, and lists the original port and register information.
#!/usr/bin/python
def find_base_addr(bitmapped):
if (0x22000000 < bitmapped and bitmapped < 0x23FFFFFF):
bit_band_base = 0x22000000
base_address = 0x20000000
elif (0x42000000 < bitmapped and bitmapped < 0x43FFFFFF):
bit_band_base = 0x42000000
base_address = 0x40000000
else:
bit_band_base = 0
base_address = 0
return bit_band_base, base_address
def bitunmapper(bitmapped):
bit_band_base, base_address = find_base_addr(bitmapped)
unmap_port = int(((bitmapped - bit_band_base) & 0xfffff00) / 32)
unmap_bits = int((bitmapped & 0x000000FF) / 4)
if unmap_bits > 32:
unmap_port += 0x4
unmap_bits %= 32
unmapped_address = base_address + int(unmap_port)
print("Base Address: " + hex(unmapped_address))
print("Port: " + hex(unmapped_address & 0xFFFFFF00))
print("Register: " + hex(unmapped_address & 0x000000FF))
print("Bit Map: " + str(int(unmap_bits)))
bitunmapper(0x4221811c)
bitunmapper(0x42218188) # GPIOB 2
bitunmapper(0x42218198) # GPIOB 6
I have also created a Ghidra plugin using this code to quickly resolve these addresses when working through a binary. You can download it from this link:
https://github.com/suidroot/ghidra_scripts/blob/main/arm-bit-unmapper.py
Finally, here is a couple of screenshots of the plugin in action.