mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-08-24 20:03:03 +02:00
Updated WEEK05
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
# 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
|
||||
|
||||
### 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
|
||||
|
||||
```bash
|
||||
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 | 2⁵ | 32 |
|
||||
| 0 | 2⁴ | 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.
|
||||
Reference in New Issue
Block a user