# 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 1: Analyze the Float Binary in Ghidra #### Objective Import and analyze the `0x000e_floating-point-data-type.bin` binary in Ghidra to understand how the compiler handles floating-point variables, discover float-to-double promotion, and decode the IEEE 754 double-precision encoding of `42.5` from two 32-bit registers. #### Prerequisites - Ghidra installed and configured - `0x000e_floating-point-data-type.bin` binary available in your build directory - Understanding of IEEE 754 encoding from Week 5 Part 2 - Basic Ghidra navigation skills from Weeks 3 and 4 #### Task Description You will import the float binary into Ghidra, configure it for ARM Cortex-M33, resolve function names, discover that the compiler promotes `float` to `double` when passing to `printf`, and manually decode the 64-bit double `0x4045400000000000` field by field to confirm it represents `42.5`. #### Step-by-Step Instructions ##### Step 1: Start Ghidra and Create New Project ```powershell ghidraRun ``` 1. Click **File** ? **New Project** 2. Select **Non-Shared Project** 3. Click **Next** 4. Enter Project Name: `week05-ex01-floating-point` 5. Choose a project directory 6. Click **Finish** ##### Step 2: Import the Binary 1. Navigate to your file explorer 2. Find `Embedded-Hacking\0x000e_floating-point-data-type\build\0x000e_floating-point-data-type.bin` 3. **Drag and drop** the `.bin` file into Ghidra's project window ##### Step 3: Configure Import Settings When the import dialog appears: 1. Click the three dots (**…**) next to **Language** 2. Search for: `Cortex` 3. Select: **ARM Cortex 32 little endian default** 4. Click **OK** Now click **Options…** button: 1. Change **Block Name** to: `.text` 2. Change **Base Address** to: `10000000` (XIP flash base) 3. Click **OK** Then click **OK** on the main import dialog. ##### Step 4: Analyze the Binary 1. Double-click the imported file in the project window 2. When prompted "Analyze now?" click **Yes** 3. Leave all default analysis options selected 4. Click **Analyze** 5. Wait for analysis to complete (watch bottom-right progress bar) ##### Step 5: Locate and Rename the Main Function Look at the **Symbol Tree** panel on the left. Expand **Functions**. From previous weeks, we know the boot sequence leads to `main()`: 1. Click on `FUN_10000234` 2. Right-click ? **Edit Function Signature** 3. Change to: `int main(void)` 4. Click **OK** ##### Step 6: Resolve stdio_init_all and printf **Rename stdio_init_all:** 1. Click on `FUN_10002f5c` in the decompile window 2. Right-click ? **Edit Function Signature** 3. Change to: `bool stdio_init_all(void)` 4. Click **OK** **Rename printf:** 1. Click on `FUN_100030ec` 2. Right-click ? **Edit Function Signature** 3. Change to: `int __wrap_printf(char *format,...)` 4. Check the **Varargs** checkbox 5. Click **OK** ##### Step 7: Observe the Decompiled Code After resolving, the decompiled `main` should look like: ```c int main(void) { undefined4 uVar1; undefined4 extraout_r1; undefined4 uVar2; undefined4 extraout_r1_00; stdio_init_all(); uVar1 = DAT_1000024c; uVar2 = extraout_r1; do { __wrap_printf(DAT_10000250,uVar2,0,uVar1); uVar2 = extraout_r1_00; } while( true ); } ``` **Critical observation:** Where is `float fav_num = 42.5`? The compiler optimized it into constants! ##### Step 8: Identify the Register Pair Look at the **Listing** window (assembly view) for `main`: ```assembly 1000023a 00 24 movs r4, #0x0 1000023c 03 4d ldr r5, [DAT_1000024c] = 40454000h ``` Two values are being passed to `printf`: - `r2 = 0x00000000` (low 32 bits) - `r3 = 0x40454000` (high 32 bits) Together they form a 64-bit double: `0x4045400000000000` **Why a double?** The C standard requires that `float` arguments to variadic functions like `printf` are **promoted to `double`**. So even though our variable is declared as `float fav_num = 42.5`, `printf` always receives a 64-bit double. ##### Step 9: Write Out the Binary Layout Convert both registers to binary: ``` r3 (high 32 bits): 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 r2 (low 32 bits): 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000 ``` Map the 64-bit IEEE 754 fields: ``` Bit 63 (sign): 0 Bits 62–52 (exponent): 10000000100 Bits 51–0 (mantissa): 0101010000000000...0000 ``` ##### Step 10: Decode the Sign Bit Bit 63 of the double = bit 31 of r3: ``` r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 ^ bit 31 = 0 ? Positive number ``` IEEE 754 sign rule: `0` = Positive, `1` = Negative. ##### Step 11: Decode the Exponent Extract bits 30–20 from r3: ``` 0x40454000: 0 10000000100 01010100000000000000 ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ sign exponent mantissa (top 20) ``` Exponent bits: `10000000100` = 2¹° + 2² = 1024 + 4 = **1028** Subtract the double-precision bias (1023): $$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ ##### Step 12: Decode the Mantissa High 20 bits of mantissa (from r3 bits 19–0): ``` 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ``` Low 32 bits of mantissa (from r2): ``` 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ``` With the implied leading `1`: ``` 1.010101 00000... ``` ##### Step 13: Reconstruct the Final Value $$1.010101_2 \times 2^5 = 101010.1_2$$ Convert to decimal: | Bit | Power | Value | |-----|-------|-------| | 1 | 25 | 32 | | 0 | 24 | 0 | | 1 | 2³ | 8 | | 0 | 2² | 0 | | 1 | 2¹ | 2 | | 0 | 2° | 0 | | 1 | 2?¹ | 0.5 | $$32 + 8 + 2 + 0.5 = \mathbf{42.5} ?$$ ##### Step 14: Find the Format String In the Listing view, click on the data reference to locate: ``` s_fav_num:_%f_100034a8 ds "fav_num: %f\r\n" ``` Note that the format specifier is `%f`, confirming this is a floating-point print call. ##### Step 15: Document Your Findings Create a table of your observations: | Item | Value | Notes | | ------------------------- | ------------------ | ---------------------------------- | | Main function address | `0x10000234` | Entry point of program | | Float value (original) | `42.5` | Declared as `float` | | Double hex encoding | `0x4045400000000000` | Promoted to double for printf | | r3 (high word) | `0x40454000` | Contains sign + exponent + mantissa top bits | | r2 (low word) | `0x00000000` | All zeros — clean fractional part | | Exponent (stored) | 1028 | Biased value | | Exponent (real) | 5 | After subtracting bias 1023 | | Format string | `"fav_num: %f\r\n"` | Located at `0x100034a8` | | Float-to-double promotion | Yes | C standard for variadic functions | #### Expected Output After completing this exercise, you should be able to: - Import and configure ARM binaries in Ghidra for float analysis - Explain why `printf` receives a `double` even when the variable is a `float` - Identify the register pair `r2:r3` that holds a 64-bit double - Manually decode an IEEE 754 double from hex to decimal - Locate format strings in the binary #### Questions for Reflection ###### Question 1: Why does the compiler promote a `float` to a `double` when passing it to `printf`? ###### Question 2: The low word (`r2`) is `0x00000000`. What does this tell you about the fractional part of `42.5`? ###### Question 3: What is the purpose of the exponent bias (1023) in IEEE 754 double-precision? ###### Question 4: If the sign bit (bit 63) were `1` instead of `0`, what value would the double represent? #### Tips and Hints - The **Listing** window shows raw assembly; the **Decompile** window shows reconstructed C - Double-click on a `DAT_` reference to jump to the data constant - Use Python to verify: `import struct; struct.pack('>d', 42.5).hex()` gives `4045400000000000` - Remember: r3 = high 32 bits (sign + exponent + top mantissa), r2 = low 32 bits (bottom mantissa) - The bias for doubles is always 1023; for floats it's 127 #### Next Steps - Proceed to Exercise 2 to patch this float value in Ghidra - Try computing the IEEE 754 encoding of other values like `3.14` or `100.0` by hand - Compare the 32-bit float encoding `0x422A0000` with the 64-bit double encoding `0x4045400000000000` — both represent `42.5` #### Additional Challenge Find the data constant `DAT_1000024c` in the Listing view. What raw bytes are stored there? Remember that ARM is little-endian — the bytes in memory are in reverse order. Write out the byte order as it appears in memory vs. as a 32-bit value.