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

272 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 6252 (exponent): 10000000100
Bits 510 (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 3020 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 190):
```
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.