mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-19 22:38:05 +02:00
174 lines
6.2 KiB
Markdown
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?
|