mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-28 10:11:29 +02:00
Refactor E and S
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 10
|
||||
Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Change Servo Angle Range
|
||||
|
||||
#### Answers
|
||||
|
||||
##### IEEE-754 Angle Encodings
|
||||
|
||||
| Angle | IEEE-754 Hex | Little-Endian Bytes | Sign | Exponent | Mantissa |
|
||||
|--------|--------------|--------------------|----- |----------|-----------|
|
||||
| 0.0f | 0x00000000 | 00 00 00 00 | 0 | 0 | 0 |
|
||||
| 45.0f | 0x42340000 | 00 00 34 42 | 0 | 132 | 0x340000 |
|
||||
| 135.0f | 0x43070000 | 00 00 07 43 | 0 | 134 | 0x070000 |
|
||||
| 180.0f | 0x43340000 | 00 00 34 43 | 0 | 134 | 0x340000 |
|
||||
|
||||
##### Patch 1: Maximum Angle 180.0f → 135.0f
|
||||
|
||||
```
|
||||
Before: 00 00 34 43 (180.0f)
|
||||
After: 00 00 07 43 (135.0f)
|
||||
```
|
||||
|
||||
##### Patch 2: Minimum Angle 0.0f → 45.0f
|
||||
|
||||
```
|
||||
Before: 00 00 00 00 (0.0f)
|
||||
After: 00 00 34 42 (45.0f)
|
||||
```
|
||||
|
||||
##### Result
|
||||
|
||||
Servo sweeps from **45° to 135°** instead of 0° to 180°, a 90° range centered at the midpoint.
|
||||
|
||||
##### Conversion and Flash
|
||||
|
||||
```powershell
|
||||
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals
|
||||
python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Break down the IEEE-754 encoding of 180.0f (0x43340000). What are the sign bit, exponent, and mantissa fields?**
|
||||
`0x43340000` in binary: `0 10000110 01101000000000000000000`. **Sign** = 0 (positive). **Exponent** = `10000110` = 134, biased exponent = 134 - 127 = 7. **Mantissa** = `0x340000` = `01101000...0`, representing 1.01101₂ = 1 + 0.25 + 0.125 + 0.03125 = 1.40625. Value = 1.40625 × 2⁷ = 1.40625 × 128 = **180.0**.
|
||||
|
||||
2. **Why is 0.0f represented as 0x00000000 (all zeros) in IEEE-754? Most floats have a non-zero exponent — what makes zero special?**
|
||||
Zero is a **special case** in IEEE-754. When both the exponent and mantissa are all zeros, the value is defined as ±0.0 (the sign bit distinguishes +0.0 from -0.0). This is by design — the IEEE-754 standard reserves the all-zeros exponent for zero and denormalized numbers. Unlike normal floats that have an implicit leading 1 in the mantissa (1.xxx), zero has no such implicit bit. This special encoding means you can check for zero by testing if all 32 bits are 0, which is efficient for hardware.
|
||||
|
||||
3. **What is the IEEE-754 encoding of 90.0f? Show the sign, exponent, and mantissa calculation.**
|
||||
90.0 = 1.40625 × 2⁶. **Sign** = 0. **Exponent** = 6 + 127 = 133 = `0x85` = `10000101`. **Mantissa**: 90.0 / 64 = 1.40625, fractional part = 0.40625 = 0.25 + 0.125 + 0.03125 = `0110100...0` = `0x340000`. Result: `0 10000101 01101000000000000000000` = `0x42B40000`. Little-endian: `00 00 B4 42`.
|
||||
|
||||
4. **The compiler might use movs r0, #0 instead of loading 0.0f from a literal pool. Why would it choose one approach over the other?**
|
||||
For integer zero, the compiler prefers `movs r0, #0` (a 2-byte Thumb instruction) because it's smaller and faster than a literal pool load. However, for **floating-point** zero used with VFP instructions, the compiler must load it into an FPU register (e.g., `s0`). If the FPU has a `vmov.f32 s0, #0.0` immediate form available, it can encode zero directly. Otherwise, it loads from a literal pool or uses `movs` to set an integer register to 0 and transfers it with `vmov s0, r0`. The choice depends on instruction context — integer vs. FPU register — and optimization level.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 10
|
||||
Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics
|
||||
|
||||
### Exercise 1: Change Servo Angle Range
|
||||
### Non-Credit Practice Exercise 1: Change Servo Angle Range
|
||||
|
||||
#### Objective
|
||||
Find the IEEE-754 floating-point value `0x43340000` (180.0f) in the `0x0020_dynamic-conditionals` binary using GDB, calculate the file offset, patch it to `0x43070000` (135.0f) using a hex editor, then find and patch the `0x00000000` (0.0f) value to `0x42340000` (45.0f), and verify on hardware that the servo now sweeps from 45° to 135° instead of 0° to 180°.
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 10
|
||||
Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Add a Third Command
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Patch 1: Compare Byte '2' → '3'
|
||||
|
||||
Change `cmp r4, #0x32` to `cmp r4, #0x33`:
|
||||
|
||||
```
|
||||
Before: 32 2C (cmp r4, #0x32 = '2')
|
||||
After: 33 2C (cmp r4, #0x33 = '3')
|
||||
```
|
||||
|
||||
Only the immediate byte changes: `0x32` → `0x33`.
|
||||
|
||||
##### Patch 2: Case Angle 1 — 180.0f → 90.0f
|
||||
|
||||
```
|
||||
Before: 00 00 34 43 (180.0f)
|
||||
After: 00 00 B4 42 (90.0f)
|
||||
```
|
||||
|
||||
##### Patch 3: Case Angle 2 — 0.0f → 90.0f
|
||||
|
||||
```
|
||||
Before: 00 00 00 00 (0.0f)
|
||||
After: 00 00 B4 42 (90.0f)
|
||||
```
|
||||
|
||||
##### IEEE-754 Reference
|
||||
|
||||
| Angle | Hex | Little-Endian |
|
||||
|--------|-------------|----------------|
|
||||
| 0.0f | 0x00000000 | 00 00 00 00 |
|
||||
| 90.0f | 0x42B40000 | 00 00 B4 42 |
|
||||
| 180.0f | 0x43340000 | 00 00 34 43 |
|
||||
|
||||
##### Behavior After Patch
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------------------------------------|
|
||||
| '1' | Sweep 0° → 180° (unchanged) |
|
||||
| '3' | Move to 90° center (new command) |
|
||||
| '2' | Falls to default — prints "??" |
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does this exercise repurpose the existing case '2' path instead of adding a completely new branch? What would adding a new branch require?**
|
||||
Adding a new branch would require inserting new instructions into the binary — additional `cmp`, `beq`, angle-loading code, and a `servo_set_angle` call. This would shift all subsequent code addresses, breaking every PC-relative branch, literal pool reference, and function call in the program. In a compiled binary without relocation information, inserting bytes is extremely difficult. Repurposing the existing case '2' path reuses the existing branch structure, angle-loading instructions, and function calls — only the data values change, not the code layout.
|
||||
|
||||
2. **The cmp instruction uses an 8-bit immediate field. What is the range of characters you could compare against? Could you use a non-ASCII value?**
|
||||
The `cmp Rn, #imm8` Thumb instruction has an 8-bit unsigned immediate, giving a range of 0–255 (`0x00`–`0xFF`). This covers all ASCII characters (0–127) plus extended values (128–255). You could compare against any byte value, including non-printable characters (`0x01`–`0x1F`), DEL (`0x7F`), or extended characters (`0x80`–`0xFF`). However, the user needs to be able to type the character via `getchar()` — non-printable characters would require special terminal input (e.g., Ctrl combinations).
|
||||
|
||||
3. **How would you keep BOTH the original '2' command AND add '3' as a new command, using only data patches (no instruction insertion)?**
|
||||
You could repurpose the **default/else** branch path. After the `cmp r4, #0x32` (case '2'), there's typically a branch to a default handler that prints "??". If you change the compare in the default path (or an unused branch) to `cmp r4, #0x33`, and redirect its logic to reuse one of the existing `servo_set_angle` code paths, you could handle both. Alternatively, if the binary has any unreachable code or NOP sleds, you could repurpose that space. The constraint is that you cannot increase the binary size — only modify existing bytes.
|
||||
|
||||
4. **What would happen if you changed the compare value to 0x00 (null)? Could a user ever trigger this case?**
|
||||
A compare against `0x00` would trigger on a null byte. In terminal input via `getchar()`, a null character is not easily typed — most terminals don't send `0x00` on any key press. On some systems, Ctrl+@ or Ctrl+Shift+2 generates a null byte, but this is platform-dependent. In practice, comparing against `0x00` would create an unreachable case — the command would exist in the binary but could never be triggered via normal serial terminal input, effectively making it a dead code path.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 10
|
||||
Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics
|
||||
|
||||
### Exercise 2: Add a Third Command
|
||||
### Non-Credit Practice Exercise 2: Add a Third Command
|
||||
|
||||
#### Objective
|
||||
Find the comparison instruction `cmp r4, #0x32` ('2') in the `0x0020_dynamic-conditionals` binary using GDB, locate the branch target for case '2', and modify existing code so that pressing '3' (0x33) moves the servo to the center position (90°) by patching one of the existing comparisons and its corresponding angle value to `0x42b40000` (90.0f).
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 10
|
||||
Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Reverse the Servo Direction
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Four Literal Pool Swaps
|
||||
|
||||
| Patch | Location | Original | Patched |
|
||||
|-------|---------------|-------------------|-------------------|
|
||||
| Case 1 Angle 1 | Literal pool | 00 00 00 00 (0.0f) | 00 00 34 43 (180.0f) |
|
||||
| Case 1 Angle 2 | Literal pool | 00 00 34 43 (180.0f) | 00 00 00 00 (0.0f) |
|
||||
| Case 2 Angle 1 | Literal pool | 00 00 34 43 (180.0f) | 00 00 00 00 (0.0f) |
|
||||
| Case 2 Angle 2 | Literal pool | 00 00 00 00 (0.0f) | 00 00 34 43 (180.0f) |
|
||||
|
||||
##### Behavior After Patch
|
||||
|
||||
| Key | Original | Patched |
|
||||
|-----|---------------------|---------------------|
|
||||
| '1' | 0° → 180° (sweep up) | 180° → 0° (sweep down) |
|
||||
| '2' | 180° → 0° (sweep down) | 0° → 180° (sweep up) |
|
||||
|
||||
The terminal output text ("Moving to 180..." / "Moving to 0...") remains unchanged — it still says the original directions. Only the physical servo behavior is reversed.
|
||||
|
||||
##### GDB Verification
|
||||
|
||||
```gdb
|
||||
(gdb) x/4wx <literal_pool_start>
|
||||
```
|
||||
|
||||
Examine all angle entries in the literal pool to identify which 4-byte words to swap.
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **After this patch, the serial output still says "Moving to 180" when the servo actually moves to 0. Why is this a security concern? What real-world attack does this mimic?**
|
||||
This is a classic **display spoofing** attack. The user interface (serial output) shows one thing while the hardware does another. In real-world systems, this mimics attacks on SCADA/ICS systems where operator displays show "normal" readings while the physical process is manipulated (similar to Stuxnet, which showed normal centrifuge speeds while actually damaging them). In medical devices, this could display a safe dosage while delivering a different amount. The lesson is that **you cannot trust the display if the firmware has been tampered with** — the display text and the actual behavior are patched independently.
|
||||
|
||||
2. **Instead of swapping the data values, could you achieve the same result by swapping the branch targets (making case '1' jump to case '2' code and vice versa)? What are the trade-offs?**
|
||||
Yes, you could swap the `beq` target addresses so that when the user presses '1', execution jumps to the case '2' code path and vice versa. **Trade-offs:** Swapping branch targets changes the instructions (modifying the offset bytes in `beq`), which is more complex — you need to correctly calculate the new PC-relative offsets. Swapping data values is simpler (just exchange 4-byte float values) and less error-prone. However, swapping branches would also swap the printf messages, so "Moving to 180" would display for the path that actually moves to 180 — keeping the display consistent. The data-swap approach intentionally creates a mismatch between display and behavior.
|
||||
|
||||
3. **If the compiler shares a single literal pool entry for 0x43340000 (180.0f) across both cases, how does swapping that one entry affect the behavior?**
|
||||
If the compiler optimized by sharing a single `0x43340000` literal pool entry for all references to 180.0f, then both case '1' and case '2' load from the same address. Changing that one entry to `0x00000000` (0.0f) would affect **both** cases simultaneously — they would both use 0.0f where they originally used 180.0f. Similarly, if there's only one `0x00000000` entry shared, changing it affects both cases. You would need to verify whether each case uses its own pool entry or shares entries by examining the `ldr` offsets. If shared, you may need to find unused space to create a second copy of the value.
|
||||
|
||||
4. **How would you verify the patch is correct without physical hardware? What GDB commands would you use?**
|
||||
Set breakpoints before each `bl servo_set_angle` call, then examine `r0` (or `s0`) which holds the angle argument. Run through both cases and verify: (1) `b *<case1_servo_call>` → `c` → press '1' → `info float` or `p $s0` — should show 180.0f (was 0.0f). (2) Continue to second call — should show 0.0f (was 180.0f). Repeat for case '2'. You can also examine the literal pool directly: `x/wx <pool_addr>` to verify the bytes were swapped. Additionally, `x/f <pool_addr>` displays the value as a float for quick verification.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 10
|
||||
Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics
|
||||
|
||||
### Exercise 3: Reverse the Servo Direction
|
||||
### Non-Credit Practice Exercise 3: Reverse the Servo Direction
|
||||
|
||||
#### Objective
|
||||
Find the branch targets for case '1' and case '2' in the `0x0020_dynamic-conditionals` binary using GDB, identify where each case loads its angle values from the literal pool, and swap the angle pairs so that pressing '1' now does what '2' originally did (180°→0°) and pressing '2' does what '1' originally did (0°→180°).
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 10
|
||||
Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Speed Profile
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Sleep Duration Values
|
||||
|
||||
| Parameter | Original | Case 1 (Fast Snap) | Case 2 (Slow Sweep) |
|
||||
|-------------|---------|--------------------|--------------------|
|
||||
| Duration | 500 ms | 100 ms | 1000 ms |
|
||||
| Hex | 0x1F4 | 0x64 | 0x3E8 |
|
||||
| LE Bytes | F4 01 00 00 | 64 00 00 00 | E8 03 00 00 |
|
||||
|
||||
##### Patch Case 1: 500ms → 100ms (Fast Snap)
|
||||
|
||||
```
|
||||
Before: F4 01 00 00 (500ms)
|
||||
After: 64 00 00 00 (100ms)
|
||||
```
|
||||
|
||||
##### Patch Case 2: 500ms → 1000ms (Slow Sweep)
|
||||
|
||||
```
|
||||
Before: F4 01 00 00 (500ms)
|
||||
After: E8 03 00 00 (1000ms)
|
||||
```
|
||||
|
||||
##### Literal Pool Considerations
|
||||
|
||||
If the compiler shares a single literal pool entry for `0x000001F4` across both cases, you **cannot** patch them independently without additional work. Verify by checking whether case 1 and case 2 `ldr` instructions reference the same pool address. If shared, you need to find unused space in the binary for a second value or repurpose another unused literal pool entry.
|
||||
|
||||
##### Behavior After Patch
|
||||
|
||||
| Key | Original | Patched |
|
||||
|-----|-------------------|----------------------------------|
|
||||
| '1' | 500ms between moves | 100ms — near-instantaneous snap |
|
||||
| '2' | 500ms between moves | 1000ms — slow, deliberate sweep |
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does 100ms feel like an instant "snap" while 1000ms feels like a smooth sweep? The servo moves the same distance either way.**
|
||||
Human perception of motion depends on the **pause between position updates**, not the motor speed. At 100ms delay, the servo reaches each angle before the next one is set — the positions update so quickly that the motion appears continuous and instant. At 1000ms delay, there's a full second between movements, so you can see the servo pause at each intermediate angle. The SG90 servo physically takes about 200–300ms to traverse its full range at no load, so 100ms is faster than the travel time (the servo is still moving when the next command arrives), creating a snappy feel. At 1000ms, the servo has already completed its move and waits idle before the next command.
|
||||
|
||||
2. **If both cases share the same literal pool entry for 500ms, what strategy would you use to give them different sleep values?**
|
||||
Several approaches: (1) **Find unused literal pool space** — look for entries that are no longer referenced and overwrite one with `0x64` (100ms) while keeping the other for `0x3E8` (1000ms). (2) **Repurpose an existing value** — if another constant in the pool happens to equal your desired value, redirect the `ldr` offset to point there. (3) **Change the `ldr` to a `movs`** — for values ≤ 255 (like 100), replace the 4-byte `ldr r0, [pc, #offset]` with `movs r0, #0x64` (2 bytes) + `nop` (2 bytes) for padding. This works for case 1 (100 fits in 8 bits) but not case 2 (1000 exceeds 255).
|
||||
|
||||
3. **What is the minimum sleep_ms value where the SG90 servo can actually complete a full 0°–180° sweep before the next command?**
|
||||
The SG90 servo has a rated speed of approximately 0.12 seconds per 60° at 4.8V. For a full 180° sweep: 0.12 × (180/60) = 0.12 × 3 = **0.36 seconds (360ms)**. In practice, with load and signal processing overhead, **400–500ms** is a safe minimum for reliable full-range travel. Below this, the servo may not reach the target angle before the next position command arrives, resulting in incomplete movements or jittery behavior. The original 500ms value was chosen to reliably allow full travel.
|
||||
|
||||
4. **What would happen if you set sleep_ms to 0 for both cases? How would the servo physically behave?**
|
||||
With `sleep_ms(0)`, the loop runs at full CPU speed, sending angle commands as fast as the processor can execute. The servo would receive thousands of position updates per second, alternating between two angles. Physically, the servo would **vibrate or oscillate** — it never has time to reach either target angle before being told to go to the other one. The PWM signal would switch so rapidly that the servo's control circuit would see constantly changing targets, producing a buzzing sound and erratic oscillation near the midpoint. This could also overheat the servo motor due to constant direction changes.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 10
|
||||
Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics
|
||||
|
||||
### Exercise 4: Speed Profile
|
||||
### Non-Credit Practice Exercise 4: Speed Profile
|
||||
|
||||
#### Objective
|
||||
Find both `sleep_ms(500)` calls in the `0x0020_dynamic-conditionals` binary using GDB, identify the literal pool values `0x1f4` (500) loaded into `r0` before each `bl sleep_ms`, calculate the file offsets, and patch case '1' to use `0x64` (100ms) for fast movement and case '2' to use `0x3e8` (1000ms) for slow movement, then verify on hardware that the two keys produce visibly different servo speeds.
|
||||
|
||||
Reference in New Issue
Block a user