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

View File

@@ -149,6 +149,16 @@ This chapter covers hacking uninitialized variables as well as an intro to GPIO
## Week 5
Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis
### Week 5 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05.md)
#### Exercise 1: Analyze the Float Binary in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-01.md)
#### Exercise 2: Patch the Float Binary — Changing 42.5 to 99.0 [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-02.md)
#### Exercise 3: Analyze the Double Binary in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-03.md)
#### Exercise 4: Patch the Double Binary — Changing 42.52525 to 99.99 [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-04.md)
### Chapter 11: Integer Data Type
This chapter covers the integer data type in addition to a deeper assembler dive into GPIO outputs as it relates to embedded development on the Pico 2.
@@ -229,8 +239,7 @@ This chapter covers debugging constants as well as an intro to I2C as we work a
This chapter covers hacking constants as well as an intro to I2C as we work a 1602 LCD as it relates to embedded development on the Pico 2.
## Week 8
### Midterm Project: The InfuSafe Pro Incident
In the aftermath of a catastrophic medical device failure, you are thrust into the role of an FDA forensic investigator facing an impossible crisis: 23 patients dead, 100 million recalled insulin pumps sitting in warehouses worldwide, and 2.3 million lives hanging in the balance all while the only evidence remaining is raw binary firmware after a rogue engineer destroyed every line of source code before fleeing to Montenegro. Armed only with GDB, Ghidra, and the reverse engineering skills honed over the first seven weeks of this course, you must excavate the truth from machine code, identify the lethal bugs spawned by an AI code generator called "OopsieGPT," and determine whether these devices can be salvaged to save millions in underserved communities or if $4.7 billion in humanitarian medical technology must be incinerated. This is not a simulation; this is triage at the intersection of embedded systems security and human survival.
### Midterm Exam
## Week 9
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics.
@@ -325,7 +334,10 @@ Unknown Firmware Debugging and Hacking
Final Review Embedded Debugging and Hacking Techniques w/ Advanced Firmware Analysis Q&A
## Week 14
### Final Project: Operation Dark Eclipse
### Final Project Option 1: The InfuSafe Pro Incident
In the aftermath of a catastrophic medical device failure, you are thrust into the role of an FDA forensic investigator facing an impossible crisis: 23 patients dead, 100 million recalled insulin pumps sitting in warehouses worldwide, and 2.3 million lives hanging in the balance all while the only evidence remaining is raw binary firmware after a rogue engineer destroyed every line of source code before fleeing to Montenegro. Armed only with GDB, Ghidra, and the reverse engineering skills honed over the first seven weeks of this course, you must excavate the truth from machine code, identify the lethal bugs spawned by an AI code generator called "OopsieGPT," and determine whether these devices can be salvaged to save millions in underserved communities or if $4.7 billion in humanitarian medical technology must be incinerated. This is not a simulation; this is triage at the intersection of embedded systems security and human survival.
### Final Project Option 2: Operation Dark Eclipse
Forty-two stories beneath frozen tundra, a shadow intelligence alliance called Dark Eyes operates centrifuges enriching weapons-grade material for a first strike against Washington, D.C. and Agent NIGHTINGALE gave her life to extract the single firmware file that now sits before you. Conventional warfare cannot reach this fortress buried beneath rock and concrete, but you can: as the architect of a precision cyber weapon in the tradition of Stuxnet, you must reverse engineer the RP2350-based centrifuge controller, craft binary patches that double the spin speed while falsifying every sensor readout to show nominal operation, and execute the sabotage that will cascade-destroy their enrichment program and set their nuclear ambitions back a decade. Every skill from the entire semester ARM assembly, Ghidra analysis, IEEE-754 floating-point manipulation, branch modification, log desynchronization converges in this final mission. Agent NIGHTINGALE's seven-year-old daughter still watches the driveway, waiting for a mother who will never return. Honor that sacrifice. Complete the mission. Do not fail.
<br>

271
WEEK05/WEEK05-01.md Normal file
View File

@@ -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 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 | 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.

173
WEEK05/WEEK05-02.md Normal file
View File

@@ -0,0 +1,173 @@
# 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 2: Patch the Float Binary — Changing 42.5 to 99.0
#### Objective
Calculate the IEEE 754 double-precision encoding of `99.0`, patch the float binary in Ghidra to change the printed value from `42.5` to `99.0`, export the patched binary, convert it to UF2 format, and flash it to the Pico 2 to verify the change.
#### Prerequisites
- Completed Exercise 1 (float binary imported and analyzed in Ghidra)
- Understanding of IEEE 754 double-precision encoding from Week 5 Part 2.7
- Python installed for UF2 conversion
- `uf2conv.py` script available in your project directory
- Raspberry Pi Pico 2 connected via USB
- Serial monitor software (PuTTY, minicom, or screen)
#### Task Description
You will convert `99.0` to its IEEE 754 double-precision encoding by hand (integer-to-binary conversion, normalization, field extraction), determine which register words need to change, patch the data constant in Ghidra, and verify on hardware that the serial output now prints `99.000000`.
#### Step-by-Step Instructions
##### Step 1: Convert 99 to Binary
Use repeated division by 2:
| Division | Quotient | Remainder |
|----------|----------|-----------|
| 99 ÷ 2 | 49 | **1** |
| 49 ÷ 2 | 24 | **1** |
| 24 ÷ 2 | 12 | **0** |
| 12 ÷ 2 | 6 | **0** |
| 6 ÷ 2 | 3 | **0** |
| 3 ÷ 2 | 1 | **1** |
| 1 ÷ 2 | 0 | **1** |
Read remainders bottom-to-top: $99_{10} = 1100011_2$
##### Step 2: Handle the Fractional Part
The fractional part of `99.0` is `.0` — exactly zero. There are no fractional bits.
$$99.0_{10} = 1100011.0_2$$
##### Step 3: Normalize to IEEE 754 Form
Move the binary point so there is exactly one `1` before it:
$$1100011.0_2 = 1.100011_2 \times 2^6$$
We shifted the binary point 6 places left, so the exponent is **6**.
##### Step 4: Extract the IEEE 754 Fields
1. **Sign:** `0` (positive)
2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$
3. **Mantissa:** `100011` followed by 46 zeros (everything after the `1.`, padded to 52 bits)
4. **Full double:** `0x4058C00000000000`
Split into register words:
- **r3 (high word):** `0x4058C000`
- **r2 (low word):** `0x00000000`
##### Step 5: Determine What to Patch
Compare old vs. new:
| Register | Old Value | New Value | Changed? |
| -------- | ------------ | ------------ | -------- |
| `r2` | `0x00000000` | `0x00000000` | No |
| `r3` | `0x40454000` | `0x4058C000` | **Yes** |
Since `r2` stays all zeros, we only need to patch the high word in `r3`.
##### Step 6: Locate the Data Constant in Ghidra
Open your Ghidra project from Exercise 1. In the Listing view, find the data constant that loads into `r3`:
```
DAT_1000024c
10000248 00 40 45 40 undefined4 40454000h
```
This is the 32-bit constant `0x40454000` — the high word of the double `42.5`.
##### Step 7: Patch the Constant
1. Click on **Window****Bytes** to open the Bytes Editor
2. Click the **Pencil Icon** to enable editing
3. Navigate to the data at `DAT_1000024c`
4. The bytes in memory (little-endian) read: `00 40 45 40`
5. Change them to: `00 C0 58 40` (which is `0x4058C000` in little-endian)
6. Press **Enter**
**Verify** in the Listing view — the data should now show `4058C000h`.
##### Step 8: Verify the Patch in the Decompile Window
The decompiled code should now reference the new constant. The value loaded into `r3` should be `0x4058C000`.
##### Step 9: Export the Patched Binary
1. Click **File****Export Program**
2. Set **Format** to **Raw Bytes**
3. Navigate to your build directory
4. Name the file: `0x000e_floating-point-data-type-h.bin`
5. Click **OK**
##### Step 10: Convert to UF2 Format
Open a terminal and navigate to your project directory:
```powershell
cd Embedded-Hacking-main\0x000e_floating-point-data-type
```
Run the conversion:
```powershell
python ..\uf2conv.py build\0x000e_floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
##### Step 11: Flash and Verify
1. Hold **BOOTSEL** and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
3. Open your serial monitor
**Expected output:**
```
fav_num: 99.000000
fav_num: 99.000000
fav_num: 99.000000
...
```
🎉 **Success!** The value changed from `42.5` to `99.0`!
#### Expected Output
After completing this exercise, you should:
- See `fav_num: 99.000000` printing instead of `fav_num: 42.500000`
- Have a patched binary file (`0x000e_floating-point-data-type-h.bin`)
- Have a UF2 file (`hacked.uf2`)
- Understand the complete IEEE 754 encoding and patching workflow for floats
#### Questions for Reflection
###### Question 1: Why did we only need to patch r3 (the high word) and not r2 (the low word)?
###### Question 2: What would the high word be if we wanted to patch the value to `-99.0` instead? (Hint: which bit controls the sign?)
###### Question 3: Walk through the encoding of `100.0` as a double. What are the high and low words?
###### Question 4: Why do we need the `--family 0xe48bff59` flag when converting to UF2?
#### Tips and Hints
- Always verify your encoding with Python: `import struct; struct.pack('>d', 99.0).hex()`
- Little-endian means the bytes in memory are reversed: `0x4058C000` is stored as `00 C0 58 40`
- If you patch the wrong bytes, use **File****Undo** in Ghidra (or re-import the original binary)
- The bias for double-precision is always 1023; for single-precision it's 127
- A clean fractional part (like `.0` or `.5`) means the low word (`r2`) is all zeros
#### Next Steps
- Proceed to Exercise 3 to analyze the double-precision binary
- Try patching to different values: `3.14`, `100.0`, `255.0`
- Compare how many register words change for clean vs. messy fractions
#### Additional Challenge
Patch the float to `3.14` instead of `99.0`. Since `3.14` has a repeating binary fraction, the low word will **not** be zero. This means you need to modify the assembly to load a non-zero value into `r2` as well. Can you figure out where `r2` gets its zero value (`movs r4, #0x0`) and change it?

277
WEEK05/WEEK05-03.md Normal file
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?

217
WEEK05/WEEK05-04.md Normal file
View File

@@ -0,0 +1,217 @@
# 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 4: Patch the Double Binary — Changing 42.52525 to 99.99
#### Objective
Calculate the IEEE 754 double-precision encoding of `99.99`, patch **both** register words in the double binary in Ghidra, export the patched binary, convert it to UF2 format, and flash it to the Pico 2 to verify the change.
#### Prerequisites
- Completed Exercise 3 (double binary imported and analyzed in Ghidra)
- Understanding of IEEE 754 double-precision encoding from Week 5 Parts 2.7 and 3.7
- Knowledge of integer-to-binary conversion and the multiply-by-2 method for fractions
- Python installed for UF2 conversion and verification
- Raspberry Pi Pico 2 connected via USB
#### Task Description
You will derive the IEEE 754 encoding of `99.99` step by step (integer part, fractional part, normalization, field extraction), patch both the low word and high word data constants in Ghidra, and verify on hardware that the serial output now prints `99.990000`. Unlike Exercise 2 where only one word changed, this exercise requires patching **both** registers.
#### Step-by-Step Instructions
##### Step 1: Convert the Integer Part (99) to Binary
Use repeated division by 2:
| Division | Quotient | Remainder |
|----------|----------|-----------|
| 99 ÷ 2 | 49 | **1** |
| 49 ÷ 2 | 24 | **1** |
| 24 ÷ 2 | 12 | **0** |
| 12 ÷ 2 | 6 | **0** |
| 6 ÷ 2 | 3 | **0** |
| 3 ÷ 2 | 1 | **1** |
| 1 ÷ 2 | 0 | **1** |
Read remainders bottom-to-top: $99_{10} = 1100011_2$
##### Step 2: Convert the Fractional Part (.99) to Binary
Use the multiply-by-2 method:
| Multiply | Result | Integer part | Remaining fraction |
|----------------|--------|--------------|-------------------|
| 0.99 × 2 | 1.98 | **1** | 0.98 |
| 0.98 × 2 | 1.96 | **1** | 0.96 |
| 0.96 × 2 | 1.92 | **1** | 0.92 |
| 0.92 × 2 | 1.84 | **1** | 0.84 |
| 0.84 × 2 | 1.68 | **1** | 0.68 |
| 0.68 × 2 | 1.36 | **1** | 0.36 |
| 0.36 × 2 | 0.72 | **0** | 0.72 |
| 0.72 × 2 | 1.44 | **1** | 0.44 |
| 0.44 × 2 | 0.88 | **0** | 0.88 |
| 0.88 × 2 | 1.76 | **1** | 0.76 |
| 0.76 × 2 | 1.52 | **1** | 0.52 |
| 0.52 × 2 | 1.04 | **1** | 0.04 |
| ... | ... | ... | *(continues — repeating fraction)* |
Reading the integer parts top-to-bottom: $0.99_{10} \approx 0.111111010111..._2$
This is a **repeating fraction** — it never terminates in binary.
##### Step 3: Combine Integer and Fractional Parts
$$99.99_{10} = 1100011.111111010111..._2$$
##### Step 4: Normalize to IEEE 754 Form
Move the binary point so there is exactly one `1` before it:
$$1100011.111111010111..._2 = 1.100011111111010111..._2 \times 2^6$$
We shifted the binary point 6 places left, so the exponent is **6**.
##### Step 5: Extract the IEEE 754 Fields
1. **Sign:** `0` (positive)
2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$
3. **Mantissa:** `1000111111110101110000101000111101011100001010001111...` (52 bits after the `1.`)
4. **Full double:** `0x4058FF5C28F5C28F`
**Verify with Python:**
```python
>>> import struct
>>> struct.pack('>d', 99.99).hex()
'4058ff5c28f5c28f'
```
##### Step 6: Split into Register Words
| Register | Old Value | New Value | Changed? |
| -------- | ------------ | ------------ | -------- |
| `r2` | `0x645A1CAC` | `0x28F5C28F` | **Yes** |
| `r3` | `0x4045433B` | `0x4058FF5C` | **Yes** |
**Both registers change!** This is the key difference from Exercise 2 where only r3 changed.
##### Step 7: Locate the Data Constants in Ghidra
Open your Ghidra project from Exercise 3. In the Listing view, find the two data constants:
**Low word (loaded into r2):**
```
DAT_10000254
10000254 ac 1c 5a 64 undefined4 645A1CACh
```
**High word (loaded into r3):**
```
DAT_10000258
10000258 3b 43 45 40 undefined4 4045433Bh
```
##### Step 8: Patch the Low Word
1. Click on **Window****Bytes** to open the Bytes Editor
2. Click the **Pencil Icon** to enable editing
3. Navigate to address `10000254`
4. The bytes read: `AC 1C 5A 64` (little-endian for `0x645A1CAC`)
5. Change to: `8F C2 F5 28` (little-endian for `0x28F5C28F`)
6. Press **Enter**
##### Step 9: Patch the High Word
1. Navigate to address `10000258`
2. The bytes read: `3B 43 45 40` (little-endian for `0x4045433B`)
3. Change to: `5C FF 58 40` (little-endian for `0x4058FF5C`)
4. Press **Enter**
**Verify** in the Listing view:
- `DAT_10000254` should show `28F5C28Fh`
- `DAT_10000258` should show `4058FF5Ch`
Together: `0x4058FF5C28F5C28F` = `99.99` as a double ✓
##### Step 10: Export the Patched Binary
1. Click **File****Export Program**
2. Set **Format** to **Raw Bytes**
3. Navigate to your build directory
4. Name the file: `0x0011_double-floating-point-data-type-h.bin`
5. Click **OK**
##### Step 11: Convert to UF2 Format
Open a terminal:
```powershell
cd Embedded-Hacking-main\0x0011_double-floating-point-data-type
```
```powershell
python ..\uf2conv.py build\0x0011_double-floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
##### Step 12: Flash and Verify
1. Hold **BOOTSEL** and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
3. Open your serial monitor
**Expected output:**
```
fav_num: 99.990000
fav_num: 99.990000
fav_num: 99.990000
...
```
🎉 **Success!** The value changed from `42.52525` to `99.99`!
#### Expected Output
After completing this exercise, you should:
- See `fav_num: 99.990000` printing instead of `fav_num: 42.525250`
- Have a patched binary file (`0x0011_double-floating-point-data-type-h.bin`)
- Have a UF2 file (`hacked.uf2`)
- Understand that patching doubles with repeating fractions requires modifying **both** register words
#### Questions for Reflection
###### Question 1: Why did both r2 and r3 change when patching 42.52525 → 99.99, but only r3 changed when patching 42.5 → 99.0?
###### Question 2: The multiply-by-2 method for 0.99 produces a repeating pattern. What does this mean for the precision of the stored value?
###### Question 3: If you wanted to patch the double to `100.0` instead of `99.99`, how many data constants would need to change?
###### Question 4: Compare the Ghidra Listing for the float binary (Exercise 1) and the double binary (Exercise 3). How does the compiler load the double differently?
#### Tips and Hints
- Always verify your encoding with Python before patching
- Little-endian byte order: `0x28F5C28F` is stored as `8F C2 F5 28` in memory
- Use Ghidra's **Bytes** window (Window → Bytes) for precise hex editing
- If `r2` was zero before and needs to be non-zero after, you need to patch the data constant — not the `movs r4, #0x0` instruction
- The `ldrd` instruction loads r4 and r5 from two consecutive memory addresses — both must be correct
#### Next Steps
- Review the complete patching workflow diagram in Week 5 Part 3.95
- Try patching to `100.0` — since it has a zero low word, you'll need to change `r2` from non-zero to zero
- Attempt the practice exercises at the end of Week 5
#### Additional Challenge
Patch the double to `3.14159265358979` (pi). This requires extreme precision in all 52 mantissa bits. Use Python to get the exact encoding, then patch both words. Verify the output prints at least 6 correct decimal places. What happens to the precision if you only patch the high word and leave the low word as `0x645A1CAC`?
#### Verification Checklist
Before moving on, confirm:
- [ ] Serial output shows `fav_num: 99.990000`
- [ ] Both data constants were patched (low word and high word)
- [ ] You can derive the IEEE 754 encoding of `99.99` from scratch
- [ ] You understand why messy fractions require patching both register words
- [ ] You can explain the difference between the float and double patching workflows
- [ ] You successfully converted and flashed the UF2
**Congratulations!** You've completed all Week 5 exercises and mastered floating-point analysis, IEEE 754 decoding, and double-precision binary patching!

1417
WEEK05/WEEK05.md Normal file

File diff suppressed because it is too large Load Diff