Updated WEEK05

This commit is contained in:
Kevin Thomas
2026-02-21 09:55:14 -05:00
parent c1626d1c96
commit a17767e099
6 changed files with 2370 additions and 3 deletions
+277
View File
@@ -0,0 +1,277 @@
# 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 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 3020 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 190):
```
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?