# Embedded Systems Reverse Engineering [Repository](https://github.com/mytechnotalent/Embedded-Hacking) ## Week 5 Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis ### Non-Credit Practice Exercise 4: Patch the Double Binary — Changing 42.52525 to 99.99 #### Objective Calculate the IEEE 754 double-precision encoding of `99.99`, patch **both** register words in the double binary in Ghidra, export the patched binary, convert it to UF2 format, and flash it to the Pico 2 to verify the change. #### Prerequisites - Completed Exercise 3 (double binary imported and analyzed in Ghidra) - Understanding of IEEE 754 double-precision encoding from Week 5 Parts 2.7 and 3.7 - Knowledge of integer-to-binary conversion and the multiply-by-2 method for fractions - Python installed for UF2 conversion and verification - Raspberry Pi Pico 2 connected via USB #### Task Description You will derive the IEEE 754 encoding of `99.99` step by step (integer part, fractional part, normalization, field extraction), patch both the low word and high word data constants in Ghidra, and verify on hardware that the serial output now prints `99.990000`. Unlike Exercise 2 where only one word changed, this exercise requires patching **both** registers. #### Step-by-Step Instructions ##### Step 1: Convert the Integer Part (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: Convert the Fractional Part (.99) to Binary Use the multiply-by-2 method: | Multiply | Result | Integer part | Remaining fraction | |----------------|--------|--------------|-------------------| | 0.99 × 2 | 1.98 | **1** | 0.98 | | 0.98 × 2 | 1.96 | **1** | 0.96 | | 0.96 × 2 | 1.92 | **1** | 0.92 | | 0.92 × 2 | 1.84 | **1** | 0.84 | | 0.84 × 2 | 1.68 | **1** | 0.68 | | 0.68 × 2 | 1.36 | **1** | 0.36 | | 0.36 × 2 | 0.72 | **0** | 0.72 | | 0.72 × 2 | 1.44 | **1** | 0.44 | | 0.44 × 2 | 0.88 | **0** | 0.88 | | 0.88 × 2 | 1.76 | **1** | 0.76 | | 0.76 × 2 | 1.52 | **1** | 0.52 | | 0.52 × 2 | 1.04 | **1** | 0.04 | | ... | ... | ... | *(continues — repeating fraction)* | Reading the integer parts top-to-bottom: $0.99_{10} \approx 0.111111010111..._2$ This is a **repeating fraction** — it never terminates in binary. ##### Step 3: Combine Integer and Fractional Parts $$99.99_{10} = 1100011.111111010111..._2$$ ##### Step 4: Normalize to IEEE 754 Form Move the binary point so there is exactly one `1` before it: $$1100011.111111010111..._2 = 1.100011111111010111..._2 \times 2^6$$ We shifted the binary point 6 places left, so the exponent is **6**. ##### Step 5: Extract the IEEE 754 Fields 1. **Sign:** `0` (positive) 2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$ 3. **Mantissa:** `1000111111110101110000101000111101011100001010001111...` (52 bits after the `1.`) 4. **Full double:** `0x4058FF5C28F5C28F` **Verify with Python:** ```python >>> import struct >>> struct.pack('>d', 99.99).hex() '4058ff5c28f5c28f' ``` ##### Step 6: Split into Register Words | Register | Old Value | New Value | Changed? | | -------- | ------------ | ------------ | -------- | | `r2` | `0x645A1CAC` | `0x28F5C28F` | **Yes** | | `r3` | `0x4045433B` | `0x4058FF5C` | **Yes** | **Both registers change!** This is the key difference from Exercise 2 where only r3 changed. ##### Step 7: Locate the Data Constants in Ghidra Open your Ghidra project from Exercise 3. In the Listing view, find the two data constants: **Low word (loaded into r2):** ``` DAT_10000254 10000254 ac 1c 5a 64 undefined4 645A1CACh ``` **High word (loaded into r3):** ``` DAT_10000258 10000258 3b 43 45 40 undefined4 4045433Bh ``` ##### Step 8: Patch the Low Word 1. Click on **Window** → **Bytes** to open the Bytes Editor 2. Click the **Pencil Icon** to enable editing 3. Navigate to address `10000254` 4. The bytes read: `AC 1C 5A 64` (little-endian for `0x645A1CAC`) 5. Change to: `8F C2 F5 28` (little-endian for `0x28F5C28F`) 6. Press **Enter** ##### Step 9: Patch the High Word 1. Navigate to address `10000258` 2. The bytes read: `3B 43 45 40` (little-endian for `0x4045433B`) 3. Change to: `5C FF 58 40` (little-endian for `0x4058FF5C`) 4. Press **Enter** **Verify** in the Listing view: - `DAT_10000254` should show `28F5C28Fh` - `DAT_10000258` should show `4058FF5Ch` Together: `0x4058FF5C28F5C28F` = `99.99` as a double ✓ ##### Step 10: Export the Patched Binary 1. Click **File** → **Export Program** 2. Set **Format** to **Raw Bytes** 3. Navigate to your build directory 4. Name the file: `0x0011_double-floating-point-data-type-h.bin` 5. Click **OK** ##### Step 11: Convert to UF2 Format Open a terminal: ```powershell cd Embedded-Hacking-main\0x0011_double-floating-point-data-type ``` ```powershell python ..\uf2conv.py build\0x0011_double-floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` ##### Step 12: 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.990000 fav_num: 99.990000 fav_num: 99.990000 ... ``` 🎉 **Success!** The value changed from `42.52525` to `99.99`! #### Expected Output After completing this exercise, you should: - See `fav_num: 99.990000` printing instead of `fav_num: 42.525250` - Have a patched binary file (`0x0011_double-floating-point-data-type-h.bin`) - Have a UF2 file (`hacked.uf2`) - Understand that patching doubles with repeating fractions requires modifying **both** register words #### Questions for Reflection ###### Question 1: Why did both r2 and r3 change when patching 42.52525 → 99.99, but only r3 changed when patching 42.5 → 99.0? ###### Question 2: The multiply-by-2 method for 0.99 produces a repeating pattern. What does this mean for the precision of the stored value? ###### Question 3: If you wanted to patch the double to `100.0` instead of `99.99`, how many data constants would need to change? ###### Question 4: Compare the Ghidra Listing for the float binary (Exercise 1) and the double binary (Exercise 3). How does the compiler load the double differently? #### Tips and Hints - Always verify your encoding with Python before patching - Little-endian byte order: `0x28F5C28F` is stored as `8F C2 F5 28` in memory - Use Ghidra's **Bytes** window (Window → Bytes) for precise hex editing - If `r2` was zero before and needs to be non-zero after, you need to patch the data constant — not the `movs r4, #0x0` instruction - The `ldrd` instruction loads r4 and r5 from two consecutive memory addresses — both must be correct #### Next Steps - Review the complete patching workflow diagram in Week 5 Part 3.95 - Try patching to `100.0` — since it has a zero low word, you'll need to change `r2` from non-zero to zero - Attempt the practice exercises at the end of Week 5 #### Additional Challenge Patch the double to `3.14159265358979` (pi). This requires extreme precision in all 52 mantissa bits. Use Python to get the exact encoding, then patch both words. Verify the output prints at least 6 correct decimal places. What happens to the precision if you only patch the high word and leave the low word as `0x645A1CAC`? #### Verification Checklist Before moving on, confirm: - [ ] Serial output shows `fav_num: 99.990000` - [ ] Both data constants were patched (low word and high word) - [ ] You can derive the IEEE 754 encoding of `99.99` from scratch - [ ] You understand why messy fractions require patching both register words - [ ] You can explain the difference between the float and double patching workflows - [ ] You successfully converted and flashed the UF2 **Congratulations!** You've completed all Week 5 exercises and mastered floating-point analysis, IEEE 754 decoding, and double-precision binary patching!