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

174 lines
6.2 KiB
Markdown

# 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 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 **Window****Bytes** 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 **File****Export 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:
```powershell
cd Embedded-Hacking-main\0x000e_floating-point-data-type
```
Run the conversion:
```powershell
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 **File****Undo** 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?