Pixhawk
In this post, we will be binary patching a firmware image in order to hook into the entry point. This is common practice when we are reverse engineering binary firmwares in an attempt to discover the interactions between machine code and hardware functionality. For the purposes of this post, we will be redirecting the control flow to custom injected code after which the original control flow will be restored. This post will take you through the tools and techniques we leverage in to order to instrument a binary image. We will be using a Pixhawk Autopilot, which is a combination of the PX4FMU (The FMU is the core component, we have the v2.4 FMU) and PX4IO. The Pixhawk is a great starter platform to explore since the hardware and software are open source and available on Github. We recognize that access to the firmware source code negates much of the actual need to make binary modifications to the firmware, however by targeting such a device we can focus this post on the actual firmware patching and not get bogged down into the details associated with reverse engineering an unknown target.

Requirements

  • Pixhawk loaded with the latest px4fmu-v2 bootloader and firmware release. We are using Release v1.4.4.
  • IDA Pro
  • JTAG/SWD Adapter (Optional)
  • Capstone
  • Keystone

Preparation

The Pixhawk comes preloaded with a bootloader and firmware image, if so desired, we can pull the latest PX4 firmware release. The firmware on our board is the nuttx-px4fmu-v2-default.px4 file in the zip archive and we were able to upload this to the hardware with the px_uploader.py script from the GitHub repository.

PX4 Firmware Extraction, Uploading

The pre-compiled PX4 Firmware releases are in JSON file format and have a .px4 file extension. Prototypes for these files are located at PX4/Firmware/Images. Example prototype:

The "image" field contains the binary application image after being max compressed with zlib and base64 encoded. The "image_size" field is also checked to confirm it matches the size of the "image" field. We will only need to update these two fields to upload a custom image to the board. We can use the script below (px_parser.py) to extract or update the binary image for a given .px4 file:

Extraction

Extract the firmware by downloading the latest release (nuttx-px4-v2-default.px4), and then executing python px_parser -e nuttx-px4-v2-default.px4

Uploading

On macOS, running: python px_uploader.py nuttx-px4fmu-v2-default.px4 --port=/dev/cu.usbmodem1 will get the image onto the PixHawk. Conversely, you could also use QGroundControl for this task by following their guide.

Firmware Modification

After the default firmware has been uploaded, we can power on the board and visually identify when the board transitions from the bootloader to the firmware application. The FMU B/E (Bootloader/Error) LED flashes repeatedly when running the bootloader, and stops when the board starts running the firmware application. Since we have not performed any wiring setup, the application will end up blinking the main LED red to signal an error.

Here is a clip of the board booting up:

Our goal is to inject code that blinks an LED at the very start of the firmware application, directly after the bootloader finishes. After the injected code runs, execution should go back into the original control flow and resume normal activity. In doing this, we should be able to visually see if the injection was successful. As you can imagine, this technique could be used just like a printf(“helloworld”) in software debugging when we start inspecting points of interest in the firmware. Board interfaces like the FMU B/E LED is a good choice for this purpose since it is normally supposed to be off during the transition into the application. Another good option on this board is the BUZZER where as instead of lighting an LED we could play a beep or tune.

The processor that we are working with is the 32-bit STM32F427 with a Cortex M4 Core that runs the THUMB instruction set. Some basic assembly knowledge is needed to accomplish the goal. In our example, we mainly use MOV and BL instructions to setup arguments and perform function calls. The FMU B/E LED corresponds with the GPIO E12 pin. It is important to be familiar with the GPIO toggling process which involves enabling the GPIO peripheral’s clock, setting the pin to output mode, and then toggling the pin.

Image Base and Entry Point

The image base address and entry point for the application are important values to identify. The board will load the firmware application at the base address. We want to replicate this as well since it is much easier to work with the program as it would be represented in memory. The entry point is also necessary as that is where the bootloader ends and the application begins, this is the area in which we will perform our LED toggling. Given that we have access to source, both of these values can be found in the source code located in PX4/Bootloader.

hw_config.h : The application is loaded at the base address of 0x08004000

bl.c : The second word of the image is our entry point. This is 0x080a4e10 in our case.

Here is a view of the disassembly at the entry point for the default firmware image:

Bootloader

The bootloader for our board (px4fmuv2_bl.elf) can provide some useful information. Since the bootloader is blinking the FMU B/E LED when running, it must already have some functions or code that allow it to do this. In fact the bootloader is using libopencm3 to perform these tasks. Of course another approach is to write custom assembly to achieve the goal, but given that someone has already written the code we need, we might as well reuse.

Let’s open up the bootloader source code and identify the regions that perform GPIO setup and toggling. main_f4.c is the place to look. Some of the constants can be looked up in hw_config.h to get the libopencm3 names.

main_f4.c:

Thanks to debugging symbols, we can go and open the bootloader binary up in IDA and locate the corresponding assembly. It is then just a matter of recording down the function addresses and constant values. Below is an image of the disassembly for the LED initialization functions as well as the gpio_toggle function.

Patching Helper

It is very useful to use a tool that helps with the patching. We decided to use Capstone and Keystone to do perform the disassembly and assembly that is necessary.

Below is a python module that was written to provide helping functionality for patching. As previously mentioned, it is much easier to work on the binary image using virtual addresses. When the image is read in, the binary is abstracted so that it appears to have been loaded at the base address in memory. The image read and write functions also provide the necessary address translation. There is also a function to extend the image with null bytes to create space for code we will inject. Another solution is to find an existing location to overwrite.

Finally there is the patching function which takes in a string of assembly instructions and a location to target. The instructions are encoded individually by using Keystone. We need to consider the possibility that an existing instruction might be partially or fully overwritten. So the approach is for Capstone to read the target location beforehand, and identify overwritten bytes as partial or full instructions. If an instruction has been partially overwritten, we need to finish overwriting it with NOPS in order to prevent issues when we resume normal program execution. The overwritten instructions are stored to be used later in repairing the execution flow.

Patching

The steps we need to take to perform the hook:

  • Extend the firmware to obtain space for our custom code
  • Inject custom functions used by our main function and identify the entry point for the hook
  • Select an existing instruction in the firmware to overwrite with our hook that calls into our entry point
  • Inject the main function which performs:
    • Toggle LED
    • Restore original state and execute overwritten code
    • Return to where we hooked from

In the example code, the image is extended by 1024 bytes (sufficient for our uses). Two functions are injected: A software NOP delay and a LED toggle function that uses the NOP delay. It is easier to inject the hook into a location where it will not overwrite any relative instructions. The main function will setup the GPIO E LED, call the LED toggle function, and then clean up. In our case the GPIO peripheral clock for the LED pin has already been enabled at the hooking location. It ends up the only setup needed is to set GPIO_E12 to output mode. It is important to properly clean up and revert to normal execution without damaging the expected state of the program. This requires us to save and restore any registers we touched, and execute the original instructions that we had overwritten. The final step is to write all changes to disk and then upload to the board via px_uploader.py.

After producing the patched image, you can open it up in IDA to review the modifications. The ARM Little-endian processor should be selected. The ROM starting address and loading address should also be set to our image base of 0x08004000.

Here is the full patching code which uses the library posted above:

Results

We should have a successfully patched image at this stage. You can view the changes to the firmware image and board behavior below.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *