# 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 3: Analyze the Double Binary in Ghidra #### Objective Import and analyze the `0x0011_double-floating-point-data-type.bin` binary in Ghidra to understand how doubles differ from floats at the binary level, observe that **both** register words carry non-zero data when the fractional part is complex, and decode the IEEE 754 double-precision encoding of `42.52525` from two 32-bit registers. #### Prerequisites - Completed Exercises 1 and 2 (float analysis and patching) - Understanding of IEEE 754 double-precision format from Week 5 Part 3 - Understanding of register pairs (`r2:r3`) from Exercise 1 - Basic Ghidra navigation skills #### Task Description You will import the double binary into Ghidra, resolve function names, identify that `42.52525` requires non-zero data in **both** r2 and r3 (unlike `42.5` which had r2 = 0), and decode the full 64-bit double `0x4045433B645A1CAC` field by field to confirm it represents `42.52525`. #### Step-by-Step Instructions ##### Step 1: Flash the Original Binary Before analysis, verify the program works: 1. Hold **BOOTSEL** and plug in your Pico 2 2. Flash `0x0011_double-floating-point-data-type.uf2` to the RPI-RP2 drive 3. Open your serial monitor **Expected output:** ``` fav_num: 42.525250 fav_num: 42.525250 fav_num: 42.525250 ... ``` ##### Step 2: Create a New Ghidra Project 1. Launch Ghidra: `ghidraRun` 2. Click **File** → **New Project** 3. Select **Non-Shared Project** 4. Project Name: `week05-ex03-double-analysis` 5. Click **Finish** ##### Step 3: Import and Configure the Binary 1. Drag and drop `0x0011_double-floating-point-data-type.bin` into Ghidra 2. Set Language: **ARM Cortex 32 little endian default** 3. Click **Options…** - Block Name: `.text` - Base Address: `10000000` 4. Click **OK** on all dialogs 5. Double-click the file and click **Yes** to analyze ##### Step 4: Locate and Rename Functions Identify the main function and standard library calls: **Rename main:** 1. Click on `FUN_10000238` 2. Right-click → **Edit Function Signature** 3. Change to: `int main(void)` 4. Click **OK** **Rename stdio_init_all:** 1. Click on `FUN_10002f64` 2. Right-click → **Edit Function Signature** 3. Change to: `bool stdio_init_all(void)` 4. Click **OK** **Rename printf:** 1. Click on `FUN_100030f4` 2. Right-click → **Edit Function Signature** 3. Change to: `int printf(char *format,...)` 4. Check the **Varargs** checkbox 5. Click **OK** ##### Step 5: Observe the Decompiled Code After renaming, the decompiled `main` should look like: ```c int main(void) { undefined4 uVar1; undefined4 uVar2; undefined4 extraout_r1; undefined4 uVar3; undefined4 extraout_r1_00; uVar2 = DAT_10000258; uVar1 = DAT_10000254; stdio_init_all(); uVar3 = extraout_r1; do { printf(DAT_10000250,uVar3,uVar1,uVar2); uVar3 = extraout_r1_00; } while( true ); } ``` **Critical observation:** There are now **two** non-zero data constants — `DAT_10000254` and `DAT_10000258`. This is the key difference from the float exercise! ##### Step 6: Examine the Assembly Listing Click on the **Listing** window and find the main function assembly: ```assembly 10000238 push {r3,r4,r5,lr} 1000023a adr r5, [0x10000254] 1000023c ldrd r4, r5, [r5, #0x0] = 645A1CACh / 4045433Bh 10000240 bl stdio_init_all ``` **Key instruction: `ldrd r4, r5, [r5, #0x0]`** This is a **load register double** instruction — it loads two consecutive 32-bit words in a single instruction: - `r4` gets the low word: `0x645A1CAC` - `r5` gets the high word: `0x4045433B` Later in the loop: ```assembly 10000244 mov r2, r4 ; r2 = 0x645A1CAC (low) 10000246 mov r3, r5 ; r3 = 0x4045433B (high) ``` ##### Step 7: Identify the Register Pair | Register | Value | Role | | -------- | ------------ | ------------ | | `r2` | `0x645A1CAC` | Low 32 bits | | `r3` | `0x4045433B` | High 32 bits | Together: `0x4045433B645A1CAC` **Compare to the float exercise:** - Float `42.5`: r2 = `0x00000000`, r3 = `0x40454000` — low word is all zeros - Double `42.52525`: r2 = `0x645A1CAC`, r3 = `0x4045433B` — **both words are non-zero!** This happens because `42.52525` has a repeating binary fraction that needs all 52 mantissa bits. ##### Step 8: Decode the Sign Bit Convert r3 to binary and check bit 31: ``` r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 ^ bit 31 = 0 → Positive number ✓ ``` ##### Step 9: Decode the Exponent Extract bits 30–20 from r3: ``` 0x4045433B: 0 10000000100 01010100001100111011 ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ sign exponent mantissa (top 20) ``` Exponent bits: `10000000100` = 2¹⁰ + 2² = 1024 + 4 = **1028** $$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ ##### Step 10: Decode the Mantissa **High 20 bits** (r3 bits 19–0): ``` 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 ``` **Low 32 bits** (all of r2 = `0x645A1CAC`): ``` 0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 ``` With the implied leading `1`: ``` 1.0101010000110011101101100100010110100001110010101100 ``` ##### Step 11: Reconstruct the Integer Part $$1.0101010000110011..._2 \times 2^5$$ Shift the binary point 5 places right → `101010.1000011001110110...` Integer part `101010`: | Bit | Power | Value | |-----|-------|-------| | 1 | 2⁵ | 32 | | 0 | 2⁴ | 0 | | 1 | 2³ | 8 | | 0 | 2² | 0 | | 1 | 2¹ | 2 | | 0 | 2⁰ | 0 | $$32 + 8 + 2 = \mathbf{42}$$ ##### Step 12: Approximate the Fractional Part The first few fractional bits `.10000110011...`: | Bit | Power | Decimal | |------|--------|---------------| | 1 | 2⁻¹ | 0.5 | | 0 | 2⁻² | 0 | | 0 | 2⁻³ | 0 | | 0 | 2⁻⁴ | 0 | | 0 | 2⁻⁵ | 0 | | 1 | 2⁻⁶ | 0.015625 | | 1 | 2⁻⁷ | 0.0078125 | | 0 | 2⁻⁸ | 0 | | 0 | 2⁻⁹ | 0 | | 1 | 2⁻¹⁰ | 0.0009765625 | | 1 | 2⁻¹¹ | 0.00048828125 | First 11 bits sum ≈ 0.5249. The remaining 36 fractional bits refine this to ≈ 0.52525. $$42 + 0.52525 = \mathbf{42.52525} ✓$$ ##### Step 13: Find the Format String In the Listing view, locate: ``` s_fav_num:_%lf_100034b0 ds "fav_num: %lf\r\n" ``` Note the `%lf` format specifier — this program explicitly uses `double`, unlike the float program which used `%f`. ##### Step 14: Document Your Findings | Item | Float (`42.5`) | Double (`42.52525`) | | ------------------------- | -------------------- | ------------------------ | | r2 (low word) | `0x00000000` | `0x645A1CAC` | | r3 (high word) | `0x40454000` | `0x4045433B` | | Low word is zero? | Yes | **No** | | Words to patch | 1 (r3 only) | **2 (both r2 and r3)** | | Format specifier | `%f` | `%lf` | | Assembly load instruction | `movs` + `ldr` | `ldrd` (load double) | #### Expected Output After completing this exercise, you should understand: - How the compiler loads a 64-bit double using `ldrd` (load register double) - Why `42.52525` requires non-zero data in both registers while `42.5` does not - How to decode a complex IEEE 754 double with a repeating binary fraction - The differences between float and double handling at the assembly level #### Questions for Reflection ###### Question 1: Why does `42.5` have a zero low word but `42.52525` does not? ###### Question 2: The assembly uses `ldrd r4, r5, [r5, #0x0]` instead of two separate `ldr` instructions. What is the advantage? ###### Question 3: Both the float and double programs have the same exponent (stored as 1028, real exponent 5). Why? ###### Question 4: If you were patching this double, how many data constants would you need to modify compared to the float exercise? #### Tips and Hints - Use Python to verify: `import struct; struct.pack('>d', 42.52525).hex()` gives `4045433b645a1cac` - The `ldrd` instruction always loads the lower-addressed word into the first register - A repeating binary fraction (like `0.52525`) can never be represented exactly — double precision uses the closest 52-bit approximation - Compare data addresses: the float binary has one `DAT_` constant; the double binary has two consecutive ones #### Next Steps - Proceed to Exercise 4 to patch both register words - Compare the mantissa of `42.5` (clean: `010101 000...`) vs. `42.52525` (complex: `0101010000110011...`) - Think about what values would have a zero low word (hint: powers of 2, halves, quarters) #### Additional Challenge Using the 52-bit mantissa `0101010000110011101101100100010110100001110010101100`, manually sum the first 20 fractional bits to see how close you get to `0.52525`. How many bits of precision does it take to get within 0.001 of the true value?