diff --git a/WEEK10/WEEK10-SLIDES.pdf b/WEEK10/WEEK10-SLIDES.pdf index 3d6454f..286c32a 100644 Binary files a/WEEK10/WEEK10-SLIDES.pdf and b/WEEK10/WEEK10-SLIDES.pdf differ diff --git a/WEEK10/WEEK10.md b/WEEK10/WEEK10.md index 55feaf7..e21510a 100644 --- a/WEEK10/WEEK10.md +++ b/WEEK10/WEEK10.md @@ -1,4 +1,4 @@ -# Week 10: Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics +# Week 10: Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics ## What You'll Learn This Week @@ -37,41 +37,50 @@ By the end of this tutorial, you will be able to: A **static conditional** is one where the outcome is predetermined because the condition variable never changes during program execution: ```c -int choice = 1; // This NEVER changes! +int choice = 1; // This NEVER changes! while (true) { if (choice == 1) { - printf("1\r\n"); // This ALWAYS runs + printf("1\r\n"); // This ALWAYS runs } else if (choice == 2) { - printf("2\r\n"); // This NEVER runs + printf("2\r\n"); // This NEVER runs } else { - printf("?\r\n"); // This NEVER runs + printf("?\r\n"); // This NEVER runs } } ``` -``` -+-----------------------------------------------------------------+ -| Static Conditional Flow | -| | -| choice = 1 (set once, never changes) | -| | | -| ? | -| +-------------+ | -| | choice == 1 |----YES----? printf("1") | -| +-------------+ | -| |NO (never taken) | -| ? | -| +-------------+ | -| | choice == 2 |----YES----? printf("2") (never reached) | -| +-------------+ | -| |NO | -| ? | -| printf("?") (never reached) | -| | -| The branching logic EXISTS but only ONE path ever executes! | -| | -+-----------------------------------------------------------------+ +```ext ++-----------------------+ +| choice = 1 | +| set once, never | +| changes | ++-----------------------+ + | + v + [ choice == 1 ] + | + | YES + v ++-----------------------+ +| printf \'1\' | ++-----------------------+ + . + . NO (never taken) + v + [ choice == 2 ] + . + . YES (never reached) + v ++-----------------------+ +| printf \'2\' | ++-----------------------+ + . + . NO + v ++-----------------------+ +| printf \'?\' | ++-----------------------+ ``` ### The if/else Statement @@ -80,11 +89,11 @@ The `if/else` structure checks conditions in order: ```c if (choice == 1) { - // Do something if choice is 1 +// Do something if choice is 1 } else if (choice == 2) { - // Do something if choice is 2 +// Do something if choice is 2 } else { - // Do something for all other values +// Do something for all other values } ``` @@ -126,7 +135,7 @@ A **dynamic conditional** is one where the condition variable changes based on r uint8_t choice = 0; while (true) { - choice = getchar(); // User types a key - VALUE CHANGES! + choice = getchar(); // User types a key - VALUE CHANGES! if (choice == '1') { printf("1\r\n"); @@ -138,32 +147,32 @@ while (true) { } ``` -``` -+-----------------------------------------------------------------+ -| Dynamic Conditional Flow | -| | -| +----------------+ | -| | choice=getchar |?---- User types 'x' on keyboard | -| +----------------+ | -| | | -| ? | -| +-------------+ | -| | choice=='1' |----YES----? printf("1"), move servo | -| +-------------+ | -| |NO | -| ? | -| +-------------+ | -| | choice=='2' |----YES----? printf("2"), move servo | -| +-------------+ | -| |NO | -| ? | -| printf("??") | -| | | -| +------------------? Loop back to getchar() | -| | -| EACH iteration can take a DIFFERENT path! | -| | -+-----------------------------------------------------------------+ +```text + +-------------------+ + | getchar() | <----------------------------------+ + +-------------------+ | + | User types input | + v | + +-------------------+ | + | choice = input | | + +-------------------+ | + | | + v | + [ choice == '1' ] -- YES --> +-----------------------+ | + | | printf("1") | | + | NO | move servo | | + | +-----------------------+ | + v | + [ choice == '2' ] -- YES --> +-----------------------+ | + | | printf("2") | | + | NO | move servo | | + | +-----------------------+ | + v | + +-------------------+ | + | printf("??") | | + +-------------------+ | + | | + +-----------------------------------------------+ ``` ### The getchar() Function @@ -186,55 +195,48 @@ uint8_t choice = getchar(); // Waits for user to type something **PWM** (Pulse Width Modulation) is a technique for controlling power by rapidly switching a signal on and off. The ratio of "on time" to "off time" determines the average power delivered. -``` -+-----------------------------------------------------------------+ -| PWM Signal - 50% Duty Cycle | -| | -| HIGH -+ +-----+ +-----+ +-----+ | -| | | | | | | | | -| LOW +-----+ +-----+ +-----+ +----- | -| ?--T--? | -| ON OFF | -| | -| Duty Cycle = ON time / Total period = 50% | -| | -+-----------------------------------------------------------------+ +```text + PWM Signal - 50% Duty Cycle + + HIGH +-----+ +-----+ +-----+ + | | | | | | + LOW + +-----+ +-----+ +----- + |--T--| + ON OFF + + Duty Cycle = ON time / Total period = 50% ``` ### PWM for Servo Control Servo motors use PWM differently - they care about the **pulse width**, not the duty cycle percentage: -``` -+-----------------------------------------------------------------+ -| Servo PWM Signal (50 Hz = 20ms period) | -| | -| 0? Position (1ms pulse): | -| HIGH -?? | -| | 1ms | -| LOW +------------------------------ (19ms) ---------- | -| ?-------------- 20ms -------------------------? | -| | -| 90? Position (1.5ms pulse): | -| HIGH -----+ | -| | 1.5ms | -| LOW +--------------------------- (18.5ms) ------- | -| | -| 180? Position (2ms pulse): | -| HIGH ---------+ | -| | 2ms | -| LOW +------------------------- (18ms) ------- | -| | -+-----------------------------------------------------------------+ +```text + Servo PWM Signal (50 Hz = 20ms period) + + 0° Position (1ms pulse): + HIGH -+ + | 1ms + LOW +----------------------------------- (19ms) ----- + + 90° Position (1.5ms pulse): + HIGH ---+ + | 1.5ms + LOW +-------------------------------- (18.5ms) ---- + + 180° Position (2ms pulse): + HIGH -----+ + | 2ms + LOW +-------------------------------- (18ms) ---- ``` ### The Magic Numbers | Angle | Pulse Width | PWM Ticks (at 1MHz) | | ----- | ----------- | ------------------- | -| 0? | 1000 ?s | 1000 | -| 90? | 1500 ?s | 1500 | -| 180? | 2000 ?s | 2000 | +| 0° | 1000 µs | 1000 | +| 90° | 1500 µs | 1500 | +| 180° | 2000 µs | 2000 | --- @@ -244,58 +246,65 @@ Servo motors use PWM differently - they care about the **pulse width**, not the The RP2350's system clock runs at **150 MHz** (150 million cycles per second). A servo needs a **50 Hz** signal (one pulse every 20 ms). How do we bridge this gap? -``` -+-----------------------------------------------------------------+ -| Clock Division Chain | -| | -| System Clock: 150 MHz | -| | | -| | ? 150 (clock divider) | -| ? | -| PWM Tick Rate: 1 MHz (1 tick = 1 microsecond) | -| | | -| | Count to 20,000 (wrap value = 19,999) | -| ? | -| PWM Frequency: 50 Hz (20 ms period) | -| | -+-----------------------------------------------------------------+ +``` ext ++-----------------------+ +| System Clock | +| 150 MHz | ++-----------------------+ + | + | Divide by 150 + v ++-----------------------+ +| PWM Tick Rate | +| 1 MHz | +| (1 tick = 1 µs) | ++-----------------------+ + | + | Count to 20,000 + | Wrap at 19,999 + v ++-----------------------+ +| Servo PWM Signal | +| 50 Hz | +| (20 ms period) | ++-----------------------+ ``` ### The Math **Step 1: Clock Division** ``` -PWM Tick Rate = System Clock ? Divider -1,000,000 Hz = 150,000,000 Hz ? 150 +PWM Tick Rate = System Clock / Divider +1,000,000 Hz = 150,000,000 Hz / 150 ``` **Step 2: Frame Period** ``` Period = (Wrap Value + 1) * Tick Duration -20 ms = 20,000 ticks * 1 ?s/tick +20 ms = 20,000 ticks * 1 µs/tick ``` **Step 3: Pulse Width to Ticks** ``` -Ticks = Pulse Width (?s) * 1 tick/?s -1500 ticks = 1500 ?s * 1 +Ticks = Pulse Width (µs) * 1 tick/µs +1500 ticks = 1500 µs * 1 ``` -### Worked Example: 90? Angle +### Worked Example: 90° Angle -Let's calculate what happens when we command 90?: +Let's calculate what happens when we command 90°: 1. **Angle to Pulse Width:** ``` Pulse = MIN + (angle/180) * (MAX - MIN) Pulse = 1000 + (90/180) * (2000 - 1000) Pulse = 1000 + 0.5 * 1000 - Pulse = 1500 ?s + Pulse = 1500 µs ``` 2. **Pulse to PWM Ticks:** ``` - Level = 1500 ?s * 1 tick/?s = 1500 ticks + Level = 1500 µs * 1 tick/µs = 1500 ticks ``` 3. **Hardware Timing:** @@ -307,33 +316,24 @@ Let's calculate what happens when we command 90?: ## Part 6: Understanding the SG90 Servo Motor -### What is the SG90? +### What is the SG90° The **SG90** is a small, inexpensive hobby servo motor commonly used in robotics projects: -``` -+-----------------------------------------------------------------+ -| SG90 Servo Motor | -| | -| +---------------+ | -| | +-----+ | | -| | | ARM | | ?-- Rotates 0? to 180? | -| | +--+--+ | | -| | | | | -| | +----+----+ | | -| | | MOTOR | | | -| | | GEAR | | | -| | | BOX | | | -| | +---------+ | | -| +-------+-------+ | -| | | -| +-------+-------+ | -| | | | | -| ORANGE RED BROWN | -| Signal VCC GND | -| (PWM) (5V) | -| | -+-----------------------------------------------------------------+ +``` ext +[ SG90 Servo Motor ] ++-----------------------------------+ +| Motor ---> Gearbox ---> Arm | +| (0° to 180°) | ++-----------------------------------+ + | Wires + v +[ Wires ] ++-----------------------------------+ +| Orange: Signal / PWM | +| Red: VCC / 5V | +| Brown: GND / Ground | ++-----------------------------------+ ``` ### SG90 Specifications @@ -341,8 +341,8 @@ The **SG90** is a small, inexpensive hobby servo motor commonly used in robotics | Parameter | Value | | ----------------- | ------------------------- | | **Voltage** | 4.8V - 6V (typically 5V) | -| **Rotation** | 0? to 180? | -| **Pulse Width** | 1000 us - 2000 us | +| **Rotation** | 0° to 180° | +| **Pulse Width** | 1000 us - 2000 us | | **Frequency** | 50 Hz (20ms period) | | **Stall Current** | ~650mA (can spike to 1A+) | @@ -369,24 +369,28 @@ Servos can draw over 1000mA during movement spikes. The Pico's 3.3V regulator ca ### Correct Power Setup -``` -+-----------------------------------------------------------------+ -| CORRECT Power Wiring | -| | -| USB ----? VBUS (5V) ---+--? Servo VCC (Red) | -| | | -| +--? Capacitor (+) | -| | | -| Pico GND --------------+----+--? Capacitor (-) | -| | | -| +------? Servo GND (Brown) | -| | -| Pico GPIO 6 ------------------? Servo Signal (Orange) | -| | -| IMPORTANT: Use a 1000 uF 25V capacitor across the servo | -| power to absorb current spikes! | -| | -+-----------------------------------------------------------------+ +``` ext ++-----------------+ +------------------------+ +| USB Power | ---> | VBUS 5V | ++-----------------+ +------------------------+ + | | + v v + [ Servo VCC ] [ Capacitor + ] + (Red) (1000 uF 25V) + ++-----------------+ +| Pico GND | ++-----------------+ + | + +--------------------+--------------------+ + | | + v v + [ Servo GND ] [ Capacitor - ] + (Brown) + ++-----------------+ +| Pico GPIO 6 | ---> [ Servo Signal ] (Orange) ++-----------------+ ``` ### Why the Capacitor? @@ -422,27 +426,22 @@ Connect your servo like this: | Red (VCC) | VBUS (5V) | | Orange (Signal) | GPIO 6 | -``` -+-----------------------------------------------------------------+ -| Servo Wiring with Capacitor | -| | -| Pico 2 SG90 Servo | -| +----------+ +----------+ | -| | | | | | -| | GPIO 6 |------- Orange -------?| Signal | | -| | | | | | -| | VBUS(5V) |---+--- Red ----------?| VCC | | -| | | | | | | -| | GND |---+--- Brown --------?| GND | | -| | | | +----------+ | -| +----------+ | | -| | +---------+ | -| +----+ + CAP - +---- GND | -| | 1000 uF | | -| | 25V | | -| +---------+ | -| | -+-----------------------------------------------------------------+ +``` ext +Hardware Setup: + + Pico 2 Servo SG90 Capacitor (1000uF 25V) ++----------+ +-------------+ +-------------+ +| | | | | | +| GPIO 6 | -------> | Signal (Org)| | | +| | | | | | +| VBUS 5V | -------> | VCC (Red) | | | +| | | | | | | +| | +----> | | -----> | + | +| | | | | | +| GND | -------> | GND (Brn) | | | +| | | | | | | +| | +----> | | -----> | - | ++----------+ +-------------+ +-------------+ ``` ### Project Structure @@ -525,24 +524,43 @@ int main(void) { Since `choice = 1` and NEVER changes: -``` -+-----------------------------------------------------------------+ -| Static Conditional Execution | -| | -| Every loop iteration: | -| | -| 1. Check if (choice == 1) -> TRUE -> print "1" | -| 2. Check switch case 1 -> MATCH -> print "one" | -| 3. Move servo to 0? | -| 4. Wait 500ms | -| 5. Move servo to 180? | -| 6. Wait 500ms | -| 7. Repeat forever... | -| | -| Output always: "1" then "one" (forever) | -| Servo: sweeps 0? -> 180? -> 0? -> 180? (forever) | -| | -+-----------------------------------------------------------------+ +``` ext ++-------------------------+ +| Start Loop Iteration | <-----------------------------------+ ++-------------------------+ | + | | + v | + [ choice == 1 ] -- TRUE --> +-------------------------+ | + | print \'1\' | | + +-------------------------+ | + | | + v | + [ switch case 1 ] | + | MATCH | + v | + +-------------------------+ | + | print \'one\' | | + +-------------------------+ | + | | + v | + +-------------------------+ | + | Move servo to 0° | | + +-------------------------+ | + | | + v | + +-------------------------+ | + | Wait 500ms | | + +-------------------------+ | + | | + v | + +-------------------------+ | + | Move servo to 180° | | + +-------------------------+ | + | | + v | + +-------------------------+ | + | Wait 500ms | ---+ + +-------------------------+ ``` ### Step 3: Flash the Binary to Your Pico 2 @@ -567,7 +585,7 @@ one ``` **Watch the servo:** -- It should sweep from 0? to 180? every second +- It should sweep from 0° to 180° every second - The movement is continuous and repetitive --- @@ -631,64 +649,94 @@ monitor reset halt continue ``` -### Step 11: Find the Comparison Instructions +### Step 11: Find the Comparison Instructions... Or Not! -Step through and examine the comparison: +To see the assembly instructions as you execute them, tell GDB to display the current instruction automatically: ```gdb -stepi 20 -info registers +display/i $pc +nexti ``` -Look for `cmp` instructions that compare the `choice` variable (value 1). +Keep pressing **Enter** to repeat the `nexti` command and watch the instructions. -### Step 12: Examine the Printf Arguments +**Wait, where is the `cmp` instruction?!** +You might notice that there is NO `cmp` instruction comparing our `choice` variable anywhere! Why? Because we hardcoded `int choice = 1;` at the start of our C code and never changed it. -Before printf calls, check r0 for the format string address: +The C compiler is smart (even at basic optimization levels). It realized the `if (choice == 1)` condition would *always* be true, and the `else` conditions would *never* happen. Instead of wasting CPU cycles checking a condition that never changes, the compiler **optimized out the check entirely**! It just compiled the code inside the `choice == 1` block unconditionally. + +This is the defining characteristic of a "Static Conditional"—the condition is resolved at compile-time, not run-time! + +### Step 12: Examine the Printf/Puts Arguments + +If you look closely at your disassembly, you'll notice there are no `printf` calls! The compiler optimized our simple `printf` statements into `puts` (specifically `__wrap_puts` in the Pico SDK) because they didn't contain any complex formatting variables. + +When you reach a `bl <__wrap_puts>` instruction, check `r0` for the string address: ```gdb x/s $r0 ``` -You should see strings like `"1\r\n"` or `"one\r\n"`. +You should see strings like `"1\r"` or `"one\r"`. + +*(Wait, what happened to the `\n`? Because the `puts` function automatically adds a newline to the end of whatever it prints, the compiler cleverly trimmed the `\n` out of the string literal in memory to save space!)* ### Step 13: Watch the Servo Commands -Set a breakpoint on servo_set_angle: +Let's set a breakpoint on `servo_set_angle`. How do you know the address? If you look at your `main` disassembly, you can find the `bl` instruction calling it (for example, `0x10000310`). + +But hardcoded addresses change every time you recompile! Luckily, GDB is smart enough to resolve function names directly from the ELF file: ```gdb -break *0x10000280 +break servo_set_angle continue ``` -Check the floating-point register for the angle: +Check the argument passed to the function. You might assume the float is in `s0`, but check out the very first instruction of `servo_set_angle`: + +`vmov s14, r0` + +Because of how the ARM compiler handles arguments, the floating-point angle is actually passed into the function via the standard integer register `r0`. The `vmov` instruction immediately moves it from `r0` into the floating-point register `s14` so the math unit can use it! + +To see the angle, you can ask GDB to interpret the raw hex value in `r0` as a float. You can also step forward (`nexti` or `n`) to let the `vmov` execute, and then check `s14` directly! + +Here is exactly what that process looks like in your GDB terminal (assuming you hit `continue` to catch the second call where the angle is 180): ```gdb -info registers s0 +=> 0x10000310 : vmov s14, r0 +(gdb) print /f $r0 +$2 = 180 +(gdb) nexti +=> 0x10000314 : push {r4, r5, lr} +(gdb) info registers s14 +s14 180 (raw 0x43340000) ``` +*(Note: On your very first breakpoint hit, `print /f $r0` will just show `0` because the first call in the C code is `servo_set_angle(0)`!)* + ### Step 14: Examine the Timing Delay -Set a breakpoint on sleep_ms and check the delay value: +Set a breakpoint on `sleep_ms` (using the function name, not a hardcoded address!) and check the delay value: ```gdb -break *0x10000290 +break sleep_ms continue info registers r0 ``` You should see `0x1f4` (500 decimal) for the 500ms delay. -### Step 15: Step Through the Loop +### Step 15: Watch the Loop Iterate -Watch one complete iteration: +Because this loop contains `sleep_ms(500)` calls, trying to use `nexti 100` to step forward will cause GDB to "hang" (either because it's waiting for multiple seconds of sleep to finish, or because GDB stepping interferes with the Pico's hardware timer interrupts!). + +Instead, since we already have breakpoints set on `servo_set_angle` and `sleep_ms`, just type `continue`! ```gdb -stepi 100 -info registers +continue ``` -Notice how the static conditional always takes the same branch path. +Every time you type `continue` (or press **Enter** to repeat it), you will see the CPU safely jump to the next function call. Because this is a *Static Conditional*, it will never hit a `cmp` instruction or take a different branch—it just bounces between `servo_set_angle` and `sleep_ms` forever! ### Step 16: Exit GDB @@ -769,8 +817,8 @@ At address `0x10000236`: Look for a function call where `r0` is loaded with `0x6` (GPIO pin 6): ```assembly -movs r0, #0x6 ; GPIO pin 6 -bl FUN_xxxxx ; servo_init +movs r0, #0x6 ; GPIO pin 6 +bl FUN_1000027c ; servo_init ``` 1. Right-click -> **Edit Function Signature** @@ -782,15 +830,15 @@ bl FUN_xxxxx ; servo_init Look for function calls that load string addresses into `r0`: ```assembly -ldr r0, =0x10001c54 ; Address of "1" string -bl FUN_xxxxx ; puts +ldr r0=>DAT_10001c54 ,[DAT_10000274 ] = 00000D31h +bl FUN_10001884 undefined FUN_10001884() ``` **How do we know it's puts?** - It takes a single string argument - The hex `0x31` is ASCII "1" - The hex `0x0d` is carriage return "\r" -- We see "1" echoed in PuTTY +- We saw "1" echoed in PuTTY 1. Right-click -> **Edit Function Signature** 2. Change to: `int puts(char *s)` @@ -798,7 +846,25 @@ bl FUN_xxxxx ; puts ### Step 26: Resolve servo_set_angle -Look for a function that loads float constants. Inside the function, you'll find: +Look for a function call (like `bl FUN_10000310`) that occurs right after the float arguments are loaded into `r0`. + +```assembly +mov r0,r5 +bl FUN_10000310 undefined FUN_10000310() +``` + +If you double-click the `FUN_10000310` label to look inside the function, you'll find a section of code checking the pulse limits: + +```assembly +cmp.w r3,#0x7d0 +it cs +mov.cs.w r3,#0x7d0 +cmp.w r3,#0x3e8 +it cc +mov.cc.w r3,#0x3e8 +``` + +These values are: - `0x7D0` (2000 decimal) - maximum pulse width - `0x3E8` (1000 decimal) - minimum pulse width @@ -813,8 +879,8 @@ These are the servo pulse limits! Look for a function where `r0` is loaded with `0x1f4` (500 decimal): ```assembly -ldr r0, =0x1f4 ; 500 milliseconds -bl FUN_xxxxx ; sleep_ms +mov.w r0,#0x1f4 +bl FUN_10000e20 undefined FUN_10000e20() ``` 1. Right-click -> **Edit Function Signature** @@ -833,19 +899,22 @@ bl FUN_xxxxx ; sleep_ms ### Step 29: Hack #1 - Change "1" to "2" -Find the string "1" in memory: +First, we need to find the string "1" in the binary. -1. Double-click on the address reference for "1" (around `0x10001c54`) -2. You'll see `31` (ASCII for "1") -3. Change `31` to `32` (ASCII for "2") +1. Go back to your `main` assembly code and look for the first `puts` call. +2. In the `ldr` instruction right before it, look for the reference next to the arrow: `r0=>DAT_10001c54`. **Double-click exactly on `DAT_10001c54`**. *(Warning: Do NOT click the reference inside the brackets like `[DAT_10000274]`, or you'll end up in the pointer table instead of the string!)* +3. The Listing and Bytes windows will automatically jump to the string's exact address! +4. Look at your **Bytes** window. You should see the byte `31` (which is ASCII for "1"). +5. Click on the `31` and type `32` to overwrite it (which is ASCII for "2"). ### Step 30: Hack #2 - Change "one" to "fun" -Find the string "one": +Next, let's change the word "one" to "fun": -1. Navigate to the "one" string address -2. Find bytes `6f 6e 65` ("one" in ASCII) -3. Change to `66 75 6e` ("fun" in ASCII) +1. Go back to `main` and look for the second `puts` call. +2. In the `ldr` instruction, double-click the `DAT_10001c5c` reference next to the arrow (again, ignore the one in the brackets!). +3. Look at the **Bytes** window again. You'll find the bytes `6f 6e 65` (which are ASCII for "o-n-e"). +4. Click on the `6f` byte and type `66 75 6e` on your keyboard to overwrite those three bytes with "f-u-n". **ASCII Reference:** | Character | Hex | @@ -859,29 +928,34 @@ Find the string "one": ### Step 31: Hack #3 - Speed Up the Servo -Find the sleep_ms delay value: +Let's change the 500ms delay to a 100ms delay. Since the compiler packed the `500` directly into a `mov.w` instruction, we can't just find it in the data section. Instead, let's use Ghidra's built-in assembler to rewrite the instruction! -1. Look for `0x1f4` (500) in the code -2. This appears TWICE (once for each sleep_ms call) -3. Change both from `f4 01` to `64 00` (100 in little-endian) +1. In your `main` assembly code, look for the two `mov.w r0,#0x1f4` instructions (which happen right before calling `sleep_ms`). +2. Right-click the first `mov.w r0,#0x1f4` instruction and select **Patch Instruction** (or press `Ctrl+Shift+G`). +3. Delete the `#0x1f4` and type `#0x64` (which is 100 in hex). Press **Enter**! +4. Repeat this for the second `mov.w r0,#0x1f4` instruction. **Before:** 500ms delay (servo moves slowly) **After:** 100ms delay (servo moves FAST!) ### Step 32: Export and Flash -1. Click **File** -> **Export Program** -2. Set **Format** to **Binary** -3. Name: `0x001d_static-conditionals-h.bin` -4. Click **OK** +When exporting from Ghidra, you must make sure to save the file inside your `build/` directory so the python script can find it! -Convert and flash: +1. Click **File** -> **Export Program** +2. Set **Format** to **Raw Bytes** +3. **IMPORTANT:** Click the `...` next to the Output File field and navigate into your `0x001d_static-conditionals/build/` folder! +4. Save the file as `0x001d_static-conditionals-h.bin`. +5. Click **OK** + +Convert and flash (make sure your terminal is inside the `0x001d_static-conditionals/` folder, NOT the `build/` folder!): ```cmd -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001d_static-conditionals python ..\uf2conv.py build\0x001d_static-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` +*(Troubleshooting: If you get `FileNotFoundError` for the `.bin` file, it means you didn't save the exported file into the `build/` folder! Go back to Ghidra and export it again. If you get an error that Python can't open `..\uf2conv.py`, it means you accidentally `cd`'d into the `build/` folder. Type `cd ..` to go back up one directory and run the command again!)* + ### Step 33: Verify the Hacks **Serial output now shows:** @@ -893,7 +967,7 @@ fun ... ``` -**The servo now moves 5x faster!** It's spinning like crazy! +**The servo now moves 5x faster!** It's spinning back and forth like crazy! --- @@ -954,8 +1028,8 @@ int main(void) { | User Types | Output | Servo Action | | ------------- | ----------- | ------------ | -| '1' (0x31) | "1" + "one" | 0? -> 180? | -| '2' (0x32) | "2" + "two" | 180? -> 0? | +| '1' (0x31) | "1" + "one" | 0° -> 180° | +| '2' (0x32) | "2" + "two" | 180° -> 0° | | Anything else | "??" + "??" | No movement | ### Step 36: Flash and Test @@ -1012,15 +1086,26 @@ Disassemble around main to see the dynamic conditionals: disassemble 0x10000234,+250 ``` -Look for the `getchar` call followed by comparison and branch instructions. +Look for the `bl <__wrap_getchar>` call. Because `choice` is now dynamic (based on user input), the compiler *had* to generate the comparison logic! It should look something like this: + +```assembly +bl 0x10001860 <__wrap_getchar> +uxtb r4, r0 +cmp r4, #49 @ 0x31 +beq.n 0x10000270 +cmp r4, #50 @ 0x32 +``` +There they are! The `cmp` instructions checking for `0x31` ('1') and `0x32` ('2'). ### Step 42: Set a Breakpoint After getchar -Find where `choice` gets its value: +We want to pause execution right after we type a character so we can inspect the comparison. +Find the address of the first `cmp` instruction in your disassembly (in the example above, it's `0x1000024a`) and set a breakpoint: ```gdb -break *0x10000250 +break *0x1000024a ``` +*(Make sure you use the exact address of the `cmp` instruction from YOUR disassembly!)* Reset and continue: @@ -1039,26 +1124,31 @@ info registers r0 If you pressed '1', you should see `0x31`. If you pressed '2', you should see `0x32`. -### Step 44: Trace the Comparison Logic +### Step 44: Execute the Comparison -Step through the comparison instructions: +Right now, GDB is paused *before* the `cmp` instruction runs (the `=>` arrow points to the instruction that will execute *next*). + +To see the result of the comparison, we must execute the `cmp` instruction by stepping forward exactly once: ```gdb -stepi 10 -info registers +display/i $pc +stepi ``` -Watch for `cmp r0, #0x31` and `cmp r0, #0x32` instructions. +Notice that the `=>` arrow has now moved to the `beq.n` (Branch if Equal) instruction. The `cmp` instruction has just executed. ### Step 45: Examine the Branch Decisions -Look at the condition flags after comparison: +Now that the comparison has run, we can look at the CPU's condition flags to see what happened: ```gdb -info registers cpsr +info registers xpsr ``` +*(Note: Older ARM chips call this `cpsr`, but Cortex-M chips like the Pico's RP2350 call it `xpsr`!)* -The zero flag (Z) determines if the branch is taken. +The zero flag (`Z`) determines if the branch is taken. The flags are stored in the highest nibble (the first hex digit) of the `xpsr` register in the order `N Z C V`. + +If you typed '1', the comparison resulted in zero difference, setting the `Z` flag to 1. You should see `xpsr` start with a `6` (like `0x69000000`). `6` in binary is `0110`, which means the `Z` flag (the second bit) is 1! The `beq.n` instruction will see this flag and take the branch. ### Step 46: Watch Different Input Paths @@ -1126,10 +1216,10 @@ Look for a function that: - That value is then compared against `0x31` ("1") ```assembly -bl FUN_xxxxx ; This is getchar! -mov r4, r0 ; Save return value -cmp r4, #0x31 ; Compare to '1' -beq LAB_xxxxx ; Branch if equal +bl FUN_10001860 undefined FUN_10001860() +uxtb r4,r0 +cmp r4,#0x31 +beq LAB_10000270 ``` 1. Right-click -> **Edit Function Signature** @@ -1180,21 +1270,24 @@ bne skip_printf ; If NOT equal, skip the printf skip_printf: ``` -``` -+-----------------------------------------------------------------+ -| Conditional Branch Flow | -| | -| cmp r4, #0x31 | -| | | -| | (Sets flags based on r4 - 0x31) | -| ? | -| beq target_address | -| | | -| +-- If r4 == 0x31: Jump to target_address | -| | | -| +-- If r4 != 0x31: Continue to next instruction | -| | -+-----------------------------------------------------------------+ +``` ext ++---------------------------------+ +| cmp r4, #0x31 | +| Sets flags based on r4 - 0x31 | ++---------------------------------+ + | + v + [ beq target_address ] + | + +--------+--------+ + | | + If r4 == 0x31 If r4 != 0x31 + | | + v v ++---------------+ +----------------------------+ +| Jump to | | Continue to | +| target_address| | next instruction | ++---------------+ +----------------------------+ ``` --- @@ -1215,20 +1308,17 @@ We want to create **secret commands** that: - '2' (0x32) -> prints "2" and "two", moves servo **Hacked behavior:** -- 'x' (0x78) -> moves servo SILENTLY -- 'y' (0x79) -> moves servo SILENTLY -- '1' (0x31) -> prints "1" and "one" (normal) -- '2' (0x32) -> prints "2" and "two" (normal) +- 'x' (0x78) -> moves servo SILENTLY (replacing '1') +- 'y' (0x79) -> moves servo SILENTLY (replacing '2') ### Step 55: Change Comparison Values -Find the compare instructions: +Navigate to the `main` function and find the two `cmp` instructions right after `getchar`: -```assembly -cmp r4, #0x31 ; Change to #0x78 ('x') -... -cmp r4, #0x32 ; Change to #0x79 ('y') -``` +1. At address `1000024a`, you will see `cmp r4,#0x31`. + - Right-click it, select **Patch Instruction**, and change it to `cmp r4,#0x78` (which is ASCII 'x'). +2. At address `1000024e`, you will see `cmp r4,#0x32`. + - Right-click it, select **Patch Instruction**, and change it to `cmp r4,#0x79` (which is ASCII 'y'). ### Step 56: Redirect Branches to Skip Prints @@ -1244,37 +1334,59 @@ compare -> branch -> printf("1") -> printf("one") -> servo code compare 'x' -> branch -> [skip prints] -> servo code ``` -Change the `beq` target addresses: -- Original: `beq 0x10000270` (goes to printf) -- Hacked: `beq 0x1000027c` (skips to servo) +Use **Patch Instruction** to rewrite the `beq` target addresses: -### Step 57: NOP Out Print Calls +1. At address `1000024c`, you will see `beq LAB_10000270`. + - `10000270` is the block that prints "1". We want to skip it! + - Right-click, select **Patch Instruction**, and change it to `beq 0x1000027c` (this jumps straight to the `mov r0,r6` servo code!). +2. At address `10000250`, you will see `beq LAB_1000029a`. + - `1000029a` is the block that prints "2". + - Right-click, select **Patch Instruction**, and change it to `beq 0x100002a6` (this jumps straight to the `mov r0,r5` servo code!). -**NOP** (No Operation) is an instruction that does nothing. We use it to "erase" code without changing the size of the binary. +### Step 57: NOP Out Print Calls (Alternative Method) -ARM Thumb NOP encoding: `00 bf` (2 bytes) +**NOP** (No Operation) is an instruction that does absolutely nothing. Hackers use it to "erase" code without changing the size of the binary! Since we already redirected the branches to skip the prints, this is technically redundant, but let's do it anyway just to learn the technique. -To NOP out a `bl puts` instruction (4 bytes), use `00 bf 00 bf`. +The `bl` instruction is 32-bits (4 bytes) long. Standard Thumb `nop` instructions are only 16-bits (2 bytes) long. If you try to patch a 4-byte instruction with a 2-byte instruction, Ghidra's assembler gets very confused and corrupts the code. -### Step 58: Apply All Patches +To fix this, we use the special 32-bit version of NOP: `nop.w` (Wide NOP). -Here's the complete patch list: +1. In the Listing view, right-click the `bl FUN_10001954` instruction at `10000272`. +2. Select **Patch Instruction**. +3. Type `nop.w` (don't forget the `.w`!) and hit Enter. +4. You will see the entire 4-byte instruction neatly get replaced by a single 32-bit NOP. +5. Repeat this for the second `bl` instruction at `10000278`. -| Offset | Original | Patched | Purpose | -| -------- | ------------ | ------------ | ---------------------------- | -| Compare1 | `31` | `78` | Check for 'x' instead of '1' | -| Compare2 | `32` | `79` | Check for 'y' instead of '2' | -| Branch1 | target=0x270 | target=0x27c | Skip printf for 'x' | -| Branch2 | target=0x29a | target=0x2a6 | Skip printf for 'y' | -| puts1 | `bl puts` | `nop nop` | Remove print | -| puts2 | `bl puts` | `nop nop` | Remove print | +### Step 58: Summary of Control Flow Patches -### Step 59: Hack the Angle Value +Here is a quick summary of the stealth command patches we just applied to the control flow: -Let's also change 180? to 30? for fun! +| Location | Original Action | Patched Action | Purpose | +| ---------- | -------------------- | -------------------- | ---------------------------- | +| `1000024a` | `cmp r4,#0x31` | `cmp r4,#0x78` | Check for 'x' instead of '1' | +| `1000024e` | `cmp r4,#0x32` | `cmp r4,#0x79` | Check for 'y' instead of '2' | +| `1000024c` | `beq LAB_10000270` | `beq LAB_1000027c` | Skip printf for 'x' | +| `10000250` | `beq LAB_1000029a` | `beq LAB_100002a6` | Skip printf for 'y' | -**Original:** `0x43340000` (180.0f in IEEE-754) -**New:** `0x41f00000` (30.0f in IEEE-754) +### Step 59: One Final Hack - Modify the Angle + +Let's also change the servo's movement angle from 180° to 30° for fun! + +If you look back near the top of the `main` function (around address `10000242`), you'll see this instruction: +`ldr r5,[DAT_100002c4] = 43340000h` + +The compiler stored the 180.0 float value (`0x43340000`) in a literal pool at address `100002c4`. To change the angle, we just need to overwrite that raw data! + +**Original:** `0x43340000` (180.0f) +**New:** `0x41f00000` (30.0f) + +Here is how to apply the patch: +1. Press `G` and jump to address `100002c4`. +2. In your **Bytes** window (make sure the Pencil icon is still clicked!), you will see the raw little-endian bytes: `00 00 34 43`. +3. Click on the first `00` and type `00 00 f0 41`. +4. The Listing view will instantly update to show the new `41f00000` value! + +*(For the math nerds, here is how we calculated `0x41f00000` manually using the IEEE-754 standard):* **Calculation for 30.0f:** ``` @@ -1290,19 +1402,19 @@ Little-endian: 00 00 f0 41 ### Step 60: Export and Test -1. Export as `0x0020_dynamic-conditionals-h.bin` -2. Convert to UF2: +When exporting, make sure to save it in your `build/` folder! + +1. Export as `0x0020_dynamic-conditionals-h.bin` inside `build/`. +2. Convert and flash (run from the `0x0020_dynamic-conditionals` directory!): ```cmd -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 ``` 3. Flash and test: - - Press '1' -> prints "1" and "one", servo moves - - Press '2' -> prints "2" and "two", servo moves - Press 'x' -> NO OUTPUT, but servo moves silently! - Press 'y' -> NO OUTPUT, but servo moves silently! + - (The original '1' and '2' keys no longer work!) --- @@ -1322,39 +1434,33 @@ python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 - ### Static vs Dynamic Summary -``` -+-----------------------------------------------------------------+ -| Static Conditionals | -| ------------------- | -| - Variable set once, never changes | -| - Same path taken every iteration | -| - Compiler may optimize out dead branches | -| - Example: int choice = 1; if (choice == 1) | -+-----------------------------------------------------------------+ -| Dynamic Conditionals | -| -------------------- | -| - Variable changes based on input/sensors | -| - Different paths taken based on runtime state | -| - All branches must remain in binary | -| - Example: choice = getchar(); if (choice == '1') | -+-----------------------------------------------------------------+ +```text + Static Conditionals + ------------------- + - Variable set once, never changes + - Same path taken every iteration + - Compiler may optimize out dead branches + - Example: int choice = 1; if (choice == 1) + + Dynamic Conditionals + -------------------- + - Variable changes based on input/sensors + - Different paths taken based on runtime state + - All branches must remain in binary + - Example: choice = getchar(); if (choice == '1') ``` ### PWM Calculation Summary -``` -+-----------------------------------------------------------------+ -| Servo PWM Calculation Chain | -| | -| Angle (degrees) -> Pulse Width (?s) -> PWM Ticks -> Servo Motion | -| | -| 0? -> 1000 ?s -> 1000 ticks -> Fully counter-clockwise | -| 90? -> 1500 ?s -> 1500 ticks -> Center position | -| 180? -> 2000 ?s -> 2000 ticks -> Fully clockwise | -| | -| Formula: pulse = 1000 + (angle/180) * 1000 | -| | -+-----------------------------------------------------------------+ +``` ext ++---------+ +-------------+ +-----------+ +--------------+ +| Angle | Formula | Pulse Width | 1us= | PWM Ticks | Servo | Servo Motion | +| degrees | ------> | µs | 1 tick | | Motion | | ++---------+ +-------------+ ------> +-----------+ ------> +--------------+ + | | | | + 0° ---------------- 1000 µs ------------- 1000 ticks --------- Fully CCW + 90° ---------------- 1500 µs ------------- 1500 ticks --------- Center + 180° ---------------- 2000 µs ------------- 2000 ticks --------- Fully CW ``` ### Key Memory Addresses @@ -1380,7 +1486,7 @@ python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 - 3. **PWM frequency = 50Hz for servos** - One pulse every 20ms -4. **Pulse width encodes position** - 1ms=0?, 1.5ms=90?, 2ms=180? +4. **Pulse width encodes position** - 1ms=0°, 1.5ms=90°, 2ms=180° 5. **beq = branch if equal** - Jumps when comparison matches @@ -1491,7 +1597,4 @@ A fast-moving servo is like a nuclear fuel rod: **Remember:** The techniques you learned today demonstrate how conditional logic can be manipulated at the binary level. Understanding these attacks helps us build more secure embedded systems. Always use your skills ethically and responsibly! -Happy hacking! ? - - - +Happy hacking! diff --git a/WEEK10/slides/WEEK10-IMG00.svg b/WEEK10/slides/WEEK10-IMG00.svg new file mode 100644 index 0000000..84c608d --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 10 + + +Conditionals in Embedded Systems: +Debugging and Hacking Static & Dynamic +Conditionals w/ SG90 Servo Motor PWM + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK10/slides/WEEK10-IMG01.svg b/WEEK10/slides/WEEK10-IMG01.svg new file mode 100644 index 0000000..38d8eb0 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG01.svg @@ -0,0 +1,82 @@ + + + + +Conditionals Overview +Static vs Dynamic Decision Making + + + +What Are Conditionals? +Structures that let programs choose +different paths based on conditions + + + +Static Conditional +Value fixed at compile time + + +int choice = 1; +// never changes +if (choice == 1) +printf("1"); +// always runs +else printf("2"); +// never runs + + + +Dynamic Conditional +Value changes at runtime + + +choice = getchar(); +// user types a key +if (choice == '1') +printf("1"); +// maybe runs + + + +if/else + +Feature +Description + +Condition +Any boolean expr +Values +Ranges, complex logic +Fall-through +No +Best for +2-3 conditions + + + +switch/case + +Feature +Description + +Condition +Single variable +Values +Discrete only +Fall-through +Yes (no break) +Best for +Many conditions + diff --git a/WEEK10/slides/WEEK10-IMG02.svg b/WEEK10/slides/WEEK10-IMG02.svg new file mode 100644 index 0000000..cbd97b4 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG02.svg @@ -0,0 +1,89 @@ + + + + +Static Conditionals +Fixed Outcome -- Same Path Every Time + + + +Static Code Pattern + +int choice = 1; +// NEVER changes +while (true) { +if (choice == 1) +printf("1"); +else if (choice == 2) +printf("2"); +// dead code +else + + + +Execution Flow + + +choice == 1? + + +YES + + +print "1" + +NO (never taken) + + + +choice == 2? + +NO (never reached) + + +print "?" + +Only ONE path ever executes! + + + +Every Loop Iteration (Always the Same) +1. if(1==1) --> TRUE +2. print "1" +3. switch(1) case 1 +4. print "one" +5. servo 0deg +6. sleep 500ms +7. servo 180deg + + + +Serial Output (Forever) + +1 +one +1 +// repeats forever + + + +Servo Motion (Forever) +0deg +--> +180deg +--> +0deg +Sweeps back and forth, 500ms each +Continuous, predictable motion + diff --git a/WEEK10/slides/WEEK10-IMG03.svg b/WEEK10/slides/WEEK10-IMG03.svg new file mode 100644 index 0000000..9a22eeb --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG03.svg @@ -0,0 +1,95 @@ + + + + +Dynamic Conditionals +Runtime Input Changes the Path + + + +Dynamic Code Pattern + +uint8_t choice = 0; +while (true) { +choice = getchar(); +// waits for keyboard input +if (choice == 0x31) +printf("1"); +else if (choice == 0x32) +printf("2"); + + + +Execution Flow + + +choice = getchar() + + + + +choice=='1'? + +YES + +servo 0-180 + + +NO + + +choice=='2'? + +YES + +servo 180-0 + + + +print "??" +Each iteration can take a DIFFERENT path + + + +getchar() Returns ASCII +'1' = 0x31 +'2' = 0x32 +'x' = 0x78 +'y' = 0x79 +Blocks until +keypress + + + +Input --> Behavior + +Key +Output +Servo + +'1' +"1" + "one" +0deg --> 180deg +'2' +"2" + "two" +180deg --> 0deg + + + +Two Projects +0x001d_static-conditionals +choice = 1 (fixed) +0x0020_dynamic-conditionals +choice = getchar() (user input) + diff --git a/WEEK10/slides/WEEK10-IMG04.svg b/WEEK10/slides/WEEK10-IMG04.svg new file mode 100644 index 0000000..a405f7e --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG04.svg @@ -0,0 +1,96 @@ + + + + +PWM Basics +Pulse Width Modulation for Servo Control + + + +What is PWM? +Rapidly switching a signal ON and OFF +Ratio of on-time to off-time controls power + + +HIGH + + + + + + + + +ON +OFF +ON +OFF + + + +Servo PWM (50Hz = 20ms period) + + +0deg (1ms pulse): + + + +1ms HIGH +19ms LOW + + +90deg (1.5ms pulse): + + + +1.5ms HIGH +18.5ms LOW + + +180deg (2ms pulse): + + + +2ms HIGH +18ms LOW + +Pulse WIDTH determines angle, not duty cycle +Total period always 20ms (50Hz) + + + +Angle to Pulse Width + +Angle +Pulse +Ticks (1MHz) + +0deg +1000us +1000 +90deg +1500us +1500 +180deg +2000us +2000 + + + +Formula +pulse = 1000 + (angle/180) x 1000 +Example for 90deg: +1000 + (90/180) x 1000 += 1500us = 1500 ticks + diff --git a/WEEK10/slides/WEEK10-IMG05.svg b/WEEK10/slides/WEEK10-IMG05.svg new file mode 100644 index 0000000..9499ded --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG05.svg @@ -0,0 +1,83 @@ + + + + +PWM Timing Chain +150MHz System Clock to 50Hz Servo Signal + + + +Clock Division + + +150 MHz Clock + +/ 150 + + + + +1 MHz PWM + +1 tick = 1us + + + +Step 1: 150,000,000 / 150 = 1,000,000 Hz +Each PWM tick = exactly 1 microsecond + +Step 2: Wrap at 20,000 ticks = 20ms = 50Hz +Wrap value = 19,999 + + + +SG90 Servo Motor + +Parameter +Value + +Voltage +4.8V - 6V (use 5V) +Rotation +0deg to 180deg +Pulse Width +1000us - 2000us +Frequency +50Hz (20ms period) + + + +Wiring to Pico 2 + + +Pico + + +SG90 + + + + +GPIO 6 = Signal (Orange) +VBUS 5V = VCC (Red) +GND = GND (Brown) +Add 1000uF capacitor on power! + + + +Power Safety +NEVER use 3.3V pin for servo! +Servos draw 650mA+ (spikes to 1A) +Use VBUS (5V from USB) with 1000uF 25V capacitor + diff --git a/WEEK10/slides/WEEK10-IMG06.svg b/WEEK10/slides/WEEK10-IMG06.svg new file mode 100644 index 0000000..bb7867d --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG06.svg @@ -0,0 +1,55 @@ + + + + +Static Source Code +0x001d_static-conditionals.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "servo.h" +#define SERVO_GPIO 6 + +int main(void) { +stdio_init_all(); +int choice = 1; +// STATIC! +servo_init(SERVO_GPIO); + +while (true) { +if (choice == 1) +printf("1\r\n"); +else if (choice == 2) +printf("2\r\n"); +// dead code + + + +switch Block + +switch(choice) { +case 1: puts("one"); break; + + +Servo Loop + +servo_set_angle(0.0f); +sleep_ms(500); +// then 180 + diff --git a/WEEK10/slides/WEEK10-IMG07.svg b/WEEK10/slides/WEEK10-IMG07.svg new file mode 100644 index 0000000..edb9558 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG07.svg @@ -0,0 +1,50 @@ + + + + +Dynamic Source Code +0x0020_dynamic-conditionals.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "servo.h" +#define SERVO_GPIO 6 + +int main(void) { +stdio_init_all(); +uint8_t choice = 0; +// DYNAMIC! +servo_init(SERVO_GPIO); + +while (true) { +choice = getchar(); +// wait for input +if (choice == 0x31) +// '1' +printf("1\r\n"); +else if (choice == 0x32) +// '2' +printf("2\r\n"); + + + +switch Block (with servo control) +case '1': print "one", servo 0-->180, sleep 500ms +case '2': print "two", servo 180-->0, sleep 500ms + diff --git a/WEEK10/slides/WEEK10-IMG08.svg b/WEEK10/slides/WEEK10-IMG08.svg new file mode 100644 index 0000000..295c649 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG08.svg @@ -0,0 +1,101 @@ + + + + +Branch Instructions +How Conditionals Become Assembly + + + +ARM Branch Instructions + +Instr +Meaning +Condition + + +b +Branch always +Always + +beq +Branch if Equal +Z flag set + +bne +Branch if != +Z flag clear + +bgt +Branch if > +Signed > + +blt +Branch if < +Signed < + + + +C --> Assembly + +C code: + +if (choice == 0x31) +printf("1"); + +Assembly: + +cmp r4, #0x31 +// compare +bne skip_printf +// skip if != + + + +Conditional Branch Flow + + +cmp r4, #0x31 + + + + +beq target_addr + + +r4==0x31: JUMP + + +r4!=0x31: continue next + +cmp sets CPU flags, branch reads them + + + +NOP (No Operation) +ARM Thumb NOP: +00 bf +2 bytes +Wide NOP: +00 f0 00 80 +Replaces 4-byte bl instruction + + + +Hacking Branches +Change branch target addr +Redirect program flow +NOP out instructions +Erase code silently + diff --git a/WEEK10/slides/WEEK10-IMG09.svg b/WEEK10/slides/WEEK10-IMG09.svg new file mode 100644 index 0000000..68d47e9 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG09.svg @@ -0,0 +1,83 @@ + + + + +Hacking Conditionals +Strings, Timing, Stealth Commands + + + +Hack 1: Change Strings +Change "1" to "2": +0x31 +--> +0x32 +"one" to "fun": +6f 6e 65 +--> +66 75 6e + + + +Hack 2: Speed Up Servo +Change sleep_ms delay: +0x1F4 (500ms) +--> +0x064 (100ms) + + + +Hack 3: Stealth Commands +Hidden keys move servo with NO output + +Patch +Original +Hacked +Purpose + + +Compare 1 +#0x31 ('1') +#0x78 ('x') +New trigger key + +Compare 2 +#0x32 ('2') +#0x79 ('y') +New trigger key + +puts calls +bl puts +00 bf 00 bf +NOP out prints + + + +Hack 4: Change Angle +180.0f --> 30.0f: +00 00 34 43 +--> +00 00 f0 41 + + + +Stealth Result +'1','2': normal output + servo +'x','y': NO output, servo moves + + + +Workflow +Patch bytes in Ghidra --> export .bin --> convert to UF2 --> flash to Pico + diff --git a/WEEK10/slides/WEEK10-IMG10.svg b/WEEK10/slides/WEEK10-IMG10.svg new file mode 100644 index 0000000..eb92463 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG10.svg @@ -0,0 +1,85 @@ + + + + +PWM & Servo Hacking +Conditionals, PWM, Servo, and Hacking + + + +Static vs Dynamic + +Static +choice = 1 (fixed) +Same path every iteration +Compiler may optimize + +Dynamic +choice = getchar() +Different paths at runtime + + + +PWM for Servos +150MHz / 150 = 1MHz tick +Wrap 20000 = 50Hz (20ms) +0deg=1000us 90deg=1500us 180deg=2000us +pulse = 1000 + (angle/180) x 1000 + + + +Branch Instructions +cmp r4, #0x31 +Compare +beq target +Jump if equal +bne target +Jump if not equal +NOP = 00 bf (erase code) + + + +Key Values +0x10000234 +main() +0x40070000 +UART0 +0x1F4 +500 (sleep_ms) +0x43340000 +180.0f IEEE-754 + + + +4 Hack Types Applied +String +"one"-->"fun" +Timing +500ms-->100ms +Stealth +NOP out prints +Angle +180.0f-->30.0f + + + +Projects +0x001d_static-conditionals +0x0020_dynamic-conditionals + + +IEEE-754 Angles +0.0f=00000000 90.0f=42b40000 +180.0f=43340000 30.0f=41f00000 + diff --git a/WEEK11/WEEK11.md b/WEEK11/WEEK11.md index bb069e5..d13d1f5 100644 --- a/WEEK11/WEEK11.md +++ b/WEEK11/WEEK11.md @@ -24,12 +24,12 @@ A **structure** (or **struct**) is a user-defined data type that groups related ```c // Define a struct type typedef struct { - uint8_t led1_pin; // GPIO pin for LED 1 - uint8_t led2_pin; // GPIO pin for LED 2 - uint8_t led3_pin; // GPIO pin for LED 3 - bool led1_state; // Is LED 1 on? - bool led2_state; // Is LED 2 on? - bool led3_state; // Is LED 3 on? + uint8_t led1_pin; // GPIO pin for LED 1 + uint8_t led2_pin; // GPIO pin for LED 2 + uint8_t led3_pin; // GPIO pin for LED 3 + bool led1_state; // Is LED 1 on? + bool led2_state; // Is LED 2 on? + bool led3_state; // Is LED 3 on? } simple_led_ctrl_t; ``` @@ -39,15 +39,15 @@ typedef struct { | | | simple_led_ctrl_t leds | | +-------------------------------------------------------------+| -| | led1_pin: 16 led2_pin: 17 led3_pin: 18 || -| | +--------+ +--------+ +--------+ || -| | | 16 | | 17 | | 18 | || -| | +--------+ +--------+ +--------+ || +| | led1_pin: 16 led2_pin: 17 led3_pin: 18 || +| | +--------+ +--------+ +--------+ || +| | | 16 | | 17 | | 18 | || +| | +--------+ +--------+ +--------+ || | | || -| | led1_state: false led2_state: false led3_state: false || -| | +--------+ +--------+ +--------+ || -| | | false | | false | | false | || -| | +--------+ +--------+ +--------+ || +| | led1_state: false led2_state: false led3_state: false || +| | +--------+ +--------+ +--------+ || +| | | false | | false | | false | || +| | +--------+ +--------+ +--------+ || | +-------------------------------------------------------------+| | | | All 6 members live together as ONE variable called "leds" | @@ -120,12 +120,12 @@ When you have a **pointer** to a struct, use the **arrow operator** (`->`): ```c simple_led_ctrl_t leds; -simple_led_ctrl_t *ptr = &leds; // Pointer to the struct +simple_led_ctrl_t *ptr = &leds; // Pointer to the struct // These are equivalent: -leds.led1_pin = 16; // Using dot with struct variable -ptr->led1_pin = 16; // Using arrow with pointer -(*ptr).led1_pin = 16; // Dereferencing then dot (same thing) +leds.led1_pin = 16; // Using dot with struct variable +ptr->led1_pin = 16; // Using arrow with pointer +(*ptr).led1_pin = 16; // Dereferencing then dot (same thing) ``` ``` @@ -181,9 +181,9 @@ simple_led_ctrl_t leds = { | Remote Control IR Receiver | | +----------+ +----------+ | | | Button | | | | -| | 1 | --- IR Light Pulses --- | ++ | | +| | 1 | --- IR Light Pulses --- | ++ | | | | +---+ | ~~~~~~~~~~~~? | Sensor | | -| | | ?? | | | | | +| | | ?? | | | | | | | +---+ | +----+-----+ | | | IR LED | | | | +----------+ ? | @@ -437,11 +437,11 @@ Before we start, make sure you have: | | | | (VS1838B) | | | | | +------+------+ | | | | | | -| | GPIO 16 |---[220 ohm]---(RED LED)----+ | +| | GPIO 16 |---[220 ohm]---(RED LED)----+ | | | | | | -| | GPIO 17 |---[220 ohm]---(GRN LED)----+ | +| | GPIO 17 |---[220 ohm]---(GRN LED)----+ | | | | | | -| | GPIO 18 |---[220 ohm]---(YEL LED)----+ | +| | GPIO 18 |---[220 ohm]---(YEL LED)----+ | | | | | | | | 3.3V |-------------------------+-- IR VCC | | | | | | diff --git a/WEEK11/slides/WEEK11-IMG00.svg b/WEEK11/slides/WEEK11-IMG00.svg new file mode 100644 index 0000000..0469f90 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 11 + + +Structures and Functions in +Embedded Systems: Debugging and Hacking +w/ IR Remote Control & NEC Protocol + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK11/slides/WEEK11-IMG01.svg b/WEEK11/slides/WEEK11-IMG01.svg new file mode 100644 index 0000000..6701ba4 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG01.svg @@ -0,0 +1,70 @@ + + + + +C Structures (Structs) +Grouping Related Data Together + + + +What is a Struct? +A user-defined type that groups +related variables under one name +Like a form with multiple fields +-- each field holds different data + + + +Struct Definition + +typedef struct { +uint8_t led1_pin; +uint8_t led2_pin; +uint8_t led3_pin; +bool led1_state; +bool led2_state; +bool led3_state; +} simple_led_ctrl_t; + + + +Why Use Structs? +1. +Organization +Related data stays together +2. +Readability +Code easier to understand +3. +Scalability +Easy to add more features +4. +Pass to Functions + + + +simple_led_ctrl_t leds + +pin1: 16 + +pin2: 17 + +pin3: 18 + +state1: 0 + +state2: 0 + +state3: 0 + diff --git a/WEEK11/slides/WEEK11-IMG02.svg b/WEEK11/slides/WEEK11-IMG02.svg new file mode 100644 index 0000000..6157da6 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG02.svg @@ -0,0 +1,85 @@ + + + + +Struct Memory Layout +How Structs Are Stored and Accessed + + + +Memory Layout (6 bytes total) + +Address +Member +Size +Value + + +0x20000000 +led1_pin +1 byte +16 (0x10) + +0x20000001 +led2_pin +1 byte +17 (0x11) + +0x20000002 +led3_pin +1 byte +18 (0x12) + +0x20000003 +led1_state +1 byte +0 (false) + +0x20000004 +led2_state +1 byte +0 (false) + +0x20000005 +led3_state +1 byte +0 (false) + + + +Dot Operator ( . ) +Use with struct variable + +leds.led1_pin = 16; +leds.led1_state = true; + + + +Arrow Operator ( -> ) +Use with pointer to struct + +ptr->led1_pin = 16; +// same as (*ptr).led1_pin + + + +Designated Initializers + +simple_led_ctrl_t leds = { +.led1_pin = 16, .led2_pin = 17, .led3_pin = 18 +}; +Clear which value goes +to which member +Order doesn't matter + diff --git a/WEEK11/slides/WEEK11-IMG03.svg b/WEEK11/slides/WEEK11-IMG03.svg new file mode 100644 index 0000000..c44ccd0 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG03.svg @@ -0,0 +1,88 @@ + + + + +NEC IR Protocol +Infrared Remote Control Communication + + + +How IR Works +Remote sends invisible light pulses +IR receiver on GPIO 5 reads signal +IR LED flashes in specific patterns +Each pattern = different button + + + +NEC Protocol Frame (32 bits) + + +Leader + +Address + +Addr Inv + +Command + +Cmd Inv + +Stop + +9ms+4.5ms +8-bit +8-bit check +8-bit +8-bit check + +Leader says "attention!", address identifies device, command is the button pressed + + + +NEC Command Codes + +Button +NEC Code +LED + + +1 +0x0C +Red (GP16) + +2 +0x18 +Green (GP17) + +3 +0x5E +Yellow (GP18) + + + +Hardware Wiring +GPIO 5 +IR Receiver (VS1838B) +GPIO 16 +Red LED + 220 ohm +GPIO 17 +Green LED + 220 ohm +GPIO 18 +Yellow LED + 220 ohm + + + +Projects: 0x0023_structures and 0x0026_functions + diff --git a/WEEK11/slides/WEEK11-IMG04.svg b/WEEK11/slides/WEEK11-IMG04.svg new file mode 100644 index 0000000..719946b --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG04.svg @@ -0,0 +1,91 @@ + + + + +Functions in C +Reusable Blocks of Code + + + +Anatomy of a Function + +int ir_to_led_number(int ir_command) { + +^^^ +^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ +ret +function name +parameter + + +if (ir_command == 0x0C) return 1; +// body + return value + + + +Function Types + +Type +Example + + +No params, no ret +leds_all_off() + +Params, no return +blink_led(..) + +No params, return +ir_getkey() + +Params + return +ir_to_led_num() + +Struct pointer +get_led_pin() + + + +Key Functions + +ir_to_led_number(cmd) +Maps NEC code to LED 1/2/3 + +get_led_pin(leds, num) +Returns GPIO pin for LED + +blink_led(pin, cnt, ms) +Blinks LED cnt times + + + +Function Call Chain + +main() +--> +process_ir_led_command() + +1. leds_all_off() +Turn all LEDs off + +2. ir_to_led_number() +Map NEC to LED + +3. get_led_pin() +Get GPIO pin + +4. blink_led() +Blink + stay on + diff --git a/WEEK11/slides/WEEK11-IMG05.svg b/WEEK11/slides/WEEK11-IMG05.svg new file mode 100644 index 0000000..9058acc --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG05.svg @@ -0,0 +1,66 @@ + + + + +Struct Pointers in Functions +Passing Data Efficiently + + + +Why Pass by Pointer? +Efficient +4 bytes (address) not 6 +Modifiable +Function can change original +Standard +Embedded systems practice + + + +Arrow Operator +leds->led1_pin +Same as (*leds).led1_pin +Use -> when leds is a pointer + + + +leds_all_off() + +void leds_all_off( +simple_led_ctrl_t *leds) { +gpio_put(leds->led1_pin, 0); +gpio_put(leds->led2_pin, 0); + + + +blink_led() + +void blink_led(uint8_t pin, +uint8_t count, uint32_t ms){ +gpio_put(pin, true); +sleep_ms(ms); + + + +process_ir_led_command() -- Main Command Processor + +int process_ir_led_command(int cmd, +simple_led_ctrl_t *leds, uint8_t blink_count) { +leds_all_off(leds); +// turn all off first +int num = ir_to_led_number(cmd); +// map NEC to LED +blink_led(get_led_pin(leds, num), +// blink then stay on + diff --git a/WEEK11/slides/WEEK11-IMG06.svg b/WEEK11/slides/WEEK11-IMG06.svg new file mode 100644 index 0000000..84c7359 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG06.svg @@ -0,0 +1,57 @@ + + + + +Structures Source Code +0x0023_structures.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "ir.h" + +typedef struct { +uint8_t led1_pin, led2_pin, led3_pin; +bool led1_state, led2_state, led3_state; +} simple_led_ctrl_t; + +int main(void) { +stdio_init_all(); +simple_led_ctrl_t leds = { +.led1_pin=16, .led2_pin=17, .led3_pin=18 +}; + +gpio_init(leds.led1_pin); +// init 16, 17, 18 +ir_init(5); +// IR on GPIO 5 +while (true) { +// main loop + + + +Main Loop Flow +ir_getkey() +--> +check NEC code +--> +set state +--> +gpio_put() +0x0C=LED1(red) 0x18=LED2(green) 0x5E=LED3(yellow) + diff --git a/WEEK11/slides/WEEK11-IMG07.svg b/WEEK11/slides/WEEK11-IMG07.svg new file mode 100644 index 0000000..514e9f0 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG07.svg @@ -0,0 +1,73 @@ + + + + +Struct Flattening +How Compilers Transform Structs + + + +C Code (High Level) + +gpio_init(leds.led1_pin); +// leds.led1_pin = 16 +gpio_init(leds.led2_pin); +// leds.led2_pin = 17 + + + +Assembly (Flattened) + +movs r0, #0x10 +// 16 +bl gpio_init +movs r0, #0x11 +// 17 +bl gpio_init + + + +The Key Insight +Struct abstraction DISAPPEARS +at assembly level +You see individual values (16, 17, 18) not struct names + + + +Struct Member Mapping + +Assembly +Struct Member +Physical +NEC Code + + +0x10 (16) +led1_pin +Red LED +0x0C + +0x11 (17) +led2_pin +Green LED +0x18 + +0x12 (18) +led3_pin +Yellow LED +0x5E + +Sequential values (16,17,18) reveal the struct pattern +Recognize patterns to reconstruct original structs in Ghidra + diff --git a/WEEK11/slides/WEEK11-IMG08.svg b/WEEK11/slides/WEEK11-IMG08.svg new file mode 100644 index 0000000..8eb24ae --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG08.svg @@ -0,0 +1,77 @@ + + + + +Hacking Structures +Swapping GPIO Pin Assignments + + + +Swap LED 1 and LED 2 +Find gpio_init values: +0x10 (16) +--> +0x11 (17) +0x11 (17) +--> +0x10 (16) +Swap the two byte values + + + +Result After Hack + +Before: +Btn 1 --> GPIO 16 --> Red +Btn 2 --> GPIO 17 --> Green + +After: +SWAPPED! + + + +Before (Normal) +Btn 1 (0x0C) --> GPIO 16 +Red +Btn 2 (0x18) --> GPIO 17 +Green +Log and LED match correctly + + +After (Hacked) +Btn 1 (0x0C) --> GPIO 17 +Green! +Btn 2 (0x18) --> GPIO 16 +Red! +Log says RED but GREEN lights + + + +Log Desynchronization + +Terminal Log: +NEC command: 0x0C +(expects Red) + +Physical LED: +GREEN LED on +MISMATCH! + +Operator sees correct logs but WRONG behavior + + + +Stuxnet: +False "normal" data to operators, equipment destroyed + diff --git a/WEEK11/slides/WEEK11-IMG09.svg b/WEEK11/slides/WEEK11-IMG09.svg new file mode 100644 index 0000000..e6ae7a2 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG09.svg @@ -0,0 +1,73 @@ + + + + +.ELF vs .BIN Analysis +Ghidra Analysis of 0x0026_functions + + + +.ELF vs .BIN Comparison + +Feature +.BIN File +.ELF File + + +Symbols +None +Function names + +Sections +Raw bytes only +.text .data .rodata + +Debug info +None +May include debug + +Use case +Flash to device +Analysis + debug + + + +Importing .BIN +Manual setup required: +ARM Cortex 32 little endian +Block: .text +Base: 10000000 +No function names + + +Importing .ELF +Auto-detected by Ghidra: +ARM format recognized +Sections auto-loaded +Symbol tree populated +Named functions visible + + + +Important Rule +Analyze the .ELF +for symbol information +Patch the .BIN +for flashing + + + +Export Workflow +Patch .bin in Ghidra --> uf2conv.py --> flash to Pico 2 + diff --git a/WEEK11/slides/WEEK11-IMG10.svg b/WEEK11/slides/WEEK11-IMG10.svg new file mode 100644 index 0000000..3906d05 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG10.svg @@ -0,0 +1,85 @@ + + + + +Structs & IR Protocol +Structs, Functions, IR, and Hacking + + + +C Structures +Group related data together +Dot (.) for variables +Arrow (->) for pointers +Designated init: .pin = 16 + + + +NEC IR Protocol +32-bit frame: addr + cmd +0x0C +Button 1 +0x18 +Button 2 +0x5E +Button 3 + + + +Functions +Reusable blocks, one job each +ir_to_led_number() +get_led_pin() / blink_led() +process_ir_led_command() + + + +Assembly Flattening +Structs vanish in assembly +Only see values: 0x10 0x11 0x12 +Pattern recognition is key +.ELF has symbols, .BIN doesn't + + + +Key Values +0x10000234 +main() +GPIO 5 +IR receiver +GPIO 16/17/18 +Red/Green/Yellow + + + +Hacking Techniques +GPIO swap +0x10 <--> 0x11 +Log desync +Logs lie! +Stuxnet +Same concept + + + +Projects +0x0023_structures +0x0026_functions + + + +Key Takeaway +Patch bytes, mislead logs +hardware does what YOU say +