Files
Embedded-Hacking/WEEK05/WEEK05-02.md
2026-03-19 15:01:07 -04:00

6.2 KiB

Embedded Systems Reverse Engineering

Repository

Week 5

Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis

Non-Credit Practice Exercise 2: Patch the Float Binary — Changing 42.5 to 99.0

Objective

Calculate the IEEE 754 double-precision encoding of 99.0, patch the float binary in Ghidra to change the printed value from 42.5 to 99.0, export the patched binary, convert it to UF2 format, and flash it to the Pico 2 to verify the change.

Prerequisites

  • Completed Exercise 1 (float binary imported and analyzed in Ghidra)
  • Understanding of IEEE 754 double-precision encoding from Week 5 Part 2.7
  • Python installed for UF2 conversion
  • uf2conv.py script available in your project directory
  • Raspberry Pi Pico 2 connected via USB
  • Serial monitor software (PuTTY, minicom, or screen)

Task Description

You will convert 99.0 to its IEEE 754 double-precision encoding by hand (integer-to-binary conversion, normalization, field extraction), determine which register words need to change, patch the data constant in Ghidra, and verify on hardware that the serial output now prints 99.000000.

Step-by-Step Instructions

Step 1: Convert 99 to Binary

Use repeated division by 2:

Division Quotient Remainder
99 ÷ 2 49 1
49 ÷ 2 24 1
24 ÷ 2 12 0
12 ÷ 2 6 0
6 ÷ 2 3 0
3 ÷ 2 1 1
1 ÷ 2 0 1

Read remainders bottom-to-top: 99_{10} = 1100011_2

Step 2: Handle the Fractional Part

The fractional part of 99.0 is .0 — exactly zero. There are no fractional bits.

99.0_{10} = 1100011.0_2
Step 3: Normalize to IEEE 754 Form

Move the binary point so there is exactly one 1 before it:

1100011.0_2 = 1.100011_2 \times 2^6

We shifted the binary point 6 places left, so the exponent is 6.

Step 4: Extract the IEEE 754 Fields
  1. Sign: 0 (positive)
  2. Exponent: 6 + 1023 = 1029 = 10000000101_2
  3. Mantissa: 100011 followed by 46 zeros (everything after the 1., padded to 52 bits)
  4. Full double: 0x4058C00000000000

Split into register words:

  • r3 (high word): 0x4058C000
  • r2 (low word): 0x00000000
Step 5: Determine What to Patch

Compare old vs. new:

Register Old Value New Value Changed?
r2 0x00000000 0x00000000 No
r3 0x40454000 0x4058C000 Yes

Since r2 stays all zeros, we only need to patch the high word in r3.

Step 6: Locate the Data Constant in Ghidra

Open your Ghidra project from Exercise 1. In the Listing view, find the data constant that loads into r3:

        DAT_1000024c
10000248 00  40  45  40    undefined4  40454000h

This is the 32-bit constant 0x40454000 — the high word of the double 42.5.

Step 7: Patch the Constant
  1. Click on WindowBytes to open the Bytes Editor
  2. Click the Pencil Icon to enable editing
  3. Navigate to the data at DAT_1000024c
  4. The bytes in memory (little-endian) read: 00 40 45 40
  5. Change them to: 00 C0 58 40 (which is 0x4058C000 in little-endian)
  6. Press Enter

Verify in the Listing view — the data should now show 4058C000h.

Step 8: Verify the Patch in the Decompile Window

The decompiled code should now reference the new constant. The value loaded into r3 should be 0x4058C000.

Step 9: Export the Patched Binary
  1. Click FileExport Program
  2. Set Format to Raw Bytes
  3. Navigate to your build directory
  4. Name the file: 0x000e_floating-point-data-type-h.bin
  5. Click OK
Step 10: Convert to UF2 Format

Open a terminal and navigate to your project directory:

cd Embedded-Hacking-main\0x000e_floating-point-data-type

Run the conversion:

python ..\uf2conv.py build\0x000e_floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
Step 11: Flash and Verify
  1. Hold BOOTSEL and plug in your Pico 2
  2. Drag and drop hacked.uf2 onto the RPI-RP2 drive
  3. Open your serial monitor

Expected output:

fav_num: 99.000000
fav_num: 99.000000
fav_num: 99.000000
...

🎉 Success! The value changed from 42.5 to 99.0!

Expected Output

After completing this exercise, you should:

  • See fav_num: 99.000000 printing instead of fav_num: 42.500000
  • Have a patched binary file (0x000e_floating-point-data-type-h.bin)
  • Have a UF2 file (hacked.uf2)
  • Understand the complete IEEE 754 encoding and patching workflow for floats

Questions for Reflection

Question 1: Why did we only need to patch r3 (the high word) and not r2 (the low word)?
Question 2: What would the high word be if we wanted to patch the value to -99.0 instead? (Hint: which bit controls the sign?)
Question 3: Walk through the encoding of 100.0 as a double. What are the high and low words?
Question 4: Why do we need the --family 0xe48bff59 flag when converting to UF2?

Tips and Hints

  • Always verify your encoding with Python: import struct; struct.pack('>d', 99.0).hex()
  • Little-endian means the bytes in memory are reversed: 0x4058C000 is stored as 00 C0 58 40
  • If you patch the wrong bytes, use FileUndo in Ghidra (or re-import the original binary)
  • The bias for double-precision is always 1023; for single-precision it's 127
  • A clean fractional part (like .0 or .5) means the low word (r2) is all zeros

Next Steps

  • Proceed to Exercise 3 to analyze the double-precision binary
  • Try patching to different values: 3.14, 100.0, 255.0
  • Compare how many register words change for clean vs. messy fractions

Additional Challenge

Patch the float to 3.14 instead of 99.0. Since 3.14 has a repeating binary fraction, the low word will not be zero. This means you need to modify the assembly to load a non-zero value into r2 as well. Can you figure out where r2 gets its zero value (movs r4, #0x0) and change it?