mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-16 13:19:14 +02:00
Refactor E and S
This commit is contained in:
@@ -47,13 +47,21 @@ Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and
|
||||
|
||||
### Week 1 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01.md)
|
||||
|
||||
#### Exercise 1: Explore in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Explore in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-01.md)
|
||||
|
||||
#### Exercise 2: Find Strings in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Explore in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-01-S.md)
|
||||
|
||||
#### Exercise 3: Find Cross-References in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Find Strings in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-02.md)
|
||||
|
||||
#### Exercise 4: Connect GDB & Basic Exploration [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Find Strings in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Find Cross-References in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Find Cross-References in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Connect GDB & Basic Exploration [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Connect GDB & Basic Exploration [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-04-S.md)
|
||||
|
||||
### Chapter 1: hello, world
|
||||
This chapter covers the basics of setting up a dev environment and basic template firmware for the Pico 2 MCU in addition to printing hello, world.
|
||||
@@ -72,13 +80,21 @@ Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Progr
|
||||
|
||||
### Week 2 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02.md)
|
||||
|
||||
#### Exercise 1: Change the Message [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Change the Message [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-01.md)
|
||||
|
||||
#### Exercise 2: Use a Different SRAM Address [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Change the Message [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-01-S.md)
|
||||
|
||||
#### Exercise 3: Examine Memory Around Your String [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Use a Different SRAM Address [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-02.md)
|
||||
|
||||
#### Exercise 4: Automate the Hack [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Use a Different SRAM Address [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Examine Memory Around Your String [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Examine Memory Around Your String [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Automate the Hack [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Automate the Hack [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-04-S.md)
|
||||
|
||||
### Chapter 3: Hacking hello, world
|
||||
This chapter covers the hacking of our firmware for the Pico 2 MCU hello, world program.
|
||||
@@ -92,13 +108,21 @@ Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive
|
||||
|
||||
### Week 3 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03.md)
|
||||
|
||||
#### Exercise 1: Trace a Reset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Trace a Reset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-01.md)
|
||||
|
||||
#### Exercise 2: Find the Stack Size [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Trace a Reset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-01-S.md)
|
||||
|
||||
#### Exercise 3: Examine All Vectors [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Find the Stack Size [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-02.md)
|
||||
|
||||
#### Exercise 4: Find Your Main Function and Trace Back [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Find the Stack Size [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Examine All Vectors [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Examine All Vectors [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Find Your Main Function and Trace Back [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Find Your Main Function and Trace Back [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-04-S.md)
|
||||
|
||||
### Chapter 4: Embedded System Analysis
|
||||
This chapter covers a comprehensive embedded system analysis reviewing parts of the RP2350 datasheet and helpful firmware analysis tools.
|
||||
@@ -112,13 +136,21 @@ Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Ba
|
||||
|
||||
### Week 4 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04.md)
|
||||
|
||||
#### Exercise 1: Analyze Variable Storage in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Analyze Variable Storage in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-01.md)
|
||||
|
||||
#### Exercise 2: Patch Binary to Change Variable Value [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Analyze Variable Storage in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-01-S.md)
|
||||
|
||||
#### Exercise 3: Analyze and Understand GPIO Control [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Patch Binary to Change Variable Value [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-02.md)
|
||||
|
||||
#### Exercise 4: Patch GPIO Binary to Change LED Pin [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Patch Binary to Change Variable Value [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Analyze and Understand GPIO Control [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Analyze and Understand GPIO Control [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Patch GPIO Binary to Change LED Pin [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Patch GPIO Binary to Change LED Pin [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-04-S.md)
|
||||
|
||||
### Chapter 5: Intro To Variables
|
||||
This chapter covers an introduction to variables as it relates to embedded development on the Pico 2.
|
||||
@@ -157,13 +189,21 @@ Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floa
|
||||
|
||||
### 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)
|
||||
#### Non-Credit Practice 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)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Analyze the Float Binary in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-01-S.md)
|
||||
|
||||
#### Exercise 3: Analyze the Double Binary in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-03.md)
|
||||
#### Non-Credit Practice 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 4: Patch the Double Binary — Changing 42.52525 to 99.99 [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Patch the Float Binary — Changing 42.5 to 99.0 [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Analyze the Double Binary in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Analyze the Double Binary in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-03-S.md)
|
||||
|
||||
#### Non-Credit Practice 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)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Patch the Double Binary — Changing 42.52525 to 99.99 [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-04-S.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.
|
||||
@@ -217,13 +257,21 @@ Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/
|
||||
|
||||
### Week 6 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06.md)
|
||||
|
||||
#### Exercise 1: Change the Static Variable Initial Value from 42 to 100 [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Change the Static Variable Initial Value from 42 to 100 [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-01.md)
|
||||
|
||||
#### Exercise 2: Reverse Engineer gpio_set_pulls with GDB [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Change the Static Variable Initial Value from 42 to 100 [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-01-S.md)
|
||||
|
||||
#### Exercise 3: Make the Overflow Happen Faster [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Reverse Engineer gpio_set_pulls with GDB [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-02.md)
|
||||
|
||||
#### Exercise 4: Invert the Button Logic with XOR [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Reverse Engineer gpio_set_pulls with GDB [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Make the Overflow Happen Faster [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Make the Overflow Happen Faster [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Invert the Button Logic with XOR [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Invert the Button Logic with XOR [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-04-S.md)
|
||||
|
||||
### Chapter 20: Static Variables
|
||||
This chapter covers static variables as well as an intro to GPIO inputs as we work with push buttons as it relates to embedded development on the Pico 2.
|
||||
@@ -247,13 +295,21 @@ Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C B
|
||||
|
||||
### Week 7 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07.md)
|
||||
|
||||
#### Exercise 1: Change Both LCD Lines [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Change Both LCD Lines [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-01.md)
|
||||
|
||||
#### Exercise 2: Find All String Literals in the Binary [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Change Both LCD Lines [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-01-S.md)
|
||||
|
||||
#### Exercise 3: Trace the I²C Struct Pointer Chain [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Find All String Literals in the Binary [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-02.md)
|
||||
|
||||
#### Exercise 4: Display Your Own Custom Message on the LCD [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Find All String Literals in the Binary [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Trace the I²C Struct Pointer Chain [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Trace the I²C Struct Pointer Chain [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Display Your Own Custom Message on the LCD [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Display Your Own Custom Message on the LCD [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-04-S.md)
|
||||
|
||||
### Chapter 23: Constants
|
||||
This chapter covers constants as well as an intro to I2C as we work a 1602 LCD as it relates to embedded development on the Pico 2.
|
||||
@@ -280,13 +336,21 @@ Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperat
|
||||
|
||||
### Week 9 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09.md)
|
||||
|
||||
#### Exercise 1: Change the Sleep Duration [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Change the Sleep Duration [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-01.md)
|
||||
|
||||
#### Exercise 2: Invert the Temperature Reading [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Change the Sleep Duration [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-01-S.md)
|
||||
|
||||
#### Exercise 3: Add a Fixed Temperature Offset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Invert the Temperature Reading [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-02.md)
|
||||
|
||||
#### Exercise 4: Find All printf Format Strings [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Invert the Temperature Reading [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Add a Fixed Temperature Offset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Add a Fixed Temperature Offset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Find All printf Format Strings [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Find All printf Format Strings [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-04-S.md)
|
||||
|
||||
### Chapter 26: Operators
|
||||
This chapter covers operators as well as an intro to single-wire protocol as we work a DHT11 temperature and humidity sensor as it relates to embedded development on the Pico 2.
|
||||
@@ -310,13 +374,21 @@ Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditi
|
||||
|
||||
### Week 10 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10.md)
|
||||
|
||||
#### Exercise 1: Change Servo Angle Range [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Change Servo Angle Range [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-01.md)
|
||||
|
||||
#### Exercise 2: Add a Third Command [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Change Servo Angle Range [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-01-S.md)
|
||||
|
||||
#### Exercise 3: Reverse the Servo Direction [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Add a Third Command [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-02.md)
|
||||
|
||||
#### Exercise 4: Speed Profile [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Add a Third Command [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Reverse the Servo Direction [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Reverse the Servo Direction [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Speed Profile [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Speed Profile [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-04-S.md)
|
||||
|
||||
### Chapter 29: Static Conditionals
|
||||
This chapter covers static conditionals as well as an intro to PWM as we work a SG90 servo motor as it relates to embedded development on the Pico 2.
|
||||
@@ -355,13 +427,21 @@ Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote
|
||||
|
||||
### Week 11 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11.md)
|
||||
|
||||
#### Exercise 1: Add a Fourth LED [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-01.md)
|
||||
#### Non-Credit Practice Exercise 1: Add a Fourth LED [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-01.md)
|
||||
|
||||
#### Exercise 2: Change Blink Count [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-02.md)
|
||||
#### Non-Credit Practice Exercise 1 Solution: Add a Fourth LED [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-01-S.md)
|
||||
|
||||
#### Exercise 3: Swap All Three LEDs [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-03.md)
|
||||
#### Non-Credit Practice Exercise 2: Change Blink Count [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-02.md)
|
||||
|
||||
#### Exercise 4: Change Blink Speed [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-04.md)
|
||||
#### Non-Credit Practice Exercise 2 Solution: Change Blink Count [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-02-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3: Swap All Three LEDs [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-03.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 3 Solution: Swap All Three LEDs [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-03-S.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4: Change Blink Speed [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-04.md)
|
||||
|
||||
#### Non-Credit Practice Exercise 4 Solution: Change Blink Speed [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-04-S.md)
|
||||
|
||||
### Chapter 35: Structures
|
||||
This chapter covers structures as well as an intro to infrared basics as we work a infrared receiver and infrared remote controller as it relates to embedded development on the Pico 2.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 1
|
||||
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Explore in Ghidra
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Question 1: What does the function return?
|
||||
`stdio_init_all()` returns `_bool` (displayed as `void` in some Ghidra versions). The function signature shows `_bool stdio_init_all(void)`.
|
||||
|
||||
##### Question 2: What parameters does it take?
|
||||
**None** - the function signature shows `(void)` in parentheses, meaning zero parameters.
|
||||
|
||||
##### Question 3: What functions does it call?
|
||||
`stdio_init_all()` calls initialization functions for:
|
||||
- **USB CDC** initialization (USB serial communication)
|
||||
- **UART** initialization (serial pin communication)
|
||||
|
||||
These set up the standard I/O subsystem so that `printf()` can output data.
|
||||
|
||||
##### Question 4: What's the purpose?
|
||||
`stdio_init_all()` initializes **Standard Input/Output** for the Pico 2:
|
||||
- **std** = Standard
|
||||
- **io** = Input/Output
|
||||
|
||||
It sets up both USB CDC and UART communication channels, which allows `printf()` to send output through the serial connection.
|
||||
|
||||
##### Expected Output
|
||||
|
||||
```
|
||||
stdio_init_all() returns: void (_bool)
|
||||
It takes 0 parameters
|
||||
It calls the following functions: USB CDC init, UART init
|
||||
Based on these calls, I believe it initializes: Standard I/O for USB and UART serial communication
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why would we need to initialize standard I/O before using `printf()`?**
|
||||
Without initialization, there is no communication channel configured. `printf()` needs a destination (USB or UART) to send its output. Without `stdio_init_all()`, output has nowhere to go.
|
||||
|
||||
2. **Can you find other functions in the Symbol Tree that might be related to I/O?**
|
||||
Yes - functions like `stdio_usb_init`, `stdio_uart_init`, `__wrap_puts`, and other I/O-related functions appear in the Symbol Tree.
|
||||
|
||||
3. **How does this function support the `printf("hello, world\r\n")` call in main?**
|
||||
It configures the USB and UART hardware so that when `printf()` (optimized to `__wrap_puts`) executes, the characters are transmitted over the serial connection to the host computer.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 1
|
||||
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
|
||||
|
||||
### Exercise 1: Explore in Ghidra
|
||||
### Non-Credit Practice Exercise 1: Explore in Ghidra
|
||||
|
||||
#### Objective
|
||||
Learn how to navigate Ghidra's Symbol Tree to find and analyze functions, specifically examining the `stdio_init_all` function.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 1
|
||||
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Find Strings in Ghidra
|
||||
|
||||
#### Answers
|
||||
|
||||
##### String Location
|
||||
|
||||
| Item | Answer |
|
||||
|------|--------|
|
||||
| **String Address** | `0x100019CC` |
|
||||
| **Actual String Content** | `"hello, world\r\n"` |
|
||||
| **String Length** | 14 bytes |
|
||||
| **Located In** | Flash memory (XIP region, starts with `0x10000...`) |
|
||||
|
||||
##### Question 1: What is the address and is it Flash or RAM?
|
||||
The string `"hello, world\r\n"` is located at address **`0x100019CC`** in **Flash memory**. We know it is Flash because the address begins with `0x10000...` (the XIP/Execute-In-Place region starts at `0x10000000`). RAM addresses start at `0x20000000`.
|
||||
|
||||
##### Question 2: How many bytes does the string take?
|
||||
**14 bytes** total:
|
||||
- 12 printable characters: `h`, `e`, `l`, `l`, `o`, `,`, ` `, `w`, `o`, `r`, `l`, `d`
|
||||
- 2 special characters: `\r` (carriage return, 0x0D) and `\n` (newline, 0x0A)
|
||||
|
||||
##### Question 3: How many times and which functions reference it?
|
||||
The string is referenced **1 time**, only in the **`main()`** function. The `ldr` instruction at `0x1000023a` loads the string address into register `r0`, which is then passed to `__wrap_puts`.
|
||||
|
||||
##### Question 4: How is the string encoded?
|
||||
The string is encoded in **ASCII**. Each character occupies exactly one byte:
|
||||
- `\r` = `0x0D` (carriage return)
|
||||
- `\n` = `0x0A` (newline/line feed)
|
||||
|
||||
##### Expected Output
|
||||
|
||||
```
|
||||
String Found: "hello, world\r\n"
|
||||
Address: 0x100019CC
|
||||
Located in: Flash (XIP region)
|
||||
Total Size: 14 bytes
|
||||
Referenced by: main()
|
||||
Used in: printf() argument (optimized to __wrap_puts) to print the string in an infinite loop
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why is the string stored in Flash instead of RAM?**
|
||||
String literals are constants that never change. Storing them in Flash (read-only) saves precious RAM for variables and the stack. The XIP feature allows the processor to read directly from Flash.
|
||||
|
||||
2. **What would happen if you tried to modify this string at runtime?**
|
||||
Flash memory is read-only at runtime. Attempting to write to a Flash address would cause a fault. To modify printed output, you must write your new string to SRAM (`0x20000000+`) and redirect the pointer.
|
||||
|
||||
3. **How does the Listing view help you understand string storage?**
|
||||
The Listing view shows the raw hex bytes alongside their ASCII interpretation, letting you see exactly how each character maps to memory and where the string boundaries are.
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 1
|
||||
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
|
||||
|
||||
### Exercise 2: Find Strings in Ghidra
|
||||
### Non-Credit Practice Exercise 2: Find Strings in Ghidra
|
||||
|
||||
#### Objective
|
||||
Learn how to locate and analyze strings in a binary, understanding where they are stored in memory and how they're used.
|
||||
@@ -74,7 +74,7 @@ Now that you're at the string's location:
|
||||
To see where this string is **used**:
|
||||
|
||||
1. In the Listing view where the string is displayed, **right-click** on the string
|
||||
2. Select **References** → **Show References to**
|
||||
2. Select **References** ? **Show References to**
|
||||
3. A dialog should appear showing which functions/instructions reference this string
|
||||
4. This tells you which parts of the code use this string
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 1
|
||||
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Find Cross-References in Ghidra
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Fill-in-the-Blank Items
|
||||
|
||||
| Item | Answer |
|
||||
|------|--------|
|
||||
| **Data reference address** | `0x10000244` (DAT_10000244) |
|
||||
| **Number of references** | 1 |
|
||||
| **Reference type** | Read (`ldr` instruction) |
|
||||
| **Function using it** | `main()` |
|
||||
| **Next instruction after ldr** | `bl __wrap_puts` at `0x100015fc` |
|
||||
|
||||
##### Question 1: What is the address of the data reference?
|
||||
The data reference is at **`0x10000244`** (labeled `DAT_10000244` in Ghidra). This location stores the pointer value `0x100019CC`, which is the address of the `"hello, world"` string.
|
||||
|
||||
##### Question 2: How many places reference this data?
|
||||
**1 place** - it is only referenced in the `main()` function via the `ldr` instruction.
|
||||
|
||||
##### Question 3: Is it a read or write operation? Why?
|
||||
It is a **READ** operation. The `ldr` (Load Register) instruction reads the pointer value from `DAT_10000244` into register `r0`. The program needs to read this pointer to pass the string address as an argument to `__wrap_puts`.
|
||||
|
||||
##### Question 4: What happens next?
|
||||
After the `ldr r0, [DAT_10000244]` instruction loads the string address into `r0`, the next instruction is **`bl 0x100015fc <__wrap_puts>`** which calls the `puts` function with `r0` as its argument (the string pointer).
|
||||
|
||||
##### Question 5: Complete Data Flow Chain
|
||||
|
||||
```
|
||||
String "hello, world\r\n" stored at 0x100019CC (Flash)
|
||||
|
|
||||
v
|
||||
Pointer to string stored at DAT_10000244 (0x10000244)
|
||||
|
|
||||
v
|
||||
main() executes: ldr r0, [DAT_10000244] -> r0 = 0x100019CC
|
||||
|
|
||||
v
|
||||
main() executes: bl __wrap_puts -> prints the string
|
||||
|
|
||||
v
|
||||
main() executes: b.n main+6 -> loops back (infinite loop)
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the compiler use a pointer (indirect reference) instead of embedding the string address directly in the instruction?**
|
||||
ARM Thumb instructions have limited immediate value sizes. The `ldr` instruction uses a PC-relative offset to reach a nearby literal pool entry (`DAT_10000244`) that holds the full 32-bit address. This pattern allows addressing any location in the 4 GB address space.
|
||||
|
||||
2. **What is a literal pool?**
|
||||
A literal pool is a region of constant data placed near the code that uses it. The compiler stores full 32-bit values here that cannot fit as immediates in Thumb instructions. The `ldr` instruction loads from the literal pool using a small PC-relative offset.
|
||||
|
||||
3. **How does cross-referencing help in reverse engineering?**
|
||||
Cross-references let you trace data flow through a program. Starting from a known string, you can find which functions use it, how data moves between functions, and understand the program's control flow without having source code.
|
||||
+12
-12
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 1
|
||||
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
|
||||
|
||||
### Exercise 3: Find Cross-References in Ghidra
|
||||
### Non-Credit Practice Exercise 3: Find Cross-References in Ghidra
|
||||
|
||||
#### Objective
|
||||
Learn how to use Ghidra's cross-reference feature to trace how data flows through code, understanding where specific data is read, written, or referenced.
|
||||
@@ -25,17 +25,17 @@ In this exercise, you'll:
|
||||
#### Background: What are Cross-References?
|
||||
|
||||
A **cross-reference** is a link between different parts of the code:
|
||||
- **Code → Data**: An instruction reads or writes data
|
||||
- **Code → Code**: A function calls another function
|
||||
- **Data → Data**: One data item references another
|
||||
- **Code ? Data**: An instruction reads or writes data
|
||||
- **Code ? Code**: A function calls another function
|
||||
- **Data ? Data**: One data item references another
|
||||
|
||||
In this exercise, we're tracking **code → data** references to understand where and how the program uses the "hello, world" string.
|
||||
In this exercise, we're tracking **code ? data** references to understand where and how the program uses the "hello, world" string.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
##### Step 1: Navigate to the main Function
|
||||
|
||||
1. In Ghidra's CodeBrowser, use **Search → For Address or Label** (or press **Ctrl+G**)
|
||||
1. In Ghidra's CodeBrowser, use **Search ? For Address or Label** (or press **Ctrl+G**)
|
||||
2. Type `main` and press Enter
|
||||
3. Ghidra will navigate to the `main` function
|
||||
4. You should see the disassembly in the Listing view (center panel)
|
||||
@@ -167,7 +167,7 @@ Read backwards: `10 00 19 CC` = `0x100019CC`
|
||||
#### Tips and Hints
|
||||
|
||||
- If you right-click and don't see "References", try right-clicking directly on the instruction address instead
|
||||
- You can also use **Search → For Cross References** from the menu for a more advanced search
|
||||
- You can also use **Search ? For Cross References** from the menu for a more advanced search
|
||||
- In the Decompile view (right side), cross-references may be shown in a different format or with different colors
|
||||
- Multi-level references: You can right-click on a data item and then follow the chain to another data item
|
||||
|
||||
@@ -193,12 +193,12 @@ By completing this exercise, you've learned:
|
||||
You should now understand this flow:
|
||||
```
|
||||
String "hello, world" is stored at address 0x100019CC in Flash
|
||||
↓
|
||||
?
|
||||
A pointer to this address is stored at DAT_10000244 in Flash
|
||||
↓
|
||||
?
|
||||
The main() function loads this pointer: ldr r0, [DAT_10000244]
|
||||
↓
|
||||
?
|
||||
main() calls printf with r0 (the string address) as the argument
|
||||
↓
|
||||
?
|
||||
printf() reads the bytes at that address and prints them
|
||||
```
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 1
|
||||
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Connect GDB & Basic Exploration
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Question 1: GDB Connection
|
||||
- **Was GDB able to connect to OpenOCD?** Yes, via `target extended-remote localhost:3333`
|
||||
- **Did program stop at breakpoint?** Yes, at `Breakpoint 1, main () at ../0x0001_hello-world.c:4`
|
||||
|
||||
##### Question 2: Memory Address of main
|
||||
- **Address of main's first instruction:** `0x10000234`
|
||||
- **Flash or RAM?** **Flash memory** - the address starts with `0x10000...` (XIP region starting at `0x10000000`)
|
||||
|
||||
##### Question 3: Stack Pointer Value
|
||||
- **SP value at main:** `0x20082000`
|
||||
- **Flash or RAM?** **RAM** - the address starts with `0x20000...` (SRAM starts at `0x20000000`)
|
||||
|
||||
##### Question 4: First Instruction
|
||||
- **First instruction in main:** `push {r3, lr}`
|
||||
- **What does it do?** Saves register `r3` and the Link Register (`lr`) onto the stack. This preserves the return address so `main()` can call other functions (like `stdio_init_all()` and `__wrap_puts`) and they can properly use `lr` themselves.
|
||||
|
||||
##### Question 5: Comparison to Ghidra
|
||||
**Yes, they match.** The GDB disassembly output is identical to what Ghidra shows in the Listing View. Both static analysis (Ghidra) and dynamic analysis (GDB) reveal the same instructions.
|
||||
|
||||
##### Register Values at Breakpoint
|
||||
|
||||
| Register | Value | Description |
|
||||
|----------|-------|-------------|
|
||||
| **pc** | `0x10000234` | Program Counter - at start of main (Flash) |
|
||||
| **sp** | `0x20082000` | Stack Pointer - top of stack (RAM) |
|
||||
| **lr** | `0x1000018f` | Link Register - return address after main |
|
||||
| **r0** | `0x0` | General Purpose - will hold function arguments |
|
||||
| **r1** | `0x10000235` | General Purpose |
|
||||
| **r2** | `0x80808080` | General Purpose |
|
||||
| **r3** | `0xe000ed08` | General Purpose |
|
||||
|
||||
##### Full Disassembly of main
|
||||
|
||||
```
|
||||
0x10000234 <+0>: push {r3, lr} # Save registers to stack
|
||||
0x10000236 <+2>: bl 0x1000156c <stdio_init_all> # Initialize I/O
|
||||
0x1000023a <+6>: ldr r0, [pc, #8] # Load string pointer
|
||||
0x1000023c <+8>: bl 0x100015fc <__wrap_puts> # Print string
|
||||
0x10000240 <+12>: b.n 0x1000023a <main+6> # Infinite loop
|
||||
0x10000242 <+14>: nop
|
||||
0x10000244 <+16>: (data: pointer to string)
|
||||
```
|
||||
|
||||
##### GDB Connection Sequence
|
||||
|
||||
```
|
||||
Terminal 1: openocd -s "..." -f interface/cmsis-dap.cfg -f target/rp2350.cfg
|
||||
Terminal 2: arm-none-eabi-gdb build/0x0001_hello-world.elf
|
||||
(gdb) target extended-remote localhost:3333
|
||||
(gdb) monitor reset halt
|
||||
(gdb) b main
|
||||
(gdb) c
|
||||
(gdb) disassemble main
|
||||
(gdb) i r
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the stack pointer start at `0x20082000`?**
|
||||
The initial stack pointer value comes from the first entry in the vector table at `0x10000000`. The linker script sets `__StackTop` to `0x20082000`, which is the top of the SCRATCH_Y region in SRAM. The stack grows downward from this address.
|
||||
|
||||
2. **Why does `push {r3, lr}` save `r3` even though it doesn't seem to be used?**
|
||||
ARM requires 8-byte stack alignment. Pushing `lr` alone would only move SP by 4 bytes. Including `r3` ensures the stack remains 8-byte aligned, which is required by the ARM Architecture Procedure Call Standard (AAPCS).
|
||||
|
||||
3. **How does the infinite loop work?**
|
||||
The instruction at `0x10000240` is `b.n 0x1000023a` - an unconditional branch back to `main+6`, which reloads the string pointer and calls `__wrap_puts` again. The function never returns.
|
||||
+8
-8
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 1
|
||||
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
|
||||
|
||||
### Exercise 4: Connect GDB & Basic Exploration
|
||||
### Non-Credit Practice Exercise 4: Connect GDB & Basic Exploration
|
||||
|
||||
#### Objective
|
||||
Set up GDB (GNU Debugger) to dynamically analyze the "hello, world" program running on your Pico 2, verifying that your debugging setup works correctly.
|
||||
@@ -350,12 +350,12 @@ This is useful when you want to break on a condition rather than every time.
|
||||
#### Summary
|
||||
|
||||
By completing this exercise, you've:
|
||||
1. ✅ Set up OpenOCD as a debug server
|
||||
2. ✅ Connected GDB to a Pico 2 board
|
||||
3. ✅ Set a breakpoint and halted execution
|
||||
4. ✅ Examined assembly language in a live debugger
|
||||
5. ✅ Viewed CPU registers and their values
|
||||
6. ✅ Verified your dynamic debugging setup works
|
||||
1. ? Set up OpenOCD as a debug server
|
||||
2. ? Connected GDB to a Pico 2 board
|
||||
3. ? Set a breakpoint and halted execution
|
||||
4. ? Examined assembly language in a live debugger
|
||||
5. ? Viewed CPU registers and their values
|
||||
6. ? Verified your dynamic debugging setup works
|
||||
|
||||
You're now ready for Week 2, where you'll:
|
||||
- Step through code line by line
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 2
|
||||
Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Change the Message
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Attack Summary
|
||||
The goal is to write a custom message into SRAM at `0x20000000` and redirect `r0` to print it instead of the original `"hello, world"` string, without changing the source code.
|
||||
|
||||
##### GDB Commands
|
||||
|
||||
```gdb
|
||||
(gdb) target extended-remote :3333
|
||||
(gdb) monitor reset halt
|
||||
(gdb) b *0x1000023c # Breakpoint before __wrap_puts
|
||||
(gdb) c # Continue to breakpoint
|
||||
(gdb) set {char[20]} 0x20000000 = {'Y','o','u','r',' ','N','a','m','e','!','\r','\0'}
|
||||
(gdb) set $r0 = 0x20000000 # Redirect r0 to injected message
|
||||
(gdb) c # Resume - serial shows custom message
|
||||
```
|
||||
|
||||
##### Verification
|
||||
```gdb
|
||||
(gdb) x/s 0x20000000 # Should show your injected message
|
||||
(gdb) x/s 0x100019cc # Original string still in Flash
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the string have to live in SRAM instead of flash during runtime?**
|
||||
Flash memory is read-only at runtime. The original string at `0x100019cc` cannot be modified. SRAM starting at `0x20000000` is read-write, so that is where we must place our replacement string.
|
||||
|
||||
2. **What would happen if you forgot the null terminator in your injected string?**
|
||||
`puts()` reads characters until it encounters `\0`. Without it, `puts()` would continue reading past the intended string, printing garbage characters from adjacent memory until a null byte happens to appear. This could crash the program or leak sensitive data.
|
||||
|
||||
3. **How does changing `r0` alter the behavior of `puts()` without touching source code?**
|
||||
In the ARM calling convention, the first function argument is passed in `r0`. When `bl __wrap_puts` executes at `0x1000023c`, it reads the string address from `r0`. By changing `r0` from `0x100019cc` (original Flash string) to `0x20000000` (our SRAM string), we redirect what `puts()` prints.
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 2
|
||||
Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2
|
||||
|
||||
### Exercise 1: Change the Message
|
||||
### Non-Credit Practice Exercise 1: Change the Message
|
||||
|
||||
#### Objective
|
||||
Write your own message into SRAM and redirect `r0` so the running program prints it without changing the source code.
|
||||
@@ -14,7 +14,7 @@ Write your own message into SRAM and redirect `r0` so the running program prints
|
||||
- OpenOCD and `arm-none-eabi-gdb` available in your PATH
|
||||
- Serial monitor (PuTTY/minicom/screen) set to 115200 baud
|
||||
- `build\0x0001_hello-world.elf` present and flashed to the board
|
||||
- Week 2 setup steps (0a–0e) completed: OpenOCD, serial monitor, and GDB ready
|
||||
- Week 2 setup steps (0a–0e) completed: OpenOCD, serial monitor, and GDB ready
|
||||
|
||||
#### Task Description
|
||||
You will create a custom string in SRAM at `0x20000000`, point `r0` at it just before `puts()` runs, and watch the live output change to your message.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 2
|
||||
Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Use a Different SRAM Address
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Attack Summary
|
||||
Write the payload to `0x20001000` instead of `0x20000000` to demonstrate that multiple safe SRAM locations can be used for injection.
|
||||
|
||||
##### GDB Commands
|
||||
|
||||
```gdb
|
||||
(gdb) b *0x1000023c
|
||||
(gdb) c
|
||||
(gdb) set {char[14]} 0x20001000 = {'h','a','c','k','e','d','!','!','!','\r','\0'}
|
||||
(gdb) set $r0 = 0x20001000
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
##### Verification
|
||||
```gdb
|
||||
(gdb) x/s 0x20001000 # Shows "hacked!!!\r"
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **How can you ensure `0x20001000` does not collide with stack usage?**
|
||||
The stack pointer was observed at `0x20082000` (top of stack) and grows downward. Since `0x20001000` is far below the stack region, there is substantial separation. Use `info registers sp` in GDB to verify the current stack pointer is well above your injection address.
|
||||
|
||||
2. **What symptoms would indicate you overwrote an active stack frame?**
|
||||
The program would crash when attempting to return from a function. Symptoms include: unexpected address exceptions, invalid memory access faults, or the program jumping to random addresses. The Link Register return path gets corrupted.
|
||||
|
||||
3. **How would you pick a safe SRAM offset in a larger program with dynamic allocations?**
|
||||
Start from the bottom of SRAM (`0x20000000`) for small static payloads, working upward. Check the linker script to understand memory regions. In this simple program with no heap allocations, both `0x20000000` and `0x20001000` are safe. In larger programs, examine the `.bss` and `.data` section boundaries.
|
||||
+3
-3
@@ -1,17 +1,17 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 2
|
||||
Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2
|
||||
|
||||
### Exercise 2: Use a Different SRAM Address
|
||||
### Non-Credit Practice Exercise 2: Use a Different SRAM Address
|
||||
|
||||
#### Objective
|
||||
Practice writing to an alternate SRAM location and redirecting `r0` so your message prints from `0x20001000` instead of `0x20000000`.
|
||||
|
||||
#### Prerequisites
|
||||
- Raspberry Pi Pico 2 with debug probe connected
|
||||
- OpenOCD, `arm-none-eabi-gdb`, and a serial monitor ready (Week 2 steps 0a–0e complete)
|
||||
- OpenOCD, `arm-none-eabi-gdb`, and a serial monitor ready (Week 2 steps 0a–0e complete)
|
||||
- `build\0x0001_hello-world.elf` flashed and running
|
||||
- Comfortable setting breakpoints at `0x1000023c`
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 2
|
||||
Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Examine Memory Around Your String
|
||||
|
||||
#### Answers
|
||||
|
||||
##### GDB Commands
|
||||
|
||||
```gdb
|
||||
(gdb) set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'}
|
||||
(gdb) x/20b 0x20000000
|
||||
```
|
||||
|
||||
##### Byte Dump Output
|
||||
|
||||
```
|
||||
0x20000000: 0x68 0x61 0x63 0x6b 0x79 0x2c 0x20 0x77
|
||||
0x20000008: 0x6f 0x72 0x6c 0x64 0x0d 0x00 0x00 0x00
|
||||
0x20000010: 0x00 0x00 0x00 0x00
|
||||
```
|
||||
|
||||
##### ASCII Mapping
|
||||
|
||||
| Offset | Hex Value | Character |
|
||||
|--------|-----------|-----------|
|
||||
| 0x00 | `0x68` | h |
|
||||
| 0x01 | `0x61` | a |
|
||||
| 0x02 | `0x63` | c |
|
||||
| 0x03 | `0x6b` | k |
|
||||
| 0x04 | `0x79` | y |
|
||||
| 0x05 | `0x2c` | , (comma) |
|
||||
| 0x06 | `0x20` | (space) |
|
||||
| 0x07 | `0x77` | w |
|
||||
| 0x08 | `0x6f` | o |
|
||||
| 0x09 | `0x72` | r |
|
||||
| 0x0a | `0x6c` | l |
|
||||
| 0x0b | `0x64` | d |
|
||||
| 0x0c | `0x0d` | \r (carriage return) |
|
||||
| 0x0d | `0x00` | \0 (null terminator) |
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Which bytes mark the end of the printable string, and why are they needed?**
|
||||
The last two meaningful bytes are `0x0d` (carriage return `\r`) and `0x00` (null terminator `\0`). The null terminator signals the end of the string to `puts()`. Without it, `puts()` would read past the intended string and print garbage memory until a null byte is encountered.
|
||||
|
||||
2. **How would misaligned writes show up in the byte view?**
|
||||
If you write to an incorrect address or use wrong character offsets, the byte dump would show unexpected values at wrong positions. Characters would appear shifted, and adjacent data structures could be corrupted.
|
||||
|
||||
3. **What risks arise if you overwrite bytes immediately after your string?**
|
||||
Overwriting adjacent bytes could corrupt other data structures in SRAM, such as variables, linked lists, or runtime metadata. This could cause unpredictable crashes or silent data corruption depending on what occupies those memory locations.
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 2
|
||||
Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2
|
||||
|
||||
### Exercise 3: Examine Memory Around Your String
|
||||
### Non-Credit Practice Exercise 3: Examine Memory Around Your String
|
||||
|
||||
#### Objective
|
||||
Inspect the byte-level layout of your injected string in SRAM and correlate bytes to characters.
|
||||
@@ -49,7 +49,7 @@ If needed, re-inject a payload:
|
||||
```
|
||||
|
||||
##### Step 5: Decode the Output
|
||||
- Map each byte to ASCII: e.g., `0x68` → `h`, `0x0d` → `\r`, `0x00` → `\0`.
|
||||
- Map each byte to ASCII: e.g., `0x68` ? `h`, `0x0d` ? `\r`, `0x00` ? `\0`.
|
||||
- Note any bytes before/after the string to ensure you did not overwrite adjacent data.
|
||||
|
||||
##### Step 6: Resume Execution
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 2
|
||||
Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Automate the Hack
|
||||
|
||||
#### Answers
|
||||
|
||||
##### GDB Command Definition
|
||||
|
||||
```gdb
|
||||
(gdb) define hack
|
||||
> set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'}
|
||||
> set $r0 = 0x20000000
|
||||
> c
|
||||
> end
|
||||
```
|
||||
|
||||
##### Usage
|
||||
|
||||
```gdb
|
||||
(gdb) b *0x1000023c
|
||||
(gdb) c
|
||||
(gdb) hack # Executes all three commands at once
|
||||
```
|
||||
|
||||
##### Expected Serial Output
|
||||
|
||||
```
|
||||
hello, world
|
||||
hello, world
|
||||
hello, world
|
||||
hacky, world <-- HACKED! (after hack command executed)
|
||||
hacky, world
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **How could you parameterize the command to accept different strings or addresses?**
|
||||
Standard GDB `define` blocks do not support function parameters directly. However, you can use GDB convenience variables (`set $myaddr = 0x20000000`) and reference them in the macro, or create multiple specific commands like `hack_addr1`, `hack_addr2`. For advanced parameterization, use GDB Python scripting.
|
||||
|
||||
2. **What happens if you define `hack` before setting the breakpoint - will it still work as expected?**
|
||||
The `define` command only creates a macro; it does not execute immediately. The breakpoint must be set and hit before invoking `hack`. The sequence matters: set breakpoint -> run/continue to hit breakpoint -> then call `hack`. Defining the macro before or after the breakpoint does not matter as long as you invoke it at the right time.
|
||||
|
||||
3. **How would you adapt this pattern for multi-step routines (e.g., patch, dump, continue)?**
|
||||
Extend the `define` block with additional commands:
|
||||
```gdb
|
||||
(gdb) define hack_verbose
|
||||
> set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'}
|
||||
> x/20b 0x20000000
|
||||
> set $r0 = 0x20000000
|
||||
> info registers r0
|
||||
> c
|
||||
> end
|
||||
```
|
||||
This dumps memory and registers before continuing, providing verification at each step.
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 2
|
||||
Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2
|
||||
|
||||
### Exercise 4: Automate the Hack
|
||||
### Non-Credit Practice Exercise 4: Automate the Hack
|
||||
|
||||
#### Objective
|
||||
Create a reusable GDB command that injects a string into SRAM, repoints `r0`, and resumes execution with a single call.
|
||||
@@ -56,7 +56,7 @@ You will define a custom GDB command `hack` that writes a payload to `0x20000000
|
||||
|
||||
###### Question 1: How could you parameterize the command to accept different strings or addresses?
|
||||
|
||||
###### Question 2: What happens if you define `hack` before setting the breakpoint—will it still work as expected?
|
||||
###### Question 2: What happens if you define `hack` before setting the breakpoint—will it still work as expected?
|
||||
|
||||
###### Question 3: How would you adapt this pattern for multi-step routines (e.g., patch, dump, continue)?
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Trace a Reset
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Instruction Trace (First 10 Instructions from 0x1000015c)
|
||||
|
||||
| # | Address | Instruction | What It Does | Key Register Change |
|
||||
|---|-------------|----------------------------------------|------------------------------------------------------|---------------------|
|
||||
| 1 | 0x1000015c | `mov.w r0, #0xd0000000` | Loads SIO base address into r0 | r0 = 0xd0000000 |
|
||||
| 2 | 0x10000160 | `ldr r0, [r0, #0]` | Reads CPUID register at SIO base + 0 | r0 = 0 (Core 0) |
|
||||
| 3 | 0x10000162 | `cbz r0, 0x1000016a` | If CPUID == 0 (Core 0), branch to data copy section | PC = 0x1000016a |
|
||||
| 4 | 0x1000016a | `ldr r0, [pc, #...]` | Loads source address for data copy (flash) | r0 = flash addr |
|
||||
| 5 | 0x1000016c | `ldr r1, [pc, #...]` | Loads destination address for data copy (RAM) | r1 = RAM addr |
|
||||
| 6 | 0x1000016e | `ldr r2, [pc, #...]` | Loads end address for data copy | r2 = end addr |
|
||||
| 7 | 0x10000170 | `cmp r1, r2` | Checks if all data has been copied | Flags updated |
|
||||
| 8 | 0x10000172 | `bhs 0x10000178` | If done (dest >= end), skip to BSS clear | PC conditional |
|
||||
| 9 | 0x10000174 | `ldm r0!, {r3}` | Loads 4 bytes from flash source, auto-increments r0 | r3 = data, r0 += 4 |
|
||||
| 10| 0x10000176 | `stm r1!, {r3}` | Stores 4 bytes to RAM destination, auto-increments r1| r1 += 4 |
|
||||
|
||||
##### GDB Session
|
||||
|
||||
```gdb
|
||||
(gdb) b *0x1000015c
|
||||
(gdb) monitor reset halt
|
||||
(gdb) c
|
||||
Breakpoint 1, 0x1000015c in _reset_handler ()
|
||||
(gdb) si
|
||||
(gdb) disas $pc,+2
|
||||
(gdb) info registers r0
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the reset handler check the CPUID before doing anything else?**
|
||||
The RP2350 has two Cortex-M33 cores that share the same reset handler entry point. The CPUID check at SIO base `0xd0000000` returns 0 for Core 0 and 1 for Core 1. Only Core 0 should perform the one-time initialization (data copy, BSS clear, calling `main()`). Without this check, both cores would simultaneously try to initialize RAM, causing data corruption and race conditions.
|
||||
|
||||
2. **What would happen if Core 1 tried to run the same initialization code as Core 0?**
|
||||
Both cores would simultaneously write to the same RAM locations during the data copy and BSS clear phases. This would cause data corruption due to race conditions—values could be partially written or overwritten unpredictably. The `cbz` instruction at `0x10000162` prevents this: if CPUID != 0, the code branches to `hold_non_core0_in_bootrom`, which sends Core 1 back to the bootrom to wait until Core 0 explicitly launches it later.
|
||||
|
||||
3. **Which registers are used in the first 10 instructions, and why those specific ones?**
|
||||
The first 10 instructions use `r0`, `r1`, `r2`, and `r3`. These are the ARM "caller-saved" scratch registers (r0–r3) that don't need to be preserved across function calls. Since the reset handler is the very first code to run (no caller to preserve state for), using these low registers is both efficient (16-bit Thumb encodings) and safe. `r0` handles CPUID check and source pointer, `r1` is the destination pointer, `r2` is the end marker, and `r3` is the data transfer register.
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Exercise 1: Trace a Reset
|
||||
### Non-Credit Practice Exercise 1: Trace a Reset
|
||||
|
||||
#### Objective
|
||||
Single-step through the first 10 instructions of the reset handler to understand exactly what happens when the RP2350 powers on or resets.
|
||||
@@ -119,7 +119,7 @@ For each of the 10 instructions:
|
||||
- Use `disas $pc,+20` to see upcoming instructions without stepping through them
|
||||
- Use `info registers` to see all register values at any point
|
||||
- If you step past where you wanted to stop, just `monitor reset halt` and start over
|
||||
- Keep notes as you go—this is detective work!
|
||||
- Keep notes as you go—this is detective work!
|
||||
|
||||
#### Next Steps
|
||||
- Try stepping all the way through to the data copy loop
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Find the Stack Size
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Initial Stack Pointer
|
||||
|
||||
```gdb
|
||||
(gdb) x/x 0x10000000
|
||||
0x10000000 <__vectors>: 0x20082000
|
||||
```
|
||||
|
||||
The initial stack pointer is **0x20082000**, stored as the first entry in the vector table.
|
||||
|
||||
##### Stack Limit
|
||||
|
||||
The stack limit is **0x20078000**, defined by the linker script.
|
||||
|
||||
```gdb
|
||||
(gdb) info symbol __StackLimit
|
||||
__StackLimit in section .stack
|
||||
```
|
||||
|
||||
##### Stack Size Calculation
|
||||
|
||||
| Value | Hex | Decimal |
|
||||
|-----------------|-------------|---------------|
|
||||
| Stack Top | 0x20082000 | 537,108,480 |
|
||||
| Stack Limit | 0x20078000 | 537,067,520 |
|
||||
| **Stack Size** | 0x0000A000 | **40,960 bytes** |
|
||||
|
||||
```
|
||||
Stack Size = 0x20082000 - 0x20078000 = 0xA000 = 40,960 bytes
|
||||
40,960 ÷ 1,024 = 40 KB
|
||||
```
|
||||
|
||||
##### Memory Region Verification
|
||||
|
||||
| Region | Start | End | Size |
|
||||
|-----------|-------------|-------------|--------|
|
||||
| RAM | 0x20000000 | 0x20080000 | 512 KB |
|
||||
| SCRATCH_X | 0x20080000 | 0x20081000 | 4 KB |
|
||||
| SCRATCH_Y | 0x20081000 | 0x20082000 | 4 KB |
|
||||
| **Stack** | 0x20078000 | 0x20082000 | **40 KB** |
|
||||
|
||||
The stack spans from the upper portion of main RAM through SCRATCH_X and into SCRATCH_Y.
|
||||
|
||||
##### Runtime Stack Usage at main()
|
||||
|
||||
```gdb
|
||||
(gdb) b main
|
||||
(gdb) c
|
||||
(gdb) info registers sp
|
||||
sp 0x20081fc8 0x20081fc8
|
||||
```
|
||||
|
||||
Stack used at `main()` entry: `0x20082000 - 0x20081fc8 = 0x38 = 56 bytes`
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why is the stack 40 KB instead of just fitting in the 4 KB SCRATCH_Y region?**
|
||||
The stack pointer is initialized at the top of SCRATCH_Y (`0x20082000`), but the stack grows **downward**. As functions are called and local variables are allocated, the stack pointer decreases past the SCRATCH_Y boundary (`0x20081000`), through SCRATCH_X, and into the upper portion of main RAM. The linker sets the stack limit at `0x20078000` to give the stack 40 KB of room, which is sufficient for typical embedded applications with nested function calls.
|
||||
|
||||
2. **What happens if the stack grows beyond 0x20078000?**
|
||||
A **stack overflow** occurs. The stack would collide with the heap or global data stored in the lower portion of RAM. This can corrupt variables, overwrite heap metadata, or cause a HardFault if memory protection is enabled. On the RP2350 with Cortex-M33 MPU support, a MemManage fault could be triggered if stack guard regions are configured.
|
||||
|
||||
3. **How would you detect a stack overflow during runtime?**
|
||||
Several methods exist: (a) Write a known "canary" pattern (e.g., `0xDEADBEEF`) at the stack limit and periodically check if it's been overwritten. (b) Use the Cortex-M33 MPU to set a guard region at `0x20078000` that triggers a MemManage fault on access. (c) In GDB, use a watchpoint: `watch *(int*)0x20078000` to break if the stack reaches the limit. (d) Use the `stackusage` GDB macro from the exercise to monitor usage.
|
||||
|
||||
4. **Why does the stack grow downward instead of upward?**
|
||||
This is an ARM architecture convention inherited from early processor designs. Growing downward allows the stack and heap to grow toward each other from opposite ends of RAM, maximizing memory utilization without needing to know each region's size in advance. The stack starts at the highest address and grows down, while the heap starts at a lower address and grows up. If they meet, memory is exhausted—but this layout gives both regions the maximum possible space.
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Exercise 2: Find the Stack Size
|
||||
### Non-Credit Practice Exercise 2: Find the Stack Size
|
||||
|
||||
#### Objective
|
||||
Calculate the size of the stack by examining the vector table, understanding the linker script's memory layout, and performing manual calculations.
|
||||
@@ -92,7 +92,7 @@ Let's convert to decimal:
|
||||
##### Step 5: Convert to Kilobytes
|
||||
|
||||
```
|
||||
Bytes to KB = 40,960 ÷ 1,024 = 40 KB
|
||||
Bytes to KB = 40,960 ÷ 1,024 = 40 KB
|
||||
```
|
||||
|
||||
So the stack is **40 KB** in size.
|
||||
@@ -102,7 +102,7 @@ So the stack is **40 KB** in size.
|
||||
Cross-check with the memory layout:
|
||||
- **RAM**: `0x20000000` - `0x20080000` (512 KB)
|
||||
- **SCRATCH_X**: `0x20080000` - `0x20081000` (4 KB)
|
||||
- **SCRATCH_Y**: `0x20081000` - `0x20082000` (4 KB) ← Stack lives here
|
||||
- **SCRATCH_Y**: `0x20081000` - `0x20082000` (4 KB) ? Stack lives here
|
||||
- **Stack range**: `0x20078000` - `0x20082000` (40 KB)
|
||||
|
||||
The stack extends from SCRATCH_Y down into the upper portion of main RAM.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Examine All Vectors
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Vector Table Dump
|
||||
|
||||
```gdb
|
||||
(gdb) x/16x 0x10000000
|
||||
0x10000000 <__vectors>: 0x20082000 0x1000015d 0x1000011b 0x1000011d
|
||||
0x10000010 <__vectors+16>: 0x1000011f 0x10000121 0x10000123 0x00000000
|
||||
0x10000020 <__vectors+32>: 0x00000000 0x00000000 0x00000000 0x10000125
|
||||
0x10000030 <__vectors+48>: 0x00000000 0x00000000 0x10000127 0x10000129
|
||||
```
|
||||
|
||||
##### Complete Vector Table Map
|
||||
|
||||
| Offset | Vector # | Raw Value | Address Type | Actual Addr | Handler Name |
|
||||
|--------|----------|-------------|---------------|-------------|------------------|
|
||||
| 0x00 | — | 0x20082000 | Stack Pointer | N/A | __StackTop |
|
||||
| 0x04 | 1 | 0x1000015d | Code (Thumb) | 0x1000015c | _reset_handler |
|
||||
| 0x08 | 2 | 0x1000011b | Code (Thumb) | 0x1000011a | isr_nmi |
|
||||
| 0x0C | 3 | 0x1000011d | Code (Thumb) | 0x1000011c | isr_hardfault |
|
||||
| 0x10 | 4 | 0x1000011f | Code (Thumb) | 0x1000011e | isr_memmanage |
|
||||
| 0x14 | 5 | 0x10000121 | Code (Thumb) | 0x10000120 | isr_busfault |
|
||||
| 0x18 | 6 | 0x10000123 | Code (Thumb) | 0x10000122 | isr_usagefault |
|
||||
| 0x1C | 7 | 0x00000000 | Reserved | N/A | (Reserved) |
|
||||
| 0x20 | 8 | 0x00000000 | Reserved | N/A | (Reserved) |
|
||||
| 0x24 | 9 | 0x00000000 | Reserved | N/A | (Reserved) |
|
||||
| 0x28 | 10 | 0x00000000 | Reserved | N/A | (Reserved) |
|
||||
| 0x2C | 11 | 0x10000125 | Code (Thumb) | 0x10000124 | isr_svcall |
|
||||
| 0x30 | 12 | 0x00000000 | Reserved | N/A | (Reserved) |
|
||||
| 0x34 | 13 | 0x00000000 | Reserved | N/A | (Reserved) |
|
||||
| 0x38 | 14 | 0x10000127 | Code (Thumb) | 0x10000126 | isr_pendsv |
|
||||
| 0x3C | 15 | 0x10000129 | Code (Thumb) | 0x10000128 | isr_systick |
|
||||
|
||||
##### Handler Verification
|
||||
|
||||
```gdb
|
||||
(gdb) info symbol 0x1000015c
|
||||
_reset_handler in section .text
|
||||
(gdb) info symbol 0x1000011a
|
||||
isr_nmi in section .text
|
||||
(gdb) x/3i 0x1000011a
|
||||
0x1000011a <isr_nmi>: bkpt 0x0000
|
||||
0x1000011c <isr_hardfault>: bkpt 0x0000
|
||||
0x1000011e <isr_svcall>: bkpt 0x0000
|
||||
```
|
||||
|
||||
Most default handlers are single `bkpt` instructions—they halt the processor if triggered unexpectedly.
|
||||
|
||||
##### Summary Statistics
|
||||
|
||||
| Category | Count |
|
||||
|---------------------|-------|
|
||||
| Stack Pointer entry | 1 |
|
||||
| Valid code entries | 10 |
|
||||
| Reserved (0x0) | 5 |
|
||||
| **Total entries** | **16** |
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why do all the code addresses end in odd numbers (LSB = 1)?**
|
||||
ARM Cortex-M processors exclusively execute in Thumb mode. The least significant bit (LSB) of vector table entries indicates the instruction set: LSB = 1 means Thumb mode. Since Cortex-M33 only supports Thumb/Thumb-2, all code addresses must have bit 0 set to 1. The actual instruction address is the value with bit 0 cleared (e.g., `0x1000015d` → instruction at `0x1000015c`). This is a hardware requirement—loading an even address into the PC on Cortex-M would cause a HardFault.
|
||||
|
||||
2. **What happens if an exception occurs for a reserved/null vector entry?**
|
||||
If the processor attempts to invoke a handler through a vector entry containing `0x00000000`, it tries to fetch instructions from address `0x00000000` (in the bootrom region). This would either execute bootrom code unexpectedly or trigger a HardFault because the address lacks the Thumb bit (LSB = 0). In practice, a HardFault would occur, and if the HardFault handler itself is invalid, the processor enters a lockup state.
|
||||
|
||||
3. **Why do most exception handlers just contain `bkpt` instructions?**
|
||||
The Pico SDK provides these as **default weak handlers**. They are intentionally minimal—a `bkpt` (breakpoint) instruction halts the processor immediately so a debugger can catch the event. This is a safety mechanism: rather than letting an unhandled exception corrupt state or silently continue, the default handlers stop execution so the developer can diagnose the problem. Application code can override any handler by defining a function with the matching name (the weak linkage gets replaced).
|
||||
|
||||
4. **How would you replace a default handler with your own custom handler?**
|
||||
Define a C function with the exact handler name (e.g., `void isr_hardfault(void)`) in your application code. Because the SDK declares these handlers as `__attribute__((weak))`, the linker will use your strong definition instead of the default `bkpt` stub. The new function's address (with Thumb bit set) will automatically appear in the vector table at the correct offset. No linker script modification is needed.
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Exercise 3: Examine All Vectors
|
||||
### Non-Credit Practice Exercise 3: Examine All Vectors
|
||||
|
||||
#### Objective
|
||||
Examine the first 16 entries of the vector table to understand the exception handler layout, identify valid code addresses, and recognize the Thumb mode addressing convention.
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Find Your Main Function and Trace Back
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Main Function Location
|
||||
|
||||
```gdb
|
||||
(gdb) info functions main
|
||||
0x10000234 int main(void);
|
||||
```
|
||||
|
||||
`main()` is at address **0x10000234**.
|
||||
|
||||
##### Disassembly of main()
|
||||
|
||||
```gdb
|
||||
(gdb) x/10i 0x10000234
|
||||
0x10000234 <main>: push {r7, lr}
|
||||
0x10000236 <main+2>: sub sp, #8
|
||||
0x10000238 <main+4>: add r7, sp, #0
|
||||
0x1000023a <main+6>: bl 0x100012c4 <stdio_init_all>
|
||||
0x1000023e <main+10>: movw r0, #404 @ 0x194
|
||||
0x10000242 <main+14>: movt r0, #4096 @ 0x1000
|
||||
0x10000246 <main+18>: bl 0x1000023c <__wrap_puts>
|
||||
0x1000024a <main+22>: b.n 0x1000023e <main+10>
|
||||
```
|
||||
|
||||
##### First Function Call
|
||||
|
||||
The first function call is at offset +6:
|
||||
```
|
||||
0x1000023a <main+6>: bl 0x100012c4 <stdio_init_all>
|
||||
```
|
||||
|
||||
`stdio_init_all()` initializes all standard I/O systems (USB CDC, UART) so `printf()` and `puts()` can output to the serial console.
|
||||
|
||||
##### Link Register (Caller Identification)
|
||||
|
||||
```gdb
|
||||
(gdb) b main
|
||||
(gdb) c
|
||||
(gdb) info registers lr
|
||||
lr 0x1000018b 268435851
|
||||
```
|
||||
|
||||
LR = **0x1000018b** (Thumb address), actual return address = **0x1000018a**.
|
||||
|
||||
##### Caller Disassembly
|
||||
|
||||
```gdb
|
||||
(gdb) x/10i 0x10000186
|
||||
0x10000186 <platform_entry>: ldr r1, [pc, #80]
|
||||
0x10000188 <platform_entry+2>: blx r1 → calls runtime_init()
|
||||
0x1000018a <platform_entry+4>: ldr r1, [pc, #80] → LR points here (return from main)
|
||||
0x1000018c <platform_entry+6>: blx r1 → THIS called main()
|
||||
0x1000018e <platform_entry+8>: ldr r1, [pc, #80]
|
||||
0x10000190 <platform_entry+10>: blx r1 → calls exit()
|
||||
0x10000192 <platform_entry+12>: bkpt 0x0000
|
||||
```
|
||||
|
||||
##### Complete Boot Chain
|
||||
|
||||
```
|
||||
Power On
|
||||
→ Bootrom (0x00000000)
|
||||
→ Vector Table (0x10000000)
|
||||
→ _reset_handler (0x1000015c)
|
||||
→ Data Copy & BSS Clear
|
||||
→ platform_entry (0x10000186)
|
||||
→ runtime_init() (first blx)
|
||||
→ main() (second blx) ← 0x10000234
|
||||
→ stdio_init_all() (first call in main)
|
||||
→ puts() loop (infinite)
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the link register point 4 bytes after the `blx` instruction that called main?**
|
||||
The LR stores the **return address**—the instruction to execute after `main()` returns. The `blx r1` instruction at `0x10000188` (which calls `runtime_init`) is 2 bytes, and the `blx r1` at `0x1000018c` (which calls `main`) is also 2 bytes. The LR is set to the instruction immediately following the `blx` that called `main()`, which is `0x1000018a` (the `ldr` after `runtime_init`'s call). Actually, `platform_entry+4` at `0x1000018a` is where execution resumes, and the actual `blx` that calls main is at `+6` (`0x1000018c`), so LR = `0x1000018e` + Thumb bit. The key point: LR always points to the next instruction after the branch, so the caller can resume where it left off.
|
||||
|
||||
2. **What would happen if `main()` tried to return (instead of looping forever)?**
|
||||
Execution would return to `platform_entry` at the address stored in LR. Looking at the disassembly, `platform_entry` would proceed to execute the third `blx r1` at offset +10, which calls `exit()`. The `exit()` function would perform cleanup and ultimately halt the processor. After `exit()`, there's a `bkpt` instruction as a safety net. In practice on bare-metal embedded systems, returning from `main()` is generally avoided because there's no OS to return to.
|
||||
|
||||
3. **How can you tell from the disassembly that main contains an infinite loop?**
|
||||
The instruction at `0x1000024a` is `b.n 0x1000023e <main+10>`, which is an **unconditional branch** back to the `movw r0, #404` instruction that loads the string address. This is a `b` (branch) with no condition code, meaning it always jumps backward. There is no path that reaches the end of the function or a `pop {r7, pc}` (return). The `push {r7, lr}` at the start saves the return address but it's never restored—the function loops forever.
|
||||
|
||||
4. **Why is `stdio_init_all()` called before the printf loop?**
|
||||
`stdio_init_all()` configures the hardware interfaces (USB CDC and/or UART) that `printf()`/`puts()` uses for output. Without this initialization, the serial output drivers are not set up—writes to stdout would go nowhere or cause a fault. It must be called exactly once before any I/O operation. Calling it inside the loop would repeatedly reinitialize the hardware, wasting time and potentially disrupting active connections.
|
||||
+17
-17
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Exercise 4: Find Your Main Function and Trace Back
|
||||
### Non-Credit Practice Exercise 4: Find Your Main Function and Trace Back
|
||||
|
||||
#### Objective
|
||||
Locate the `main()` function, examine its first instructions, identify the first function call, and trace backward to discover where `main()` was called from.
|
||||
@@ -129,8 +129,8 @@ Subtract 1 to remove the Thumb bit and disassemble:
|
||||
```
|
||||
0x10000186 <platform_entry>: ldr r1, [pc, #80]
|
||||
0x10000188 <platform_entry+2>: blx r1
|
||||
0x1000018a <platform_entry+4>: ldr r1, [pc, #80] ← LR points here
|
||||
0x1000018c <platform_entry+6>: blx r1 ← This called main
|
||||
0x1000018a <platform_entry+4>: ldr r1, [pc, #80] ? LR points here
|
||||
0x1000018c <platform_entry+6>: blx r1 ? This called main
|
||||
0x1000018e <platform_entry+8>: ldr r1, [pc, #80]
|
||||
0x10000190 <platform_entry+10>: blx r1
|
||||
0x10000192 <platform_entry+12>: bkpt 0x0000
|
||||
@@ -142,11 +142,11 @@ Working backward from `main()`:
|
||||
|
||||
```
|
||||
platform_entry (0x10000186)
|
||||
↓ calls (blx at +2)
|
||||
? calls (blx at +2)
|
||||
runtime_init() (0x1000024c)
|
||||
↓ calls (blx at +6)
|
||||
main() (0x10000234) ← We are here
|
||||
↓ will call (blx at +6)
|
||||
? calls (blx at +6)
|
||||
main() (0x10000234) ? We are here
|
||||
? will call (blx at +6)
|
||||
stdio_init_all() (0x100012c4)
|
||||
```
|
||||
|
||||
@@ -173,21 +173,21 @@ You've now traced the complete path:
|
||||
|
||||
```
|
||||
1. Reset (Power-on)
|
||||
↓
|
||||
?
|
||||
2. Bootrom (0x00000000)
|
||||
↓
|
||||
?
|
||||
3. Vector Table (0x10000000)
|
||||
↓
|
||||
?
|
||||
4. _reset_handler (0x1000015c)
|
||||
↓
|
||||
?
|
||||
5. Data Copy & BSS Clear
|
||||
↓
|
||||
?
|
||||
6. platform_entry (0x10000186)
|
||||
↓
|
||||
?
|
||||
7. runtime_init() (first call)
|
||||
↓
|
||||
8. main() (second call) ← Exercise focus
|
||||
↓
|
||||
?
|
||||
8. main() (second call) ? Exercise focus
|
||||
?
|
||||
9. stdio_init_all() (first line of main)
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Analyze Variable Storage in Ghidra
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Main Function Analysis
|
||||
|
||||
| Item | Value/Location | Notes |
|
||||
|-----------------------|---------------|---------------------------------|
|
||||
| Main function address | 0x10000234 | Entry point of program |
|
||||
| Age value (hex) | 0x2b | Optimized constant |
|
||||
| Age value (decimal) | 43 | Original variable value |
|
||||
| Variable in memory? | No | Compiler optimized it away |
|
||||
| printf address | 0x10003100 | Standard library function |
|
||||
| stdio_init_all addr | 0x100030cc | I/O initialization |
|
||||
| Format string | "age: %d\r\n" | Located in .rodata section |
|
||||
|
||||
##### Decompiled main() After Renaming
|
||||
|
||||
```c
|
||||
int main(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
do {
|
||||
printf("age: %d\r\n", 0x2b);
|
||||
} while (true);
|
||||
}
|
||||
```
|
||||
|
||||
##### Hex-to-Decimal Conversion
|
||||
|
||||
```
|
||||
0x2b = (2 × 16) + 11 = 32 + 11 = 43
|
||||
```
|
||||
|
||||
The compiler replaced both `age = 42` and `age = 43` with the final constant value `0x2b` (43) as an immediate operand in `movs r1, #0x2b`.
|
||||
|
||||
##### Assembly Listing
|
||||
|
||||
```assembly
|
||||
movs r1, #0x2b ; Load 43 directly into r1 (printf argument)
|
||||
ldr r0, [pc, #...] ; Load format string address
|
||||
bl printf ; Call printf
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why did the compiler optimize away the `age` variable?**
|
||||
The compiler performs **dead store elimination** and **constant propagation**. The initial assignment `age = 42` is immediately overwritten by `age = 43` with no intervening reads of the value 42. Since the only value ever observed is 43, the compiler replaces all references to `age` with the constant `0x2b` (43) as an immediate operand. No memory allocation is needed—the value lives entirely in the instruction encoding (`movs r1, #0x2b`).
|
||||
|
||||
2. **In what memory section would `age` have been stored if it wasn't optimized away?**
|
||||
As a local variable declared inside `main()`, `age` would have been stored on the **stack** (in RAM at `0x2000xxxx`). The compiler would allocate space by subtracting from SP, store the value with a `str` instruction, and load it back with `ldr` before passing it to `printf`. If `age` were declared as a global initialized variable, it would be placed in the **`.data`** section (RAM, initialized from flash at boot). If declared as `static` or global but uninitialized, it would go in the **`.bss`** section (RAM, zeroed at boot).
|
||||
|
||||
3. **Where is the string "age: %d\r\n" stored, and why can't it be in RAM?**
|
||||
The format string is stored in the **`.rodata`** (read-only data) section in flash memory at `0x1000xxxx`. It cannot be in RAM because: (a) string literals are constants that never change, so storing them in limited RAM would waste space; (b) flash is non-volatile—the string persists across power cycles without needing to be copied from anywhere; (c) the XIP (Execute In Place) mechanism allows the CPU to read directly from flash, so `.rodata` access is efficient.
|
||||
|
||||
4. **What would happen if we had used `age` in a calculation before reassigning it to 43?**
|
||||
The compiler would be forced to preserve the value 42 because it's actually read before being overwritten. For example, `printf("before: %d\n", age); age = 43;` would require the compiler to generate both `movs r1, #0x2a` (42) for the first print and `movs r1, #0x2b` (43) for subsequent uses. Alternatively, a calculation like `age = age + 1` would allow the compiler to constant-fold `42 + 1 = 43` at compile time and still emit just `#0x2b`. Only if the value depends on runtime input would the variable require actual memory allocation.
|
||||
+9
-9
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 1: Analyze Variable Storage in Ghidra
|
||||
### Non-Credit Practice Exercise 1: Analyze Variable Storage in Ghidra
|
||||
|
||||
#### Objective
|
||||
Import and analyze the `0x0005_intro-to-variables.bin` binary in Ghidra to understand where variables are stored, identify memory sections, and trace how the compiler optimizes variable usage.
|
||||
@@ -26,7 +26,7 @@ You will import the binary into Ghidra, configure it for ARM Cortex-M33, analyze
|
||||
ghidraRun
|
||||
```
|
||||
|
||||
1. Click **File** → **New Project**
|
||||
1. Click **File** ? **New Project**
|
||||
2. Select **Non-Shared Project**
|
||||
3. Click **Next**
|
||||
4. Enter Project Name: `week04-ex01-intro-to-variables`
|
||||
@@ -43,12 +43,12 @@ ghidraRun
|
||||
|
||||
When the import dialog appears:
|
||||
|
||||
1. Click the three dots (**…**) next to **Language**
|
||||
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:
|
||||
Now click **Options…** button:
|
||||
1. Change **Block Name** to: `.text`
|
||||
2. Change **Base Address** to: `10000000` (XIP flash base)
|
||||
3. Click **OK**
|
||||
@@ -107,7 +107,7 @@ Let's figure out what `0x2b` means:
|
||||
|
||||
**Manual calculation:**
|
||||
- `0x2b` in hexadecimal
|
||||
- `2 × 16 + 11 = 32 + 11 = 43` in decimal
|
||||
- `2 × 16 + 11 = 32 + 11 = 43` in decimal
|
||||
|
||||
**In GDB (alternative method):**
|
||||
```gdb
|
||||
@@ -135,14 +135,14 @@ Let's rename the functions to their actual names:
|
||||
|
||||
**Rename FUN_100030cc to stdio_init_all:**
|
||||
1. Click on `FUN_100030cc` in the decompile window
|
||||
2. Right-click → **Edit Function Signature**
|
||||
2. Right-click ? **Edit Function Signature**
|
||||
3. Change name to: `stdio_init_all`
|
||||
4. Change signature to: `bool stdio_init_all(void)`
|
||||
5. Click **OK**
|
||||
|
||||
**Rename FUN_10003100 to printf:**
|
||||
1. Click on `FUN_10003100`
|
||||
2. Right-click → **Edit Function Signature**
|
||||
2. Right-click ? **Edit Function Signature**
|
||||
3. Change name to: `printf`
|
||||
4. Check the **Varargs** checkbox (printf accepts variable arguments)
|
||||
5. Click **OK**
|
||||
@@ -230,7 +230,7 @@ After completing this exercise, you should be able to:
|
||||
- The **Data Type Manager** window shows all recognized data types
|
||||
- If Ghidra's decompiler output looks wrong, try re-analyzing with different options
|
||||
- Remember: optimized code often looks very different from source code
|
||||
- The **Display** → **Function Graph** shows control flow visually
|
||||
- The **Display** ? **Function Graph** shows control flow visually
|
||||
|
||||
#### Next Steps
|
||||
- Proceed to Exercise 2 to learn binary patching
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Patch Binary to Change Variable Value
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Patch Details
|
||||
|
||||
| Item | Original | Patched |
|
||||
|--------------------|-------------- |---------------|
|
||||
| Instruction | movs r1, #0x2b | movs r1, #0x46 |
|
||||
| Hex bytes | 21 2b | 21 46 |
|
||||
| Decimal value | 43 | 70 |
|
||||
| Output | age: 43 | age: 70 |
|
||||
|
||||
##### Patching Steps
|
||||
|
||||
1. Located `movs r1, #0x2b` in Ghidra Listing view
|
||||
2. Right-click → **Patch Instruction** → changed `#0x2b` to `#0x46`
|
||||
3. Verified in Decompile window: `printf("age: %d\r\n", 0x46)`
|
||||
4. Exported: **File → Export Program → Binary** → `0x0005_intro-to-variables-h.bin`
|
||||
5. Converted to UF2:
|
||||
```powershell
|
||||
python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
|
||||
```
|
||||
6. Flashed via BOOTSEL → RPI-RP2 drive
|
||||
|
||||
##### Serial Output
|
||||
|
||||
```
|
||||
age: 70
|
||||
age: 70
|
||||
age: 70
|
||||
...
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why do we need to convert to UF2 format instead of flashing the raw .bin file?**
|
||||
The RP2350 bootloader (accessed via BOOTSEL) expects **UF2 (USB Flashing Format)** files. UF2 is a container format that includes metadata: the target flash address for each 256-byte block, a family ID (`0xe48bff59` for RP2350), and checksums. A raw `.bin` file contains only code bytes with no addressing information—the bootloader wouldn't know where in flash to write the data. UF2 also supports partial updates and is self-describing, making it safer for USB mass storage flashing.
|
||||
|
||||
2. **What is the significance of the base address 0x10000000 in the conversion command?**
|
||||
`0x10000000` is the **XIP (Execute In Place) flash base address** on the RP2350. This tells the UF2 converter that byte 0 of the binary should be written to flash address `0x10000000`. The CPU fetches instructions directly from this address range via the XIP controller. If the base address were wrong (e.g., `0x20000000` for RAM), the code would be written to the wrong location and the processor would fail to boot because the vector table wouldn't be found at the expected address.
|
||||
|
||||
3. **What would happen if you patched the wrong instruction by mistake?**
|
||||
The consequences depend on what was changed: (a) Patching a different `movs` might corrupt an unrelated function parameter, causing incorrect behavior or a crash. (b) Patching opcode bytes (not just the immediate) could create an invalid instruction, triggering a HardFault or UsageFault. (c) Patching inside a multi-byte instruction could split it into two unintended instructions, corrupting the entire subsequent instruction stream. The program would likely crash, output garbage, or hang—requiring reflashing with the original UF2 to recover.
|
||||
|
||||
4. **How can you verify a patch was applied correctly before exporting?**
|
||||
Multiple verification methods: (a) Check the **Decompile window**—it should reflect the new value (e.g., `printf("age: %d\r\n", 0x46)`). (b) Inspect the **Listing window** bytes—confirm the instruction bytes changed from `21 2b` to `21 46`. (c) Use **right-click → Highlight → Highlight Difference** to see patched bytes highlighted. (d) Compare the patched instruction against the ARM Thumb encoding reference to verify the encoding is valid. (e) Check surrounding instructions are unchanged—patches should not accidentally modify adjacent code.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 2: Patch Binary to Change Variable Value
|
||||
### Non-Credit Practice Exercise 2: Patch Binary to Change Variable Value
|
||||
|
||||
#### Objective
|
||||
Use Ghidra's patching capabilities to modify the compiled binary, changing the value printed by the program from 43 to a different value of your choice, then convert and flash the modified binary to the Pico 2.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Analyze and Understand GPIO Control
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Main Function Decompiled (After Renaming)
|
||||
|
||||
```c
|
||||
int main(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
gpio_init(0x10); // Initialize GPIO 16
|
||||
gpio_set_dir(0x10, 1); // Set as output (GPIO_OUT = 1)
|
||||
|
||||
while (true) {
|
||||
printf("age: %d\r\n", 0); // Uninitialized variable = 0
|
||||
gpio_put(0x10, 1); // LED ON
|
||||
sleep_ms(0x1f4); // Wait 500ms
|
||||
gpio_put(0x10, 0); // LED OFF
|
||||
sleep_ms(0x1f4); // Wait 500ms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### GPIO Function Identification Table
|
||||
|
||||
| Auto-Generated Name | Actual Function | Parameters | Identification Method |
|
||||
|---------------------|-----------------|--------------------------------|--------------------------------------|
|
||||
| FUN_100030cc | stdio_init_all | (void) | Called first, no parameters |
|
||||
| FUN_xxxxxxxx | gpio_init | (uint gpio) | Single parameter = pin number (0x10) |
|
||||
| FUN_xxxxxxxx | gpio_set_dir | (uint gpio, bool out) | Two params: pin + direction (1=out) |
|
||||
| FUN_xxxxxxxx | gpio_put | (uint gpio, bool value) | Two params: pin + value (0 or 1) |
|
||||
| FUN_xxxxxxxx | sleep_ms | (uint32_t ms) | Single param = delay (0x1f4 = 500) |
|
||||
| FUN_10003100 | printf | (const char *fmt, ...) | Format string + varargs |
|
||||
|
||||
##### Key Value Conversions
|
||||
|
||||
| Hex | Decimal | Binary | Purpose |
|
||||
|--------|---------|-------------|------------------------|
|
||||
| 0x10 | 16 | 0000 1000 | GPIO pin number (red LED) |
|
||||
| 0x1f4 | 500 | — | Sleep duration (ms) |
|
||||
| 0x01 | 1 | 0000 0001 | GPIO_OUT / LED ON |
|
||||
| 0x00 | 0 | 0000 0000 | GPIO_IN / LED OFF |
|
||||
|
||||
```
|
||||
0x10 = (1 × 16) + 0 = 16
|
||||
0x1f4 = (1 × 256) + (15 × 16) + 4 = 256 + 240 + 4 = 500
|
||||
```
|
||||
|
||||
##### GPIO Memory Map
|
||||
|
||||
| Address | Register | Purpose |
|
||||
|-------------|-------------|------------------------------|
|
||||
| 0x40028000 | IO_BANK0 | GPIO function selection |
|
||||
| 0x40038000 | PADS_BANK0 | GPIO pad configuration |
|
||||
| 0xd0000000 | SIO | Single-cycle I/O |
|
||||
|
||||
GPIO 16 specific:
|
||||
- Pad control: `0x40038000 + (16 × 4) = 0x40038040`
|
||||
- Function select: `0x40028000 + (16 × 4) = 0x40028040`
|
||||
|
||||
##### gpio_put Coprocessor Instruction
|
||||
|
||||
```assembly
|
||||
mcrr p0, #4, r4, r5, c0
|
||||
```
|
||||
|
||||
- `mcrr` = Move to Coprocessor from two ARM Registers
|
||||
- `p0` = Coprocessor 0 (GPIO coprocessor on RP2350)
|
||||
- `r4` = GPIO pin number, `r5` = value (0 or 1)
|
||||
- Single-cycle GPIO operation
|
||||
|
||||
##### Blink Timing Analysis
|
||||
|
||||
- ON duration: `sleep_ms(0x1f4)` = 500ms
|
||||
- OFF duration: `sleep_ms(0x1f4)` = 500ms
|
||||
- Total cycle: 1000ms = 1 second
|
||||
- Blink rate: **1 Hz**
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does gpio_init() need to configure both PADS_BANK0 and IO_BANK0 registers?**
|
||||
These registers control different aspects of GPIO operation. **PADS_BANK0** (`0x40038000`) configures the physical pad properties: input enable (IE), output disable (OD), pull-up/pull-down resistors, drive strength, and slew rate. **IO_BANK0** (`0x40028000`) configures the function multiplexer (FUNCSEL), selecting which internal peripheral drives the pin—SIO (function 5) for software control, UART, SPI, I2C, PWM, etc. Both must be configured: PADS sets the electrical characteristics of the physical pin, while IO_BANK0 routes the correct internal signal to it.
|
||||
|
||||
2. **What is the advantage of using the GPIO coprocessor instruction (`mcrr`) instead of writing to memory-mapped registers?**
|
||||
The `mcrr` coprocessor instruction completes in a **single CPU cycle**, whereas writing to memory-mapped GPIO registers requires multiple cycles: an address load, a data load, and a store instruction (plus potential bus wait states). On the RP2350, the SIO coprocessor provides deterministic, single-cycle access to GPIO outputs, which is critical for bit-banging protocols (like SPI or custom serial) where precise timing is required. The coprocessor path bypasses the AHB/APB bus entirely.
|
||||
|
||||
3. **If you wanted to blink the LED at 10 Hz instead of 1 Hz, what value should `sleep_ms()` use?**
|
||||
At 10 Hz, each full on/off cycle is 100ms. Since the loop has two `sleep_ms()` calls (one for ON, one for OFF), each should be `100 / 2 = 50ms`. In hex: `50 = 0x32`. So both `sleep_ms()` calls should use `sleep_ms(0x32)`.
|
||||
|
||||
4. **What would happen if you called `gpio_put()` on a pin that hasn't been initialized with `gpio_init()` first?**
|
||||
The GPIO pin's function would still be set to its reset default (typically NULL/function 0), not SIO (function 5). The `gpio_put()` coprocessor instruction would update the SIO output register internally, but since the pin's function multiplexer isn't routing SIO to the physical pad, the electrical state of the pin wouldn't change. The LED would remain off. Additionally, without pad configuration, input enable and output disable bits may not be set correctly, further preventing any observable output.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 3: Analyze and Understand GPIO Control
|
||||
### Non-Credit Practice Exercise 3: Analyze and Understand GPIO Control
|
||||
|
||||
#### Objective
|
||||
Import the `0x0008_uninitialized-variables.bin` binary, analyze the GPIO initialization and control sequences, understand how `gpio_init()`, `gpio_set_dir()`, and `gpio_put()` work at the assembly level.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Patch GPIO Binary to Change LED Pin
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Patch Summary
|
||||
|
||||
| Item | Original | Patched | Hex Change |
|
||||
|---------------|-----------------|-----------------|---------------|
|
||||
| LED pin | GPIO 16 | GPIO 17 | 0x10 → 0x11 |
|
||||
| Printed value | 0 (uninitialized)| 66 | 0x00 → 0x42 |
|
||||
| Blink timing | 500ms | 100ms | 0x1f4 → 0x64 |
|
||||
|
||||
##### Detailed Patch Locations
|
||||
|
||||
**1. gpio_init parameter:**
|
||||
```assembly
|
||||
; Before: movs r0, #0x10 (bytes: 10 20)
|
||||
; After: movs r0, #0x11 (bytes: 11 20)
|
||||
```
|
||||
|
||||
**2. gpio_set_dir parameter:**
|
||||
```assembly
|
||||
; Before: movs r3, #0x10 (bytes: 10 23)
|
||||
; After: movs r3, #0x11 (bytes: 11 23)
|
||||
```
|
||||
|
||||
**3. gpio_put (LED ON) parameter:**
|
||||
```assembly
|
||||
; Before: movs r4, #0x10 (bytes: 10 24)
|
||||
; After: movs r4, #0x11 (bytes: 11 24)
|
||||
```
|
||||
|
||||
**4. gpio_put (LED OFF) parameter:**
|
||||
```assembly
|
||||
; Before: movs r4, #0x10 (bytes: 10 24)
|
||||
; After: movs r4, #0x11 (bytes: 11 24)
|
||||
```
|
||||
|
||||
**5. printf value:**
|
||||
```assembly
|
||||
; Before: movs r1, #0x00 (bytes: 00 21)
|
||||
; After: movs r1, #0x42 (bytes: 42 21)
|
||||
```
|
||||
|
||||
**6. sleep_ms (both calls):**
|
||||
```assembly
|
||||
; Before: loads 0x1f4 (500ms)
|
||||
; After: movs r0, #0x64 (100ms)
|
||||
```
|
||||
|
||||
##### Hex Conversions
|
||||
|
||||
```
|
||||
GPIO 17: 17 = 0x11 = 0001 0001 binary
|
||||
Value 66: 66 = 0x42 = 0100 0010 binary
|
||||
100ms: 100 = 0x64 = 0110 0100 binary
|
||||
```
|
||||
|
||||
##### Decompiled Result After All Patches
|
||||
|
||||
```c
|
||||
int main(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
gpio_init(0x11); // GPIO 17 (green LED)
|
||||
gpio_set_dir(0x11, 1); // Output
|
||||
|
||||
while (true) {
|
||||
printf("age: %d\r\n", 0x42); // Prints 66
|
||||
gpio_put(0x11, 1); // Green LED ON
|
||||
sleep_ms(0x64); // 100ms
|
||||
gpio_put(0x11, 0); // Green LED OFF
|
||||
sleep_ms(0x64); // 100ms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Hardware Verification
|
||||
|
||||
- GREEN LED (GPIO 17) blinks at 10 Hz (100ms on, 100ms off)
|
||||
- RED LED (GPIO 16) remains off
|
||||
- Serial output: `age: 66` repeating
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why did we need to patch GPIO 16 in multiple places (gpio_init, gpio_set_dir, gpio_put)?**
|
||||
Each function takes the GPIO pin number as a separate parameter. `gpio_init(16)` configures the pad and function mux for pin 16. `gpio_set_dir(16, 1)` sets pin 16's direction to output. `gpio_put(16, value)` toggles pin 16's output state. These are independent function calls with independent immediate values in the instruction stream—the compiler doesn't share or reuse a single "pin number variable." Each `movs rN, #0x10` loads the pin number fresh for its respective function call. Missing any one patch would result in a mismatch: e.g., initializing pin 17 but toggling pin 16.
|
||||
|
||||
2. **What would happen if you forgot to patch one of the gpio_put calls?**
|
||||
You would get asymmetric behavior. For example, if you patched the "LED ON" `gpio_put` to pin 17 but left the "LED OFF" at pin 16: GPIO 17 (green) would turn on but never turn off (staying permanently lit), while GPIO 16 (red) would receive the "off" command for a pin that was never initialized—which would have no visible effect. The result: green LED stuck on, no blinking.
|
||||
|
||||
3. **How does the instruction encoding differ for immediate values less than 256 vs. greater than 255?**
|
||||
In 16-bit Thumb encoding, `movs Rd, #imm8` can only encode immediate values 0–255 in a single 2-byte instruction. For values > 255 (like 500 = 0x1f4), the compiler must use either: (a) a 32-bit Thumb-2 `movw Rd, #imm16` instruction (4 bytes, can encode 0–65535), (b) a multi-instruction sequence that constructs the value (e.g., `movs` + `lsls` + `add`), or (c) an `ldr Rd, [pc, #offset]` that loads the constant from a literal pool in flash. This is why patching `sleep_ms(500)` may be more complex than patching `gpio_put(16, 1)`.
|
||||
|
||||
4. **If you wanted to make the LED blink at exactly 5 Hz, what sleep_ms value would you use?**
|
||||
At 5 Hz, each complete cycle is `1000 / 5 = 200ms`. With two `sleep_ms()` calls per cycle (ON and OFF), each call should be `200 / 2 = 100ms`. In hex: `100 = 0x64`. So `sleep_ms(0x64)` for each call—which is exactly the value used in this exercise's patch. For a different duty cycle (e.g., 150ms on, 50ms off), you'd use different values for each call while keeping the total at 200ms.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 4: Patch GPIO Binary to Change LED Pin
|
||||
### Non-Credit Practice Exercise 4: Patch GPIO Binary to Change LED Pin
|
||||
|
||||
#### Objective
|
||||
Patch the `0x0008_uninitialized-variables.bin` binary to change which LED blinks, modify the printed value, and change the blink timing, then verify all changes work correctly on hardware.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# 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
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Analyze the Float Binary in Ghidra
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Main Function Analysis
|
||||
|
||||
| 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 | Sign + exponent + top mantissa |
|
||||
| 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 |
|
||||
| stdio_init_all address | 0x10002f5c | I/O initialization |
|
||||
| printf address | 0x100030ec | Standard library function |
|
||||
|
||||
##### IEEE 754 Decoding of 0x4045400000000000
|
||||
|
||||
```
|
||||
r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000
|
||||
r2 = 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000
|
||||
|
||||
Sign bit (bit 63): 0 → Positive
|
||||
Exponent (bits 62-52): 10000000100 = 1028 → 1028 - 1023 = 5
|
||||
Mantissa (bits 51-0): 0101010000...0 → 1.010101 (with implied 1)
|
||||
|
||||
Value = 1.010101₂ × 2⁵ = 101010.1₂ = 32 + 8 + 2 + 0.5 = 42.5 ✓
|
||||
```
|
||||
|
||||
##### Decompiled main() After Renaming
|
||||
|
||||
```c
|
||||
int main(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
do {
|
||||
__wrap_printf("fav_num: %f\r\n", /* r2:r3 = 0x4045400000000000 = 42.5 */);
|
||||
} while (true);
|
||||
}
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the compiler promote a `float` to a `double` when passing it to `printf`?**
|
||||
The C standard (§6.5.2.2) specifies **default argument promotions** for variadic functions like `printf`. When a `float` is passed to a variadic parameter (the `...` part), it is automatically promoted to `double`. This is because historically, floating-point hardware and calling conventions operated more efficiently with double precision. The `printf` function with `%f` always expects a 64-bit `double` on the stack or in the register pair `r2:r3`, never a 32-bit `float`.
|
||||
|
||||
2. **The low word (`r2`) is `0x00000000`. What does this tell you about the fractional part of `42.5`?**
|
||||
It means the fractional part of 42.5 can be represented exactly with very few mantissa bits. The value 0.5 is exactly 2⁻¹ in binary—a single bit. After normalization, the mantissa is `010101000...` which only needs 6 significant bits. All remaining 46 bits (including the entire low 32-bit word) are zero. Values like 0.5, 0.25, 0.125 (negative powers of 2) and their sums always produce clean low words, while values like 0.1 or 0.3 produce repeating binary fractions that fill both words.
|
||||
|
||||
3. **What is the purpose of the exponent bias (1023) in IEEE 754 double-precision?**
|
||||
The bias allows the exponent field to represent both positive and negative exponents using only unsigned integers. The 11-bit exponent field stores values 0–2047. By subtracting the bias (1023), the actual exponent range is −1022 to +1023. This avoids needing a separate sign bit for the exponent and simplifies hardware comparison—doubles can be compared as unsigned integers (for positive values) because larger exponents produce larger bit patterns. The bias value 1023 = 2¹⁰ − 1 is chosen to center the range symmetrically.
|
||||
|
||||
4. **If the sign bit (bit 63) were `1` instead of `0`, what value would the double represent?**
|
||||
The value would be **−42.5**. The sign bit in IEEE 754 is independent of all other fields: flipping bit 63 from 0 to 1 simply negates the value. The hex encoding would change from `0x4045400000000000` to `0xC045400000000000`—only the most significant nibble changes from `4` (`0100`) to `C` (`1100`), with bit 31 of r3 changing from 0 to 1.
|
||||
+25
-25
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# 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
|
||||
### Non-Credit Practice 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.
|
||||
@@ -26,7 +26,7 @@ You will import the float binary into Ghidra, configure it for ARM Cortex-M33, r
|
||||
ghidraRun
|
||||
```
|
||||
|
||||
1. Click **File** → **New Project**
|
||||
1. Click **File** ? **New Project**
|
||||
2. Select **Non-Shared Project**
|
||||
3. Click **Next**
|
||||
4. Enter Project Name: `week05-ex01-floating-point`
|
||||
@@ -43,12 +43,12 @@ ghidraRun
|
||||
|
||||
When the import dialog appears:
|
||||
|
||||
1. Click the three dots (**…**) next to **Language**
|
||||
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:
|
||||
Now click **Options…** button:
|
||||
1. Change **Block Name** to: `.text`
|
||||
2. Change **Base Address** to: `10000000` (XIP flash base)
|
||||
3. Click **OK**
|
||||
@@ -70,7 +70,7 @@ 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**
|
||||
2. Right-click ? **Edit Function Signature**
|
||||
3. Change to: `int main(void)`
|
||||
4. Click **OK**
|
||||
|
||||
@@ -78,13 +78,13 @@ From previous weeks, we know the boot sequence leads to `main()`:
|
||||
|
||||
**Rename stdio_init_all:**
|
||||
1. Click on `FUN_10002f5c` in the decompile window
|
||||
2. Right-click → **Edit Function Signature**
|
||||
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**
|
||||
2. Right-click ? **Edit Function Signature**
|
||||
3. Change to: `int __wrap_printf(char *format,...)`
|
||||
4. Check the **Varargs** checkbox
|
||||
5. Click **OK**
|
||||
@@ -143,8 +143,8 @@ Map the 64-bit IEEE 754 fields:
|
||||
|
||||
```
|
||||
Bit 63 (sign): 0
|
||||
Bits 62–52 (exponent): 10000000100
|
||||
Bits 51–0 (mantissa): 0101010000000000...0000
|
||||
Bits 62–52 (exponent): 10000000100
|
||||
Bits 51–0 (mantissa): 0101010000000000...0000
|
||||
```
|
||||
|
||||
##### Step 10: Decode the Sign Bit
|
||||
@@ -154,14 +154,14 @@ Bit 63 of the double = bit 31 of r3:
|
||||
```
|
||||
r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000
|
||||
^
|
||||
bit 31 = 0 → Positive number
|
||||
bit 31 = 0 ? Positive number
|
||||
```
|
||||
|
||||
IEEE 754 sign rule: `0` = Positive, `1` = Negative.
|
||||
|
||||
##### Step 11: Decode the Exponent
|
||||
|
||||
Extract bits 30–20 from r3:
|
||||
Extract bits 30–20 from r3:
|
||||
|
||||
```
|
||||
0x40454000: 0 10000000100 01010100000000000000
|
||||
@@ -169,7 +169,7 @@ Extract bits 30–20 from r3:
|
||||
sign exponent mantissa (top 20)
|
||||
```
|
||||
|
||||
Exponent bits: `10000000100` = 2¹⁰ + 2² = 1024 + 4 = **1028**
|
||||
Exponent bits: `10000000100` = 2¹° + 2² = 1024 + 4 = **1028**
|
||||
|
||||
Subtract the double-precision bias (1023):
|
||||
|
||||
@@ -177,7 +177,7 @@ $$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$
|
||||
|
||||
##### Step 12: Decode the Mantissa
|
||||
|
||||
High 20 bits of mantissa (from r3 bits 19–0):
|
||||
High 20 bits of mantissa (from r3 bits 19–0):
|
||||
```
|
||||
0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
```
|
||||
@@ -200,15 +200,15 @@ 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 |
|
||||
| 1 | 25 | 32 |
|
||||
| 0 | 24 | 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} ✓$$
|
||||
$$32 + 8 + 2 + 0.5 = \mathbf{42.5} ?$$
|
||||
|
||||
##### Step 14: Find the Format String
|
||||
|
||||
@@ -230,7 +230,7 @@ Create a table of your observations:
|
||||
| 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 |
|
||||
| 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` |
|
||||
@@ -265,7 +265,7 @@ After completing this exercise, you should be able to:
|
||||
#### 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`
|
||||
- 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.
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# 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
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Patch the Float Binary — Changing 42.5 to 99.0
|
||||
|
||||
#### Answers
|
||||
|
||||
##### IEEE 754 Encoding of 99.0
|
||||
|
||||
```
|
||||
Integer: 99 = 1100011₂
|
||||
Fractional: .0 = .0₂
|
||||
Combined: 1100011.0₂
|
||||
Normalized: 1.100011₂ × 2⁶
|
||||
|
||||
Sign: 0 (positive)
|
||||
Exponent: 6 + 1023 = 1029 = 10000000101₂
|
||||
Mantissa: 100011 followed by 46 zeros
|
||||
|
||||
Full double: 0x4058C00000000000
|
||||
```
|
||||
|
||||
##### Patch Summary
|
||||
|
||||
| Register | Old Value (42.5) | New Value (99.0) | Changed? |
|
||||
|----------|-----------------|------------------|----------|
|
||||
| r2 | 0x00000000 | 0x00000000 | No |
|
||||
| r3 | 0x40454000 | 0x4058C000 | **Yes** |
|
||||
|
||||
##### Ghidra Patch
|
||||
|
||||
```
|
||||
DAT_1000024c:
|
||||
Before (little-endian): 00 40 45 40 → 0x40454000
|
||||
After (little-endian): 00 C0 58 40 → 0x4058C000
|
||||
```
|
||||
|
||||
##### Serial Output
|
||||
|
||||
```
|
||||
fav_num: 99.000000
|
||||
fav_num: 99.000000
|
||||
fav_num: 99.000000
|
||||
...
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why did we only need to patch r3 (the high word) and not r2 (the low word)?**
|
||||
Both 42.5 and 99.0 have "clean" fractional parts that can be exactly represented with few mantissa bits. For 42.5, the mantissa is `010101000...0`; for 99.0, it's `100011000...0`. In both cases, all significant mantissa bits fit within the top 20 bits (stored in r3 bits 19–0), leaving the bottom 32 bits (r2) as all zeros. Only values with complex or repeating binary fractions (like 42.52525 or 99.99) need non-zero low words.
|
||||
|
||||
2. **What would the high word be if we wanted to patch the value to `-99.0` instead?**
|
||||
Flip bit 31 of r3 (the sign bit). The current r3 = `0x4058C000` = `0100 0000 0101 1000 1100...`. Setting bit 31 to 1: `0xC058C000` = `1100 0000 0101 1000 1100...`. The full double encoding of −99.0 is `0xC058C00000000000`. Only the most significant nibble changes from `4` to `C`.
|
||||
|
||||
3. **Walk through the encoding of `100.0` as a double. What are the high and low words?**
|
||||
```
|
||||
100 = 1100100₂
|
||||
100.0 = 1100100.0₂ = 1.1001₂ × 2⁶
|
||||
Sign: 0
|
||||
Exponent: 6 + 1023 = 1029 = 10000000101₂
|
||||
Mantissa: 1001 followed by 48 zeros
|
||||
|
||||
Full 64-bit: 0 10000000101 1001000000...0
|
||||
High word (r3): 0x40590000
|
||||
Low word (r2): 0x00000000
|
||||
```
|
||||
Verification: `struct.pack('>d', 100.0).hex()` → `4059000000000000` ✓
|
||||
|
||||
4. **Why do we need the `--family 0xe48bff59` flag when converting to UF2?**
|
||||
The `--family` flag specifies the target chip family in the UF2 file header. `0xe48bff59` is the registered family ID for the RP2350. The bootloader reads this field to verify the firmware is intended for the correct chip before flashing. If the family ID doesn't match (e.g., using the RP2040 ID `0xe48bff56`), the bootloader may reject the firmware or write it incorrectly. This prevents accidentally flashing RP2040 firmware onto an RP2350 (or vice versa), which could cause undefined behavior since the chips have different architectures (Cortex-M0+ vs Cortex-M33).
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## 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
|
||||
### Non-Credit Practice 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.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Analyze the Double Binary in Ghidra
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Register Pair for 42.52525
|
||||
|
||||
| Register | Value | Role |
|
||||
|----------|-------------|---------------|
|
||||
| r2 | 0x645A1CAC | Low 32 bits |
|
||||
| r3 | 0x4045433B | High 32 bits |
|
||||
|
||||
Full double: **0x4045433B645A1CAC**
|
||||
|
||||
##### IEEE 754 Decoding
|
||||
|
||||
```
|
||||
r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011
|
||||
r2 = 0x645A1CAC = 0110 0100 0101 1010 0001 1100 1010 1100
|
||||
|
||||
Sign bit (bit 63): 0 → Positive
|
||||
Exponent (bits 62-52): 10000000100 = 1028 → 1028 - 1023 = 5
|
||||
Mantissa (bits 51-0): 0101010000110011101101100100010110100001110010101100
|
||||
|
||||
Value = 1.0101010000110011...₂ × 2⁵ = 101010.10000110011...₂
|
||||
Integer part: 101010₂ = 32 + 8 + 2 = 42
|
||||
Fractional part: .10000110011... ≈ 0.52525
|
||||
Result: 42.52525 ✓
|
||||
```
|
||||
|
||||
##### Float vs Double Comparison
|
||||
|
||||
| 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) |
|
||||
|
||||
##### Key Assembly
|
||||
|
||||
```assembly
|
||||
10000238 push {r3,r4,r5,lr}
|
||||
1000023a adr r5, [0x10000254]
|
||||
1000023c ldrd r4, r5, [r5, #0x0] ; r4 = 0x645A1CAC, r5 = 0x4045433B
|
||||
10000240 bl stdio_init_all
|
||||
10000244 mov r2, r4 ; r2 = low word
|
||||
10000246 mov r3, r5 ; r3 = high word
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does `42.5` have a zero low word but `42.52525` does not?**
|
||||
The fractional part determines whether the low word is zero. 0.5 in binary is exactly 2⁻¹ = `0.1₂`—a single bit. After normalization, the mantissa for 42.5 is `010101000...0`, needing only 6 significant bits, all fitting in the top 20 mantissa bits within r3. In contrast, 0.52525 is a **repeating binary fraction** that cannot be represented exactly—it requires all 52 mantissa bits to approximate as closely as possible. The lower 32 bits in r2 (`0x645A1CAC`) carry the additional precision needed for this approximation.
|
||||
|
||||
2. **The assembly uses `ldrd r4, r5, [r5, #0x0]` instead of two separate `ldr` instructions. What is the advantage?**
|
||||
`ldrd` (Load Register Double) loads two consecutive 32-bit words from memory in a single instruction, completing in one memory access cycle (or two back-to-back aligned accesses on the bus). Using two separate `ldr` instructions would require two instruction fetches, two decode cycles, and two memory accesses. `ldrd` reduces code size by 4 bytes (one 4-byte instruction vs. two) and improves performance by allowing the memory controller to pipeline both loads. For 64-bit doubles that are always loaded in pairs, `ldrd` is the optimal choice.
|
||||
|
||||
3. **Both the float and double programs have the same exponent (stored as 1028, real exponent 5). Why?**
|
||||
Both 42.5 and 42.52525 fall in the same range: between 32 (2⁵) and 64 (2⁶). Normalization produces `1.xxx × 2⁵` for both values. The exponent is determined solely by the magnitude (which power of 2 the number falls between), not by the fractional precision. Any number from 32.0 to 63.999... would have real exponent 5 (stored as 1028). The mantissa captures the differences—42.5 has mantissa `010101000...` while 42.52525 has `0101010000110011...`.
|
||||
|
||||
4. **If you were patching this double, how many data constants would you need to modify compared to the float exercise?**
|
||||
**Two** data constants—both `DAT_10000254` (low word, r2) and `DAT_10000258` (high word, r3). In the float exercise (42.5), only one constant needed patching because r2 was zero and stayed zero when patching to 99.0. For the double 42.52525, since the low word is already non-zero (`0x645A1CAC`), any new value with a different repeating fraction will require changing both words. The only exception would be patching to a value whose low word happens to also be `0x645A1CAC` (virtually impossible for an arbitrary target value).
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## 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
|
||||
### Non-Credit Practice 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.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Patch the Double Binary — Changing 42.52525 to 99.99
|
||||
|
||||
#### Answers
|
||||
|
||||
##### IEEE 754 Encoding of 99.99
|
||||
|
||||
```
|
||||
Integer: 99 = 1100011₂
|
||||
Fractional: .99 ≈ .111111010111...₂ (repeating)
|
||||
Combined: 1100011.111111010111...₂
|
||||
Normalized: 1.100011111111010111...₂ × 2⁶
|
||||
|
||||
Sign: 0 (positive)
|
||||
Exponent: 6 + 1023 = 1029 = 10000000101₂
|
||||
Mantissa: 1000111111110101 11000010100011110101 11000010100011110...₂ (52 bits)
|
||||
|
||||
Full double: 0x4058FF5C28F5C28F
|
||||
```
|
||||
|
||||
Python verification: `struct.pack('>d', 99.99).hex()` → `4058ff5c28f5c28f` ✓
|
||||
|
||||
##### Patch Summary
|
||||
|
||||
| Register | Old Value (42.52525) | New Value (99.99) | Changed? |
|
||||
|----------|---------------------|-------------------|----------|
|
||||
| r2 | 0x645A1CAC | 0x28F5C28F | **Yes** |
|
||||
| r3 | 0x4045433B | 0x4058FF5C | **Yes** |
|
||||
|
||||
##### Ghidra Patches
|
||||
|
||||
**Low word (DAT_10000254):**
|
||||
```
|
||||
Before (little-endian): AC 1C 5A 64 → 0x645A1CAC
|
||||
After (little-endian): 8F C2 F5 28 → 0x28F5C28F
|
||||
```
|
||||
|
||||
**High word (DAT_10000258):**
|
||||
```
|
||||
Before (little-endian): 3B 43 45 40 → 0x4045433B
|
||||
After (little-endian): 5C FF 58 40 → 0x4058FF5C
|
||||
```
|
||||
|
||||
##### Serial Output
|
||||
|
||||
```
|
||||
fav_num: 99.990000
|
||||
fav_num: 99.990000
|
||||
fav_num: 99.990000
|
||||
...
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why did both r2 and r3 change when patching 42.52525 → 99.99, but only r3 changed when patching 42.5 → 99.0?**
|
||||
Both 42.5 and 99.0 have "clean" fractional parts (0.5 and 0.0 respectively) that are exact in binary—they need very few mantissa bits, all fitting in the top 20 bits of r3. The low word (r2) remains `0x00000000` for both. In contrast, 42.52525 and 99.99 both have repeating binary fractions (0.52525 and 0.99 respectively) that require all 52 mantissa bits to approximate. Since the low 32 bits of the mantissa live in r2, changing from one repeating fraction to another necessarily changes both r2 and r3.
|
||||
|
||||
2. **The multiply-by-2 method for 0.99 produces a repeating pattern. What does this mean for the precision of the stored value?**
|
||||
It means 99.99 **cannot** be represented exactly as an IEEE 754 double. The binary fraction 0.111111010111... repeats indefinitely, but the mantissa only has 52 bits. The stored value is the closest 52-bit approximation, which is 99.98999999999999... (off by approximately 10⁻¹⁴). This is a fundamental limitation of binary floating-point: decimal fractions that aren't sums of negative powers of 2 always produce repeating binary expansions. The `printf` output rounds to `99.990000` because the default `%lf` precision (6 decimal places) hides the tiny error.
|
||||
|
||||
3. **If you wanted to patch the double to `100.0` instead of `99.99`, how many data constants would need to change?**
|
||||
**Both** would need to change—but for the opposite reason. Currently r2 = `0x645A1CAC` (non-zero). For 100.0: `struct.pack('>d', 100.0).hex()` = `4059000000000000`, so r3 = `0x40590000` and r2 = `0x00000000`. The r2 constant must be patched from `0x645A1CAC` to `0x00000000`, and r3 from `0x4045433B` to `0x40590000`. Even though the low word becomes zero, you still need to patch it because it was previously non-zero.
|
||||
|
||||
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?**
|
||||
The float binary uses separate instructions: `movs r4, #0x0` (loads zero into r4 for the low word) and `ldr r5, [DAT_1000024c]` (loads the high word from a literal pool). The double binary uses a single `ldrd r4, r5, [r5, #0x0]` instruction that loads both words from consecutive memory addresses in one operation. The `ldrd` approach is more efficient (fewer instructions, single memory transaction) and is preferred when both words carry meaningful data. The float's approach works fine because one word is a trivially loaded zero.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## 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
|
||||
### Non-Credit Practice 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.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 6
|
||||
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Change the Static Variable Initial Value from 42 to 100
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Static Variable Location
|
||||
|
||||
| Item | Value | Notes |
|
||||
|---------------------------|-------------|--------------------------------|
|
||||
| RAM address (runtime) | 0x200005a8 | Where variable lives at runtime |
|
||||
| Flash address (init value)| Calculated | In .data section of flash |
|
||||
| Original value (hex) | 0x2A | 42 decimal |
|
||||
| Patched value (hex) | 0x64 | 100 decimal |
|
||||
| File offset | flash_addr - 0x10000000 | Binary base subtraction |
|
||||
|
||||
##### GDB Session
|
||||
|
||||
```gdb
|
||||
(gdb) x/1db 0x200005a8
|
||||
0x200005a8: 42
|
||||
(gdb) find /b 0x10000000, 0x10010000, 0x2a
|
||||
```
|
||||
|
||||
##### Serial Output After Patch
|
||||
|
||||
```
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 100
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 101
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 102
|
||||
...
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the initial value live in flash AND get copied to RAM? Why not just use flash directly?**
|
||||
Static variables need to be **modifiable** at runtime—the program increments `static_fav_num` each iteration. Flash memory is read-only during normal execution (it requires a special erase/program sequence to modify). So the initial value is stored in flash as a template, and the startup code (`crt0.S`) copies the entire `.data` section from flash to RAM before `main()` runs. This gives the variable its correct starting value (42) in writable RAM where subsequent `adds` and `strb` instructions can modify it freely.
|
||||
|
||||
2. **The static variable wraps around at 255 (since it's `uint8_t`). After patching the initial value to 100, after how many iterations will it overflow back to 0?**
|
||||
A `uint8_t` overflows from 255 to 0. Starting at 100 and incrementing by 1: it takes `255 - 100 = 155` increments to reach 255, then one more to wrap to 0. So it overflows after **156 iterations**. Compare to the original: starting at 42, it takes `255 - 42 + 1 = 214` iterations.
|
||||
|
||||
3. **If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not?**
|
||||
No. `regular_fav_num` is a **local variable** that the compiler optimized to an immediate constant (`movs r1, #0x2a`), just like Week 4's `age` variable. It's encoded directly in the instruction opcode in the `.text` section, not in the `.data` section. You would need to find the `movs r1, #0x2a` instruction in the code and patch the immediate byte from `0x2a` to your desired value. The `.data` section only contains initialized static/global variables.
|
||||
|
||||
4. **What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?**
|
||||
Yes. The linker places all initialized static variables contiguously in the `.data` section. Their initial values are stored in the same order in flash, packed adjacent to each other (possibly with alignment padding). The startup code performs a single `memcpy`-like loop that copies the entire `.data` block from flash to RAM. So if you had `static uint8_t a = 42;` and `static uint8_t b = 99;`, the bytes `0x2A` and `0x63` would be adjacent (or nearly so) in flash, and both would be copied to their respective RAM addresses during boot.
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 6
|
||||
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
|
||||
|
||||
### Exercise 1: Change the Static Variable Initial Value from 42 to 100
|
||||
### Non-Credit Practice Exercise 1: Change the Static Variable Initial Value from 42 to 100
|
||||
|
||||
#### Objective
|
||||
Use GDB to locate the static variable `static_fav_num` in the `.data` section of the `0x0014_static-variables` binary, calculate the corresponding file offset, patch the initial value from `42` (`0x2A`) to `100` (`0x64`) using a hex editor, convert the patched binary to UF2 format, and flash it to the Pico 2 to verify the change.
|
||||
@@ -68,7 +68,7 @@ Examine the disassembly to find data copy references. The initial value `42` (`0
|
||||
(gdb) find /b 0x10000000, 0x10010000, 0x2a
|
||||
```
|
||||
|
||||
This searches the flash region for the byte `0x2A`. You may get multiple hits — look for one that is in the data initialization area (typically near the end of the code section).
|
||||
This searches the flash region for the byte `0x2A`. You may get multiple hits — look for one that is in the data initialization area (typically near the end of the code section).
|
||||
|
||||
##### Step 4: Confirm the Address
|
||||
|
||||
@@ -98,7 +98,7 @@ For example, if the initial value is at `0x10004xxx`:
|
||||
3. Enter the calculated offset
|
||||
4. You should see the byte `2A` at this position
|
||||
5. Change `2A` to `64` (100 in decimal)
|
||||
6. Click **File** → **Save As** → `0x0014_static-variables-h.bin` (in the same `build` directory)
|
||||
6. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory)
|
||||
|
||||
##### Step 7: Convert to UF2 and Flash
|
||||
|
||||
@@ -116,7 +116,7 @@ python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --fam
|
||||
**Expected serial output:**
|
||||
```
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 100 ← Starts at 100 now!
|
||||
static_fav_num: 100 ? Starts at 100 now!
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 101
|
||||
regular_fav_num: 42
|
||||
@@ -143,13 +143,13 @@ After completing this exercise, you should be able to:
|
||||
|
||||
###### Question 3: If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not?
|
||||
|
||||
###### Question 4: What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?
|
||||
###### Question 4: What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?
|
||||
|
||||
#### Tips and Hints
|
||||
- The `find` command in GDB can search for bytes, halfwords, or words in any memory range
|
||||
- Static variables with initial values are in `.data`; without initial values they're in `.bss`
|
||||
- The startup code (`crt0`) copies the entire `.data` section from flash to RAM before calling `main()`
|
||||
- HxD shows both hex and ASCII — the value `0x2A` is the ASCII character `*`
|
||||
- HxD shows both hex and ASCII — the value `0x2A` is the ASCII character `*`
|
||||
|
||||
#### Next Steps
|
||||
- Proceed to Exercise 2 to try a more complex hack (reversing GPIO logic)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 6
|
||||
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Reverse Engineer gpio_set_pulls with GDB
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Function Arguments
|
||||
|
||||
```gdb
|
||||
(gdb) b *0x100002d8
|
||||
(gdb) c
|
||||
(gdb) info registers r0 r1 r2
|
||||
r0 = 15 ; GPIO pin
|
||||
r1 = 1 ; pull-up enable (true)
|
||||
r2 = 0 ; pull-down disable (false)
|
||||
```
|
||||
|
||||
##### PADS_BANK0 Address Calculation
|
||||
|
||||
```
|
||||
Base: 0x40038000 (PADS_BANK0)
|
||||
GPIO 0 offset: 0x04 (first pad register)
|
||||
GPIO N offset: 0x04 + (N × 4)
|
||||
GPIO 15: 0x40038000 + 0x04 + (15 × 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040
|
||||
```
|
||||
|
||||
##### Function Behavior Summary
|
||||
|
||||
| Step | Instruction(s) | Effect |
|
||||
|------------------------|---------------------|-----------------------------------|
|
||||
| Load PADS base address | `ldr rX, =0x40038000` | rX = PADS_BANK0 base |
|
||||
| Calculate pad offset | `adds`, `lsls` | offset = 0x04 + pin × 4 |
|
||||
| Read current config | `ldr rX, [addr]` | Read existing pad register value |
|
||||
| Clear pull bits | `bic rX, rX, #0xC` | Clear bits 2 (PDE) and 3 (PUE) |
|
||||
| Set pull-up bit | `orr rX, rX, #0x8` | Set bit 3 (PUE = pull-up enable) |
|
||||
| Write back | `str rX, [addr]` | Write updated config to hardware |
|
||||
|
||||
##### Pad Register Bits
|
||||
|
||||
| Bit | Name | Value | Meaning |
|
||||
|-----|----------|-------|-------------------------|
|
||||
| 3 | PUE | 1 | Pull-up enable |
|
||||
| 2 | PDE | 0 | Pull-down disable |
|
||||
| 1 | SCHMITT | 1 | Schmitt trigger enabled |
|
||||
| 0 | SLEWFAST | 0 | Slow slew rate |
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the function read-modify-write the register instead of just writing a new value? What would happen if it just wrote without reading first?**
|
||||
The pad register contains multiple configuration fields (drive strength, slew rate, Schmitt trigger, input enable, output disable, pull-up, pull-down). If the function wrote a new value without reading first, it would overwrite all other fields with zeros or arbitrary values, potentially disabling the Schmitt trigger, changing drive strength, or disabling input/output. The read-modify-write pattern uses `bic` to clear only the pull bits (2 and 3) and `orr` to set the desired pull configuration, leaving all other bits untouched.
|
||||
|
||||
2. **The pad register for GPIO 15 is at offset `0x40` from the PADS base. How is this offset calculated? What would the offset be for GPIO 0?**
|
||||
The formula is: offset = `0x04 + (GPIO_number × 4)`. For GPIO 15: `0x04 + (15 × 4) = 0x04 + 0x3C = 0x40`. The `0x04` initial offset exists because offset `0x00` is the VOLTAGE_SELECT register, not a pad register. For GPIO 0: offset = `0x04 + (0 × 4) = 0x04`. So GPIO 0's pad register is at `0x40038004`.
|
||||
|
||||
3. **What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)?**
|
||||
Enabling both creates a **resistive voltage divider** between VDD and GND through the internal pull resistors. On the RP2350, both pull resistors are typically ~50kΩ. The pin voltage would settle at approximately VDD/2 (1.65V for 3.3V supply), which is in the undefined region between logic HIGH and LOW thresholds. This makes the digital input unreliable—the Schmitt trigger may oscillate or read randomly. While not damaging to the hardware, it wastes power and produces unpredictable input reads. The SDK intentionally never sets both bits simultaneously.
|
||||
|
||||
4. **The compiler inlines `gpio_pull_up` into `gpio_set_pulls`. What does this tell you about the compiler's optimization level? How does inlining affect binary analysis?**
|
||||
This indicates at least `-O1` or higher optimization (the Pico SDK defaults to `-O2`). The `gpio_pull_up` function is declared `static inline` in the SDK header, and the compiler eliminates the function call overhead by inserting `gpio_set_pulls(pin, true, false)` directly. For binary analysis, inlining means: (a) the original function name `gpio_pull_up` doesn't appear in the symbol table, (b) you see `gpio_set_pulls` called directly with hardcoded arguments, making it harder to identify the programmer's original intent, and (c) multiple calls to `gpio_pull_up` with different pins each become separate `gpio_set_pulls` calls rather than referencing a single function body.
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 6
|
||||
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
|
||||
|
||||
### Exercise 2: Reverse Engineer gpio_set_pulls with GDB
|
||||
### Non-Credit Practice Exercise 2: Reverse Engineer gpio_set_pulls with GDB
|
||||
|
||||
#### Objective
|
||||
Use GDB to disassemble the `gpio_set_pulls` function, trace its register writes to identify the PADS_BANK0 hardware register it modifies, and document how the Pico 2 configures internal pull-up and pull-down resistors at the hardware level.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 6
|
||||
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Make the Overflow Happen Faster
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Patch Details
|
||||
|
||||
| Item | Original | Patched |
|
||||
|--------------------|-----------------|-----------------|
|
||||
| Instruction | adds r3, #0x1 | adds r3, #0xa |
|
||||
| Address | 0x1000027c | 0x1000027c |
|
||||
| Hex bytes | 01 33 | 0A 33 |
|
||||
| Increment value | 1 | 10 |
|
||||
| File offset | 0x27c | 0x27c |
|
||||
|
||||
##### Instruction Encoding
|
||||
|
||||
```
|
||||
Thumb adds rD, #imm8:
|
||||
Byte 0: immediate value (0x01 → 0x0A)
|
||||
Byte 1: opcode + register (0x33 = adds r3)
|
||||
```
|
||||
|
||||
##### Serial Output After Patch
|
||||
|
||||
```
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 42
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 52
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 62
|
||||
...
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 252
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 6 ← Overflow! 252 + 10 = 262 mod 256 = 6
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **The overflow now wraps to 6 instead of 0. Explain why, using the modular arithmetic of a `uint8_t` (range 0-255).**
|
||||
A `uint8_t` stores values modulo 256. Starting at 42 and incrementing by 10: the sequence passes through 42, 52, 62, ..., 242, 252. The next value is 252 + 10 = 262. Since `uint8_t` can only hold 0–255: $262 \bmod 256 = 6$. The wrap value is non-zero because the increment (10) does not evenly divide into 256. With increment 1, the value hits exactly 255, and $255 + 1 = 256 \bmod 256 = 0$. With increment 10, it skips from 252 directly to 262, bypassing 0 and landing on 6.
|
||||
|
||||
2. **What is the maximum value you could change the increment to while still using `adds r3, #imm8`? What would happen if you needed an increment larger than 255?**
|
||||
The maximum is **255** (`0xFF`). The `adds rD, #imm8` Thumb encoding has an 8-bit immediate field, so valid values are 0–255. For an increment larger than 255, you would need to: (a) use a 32-bit Thumb-2 `adds.w` instruction which supports a wider range of modified immediates, (b) use a `movs` + `adds` two-instruction sequence to load a larger value, or (c) load the value from a literal pool with `ldr` then use a register-register `add`. Each approach requires different instruction sizes, so patching in a hex editor becomes more complex—you may need to shift code or use NOP padding.
|
||||
|
||||
3. **If you changed the increment to 128 (`0x80`), how many iterations would it take to wrap, and what value would it wrap to?**
|
||||
Starting at 42, incrementing by 128: 42 → 170 → 42 → 170 → ... Wait, let's compute: $42 + 128 = 170$ (first iteration), $170 + 128 = 298 \bmod 256 = 42$ (second iteration). It wraps after **2 iterations** back to 42. The variable alternates between 42 and 170 forever because $2 \times 128 = 256$, and $42 + 256 = 42 \bmod 256$. The value never reaches 0—it cycles between exactly two values.
|
||||
|
||||
4. **Could you achieve the same speedup by changing the `strb` (store byte) to `strh` (store halfword)? Why or why not?**
|
||||
No. Changing `strb` to `strh` would store 16 bits instead of 8, which means the variable would be treated as a `uint16_t` (range 0–65535). This would actually **slow down** overflow—it would take 65,535 − 42 = 65,493 iterations to wrap instead of 213. Additionally, `strh` writes 2 bytes to RAM, potentially corrupting the adjacent byte at `0x200005a9`. The proper way to speed up overflow is to increase the increment value, not change the storage width. The `strb` truncation to 8 bits is what enforces the `uint8_t` modular arithmetic.
|
||||
+10
-10
@@ -1,13 +1,13 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 6
|
||||
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
|
||||
|
||||
### Exercise 3: Make the Overflow Happen Faster
|
||||
### Non-Credit Practice Exercise 3: Make the Overflow Happen Faster
|
||||
|
||||
#### Objective
|
||||
Patch the `adds r3, #0x1` instruction — which increments `static_fav_num` by 1 each loop iteration — to `adds r3, #0xa` so the variable increments by 10 instead. Use GDB to locate the instruction, calculate the hex editor file offset, patch the binary, and verify on hardware that the `uint8_t` overflow occurs roughly 10 times sooner.
|
||||
Patch the `adds r3, #0x1` instruction — which increments `static_fav_num` by 1 each loop iteration — to `adds r3, #0xa` so the variable increments by 10 instead. Use GDB to locate the instruction, calculate the hex editor file offset, patch the binary, and verify on hardware that the `uint8_t` overflow occurs roughly 10 times sooner.
|
||||
|
||||
#### Prerequisites
|
||||
- Completed Week 6 tutorial (GDB and hex editor sections)
|
||||
@@ -61,7 +61,7 @@ Look for this sequence:
|
||||
```
|
||||
0x10000278: ldrb r3, [r4, #0] ; Load static_fav_num from RAM
|
||||
0x1000027a: movs r2, #16 ; LED GPIO pin number
|
||||
0x1000027c: adds r3, #1 ; Increment by 1 ← THIS IS OUR TARGET
|
||||
0x1000027c: adds r3, #1 ; Increment by 1 ? THIS IS OUR TARGET
|
||||
0x1000027e: strb r3, [r4, #0] ; Store back to RAM
|
||||
```
|
||||
|
||||
@@ -116,10 +116,10 @@ For the `adds r3, #0x1` instruction at its address, calculate the offset.
|
||||
2. Press **Ctrl+G** (Go to offset) and enter the calculated offset
|
||||
3. You should see the byte `01` followed by `33`
|
||||
4. Change `01` to `0A` (10 in decimal)
|
||||
5. Verify: the bytes should now read `0A 33` — encoding `adds r3, #0xa`
|
||||
6. Click **File** → **Save As** → `0x0014_static-variables-h.bin` (in the same `build` directory)
|
||||
5. Verify: the bytes should now read `0A 33` — encoding `adds r3, #0xa`
|
||||
6. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory)
|
||||
|
||||
> 🔍 **Why this works:** In Thumb `adds rD, #imm8` encoding, the immediate value is stored in the first byte. The `#imm8` field accepts values 0-255, so changing 1 to 10 is safe.
|
||||
> ?? **Why this works:** In Thumb `adds rD, #imm8` encoding, the immediate value is stored in the first byte. The `#imm8` field accepts values 0-255, so changing 1 to 10 is safe.
|
||||
|
||||
##### Step 7: Convert to UF2 and Flash
|
||||
|
||||
@@ -139,7 +139,7 @@ python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --fam
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 42
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 52 ← Jumped by 10!
|
||||
static_fav_num: 52 ? Jumped by 10!
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 62
|
||||
...
|
||||
@@ -148,10 +148,10 @@ static_fav_num: 242
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 252
|
||||
regular_fav_num: 42
|
||||
static_fav_num: 6 ← Overflow! 252 + 10 = 262, but uint8_t wraps: 262 - 256 = 6
|
||||
static_fav_num: 6 ? Overflow! 252 + 10 = 262, but uint8_t wraps: 262 - 256 = 6
|
||||
```
|
||||
|
||||
Notice the overflow now happens much sooner, and the wrap value is no longer 0 — it's 6 because `252 + 10 = 262` which wraps to `262 mod 256 = 6`.
|
||||
Notice the overflow now happens much sooner, and the wrap value is no longer 0 — it's 6 because `252 + 10 = 262` which wraps to `262 mod 256 = 6`.
|
||||
|
||||
#### Expected Output
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 6
|
||||
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Invert the Button Logic with XOR
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Patch Details
|
||||
|
||||
| Item | Original | Patched |
|
||||
|--------------------|-----------------------|-----------------------|
|
||||
| Instruction | eor.w r3, r3, #1 | eor.w r3, r3, #0 |
|
||||
| Address | 0x10000286 | 0x10000286 |
|
||||
| Hex bytes | 83 F0 01 03 | 83 F0 00 03 |
|
||||
| Patched byte offset| 0x288 (3rd byte) | 01 → 00 |
|
||||
|
||||
##### Logic Table Comparison
|
||||
|
||||
**Original (EOR #1):**
|
||||
|
||||
| Button State | GPIO 15 | After UBFX | After EOR #1 | LED (GPIO 16) |
|
||||
|-------------|---------|------------|-------------|---------------|
|
||||
| Released | 1 (HIGH)| 1 | 0 | OFF |
|
||||
| Pressed | 0 (LOW) | 0 | 1 | ON |
|
||||
|
||||
**Patched (EOR #0):**
|
||||
|
||||
| Button State | GPIO 15 | After UBFX | After EOR #0 | LED (GPIO 16) |
|
||||
|-------------|---------|------------|-------------|---------------|
|
||||
| Released | 1 (HIGH)| 1 | 1 | **ON** |
|
||||
| Pressed | 0 (LOW) | 0 | 0 | **OFF** |
|
||||
|
||||
##### Hardware Result
|
||||
|
||||
- Button NOT pressed: LED **ON** (was OFF)
|
||||
- Button PRESSED: LED **OFF** (was ON)
|
||||
- Behavior completely reversed by changing a single byte (01 → 00)
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does XOR with 1 act as a NOT for single-bit values? Write out the truth table for `x XOR 1` and `x XOR 0` where x is 0 or 1.**
|
||||
|
||||
| x | x XOR 1 | x XOR 0 |
|
||||
|---|---------|---------|
|
||||
| 0 | 1 | 0 |
|
||||
| 1 | 0 | 1 |
|
||||
|
||||
`x XOR 1` always flips the bit (acts as NOT): 0→1, 1→0. `x XOR 0` always preserves the bit (acts as identity): 0→0, 1→1. This works because XOR returns 1 when inputs differ and 0 when they match. XOR with 1 forces a difference; XOR with 0 forces a match. This property only applies to the single affected bit—for multi-bit values, each bit is XORed independently.
|
||||
|
||||
2. **Instead of changing `eor.w r3, r3, #1` to `eor.w r3, r3, #0`, could you achieve the same result by NOPing (removing) the instruction entirely? What bytes encode a NOP in Thumb?**
|
||||
Yes. Removing the EOR instruction entirely would have the same effect as EOR #0—the value passes through unchanged. A Thumb NOP is encoded as `00 BF` (2 bytes). Since `eor.w` is a 32-bit Thumb-2 instruction (4 bytes), you would need **two** NOPs to replace it: `00 BF 00 BF`. In the hex editor, replace bytes at offset 0x286–0x289 from `83 F0 01 03` to `00 BF 00 BF`. Both approaches yield identical behavior, but the EOR #0 patch is "cleaner" because it preserves the instruction structure—making the modification more obvious during reverse engineering.
|
||||
|
||||
3. **The pull-up resistor means "pressed = LOW." If you removed the pull-up (changed `gpio_pull_up` to no pull), would the button still work? Why or why not?**
|
||||
It would be unreliable. Without a pull-up or pull-down resistor, the GPIO input is **floating** when the button is not pressed—there's no defined voltage on the pin. The input would pick up electrical noise, stray capacitance, and electromagnetic interference, causing random readings (0 or 1 unpredictably). When the button IS pressed, it connects to ground (LOW), which works. But when released, the pin has no path to any voltage, so `gpio_get(15)` returns garbage. The pull-up provides a defined HIGH state when the button circuit is open.
|
||||
|
||||
4. **The `ubfx r3, r3, #0xf, #0x1` instruction extracts bit 15. If you changed `#0xf` to `#0x10` (bit 16), what GPIO pin would you be reading? What value would you get if nothing is connected to that pin?**
|
||||
You would be reading **GPIO 16**, which is the LED output pin. Since GPIO 16 is configured as an output (not input), reading its input register returns the current output state—either 0 or 1 depending on whether the LED is currently on or off. This would create a **feedback loop**: the LED's current state determines its next state (after the XOR), causing unpredictable oscillation or a stuck state. If GPIO 16 had nothing connected and was unconfigured, the floating input would return random values, similar to Q3's scenario.
|
||||
+14
-14
@@ -1,13 +1,13 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 6
|
||||
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
|
||||
|
||||
### Exercise 4: Invert the Button Logic with XOR
|
||||
### Non-Credit Practice Exercise 4: Invert the Button Logic with XOR
|
||||
|
||||
#### Objective
|
||||
Find the `eor.w r3, r3, #1` instruction that implements the ternary operator's button inversion, patch it to `eor.w r3, r3, #0` using a hex editor to reverse the LED behavior, and verify that the LED is now ON when the button is pressed and OFF when released — the opposite of the original behavior.
|
||||
Find the `eor.w r3, r3, #1` instruction that implements the ternary operator's button inversion, patch it to `eor.w r3, r3, #0` using a hex editor to reverse the LED behavior, and verify that the LED is now ON when the button is pressed and OFF when released — the opposite of the original behavior.
|
||||
|
||||
#### Prerequisites
|
||||
- Completed Week 6 tutorial (all GDB and hex editor sections)
|
||||
@@ -61,7 +61,7 @@ Look for this sequence:
|
||||
0x10000274: mov.w r1, #0xd0000000 ; SIO base address
|
||||
0x10000280: ldr r3, [r1, #4] ; Read GPIO input register
|
||||
0x10000282: ubfx r3, r3, #15, #1 ; Extract bit 15 (button state)
|
||||
0x10000286: eor.w r3, r3, #1 ; XOR with 1 — INVERT ← OUR TARGET
|
||||
0x10000286: eor.w r3, r3, #1 ; XOR with 1 — INVERT ? OUR TARGET
|
||||
0x1000028a: mcrr 0, 4, r2, r3, cr0 ; Write to GPIO output
|
||||
```
|
||||
|
||||
@@ -93,8 +93,8 @@ When it hits, check what value is about to be XORed:
|
||||
(gdb) info registers r3
|
||||
```
|
||||
|
||||
- If button is **released**: `r3 = 1` → after EOR: `r3 = 0`
|
||||
- If button is **pressed**: `r3 = 0` → after EOR: `r3 = 1`
|
||||
- If button is **released**: `r3 = 1` ? after EOR: `r3 = 0`
|
||||
- If button is **pressed**: `r3 = 0` ? after EOR: `r3 = 1`
|
||||
|
||||
##### Step 5: Test the Patch in GDB
|
||||
|
||||
@@ -118,9 +118,9 @@ Look at the raw bytes:
|
||||
```
|
||||
|
||||
The `eor.w` instruction is a 32-bit Thumb-2 encoding. The 4 bytes break down as:
|
||||
- `0x83 0xF0` — opcode + source register (r3)
|
||||
- `0x01` — **the immediate value (`#1`)** ← this is what we change
|
||||
- `0x03` — destination register (r3)
|
||||
- `0x83 0xF0` — opcode + source register (r3)
|
||||
- `0x01` — **the immediate value (`#1`)** ? this is what we change
|
||||
- `0x03` — destination register (r3)
|
||||
|
||||
##### Step 7: Patch with the Hex Editor
|
||||
|
||||
@@ -128,11 +128,11 @@ The `eor.w` instruction is a 32-bit Thumb-2 encoding. The 4 bytes break down as:
|
||||
2. The instruction starts at file offset: `0x10000286 - 0x10000000 = 0x286`
|
||||
3. The immediate byte is the 3rd byte: offset `0x286 + 2 = 0x288`
|
||||
4. Press **Ctrl+G** and enter offset: `288`
|
||||
5. You should see `01` — change it to `00`
|
||||
5. You should see `01` — change it to `00`
|
||||
6. Verify the surrounding bytes (`83 F0` before and `03` after) are unchanged
|
||||
7. Click **File** → **Save As** → `0x0014_static-variables-h.bin` (in the same `build` directory)
|
||||
7. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory)
|
||||
|
||||
> 🔍 **Why offset `0x288`?** The 4-byte instruction starts at `0x286`, but the immediate value `#1` is in the **third byte** (index 2), so it's at `0x286 + 2 = 0x288`.
|
||||
> ?? **Why offset `0x288`?** The 4-byte instruction starts at `0x286`, but the immediate value `#1` is in the **third byte** (index 2), so it's at `0x286 + 2 = 0x288`.
|
||||
|
||||
##### Step 8: Predict the New Behavior
|
||||
|
||||
@@ -183,9 +183,9 @@ After completing this exercise, you should be able to:
|
||||
|
||||
#### Tips and Hints
|
||||
- `eor.w r3, r3, #1` is a 32-bit Thumb-2 instruction (4 bytes), not a 16-bit Thumb instruction
|
||||
- A Thumb NOP is `00 bf` (2 bytes) — you would need two NOPs to replace a 4-byte instruction
|
||||
- A Thumb NOP is `00 bf` (2 bytes) — you would need two NOPs to replace a 4-byte instruction
|
||||
- Use GDB `x/1tw` to view a word in binary format, making bit manipulation easier to see
|
||||
- The SIO base address `0xd0000000` provides single-cycle access to GPIO — it's separate from the IO_BANK0 registers at `0x40028000`
|
||||
- The SIO base address `0xd0000000` provides single-cycle access to GPIO — it's separate from the IO_BANK0 registers at `0x40028000`
|
||||
|
||||
#### Next Steps
|
||||
- Review all four exercises and verify you can patch any part of the binary: data values, arithmetic operations, and logic operations
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 7
|
||||
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Change Both LCD Lines
|
||||
|
||||
#### Answers
|
||||
|
||||
##### String Locations in Flash
|
||||
|
||||
| String | Address | File Offset | Length (bytes) | Hex Encoding |
|
||||
|---------------|--------------|-------------|----------------|---------------------------------------------------|
|
||||
| "Reverse" | 0x10003ee8 | 0x3EE8 | 8 (7 + null) | 52 65 76 65 72 73 65 00 |
|
||||
| "Engineering" | 0x10003ef0 | 0x3EF0 | 12 (11 + null) | 45 6E 67 69 6E 65 65 72 69 6E 67 00 |
|
||||
|
||||
##### Line 1 Patch: "Reverse" → "Exploit"
|
||||
|
||||
| Character | Hex |
|
||||
|-----------|--------|
|
||||
| E | 0x45 |
|
||||
| x | 0x78 |
|
||||
| p | 0x70 |
|
||||
| l | 0x6c |
|
||||
| o | 0x6f |
|
||||
| i | 0x69 |
|
||||
| t | 0x74 |
|
||||
| \0 | 0x00 |
|
||||
|
||||
```
|
||||
Offset 0x3EE8:
|
||||
Before: 52 65 76 65 72 73 65 00 ("Reverse")
|
||||
After: 45 78 70 6C 6F 69 74 00 ("Exploit")
|
||||
```
|
||||
|
||||
##### Line 2 Patch: "Engineering" → "Hacking!!!!"
|
||||
|
||||
| Character | Hex |
|
||||
|-----------|--------|
|
||||
| H | 0x48 |
|
||||
| a | 0x61 |
|
||||
| c | 0x63 |
|
||||
| k | 0x6b |
|
||||
| i | 0x69 |
|
||||
| n | 0x6e |
|
||||
| g | 0x67 |
|
||||
| ! | 0x21 |
|
||||
| ! | 0x21 |
|
||||
| ! | 0x21 |
|
||||
| ! | 0x21 |
|
||||
| \0 | 0x00 |
|
||||
|
||||
```
|
||||
Offset 0x3EF0:
|
||||
Before: 45 6E 67 69 6E 65 65 72 69 6E 67 00 ("Engineering")
|
||||
After: 48 61 63 6B 69 6E 67 21 21 21 21 00 ("Hacking!!!!")
|
||||
```
|
||||
|
||||
##### Conversion and Flash
|
||||
|
||||
```powershell
|
||||
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants
|
||||
python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
|
||||
```
|
||||
|
||||
##### LCD Verification
|
||||
|
||||
```
|
||||
Line 1: Exploit
|
||||
Line 2: Hacking!!!!
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why must the replacement string be the same length (or shorter) as the original? What specific data would you corrupt if you used a longer string?**
|
||||
Strings are stored consecutively in the `.rodata` section. "Reverse" occupies 8 bytes starting at `0x10003ee8` and "Engineering" starts immediately at `0x10003ef0`. If the replacement string is longer than 8 bytes, the extra bytes would overwrite the beginning of "Engineering" (or whatever data follows). The `.rodata` section has no gaps—it's a packed sequence of constants, format strings, and other read-only data. Corrupting adjacent data could break LCD line 2, crash `printf` format strings, or cause undefined behavior.
|
||||
|
||||
2. **The two strings are stored only 8 bytes apart (0x3EE8 to 0x3EF0). "Reverse" is 7 characters + null = 8 bytes. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)?**
|
||||
"Reversal" needs 9 bytes (8 chars + null terminator). The 9th byte (the `0x00` null terminator) would be written to address `0x10003ef0`, which is the first byte of "Engineering" — the letter 'E' (`0x45`). This would overwrite 'E' with `0x00`, turning "Engineering" into an empty string. The LCD would display "Reversal" on line 1 and nothing on line 2, because `lcd_puts` would see a null terminator immediately at the start of the second string.
|
||||
|
||||
3. **If you wanted the LCD to display "Hello" on line 1 (5 characters instead of 7), what would you put in the remaining 2 bytes plus null? Write out the full 8-byte hex sequence.**
|
||||
"Hello" = 5 characters, followed by the null terminator and 2 padding null bytes:
|
||||
```
|
||||
48 65 6C 6C 6F 00 00 00
|
||||
H e l l o \0 \0 \0
|
||||
```
|
||||
The first `0x00` at position 5 terminates the string. The remaining two `0x00` bytes are padding that fills the original 8-byte allocation. These padding bytes are never read by `lcd_puts` because it stops at the first null terminator.
|
||||
|
||||
4. **Could you change the LCD to display nothing on line 1 by patching just one byte? Which byte and what value?**
|
||||
Yes. Change the first byte at offset `0x3EE8` from `0x52` ('R') to `0x00` (null). This makes the string start with a null terminator, so `lcd_puts` sees an empty string and displays nothing. Only one byte needs to change: the byte at file offset `0x3EE8`, from `0x52` to `0x00`.
|
||||
+14
-14
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 7
|
||||
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
|
||||
|
||||
### Exercise 1: Change Both LCD Lines
|
||||
### Non-Credit Practice Exercise 1: Change Both LCD Lines
|
||||
|
||||
#### Objective
|
||||
Patch the LCD display strings in the `.bin` file to change "Reverse" to "Exploit" on line 1 and "Engineering" to "Hacking!!!!" on line 2, using GDB to locate the string addresses, a hex editor to perform the patches, and the Pico 2 hardware to verify the changes on the physical LCD.
|
||||
@@ -15,10 +15,10 @@ Patch the LCD display strings in the `.bin` file to change "Reverse" to "Exploit
|
||||
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
|
||||
- A hex editor (HxD, ImHex, or similar)
|
||||
- Python installed (for UF2 conversion)
|
||||
- Raspberry Pi Pico 2 with 1602 LCD connected via I²C
|
||||
- Raspberry Pi Pico 2 with 1602 LCD connected via I²C
|
||||
|
||||
#### Task Description
|
||||
The LCD currently displays "Reverse" on line 1 and "Engineering" on line 2. You will find both string literals in flash memory using GDB, calculate their file offsets, and patch them to display custom text. The critical constraint is that replacement strings must be the **same length** as the originals (or shorter, padded with null bytes) — otherwise you'll corrupt adjacent data.
|
||||
The LCD currently displays "Reverse" on line 1 and "Engineering" on line 2. You will find both string literals in flash memory using GDB, calculate their file offsets, and patch them to display custom text. The critical constraint is that replacement strings must be the **same length** as the originals (or shorter, padded with null bytes) — otherwise you'll corrupt adjacent data.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
@@ -102,8 +102,8 @@ Output:
|
||||
- "Engineering" = 11 characters + null terminator = 12 bytes
|
||||
|
||||
**Replacement strings (MUST be same length or shorter):**
|
||||
- "Exploit" = 7 characters ✅ (same as "Reverse")
|
||||
- "Hacking!!!!" = 11 characters ✅ (same as "Engineering")
|
||||
- "Exploit" = 7 characters ? (same as "Reverse")
|
||||
- "Hacking!!!!" = 11 characters ? (same as "Engineering")
|
||||
|
||||
Build the ASCII hex for "Exploit":
|
||||
|
||||
@@ -141,17 +141,17 @@ Build the ASCII hex for "Hacking!!!!":
|
||||
file_offset = address - 0x10000000
|
||||
```
|
||||
|
||||
- "Reverse" at `0x10003ee8` → file offset `0x3EE8`
|
||||
- "Engineering" at `0x10003ef0` → file offset `0x3EF0`
|
||||
- "Reverse" at `0x10003ee8` ? file offset `0x3EE8`
|
||||
- "Engineering" at `0x10003ef0` ? file offset `0x3EF0`
|
||||
|
||||
##### Step 6: Patch String 1 — "Reverse" → "Exploit"
|
||||
##### Step 6: Patch String 1 — "Reverse" ? "Exploit"
|
||||
|
||||
1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants\build\0x0017_constants.bin`
|
||||
2. Press **Ctrl+G** and enter offset: `3EE8`
|
||||
3. You should see: `52 65 76 65 72 73 65 00` ("Reverse\0")
|
||||
4. Replace with: `45 78 70 6C 6F 69 74 00` ("Exploit\0")
|
||||
|
||||
##### Step 7: Patch String 2 — "Engineering" → "Hacking!!!!"
|
||||
##### Step 7: Patch String 2 — "Engineering" ? "Hacking!!!!"
|
||||
|
||||
1. Press **Ctrl+G** and enter offset: `3EF0`
|
||||
2. You should see: `45 6E 67 69 6E 65 65 72 69 6E 67 00` ("Engineering\0")
|
||||
@@ -159,7 +159,7 @@ file_offset = address - 0x10000000
|
||||
|
||||
##### Step 8: Save and Convert
|
||||
|
||||
1. Click **File** → **Save As** → `0x0017_constants-h.bin`
|
||||
1. Click **File** ? **Save As** ? `0x0017_constants-h.bin`
|
||||
|
||||
```powershell
|
||||
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants
|
||||
@@ -187,7 +187,7 @@ After completing this exercise, you should be able to:
|
||||
|
||||
###### Question 1: Why must the replacement string be the same length (or shorter) as the original? What specific data would you corrupt if you used a longer string?
|
||||
|
||||
###### Question 2: The two strings "Reverse" and "Engineering" are stored only 8 bytes apart (`0x3EE8` to `0x3EF0`). "Reverse" is 7 characters + null = 8 bytes — it perfectly fills the gap. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)?
|
||||
###### Question 2: The two strings "Reverse" and "Engineering" are stored only 8 bytes apart (`0x3EE8` to `0x3EF0`). "Reverse" is 7 characters + null = 8 bytes — it perfectly fills the gap. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)?
|
||||
|
||||
###### Question 3: If you wanted the LCD to display "Hello" on line 1 (5 characters instead of 7), what would you put in the remaining 2 bytes plus null? Write out the full 8-byte hex sequence.
|
||||
|
||||
@@ -195,9 +195,9 @@ After completing this exercise, you should be able to:
|
||||
|
||||
#### Tips and Hints
|
||||
- Use an ASCII table to convert characters: uppercase A-Z = `0x41`-`0x5A`, lowercase a-z = `0x61`-`0x7A`
|
||||
- The null terminator `0x00` marks the end of the string — anything after it is ignored by `lcd_puts`
|
||||
- The null terminator `0x00` marks the end of the string — anything after it is ignored by `lcd_puts`
|
||||
- If your replacement is shorter, pad with `0x00` bytes to fill the original length
|
||||
- The 1602 LCD has 16 characters per line — you cannot display more than 16 characters per line regardless of string length
|
||||
- The 1602 LCD has 16 characters per line — you cannot display more than 16 characters per line regardless of string length
|
||||
|
||||
#### Next Steps
|
||||
- Proceed to Exercise 2 to explore finding all string literals in the binary
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 7
|
||||
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Find All String Literals in the Binary
|
||||
|
||||
#### Answers
|
||||
|
||||
##### String Catalog
|
||||
|
||||
| Address | File Offset | String Content | Length | Purpose |
|
||||
|---------------|-------------|--------------------------|--------|-----------------------------|
|
||||
| `0x10003ee8` | `0x3EE8` | "Reverse" | 8 | LCD line 1 text |
|
||||
| `0x10003ef0` | `0x3EF0` | "Engineering" | 12 | LCD line 2 text |
|
||||
| `0x10003efc` | `0x3EFC` | "FAV_NUM: %d\r\n" | 16 | printf format string |
|
||||
| `0x10003f0c` | `0x3F0C` | "OTHER_FAV_NUM: %d\r\n" | 22 | printf format string |
|
||||
| Various | Various | SDK panic/assert strings | Varies | Pico SDK internal messages |
|
||||
| Various | Various | Source file paths | Varies | SDK debug/assert references |
|
||||
|
||||
##### GDB String Search Commands
|
||||
|
||||
```gdb
|
||||
(gdb) x/s 0x10003ee8
|
||||
0x10003ee8: "Reverse"
|
||||
|
||||
(gdb) x/s 0x10003ef0
|
||||
0x10003ef0: "Engineering"
|
||||
|
||||
(gdb) x/s 0x10003efc
|
||||
0x10003efc: "FAV_NUM: %d\r\n"
|
||||
|
||||
(gdb) x/s 0x10003f0c
|
||||
0x10003f0c: "OTHER_FAV_NUM: %d\r\n"
|
||||
```
|
||||
|
||||
##### Scanning for Strings
|
||||
|
||||
```gdb
|
||||
(gdb) x/20s 0x10003e00
|
||||
(gdb) x/50s 0x10003d00
|
||||
```
|
||||
|
||||
##### Literal Pool Reference
|
||||
|
||||
From the literal pool at `0x100002a4`:
|
||||
|
||||
| Pool Address | Value | String It Points To |
|
||||
|----------------|---------------|---------------------------|
|
||||
| `0x100002ac` | `0x10003EE8` | "Reverse" |
|
||||
| `0x100002b0` | `0x10003EF0` | "Engineering" |
|
||||
| `0x100002b4` | `0x10003EFC` | "FAV_NUM: %d\r\n" |
|
||||
| `0x100002b8` | `0x10003F0C` | "OTHER_FAV_NUM: %d\r\n" |
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **How many distinct strings did you find? Were any of them surprising or unexpected?**
|
||||
At minimum 4 application-level strings: "Reverse", "Engineering", "FAV_NUM: %d\r\n", and "OTHER_FAV_NUM: %d\r\n". Beyond these, the Pico SDK embeds additional strings — panic handler messages, assert failure messages, and source file path strings used for debug output. The SDK strings are surprising because they reveal internal implementation details: file paths expose the build environment directory structure, and error messages reveal which SDK functions have built-in error checking. A reverse engineer can learn the SDK version and build configuration just from these strings.
|
||||
|
||||
2. **Why are strings so valuable to a reverse engineer? What can an attacker learn about a program just from its strings?**
|
||||
Strings are high-entropy human-readable data that reveals program behavior without reading assembly. An attacker can learn: what the program displays or communicates (LCD messages, serial output), what libraries it uses (SDK error messages), how it handles errors (panic/assert strings), what data formats it processes (`printf` format strings with `%d`, `%s`, `%f`), network endpoints or credentials (URLs, passwords, API keys), the build environment (file paths), and the overall purpose of the firmware. Strings are often the first thing a reverse engineer examines in an unknown binary.
|
||||
|
||||
3. **What technique could a developer use to make strings harder to find in a binary? (Think about what the strings look like in memory.)**
|
||||
String encryption/obfuscation: encrypt all string literals at compile time using XOR, AES, or a custom cipher, and decrypt them into a RAM buffer only when needed at runtime. This way, scanning the binary with `strings` or a hex editor reveals only ciphertext — random-looking bytes instead of readable text. Other techniques include: splitting strings across multiple locations and assembling them at runtime, using character arrays initialized by code rather than string literals, replacing strings with numeric lookup indices into an encrypted table, or using compile-time obfuscation tools that automatically transform string constants.
|
||||
|
||||
4. **The printf format strings contain \r\n. In the binary, these appear as two bytes: 0x0D 0x0A. Why two bytes instead of the four characters \, r, \, n?**
|
||||
The C compiler processes escape sequences during compilation. In source code, `\r` is written as two characters (backslash + r), but the compiler converts it to a single byte: `0x0D` (carriage return, ASCII 13). Similarly, `\n` becomes `0x0A` (line feed, ASCII 10). These are **control characters** — non-printable ASCII codes that control terminal behavior. The backslash notation is just a human-readable way to represent these bytes in source code. By the time the string reaches the binary, all escape sequences have been resolved to their single-byte equivalents.
|
||||
+8
-8
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 7
|
||||
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
|
||||
|
||||
### Exercise 2: Find All String Literals in the Binary
|
||||
### Non-Credit Practice Exercise 2: Find All String Literals in the Binary
|
||||
|
||||
#### Objective
|
||||
Systematically search through the `0x0017_constants` binary using GDB and a hex editor to locate every human-readable string literal, catalog their addresses, contents, and purposes, and gain experience identifying data structures in compiled binaries.
|
||||
@@ -16,7 +16,7 @@ Systematically search through the `0x0017_constants` binary using GDB and a hex
|
||||
- A hex editor (HxD, ImHex, or similar)
|
||||
|
||||
#### Task Description
|
||||
Compiled binaries contain string literals in the `.rodata` section — format strings for `printf`, LCD messages, library strings, and more. You will use two techniques to find them: (1) searching with GDB's `x/s` command to examine suspected string regions, and (2) visually scanning the binary in a hex editor for ASCII sequences. You will document every string you find, its address, and its likely purpose.
|
||||
Compiled binaries contain string literals in the `.rodata` section — format strings for `printf`, LCD messages, library strings, and more. You will use two techniques to find them: (1) searching with GDB's `x/s` command to examine suspected string regions, and (2) visually scanning the binary in a hex editor for ASCII sequences. You will document every string you find, its address, and its likely purpose.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
@@ -67,7 +67,7 @@ The program uses `printf("FAV_NUM: %d\r\n", ...)` and `printf("OTHER_FAV_NUM: %d
|
||||
(gdb) x/10s 0x10003ec0
|
||||
```
|
||||
|
||||
This displays 10 consecutive strings starting from that address. Examine the output — you should find the `printf` format strings. Try different starting addresses if needed:
|
||||
This displays 10 consecutive strings starting from that address. Examine the output — you should find the `printf` format strings. Try different starting addresses if needed:
|
||||
|
||||
```gdb
|
||||
(gdb) x/20s 0x10003e00
|
||||
@@ -92,7 +92,7 @@ Many results will be garbage (non-ASCII data interpreted as text), but real stri
|
||||
2. Switch to the "Text" pane (right side) to see ASCII representation
|
||||
3. Scroll through the binary and look for readable text sequences
|
||||
|
||||
In HxD, printable ASCII characters (0x20–0x7E) are displayed as text; non-printable bytes appear as dots.
|
||||
In HxD, printable ASCII characters (0x20–0x7E) are displayed as text; non-printable bytes appear as dots.
|
||||
|
||||
##### Step 6: Use Hex Editor Search
|
||||
|
||||
@@ -145,13 +145,13 @@ After completing this exercise, you should be able to:
|
||||
###### Question 4: The `printf` format strings contain `\r\n`. In the binary, these appear as two bytes: `0x0D 0x0A`. Why two bytes instead of the four characters `\`, `r`, `\`, `n`?
|
||||
|
||||
#### Tips and Hints
|
||||
- In GDB, `x/s` treats any address as the start of a null-terminated string — it will print garbage if the address isn't really a string
|
||||
- In GDB, `x/s` treats any address as the start of a null-terminated string — it will print garbage if the address isn't really a string
|
||||
- Use `x/Ns address` where N is a number to print N consecutive strings (useful for scanning regions)
|
||||
- In HxD, use **Edit** → **Select Block** to highlight a region and examine the text pane
|
||||
- In HxD, use **Edit** ? **Select Block** to highlight a region and examine the text pane
|
||||
- Real strings are typically 4+ printable ASCII characters followed by a null byte (`0x00`)
|
||||
- The `.rodata` section is usually located after the `.text` (code) section in the binary
|
||||
|
||||
#### Next Steps
|
||||
- Proceed to Exercise 3 to trace the I²C struct pointer chain
|
||||
- Proceed to Exercise 3 to trace the I²C struct pointer chain
|
||||
- Try the `strings` command if available: `strings 0x0017_constants.bin` will extract all printable character sequences
|
||||
- Consider: if you found a password string in an embedded device binary, what security implications would that have?
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 7
|
||||
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Trace the I²C Struct Pointer Chain
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Complete Pointer Chain
|
||||
|
||||
```
|
||||
I2C_PORT (source macro: #define I2C_PORT i2c1)
|
||||
↓
|
||||
i2c1 (SDK macro: #define i2c1 (&i2c1_inst))
|
||||
↓
|
||||
&i2c1_inst = 0x2000062c (SRAM address of i2c_inst_t struct)
|
||||
↓
|
||||
i2c1_inst.hw = 0x40098000 (pointer to I²C1 hardware register base)
|
||||
↓
|
||||
I²C1 Hardware Registers (memory-mapped I/O silicon)
|
||||
+-- IC_CON at 0x40098000
|
||||
+-- IC_TAR at 0x40098004
|
||||
+-- IC_SAR at 0x40098008
|
||||
+-- IC_DATA_CMD at 0x40098010
|
||||
```
|
||||
|
||||
##### Literal Pool Load
|
||||
|
||||
```gdb
|
||||
(gdb) x/6wx 0x100002a4
|
||||
0x100002a4: 0x000186a0 0x2000062c 0x10003ee8 0x10003ef0
|
||||
0x100002b4: 0x10003efc 0x10003f0c
|
||||
```
|
||||
|
||||
The value `0x2000062c` at pool address `0x100002a8` is loaded into `r0` by a `ldr r0, [pc, #offset]` instruction before the `bl i2c_init` call.
|
||||
|
||||
##### i2c1_inst Struct in SRAM
|
||||
|
||||
```gdb
|
||||
(gdb) x/2wx 0x2000062c
|
||||
0x2000062c <i2c1_inst>: 0x40098000 0x00000000
|
||||
```
|
||||
|
||||
| Offset | Field | Value | Size | Meaning |
|
||||
|--------|-------------------|-------------|---------|-------------------------------|
|
||||
| +0x00 | hw | 0x40098000 | 4 bytes | Pointer to I²C1 hardware regs |
|
||||
| +0x04 | restart_on_next | 0x00000000 | 4 bytes | false (no pending restart) |
|
||||
|
||||
Total struct size: 8 bytes (4-byte pointer + 4-byte bool padded to word alignment).
|
||||
|
||||
##### Hardware Registers at 0x40098000
|
||||
|
||||
```gdb
|
||||
(gdb) x/8wx 0x40098000
|
||||
```
|
||||
|
||||
| Offset | Register | Address | Description |
|
||||
|--------|-------------|-------------|---------------------------|
|
||||
| +0x00 | IC_CON | 0x40098000 | I²C control register |
|
||||
| +0x04 | IC_TAR | 0x40098004 | Target address register |
|
||||
| +0x08 | IC_SAR | 0x40098008 | Slave address register |
|
||||
| +0x10 | IC_DATA_CMD | 0x40098010 | Data command register |
|
||||
|
||||
##### I²C0 Comparison
|
||||
|
||||
```gdb
|
||||
(gdb) x/2wx 0x20000628
|
||||
```
|
||||
|
||||
| Controller | Struct Address | hw Pointer | Separation |
|
||||
|------------|---------------|-------------|-------------|
|
||||
| I²C0 | 0x20000628 | 0x40090000 | Base |
|
||||
| I²C1 | 0x2000062c | 0x40098000 | +0x8000 |
|
||||
|
||||
Same struct layout, different hardware pointer — demonstrating the SDK's abstraction.
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does the SDK use a struct with a pointer to hardware registers instead of accessing 0x40098000 directly? What advantage does this abstraction provide?**
|
||||
The struct abstraction allows the same code to work for both I²C controllers — I²C0 at `0x40090000` and I²C1 at `0x40098000` — by simply passing a different struct pointer. Functions like `i2c_init(i2c_inst_t *i2c, uint baudrate)` accept a pointer parameter, so one implementation serves both controllers. Without the struct, every I²C function would need either hardcoded addresses (duplicating code for each controller) or `if/else` branches. The abstraction also enables portability: if a future chip moves the hardware registers, only the struct initialization changes — not every function that accesses I²C.
|
||||
|
||||
2. **The hw pointer stores 0x40098000. In the binary, this appears as bytes 00 80 09 40. Why is the byte order reversed from how we write the address?**
|
||||
ARM Cortex-M33 uses **little-endian** byte ordering: the least significant byte (LSB) is stored at the lowest memory address. For the 32-bit value `0x40098000`: byte 0 (lowest address) = `0x00` (LSB), byte 1 = `0x80`, byte 2 = `0x09`, byte 3 = `0x40` (MSB). We write numbers with the MSB first (big-endian notation), but the processor stores them LSB-first. This is a fundamental property of the ARM architecture that affects how you read multi-byte values in hex editors and GDB `x/bx` output.
|
||||
|
||||
3. **If you changed the hw pointer at 0x2000062c from 0x40098000 to 0x40090000 using GDB, what I²C controller would the program use? What would happen to the LCD?**
|
||||
The program would use **I²C0** instead of I²C1, because all subsequent hardware register accesses (via `i2c1_inst.hw->...`) would read/write the I²C0 registers at `0x40090000`. However, the LCD is physically wired to the I²C1 pins (GPIO 14 for SDA, GPIO 15 for SCL), and those GPIOs are configured for the I²C1 peripheral. The I²C0 controller drives different default pins (GPIO 0/1). So the program would send I²C commands through the wrong controller on the wrong pins — the LCD would receive no signals and would stop updating, displaying whatever was last written before the pointer change.
|
||||
|
||||
4. **The macro chain has 4 levels of indirection (I2C_PORT → i2c1 → &i2c1_inst → hw → registers). Is this typical for embedded SDKs? What are the trade-offs of this approach?**
|
||||
Yes, this is typical. STM32 HAL, Nordic nRF5 SDK, ESP-IDF, and most professional embedded SDKs use similar multi-level abstractions. **Benefits:** code reuse across multiple peripheral instances, clean type-safe APIs, portability across chip revisions, and testability (you can mock the struct for unit tests). **Costs:** complexity for reverse engineers (harder to trace from API call to hardware), potential code bloat if not optimized, and a steeper learning curve for SDK users. In practice, modern compilers (with `-O2` or higher) optimize away most indirection — the final binary often inlines the pointer dereferences into direct register accesses, so the runtime overhead is negligible.
|
||||
+37
-37
@@ -1,13 +1,13 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 7
|
||||
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
|
||||
|
||||
### Exercise 3: Trace the I²C Struct Pointer Chain
|
||||
### Non-Credit Practice Exercise 3: Trace the I²C Struct Pointer Chain
|
||||
|
||||
#### Objective
|
||||
Use GDB to follow the `i2c1_inst` struct pointer chain from the code instruction that loads it, through the struct in SRAM, to the hardware registers at `0x40098000`. Document every step of the chain: `I2C_PORT` → `i2c1` → `&i2c1_inst` → `hw` → `0x40098000`, and verify each pointer and value in memory.
|
||||
Use GDB to follow the `i2c1_inst` struct pointer chain from the code instruction that loads it, through the struct in SRAM, to the hardware registers at `0x40098000`. Document every step of the chain: `I2C_PORT` ? `i2c1` ? `&i2c1_inst` ? `hw` ? `0x40098000`, and verify each pointer and value in memory.
|
||||
|
||||
#### Prerequisites
|
||||
- Completed Week 7 tutorial (Parts 3-4 on structs and the macro chain)
|
||||
@@ -16,7 +16,7 @@ Use GDB to follow the `i2c1_inst` struct pointer chain from the code instruction
|
||||
- Understanding of C pointers and structs
|
||||
|
||||
#### Task Description
|
||||
The Pico SDK uses a chain of macros and structs to abstract hardware access. When you write `I2C_PORT` in C, it expands through multiple macro definitions to ultimately become a pointer to an `i2c_inst_t` struct in SRAM, which in turn contains a pointer to the I²C hardware registers. You will trace this entire chain in GDB, examining each link to understand how the SDK connects your code to silicon.
|
||||
The Pico SDK uses a chain of macros and structs to abstract hardware access. When you write `I2C_PORT` in C, it expands through multiple macro definitions to ultimately become a pointer to an `i2c_inst_t` struct in SRAM, which in turn contains a pointer to the I²C hardware registers. You will trace this entire chain in GDB, examining each link to understand how the SDK connects your code to silicon.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
@@ -70,13 +70,13 @@ Look for the instruction that loads the `i2c1_inst` pointer into `r0`:
|
||||
|
||||
##### Step 3: Follow the PC-Relative Load
|
||||
|
||||
The `ldr r0, [pc, #offset]` instruction loads a value from a **literal pool** — a data area near the code. Examine what's at the literal pool:
|
||||
The `ldr r0, [pc, #offset]` instruction loads a value from a **literal pool** — a data area near the code. Examine what's at the literal pool:
|
||||
|
||||
```gdb
|
||||
(gdb) x/4wx 0x100002a8
|
||||
```
|
||||
|
||||
Look for a value in the `0x2000xxxx` range — this is the SRAM address of `i2c1_inst`. It should be `0x2000062c`.
|
||||
Look for a value in the `0x2000xxxx` range — this is the SRAM address of `i2c1_inst`. It should be `0x2000062c`.
|
||||
|
||||
##### Step 4: Examine the i2c1_inst Struct in SRAM
|
||||
|
||||
@@ -95,28 +95,28 @@ This maps to the `i2c_inst_t` struct:
|
||||
|
||||
| Offset | Field | Value | Meaning |
|
||||
| ------ | ----------------- | ------------ | -------------------------------- |
|
||||
| `+0x00` | `hw` | `0x40098000` | Pointer to I²C1 hardware regs |
|
||||
| `+0x00` | `hw` | `0x40098000` | Pointer to I²C1 hardware regs |
|
||||
| `+0x04` | `restart_on_next` | `0x00000000` | `false` (no pending restart) |
|
||||
|
||||
##### Step 5: Follow the hw Pointer to Hardware Registers
|
||||
|
||||
The first member of the struct (`hw`) points to `0x40098000` — the I²C1 hardware register block. Examine it:
|
||||
The first member of the struct (`hw`) points to `0x40098000` — the I²C1 hardware register block. Examine it:
|
||||
|
||||
```gdb
|
||||
(gdb) x/8wx 0x40098000
|
||||
```
|
||||
|
||||
You should see the I²C1 control and status registers:
|
||||
You should see the I²C1 control and status registers:
|
||||
|
||||
| Offset | Register | Description |
|
||||
| ------ | -------------- | ------------------------------ |
|
||||
| `+0x00` | IC_CON | I²C control register |
|
||||
| `+0x00` | IC_CON | I²C control register |
|
||||
| `+0x04` | IC_TAR | Target address register |
|
||||
| `+0x08` | IC_SAR | Slave address register |
|
||||
| `+0x0C` | (reserved) | — |
|
||||
| `+0x0C` | (reserved) | — |
|
||||
| `+0x10` | IC_DATA_CMD | Data command register |
|
||||
|
||||
##### Step 6: Verify the I²C Target Address
|
||||
##### Step 6: Verify the I²C Target Address
|
||||
|
||||
After `i2c_init` and `lcd_i2c_init` have run, check the target address register:
|
||||
|
||||
@@ -134,7 +134,7 @@ Then examine IC_TAR:
|
||||
(gdb) x/1wx 0x40098004
|
||||
```
|
||||
|
||||
You should see `0x27` (or a value containing 0x27) — this is the LCD's I²C address!
|
||||
You should see `0x27` (or a value containing 0x27) — this is the LCD's I²C address!
|
||||
|
||||
##### Step 7: Document the Complete Chain
|
||||
|
||||
@@ -142,38 +142,38 @@ Create a diagram of the complete pointer chain:
|
||||
|
||||
```
|
||||
Your Code: I2C_PORT
|
||||
│
|
||||
▼ (preprocessor macro)
|
||||
¦
|
||||
? (preprocessor macro)
|
||||
i2c1
|
||||
│
|
||||
▼ (macro: #define i2c1 (&i2c1_inst))
|
||||
¦
|
||||
? (macro: #define i2c1 (&i2c1_inst))
|
||||
&i2c1_inst = 0x2000062c (SRAM address)
|
||||
│
|
||||
▼ (struct member access)
|
||||
¦
|
||||
? (struct member access)
|
||||
i2c1_inst.hw = 0x40098000 (hardware register base)
|
||||
│
|
||||
▼ (memory-mapped I/O)
|
||||
I²C1 Hardware Registers (silicon)
|
||||
│
|
||||
├── IC_CON at 0x40098000
|
||||
├── IC_TAR at 0x40098004
|
||||
├── IC_DATA_CMD at 0x40098010
|
||||
└── ...
|
||||
¦
|
||||
? (memory-mapped I/O)
|
||||
I²C1 Hardware Registers (silicon)
|
||||
¦
|
||||
+-- IC_CON at 0x40098000
|
||||
+-- IC_TAR at 0x40098004
|
||||
+-- IC_DATA_CMD at 0x40098010
|
||||
+-- ...
|
||||
```
|
||||
|
||||
##### Step 8: Compare with I²C0
|
||||
##### Step 8: Compare with I²C0
|
||||
|
||||
The Pico 2 has two I²C controllers. Find the `i2c0_inst` struct and compare:
|
||||
The Pico 2 has two I²C controllers. Find the `i2c0_inst` struct and compare:
|
||||
|
||||
```gdb
|
||||
(gdb) x/2wx 0x20000628
|
||||
```
|
||||
|
||||
If I²C0's struct is at a nearby address, you should see:
|
||||
- `hw` pointing to `0x40090000` (I²C0 base, different from I²C1's `0x40098000`)
|
||||
If I²C0's struct is at a nearby address, you should see:
|
||||
- `hw` pointing to `0x40090000` (I²C0 base, different from I²C1's `0x40098000`)
|
||||
- `restart_on_next` = 0
|
||||
|
||||
This demonstrates how the SDK uses the same struct layout for both I²C controllers, with only the hardware pointer changing.
|
||||
This demonstrates how the SDK uses the same struct layout for both I²C controllers, with only the hardware pointer changing.
|
||||
|
||||
#### Expected Output
|
||||
|
||||
@@ -189,18 +189,18 @@ After completing this exercise, you should be able to:
|
||||
|
||||
###### Question 2: The `hw` pointer stores `0x40098000`. In the binary, this appears as bytes `00 80 09 40`. Why is the byte order reversed from how we write the address?
|
||||
|
||||
###### Question 3: If you changed the `hw` pointer at `0x2000062c` from `0x40098000` to `0x40090000` using GDB (`set {int}0x2000062c = 0x40090000`), what I²C controller would the program use? What would happen to the LCD?
|
||||
###### Question 3: If you changed the `hw` pointer at `0x2000062c` from `0x40098000` to `0x40090000` using GDB (`set {int}0x2000062c = 0x40090000`), what I²C controller would the program use? What would happen to the LCD?
|
||||
|
||||
###### Question 4: The macro chain has 4 levels of indirection (I2C_PORT → i2c1 → &i2c1_inst → hw → registers). Is this typical for embedded SDKs? What are the trade-offs of this approach?
|
||||
###### Question 4: The macro chain has 4 levels of indirection (I2C_PORT ? i2c1 ? &i2c1_inst ? hw ? registers). Is this typical for embedded SDKs? What are the trade-offs of this approach?
|
||||
|
||||
#### Tips and Hints
|
||||
- Use `x/wx` to examine 32-bit words (pointers are 32 bits on ARM Cortex-M33)
|
||||
- SRAM addresses start with `0x20xxxxxx`; hardware register addresses start with `0x40xxxxxx`
|
||||
- The literal pool (where PC-relative loads get their data) is usually right after the function's code
|
||||
- `i2c_inst_t` is only 8 bytes: 4-byte pointer + 4-byte bool (padded to 4 bytes for alignment)
|
||||
- I²C0 base = `0x40090000`, I²C1 base = `0x40098000` — they are `0x8000` bytes apart
|
||||
- I²C0 base = `0x40090000`, I²C1 base = `0x40098000` — they are `0x8000` bytes apart
|
||||
|
||||
#### Next Steps
|
||||
- Proceed to Exercise 4 to patch the LCD to display your own custom message
|
||||
- Try modifying the `restart_on_next` field in GDB and observe if it changes I²C behavior
|
||||
- Explore the I²C hardware registers at `0x40098000` — can you read the IC_STATUS register to see if the bus is active?
|
||||
- Try modifying the `restart_on_next` field in GDB and observe if it changes I²C behavior
|
||||
- Explore the I²C hardware registers at `0x40098000` — can you read the IC_STATUS register to see if the bus is active?
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 7
|
||||
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Display Your Own Custom Message on the LCD
|
||||
|
||||
#### Answers
|
||||
|
||||
##### String Constraints
|
||||
|
||||
| Line | Original | Address | File Offset | Max Chars | Allocated Bytes |
|
||||
|------|--------------|-------------|-------------|-----------|-----------------|
|
||||
| 1 | "Reverse" | 0x10003ee8 | 0x3EE8 | 7 | 8 |
|
||||
| 2 | "Engineering" | 0x10003ef0 | 0x3EF0 | 11 | 12 |
|
||||
|
||||
##### Example Patch: "Hello!!" and "World!!"
|
||||
|
||||
**Line 1: "Hello!!" (7 characters — exact fit)**
|
||||
|
||||
| Character | Hex |
|
||||
|-----------|--------|
|
||||
| H | 0x48 |
|
||||
| e | 0x65 |
|
||||
| l | 0x6C |
|
||||
| l | 0x6C |
|
||||
| o | 0x6F |
|
||||
| ! | 0x21 |
|
||||
| ! | 0x21 |
|
||||
| \0 | 0x00 |
|
||||
|
||||
```
|
||||
Offset 0x3EE8:
|
||||
Before: 52 65 76 65 72 73 65 00 ("Reverse")
|
||||
After: 48 65 6C 6C 6F 21 21 00 ("Hello!!")
|
||||
```
|
||||
|
||||
**Line 2: "World!!" (7 characters — needs 5 bytes of null padding)**
|
||||
|
||||
| Character | Hex |
|
||||
|-----------|--------|
|
||||
| W | 0x57 |
|
||||
| o | 0x6F |
|
||||
| r | 0x72 |
|
||||
| l | 0x6C |
|
||||
| d | 0x64 |
|
||||
| ! | 0x21 |
|
||||
| ! | 0x21 |
|
||||
| \0 | 0x00 |
|
||||
| \0 (pad) | 0x00 |
|
||||
| \0 (pad) | 0x00 |
|
||||
| \0 (pad) | 0x00 |
|
||||
| \0 (pad) | 0x00 |
|
||||
|
||||
```
|
||||
Offset 0x3EF0:
|
||||
Before: 45 6E 67 69 6E 65 65 72 69 6E 67 00 ("Engineering")
|
||||
After: 57 6F 72 6C 64 21 21 00 00 00 00 00 ("World!!")
|
||||
```
|
||||
|
||||
##### Example Patch: Short String "Hi"
|
||||
|
||||
**Line 1: "Hi" (2 characters — needs 5 bytes of null padding)**
|
||||
|
||||
```
|
||||
Offset 0x3EE8:
|
||||
Before: 52 65 76 65 72 73 65 00 ("Reverse")
|
||||
After: 48 69 00 00 00 00 00 00 ("Hi")
|
||||
```
|
||||
|
||||
##### Conversion and Flash
|
||||
|
||||
```powershell
|
||||
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants
|
||||
python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **You padded short strings with 0x00 null bytes. Would it also work to pad with 0x20 (space characters)? What would be the difference on the LCD display?**
|
||||
Both approaches produce valid strings, but the display differs. With `0x00` padding, the string terminates at the first null byte — `lcd_puts` stops reading there, and the remaining bytes are ignored. The LCD shows only your text. With `0x20` (space) padding, the spaces become part of the string — `lcd_puts` sends them to the LCD as visible blank characters. The LCD would show your text followed by trailing spaces. Functionally both work, but `0x00` padding is cleaner because the string length matches your intended text, and the LCD positions after your text remain in whatever state the LCD controller initialized them to (typically blank anyway).
|
||||
|
||||
2. **The LCD is a 1602 (16 columns × 2 rows). What would happen if you could somehow put a 20-character string in memory? Would the LCD display all 20, or only the first 16?**
|
||||
The LCD would display only the first 16 characters in the visible area. The HD44780 controller (used in 1602 LCD modules) has 40 bytes of DDRAM per line, so characters 17-20 would be written to DDRAM but are beyond the visible 16-column window. They would only become visible if you issued a display shift command to scroll the view. The `lcd_puts` function writes all characters to the controller regardless of line length — it has no built-in truncation. The 16-character limit is a physical display constraint, not a software one.
|
||||
|
||||
3. **If you wanted to combine the string hacks from Exercise 1 (changing both LCD lines) AND a hypothetical numeric hack (e.g., changing the movs r1, #42 encoding at offset 0x28E), could you do all patches in a single .bin file? What offsets would you need to modify?**
|
||||
Yes, all patches can be applied to the same `.bin` file since they are at non-overlapping offsets. The three patch locations are:
|
||||
- **Offset 0x28E**: FAV_NUM — change `movs r1, #42` immediate byte from `0x2A` to desired value (1 byte)
|
||||
- **Offset 0x3EE8**: LCD line 1 — replace the 8-byte "Reverse" string
|
||||
- **Offset 0x3EF0**: LCD line 2 — replace the 12-byte "Engineering" string
|
||||
|
||||
Each patch modifies a different region of the binary, so they are completely independent. You could also patch the `movw r1, #1337` instruction at offset `0x298` (the imm8 byte of the OTHER_FAV_NUM encoding) for a fourth independent patch.
|
||||
|
||||
4. **Besides LCD text, what other strings could you patch in a real-world embedded device to change its behavior? Think about Wi-Fi SSIDs, Bluetooth device names, HTTP headers, etc.**
|
||||
Real-world embedded devices contain many patchable strings: **Wi-Fi SSIDs** and **passwords** (change what network the device connects to), **Bluetooth device names** (change how it appears during pairing), **HTTP/HTTPS URLs** (redirect API calls to a different server), **MQTT broker addresses** (redirect IoT telemetry), **DNS hostnames**, **firmware version strings** (spoof version for update bypass), **serial number formats**, **command-line interface prompts**, **error and debug messages** (hide forensic evidence), **TLS/SSL certificate fields**, **NTP server addresses** (manipulate time synchronization), and **authentication tokens or API keys**. String patching is one of the most practical firmware modification techniques because it's simple to execute and can dramatically change device behavior.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 7
|
||||
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
|
||||
|
||||
### Exercise 4: Display Your Own Custom Message on the LCD
|
||||
### Non-Credit Practice Exercise 4: Display Your Own Custom Message on the LCD
|
||||
|
||||
#### Objective
|
||||
Patch both LCD string literals in the binary to display your name (or any custom message) on the 1602 LCD, respecting the character length constraints, converting your text to hex bytes, and verifying the result on hardware.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 9
|
||||
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Change the Sleep Duration
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Sleep Duration Values
|
||||
|
||||
| Parameter | Original | Patched |
|
||||
|---------------|-------------|-------------|
|
||||
| Duration (ms) | 2000 | 5000 |
|
||||
| Hex | 0x000007D0 | 0x00001388 |
|
||||
| Little-endian | D0 07 00 00 | 88 13 00 00 |
|
||||
|
||||
##### Why a Literal Pool Is Needed
|
||||
|
||||
The value 2000 exceeds the 8-bit immediate range of `movs` (0–255) and the 16-bit range is also impractical for a single-instruction load. The compiler stores `0x000007D0` in a **literal pool** near the function code and loads it with a `ldr r0, [pc, #offset]` instruction that reads the 32-bit word from the pool into `r0` before the `bl sleep_ms` call.
|
||||
|
||||
##### Patch Procedure
|
||||
|
||||
1. Find the literal pool entry containing `D0 07 00 00` in HxD
|
||||
2. Replace with `88 13 00 00`
|
||||
|
||||
```
|
||||
Before: D0 07 00 00 (2000)
|
||||
After: 88 13 00 00 (5000)
|
||||
```
|
||||
|
||||
##### Conversion and Flash
|
||||
|
||||
```powershell
|
||||
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators
|
||||
python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
|
||||
```
|
||||
|
||||
##### Serial Output After Patch
|
||||
|
||||
```
|
||||
arithmetic_operator: 50
|
||||
increment_operator: 5
|
||||
relational_operator: 0
|
||||
logical_operator: 0
|
||||
bitwise_operator: 12
|
||||
assignment_operator: 11
|
||||
Humidity: 51.0%, Temperature: 24.0°C
|
||||
```
|
||||
|
||||
Output repeats every **5 seconds** instead of 2 seconds.
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why can't 2000 be encoded as a movs immediate? What is the maximum value movs can hold?**
|
||||
The `movs Rd, #imm8` instruction is a 16-bit Thumb encoding that has only 8 bits for the immediate value, giving a range of 0–255. The value 2000 (`0x7D0`) is far beyond this range. Even the 32-bit Thumb-2 `movw` instruction can only encode 0–65535, which could handle 2000, but the compiler chose a literal pool approach. The literal pool is a general-purpose solution that works for any 32-bit value, including addresses and large constants.
|
||||
|
||||
2. **If you wanted to change the sleep to exactly 1 second (1000ms), what 4 bytes would you write in little-endian? Show your work.**
|
||||
1000 decimal = `0x000003E8` hex. In little-endian byte order (LSB first): `E8 03 00 00`. Breakdown: byte 0 = `0xE8` (LSB), byte 1 = `0x03`, byte 2 = `0x00`, byte 3 = `0x00` (MSB).
|
||||
|
||||
3. **Could other code in the program reference the same literal pool entry containing 0x7D0? What would happen if it did?**
|
||||
Yes, the compiler may share literal pool entries to save space. If another instruction also loads `0x7D0` from the same pool address (using its own `ldr rN, [pc, #offset]`), then patching that pool entry would change the value for ALL instructions that reference it. This is a risk with literal pool patching — you might unintentionally modify other parts of the program. To check, search for all `ldr` instructions whose PC-relative offset resolves to the same pool address.
|
||||
|
||||
4. **What would happen if you set sleep_ms to 0? Would the program crash or just run extremely fast?**
|
||||
The program would not crash — `sleep_ms(0)` is a valid call that returns immediately. The loop would run as fast as the processor can execute it, printing operator values and reading the DHT11 sensor with no delay between iterations. The serial output would flood extremely quickly. However, the DHT11 sensor has a minimum sampling interval of about 1 second; reading it more frequently may return stale data or read errors ("DHT11 read failed"), but the program itself would continue running.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 9
|
||||
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics
|
||||
|
||||
### Exercise 1: Change the Sleep Duration
|
||||
### Non-Credit Practice Exercise 1: Change the Sleep Duration
|
||||
|
||||
#### Objective
|
||||
Find the `sleep_ms(2000)` call in the `0x001a_operators` binary using GDB, identify the immediate value `0x7d0` (2000) being loaded into `r0`, calculate the file offset, patch it to `0x1388` (5000) using a hex editor, and verify on hardware that the serial output now prints every 5 seconds instead of every 2 seconds.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 9
|
||||
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Invert the Temperature Reading
|
||||
|
||||
#### Answers
|
||||
|
||||
##### IEEE-754 Sign Bit Flip
|
||||
|
||||
| Value | IEEE-754 Hex | Little-Endian Bytes | Sign Bit |
|
||||
|---------|--------------|--------------------|---------|
|
||||
| +0.1f | 0x3DCCCCCD | CD CC CC 3D | 0 |
|
||||
| -0.1f | 0xBDCCCCCD | CD CC CC BD | 1 |
|
||||
|
||||
Only the **last byte** changes in little-endian: `3D` → `BD`. This flips bit 31 (the IEEE-754 sign bit) from 0 to 1, negating the value.
|
||||
|
||||
##### Patch at File Offset 0x42C
|
||||
|
||||
```
|
||||
Offset 0x42C:
|
||||
Before: CD CC CC 3D (+0.1f)
|
||||
After: CD CC CC BD (-0.1f)
|
||||
```
|
||||
|
||||
Only 1 byte changes: offset `0x42F` from `0x3D` to `0xBD`.
|
||||
|
||||
##### How the Temperature Changes
|
||||
|
||||
The DHT11 returns raw integer and decimal parts (e.g., integer=24, decimal=0). The firmware computes:
|
||||
|
||||
```
|
||||
temperature = integer_part + (decimal_part × 0.1f)
|
||||
```
|
||||
|
||||
With `vfma.f32 s15, s13, s11`: result = s15 + (s13 × s11) = integer + (decimal × 0.1f)
|
||||
|
||||
After patching to -0.1f: result = integer + (decimal × -0.1f)
|
||||
|
||||
For a reading of 24.5°C: original = 24 + (5 × 0.1) = 24.5°C, patched = 24 + (5 × -0.1) = 23.5°C
|
||||
|
||||
For a reading of 24.0°C: integer=24, decimal=0, so 24 + (0 × -0.1) = 24.0°C (unchanged when decimal is 0).
|
||||
|
||||
##### Serial Output After Patch
|
||||
|
||||
```
|
||||
Humidity: 51.0%, Temperature: 23.5°C
|
||||
```
|
||||
|
||||
(Temperature decimal contribution is inverted; effect depends on the decimal component.)
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does changing the byte from 0x3D to 0xBD negate the float? What specific bit is being flipped?**
|
||||
In IEEE-754 single-precision format, bit 31 is the **sign bit**: 0 = positive, 1 = negative. The byte `0x3D` in binary is `0011 1101` and `0xBD` is `1011 1101` — only bit 7 of that byte differs, which corresponds to bit 31 of the 32-bit float (since it's the MSB of the last byte in little-endian storage). The exponent and mantissa bits remain identical, so the magnitude stays exactly `0.1` — only the sign changes.
|
||||
|
||||
2. **Why does patching one constant affect both humidity AND temperature? They use different vfma instructions (at 0x410 and 0x414) — so why are both affected?**
|
||||
Both `vfma` instructions at `0x410` (humidity) and `0x414` (temperature) load the scaling constant from the **same literal pool entry** at offset `0x42C`. The compiler recognized that both computations use the same `0.1f` value and stored it only once to save space. Both `vldr s11, [pc, #offset]` instructions resolve to address `0x1000042C`. So patching that single 4-byte value changes the scaling factor for both humidity and temperature simultaneously.
|
||||
|
||||
3. **What is the IEEE-754 encoding of 0.5f? If the raw sensor decimal reading was 8, what would the computed value be with 0.5f instead of 0.1f?**
|
||||
0.5f in IEEE-754: sign=0, exponent=126 (`0x7E`), mantissa=0. Hex = `0x3F000000`. Little-endian bytes: `00 00 00 3F`. With a raw decimal reading of 8: `8 × 0.5 = 4.0`. So if the integer part was 24, the result would be `24 + 4.0 = 28.0°C` instead of `24 + 0.8 = 24.8°C`.
|
||||
|
||||
4. **Could you achieve the same inversion by patching the vfma instruction instead of the constant? What instruction change would work?**
|
||||
Yes. You could change `vfma.f32` (fused multiply-add: d = d + a×b) to `vfms.f32` (fused multiply-subtract: d = d - a×b). This would compute `temperature = integer - (decimal × 0.1f)` instead of `integer + (decimal × 0.1f)`, achieving the same sign inversion on the decimal contribution. The instruction encoding difference between `vfma` and `vfms` is typically a single bit in the opcode. However, this approach is more complex than simply flipping the sign bit of the constant.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 9
|
||||
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics
|
||||
|
||||
### Exercise 2: Invert the Temperature Reading
|
||||
### Non-Credit Practice Exercise 2: Invert the Temperature Reading
|
||||
|
||||
#### Objective
|
||||
Using GDB to locate the IEEE-754 scaling constant `0.1f` at file offset `0x42C`, patch it to `-0.1f` using a hex editor, and verify on hardware that the serial output now displays negative temperature values.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 9
|
||||
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Add a Fixed Temperature Offset
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Two Patches Required
|
||||
|
||||
This exercise requires **two** simultaneous patches to add a fixed +10°C offset to every temperature reading.
|
||||
|
||||
##### Patch 1: Instruction at Offset 0x414
|
||||
|
||||
Change `vfma.f32` to `vadd.f32`:
|
||||
|
||||
| Item | Original | Patched |
|
||||
|------------|------------------------------|------------------------------|
|
||||
| Instruction | vfma.f32 s15, s13, s11 | vadd.f32 s15, s15, s11 |
|
||||
| Encoding | e6 ee a5 7a | b4 ee a5 7a |
|
||||
| Operation | s15 = s15 + (s13 × s11) | s15 = s15 + s11 |
|
||||
|
||||
```
|
||||
Offset 0x414:
|
||||
Before: E6 EE A5 7A (vfma.f32)
|
||||
After: B4 EE A5 7A (vadd.f32)
|
||||
```
|
||||
|
||||
Only the first two bytes change: `E6 EE` → `B4 EE`.
|
||||
|
||||
##### Patch 2: Constant at Offset 0x42C
|
||||
|
||||
Change the constant from 0.1f to 10.0f:
|
||||
|
||||
| Value | IEEE-754 Hex | Little-Endian Bytes |
|
||||
|--------|--------------|--------------------|
|
||||
| 0.1f | 0x3DCCCCCD | CD CC CC 3D |
|
||||
| 10.0f | 0x41200000 | 00 00 20 41 |
|
||||
|
||||
```
|
||||
Offset 0x42C:
|
||||
Before: CD CC CC 3D (0.1f)
|
||||
After: 00 00 20 41 (10.0f)
|
||||
```
|
||||
|
||||
##### Why Both Patches Are Needed
|
||||
|
||||
- **Original:** `vfma.f32` computes `temp = integer + (decimal × 0.1f)` — fractional scaling
|
||||
- **After both patches:** `vadd.f32` computes `temp = integer + 10.0f` — fixed offset addition
|
||||
- **If only constant changed:** `vfma.f32` would compute `temp = integer + (decimal × 10.0f)` — amplified decimal, not a fixed offset
|
||||
|
||||
##### Serial Output After Patch
|
||||
|
||||
```
|
||||
Humidity: 51.0%, Temperature: 34.0°C
|
||||
```
|
||||
|
||||
(Original 24.0°C + 10.0°C offset = 34.0°C)
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why are both patches needed? What would happen if you only changed the constant to 10.0f but left vfma unchanged?**
|
||||
If you only changed `0.1f` to `10.0f` but left `vfma.f32`, the computation would be `temp = integer + (decimal × 10.0f)`. For a reading of 24.5°C (integer=24, decimal=5): result = 24 + (5 × 10.0) = 74.0°C — wildly incorrect. The `vfma` instruction multiplies two operands and adds, so the constant serves as a multiplier for the decimal part. By changing to `vadd.f32`, we eliminate the multiplication entirely and just add the constant directly to the integer, giving `24 + 10.0 = 34.0°C`.
|
||||
|
||||
2. **The humidity vfma instruction at 0x410 was NOT changed. Both vfma instructions (0x410 and 0x414) load the same 0.1f constant from 0x42C. With the constant now 10.0f, what happens to the humidity computation?**
|
||||
The humidity `vfma` at `0x410` now computes `hum = integer + (decimal × 10.0f)`. If the humidity decimal part is 0 (e.g., raw humidity = 51.0%), then `51 + (0 × 10.0) = 51.0%` — unchanged. But if the decimal part is non-zero (e.g., raw = 51.3%, decimal=3), the result would be `51 + (3 × 10.0) = 81.0%` — grossly incorrect. The DHT11 sensor's humidity decimal is often 0, so you might not notice the bug immediately, but it's a latent defect.
|
||||
|
||||
3. **If you wanted to add a 10°C offset to temperature WITHOUT affecting humidity, what additional patch(es) would you need?**
|
||||
You would need to ensure humidity still uses the original `0.1f` scaling. Options: (1) Also change the humidity `vfma` at `0x410` to `vadd.f32` and create a separate literal pool entry with `0.1f` for it — but this requires finding free space. (2) More practically, place a second copy of `0.1f` (`CD CC CC 3D`) in unused space in the binary, and redirect the humidity `vldr` instruction's PC-relative offset to point to that new location instead of `0x42C`. (3) Alternatively, NOP out the humidity `vfma` entirely if the decimal contribution is negligible.
|
||||
|
||||
4. **Why do only the first 2 bytes differ between vfma and vadd? What do the last 2 bytes encode?**
|
||||
In the ARM VFPv4 encoding, the first two bytes (`E6 EE` vs `B4 EE`) contain the **opcode** that distinguishes the operation type (fused multiply-add vs addition). The last two bytes (`A5 7A`) encode the **operand registers**: the source and destination VFP registers (s15, s13, s11). Since both instructions operate on the same registers, the operand encoding is identical. Only the operation code changes — this is a characteristic of the ARM instruction set where opcode and operand fields are cleanly separated.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 9
|
||||
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics
|
||||
|
||||
### Exercise 3: Add a Fixed Temperature Offset
|
||||
### Non-Credit Practice Exercise 3: Add a Fixed Temperature Offset
|
||||
|
||||
#### Objective
|
||||
Patch both the `vfma.f32` instruction at file offset `0x414` and the scaling constant at `0x42C` to replace the multiply-add with a simple add of `10.0f`, causing every temperature reading to be increased by exactly 10°C, and verify on hardware.
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 9
|
||||
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Find All printf Format Strings
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Complete String Catalog
|
||||
|
||||
| # | String | Type | Used By |
|
||||
|---|-----------------------------------------------------|---------------|-------------|
|
||||
| 1 | `"arithmetic_operator: %d\r\n"` | Format string | printf #1 |
|
||||
| 2 | `"increment_operator: %d\r\n"` | Format string | printf #2 |
|
||||
| 3 | `"relational_operator: %d\r\n"` | Format string | printf #3 |
|
||||
| 4 | `"logical_operator: %d\r\n"` | Format string | printf #4 |
|
||||
| 5 | `"bitwise_operator: %d\r\n"` | Format string | printf #5 |
|
||||
| 6 | `"assignment_operator: %d\r\n"` | Format string | printf #6 |
|
||||
| 7 | `"Humidity: %.1f%%, Temperature: %.1f°C\r\n"` | Format string | printf #7 |
|
||||
| 8 | `"DHT11 read failed\r\n"` | Plain string | puts |
|
||||
|
||||
##### Format Specifier Analysis
|
||||
|
||||
| Specifier | Meaning | Used In |
|
||||
|-----------|------------------------------------------|---------------|
|
||||
| `%d` | Signed decimal integer | Strings 1–6 |
|
||||
| `%.1f` | Float with 1 decimal place | String 7 |
|
||||
| `%%` | Literal percent sign (escaped) | String 7 |
|
||||
| `\r\n` | Carriage return + line feed (0x0D 0x0A) | All strings |
|
||||
| `°C` | UTF-8 degree symbol + C (0xC2 0xB0 0x43)| String 7 |
|
||||
|
||||
##### Expected Operator Output Values
|
||||
|
||||
| Operator | Expression | Value | Explanation |
|
||||
|-----------------------|------------------------|-------|------------------------------------|
|
||||
| arithmetic_operator | 5 × 10 | 50 | Multiplication |
|
||||
| increment_operator | x++ (x=5) | 5 | Post-increment returns old value |
|
||||
| relational_operator | 6 > 10 | 0 | False |
|
||||
| logical_operator | (6>10) && (10>6) | 0 | Short-circuit: first operand false |
|
||||
| bitwise_operator | 6 << 1 | 12 | Left shift = multiply by 2 |
|
||||
| assignment_operator | 6 + 5 | 11 | Addition assignment |
|
||||
|
||||
##### GDB Search Commands
|
||||
|
||||
```gdb
|
||||
(gdb) x/20s 0x10003e00
|
||||
(gdb) x/50s 0x10003d00
|
||||
```
|
||||
|
||||
##### Special Byte Sequences in Strings
|
||||
|
||||
| Sequence | Bytes | Meaning |
|
||||
|----------|------------|---------------------|
|
||||
| `\r\n` | 0x0D 0x0A | CRLF line ending |
|
||||
| `%%` | 0x25 0x25 | Literal % character |
|
||||
| `°` | 0xC2 0xB0 | UTF-8 degree symbol |
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **The humidity/temperature format string contains %%. What would happen if you patched one of the % characters to a different character (e.g., changed %% to %,)?**
|
||||
The `%%` escape produces a literal `%` in the output. If you change it to `%,` (bytes `25 2C`), `printf` would interpret `%,` as the start of a format specifier where `,` is the conversion character. Since `,` is not a valid `printf` conversion specifier, the behavior is **undefined** — most implementations would either print garbage, skip it, or consume the next argument from the stack incorrectly. This could corrupt the remaining output or even crash if `printf` tries to read a non-existent argument.
|
||||
|
||||
2. **If you changed "arithmetic_operator" to "hacked_operator__" (same length) in the binary, what would the serial output look like? Would the computed value change?**
|
||||
The serial output would show `hacked_operator__: 50` instead of `arithmetic_operator: 50`. The **computed value (50) would not change** — it's determined by the actual multiplication instruction in the code, not by the format string. The format string is just a label for display purposes. The `%d` specifier still reads the same `r1` register value (50) passed as the second argument to `printf`.
|
||||
|
||||
3. **What happens if you make a format string 1 byte longer (e.g., add a character)? Where would the extra byte be stored?**
|
||||
The extra byte would overwrite the **null terminator** (`0x00`) of the current string, and the byte after that is the first byte of the next consecutive string in `.rodata`. This effectively merges the two strings: `printf` would continue reading past the intended end into the next string's data until it finds another `0x00`. The output would include garbage characters from the adjacent string. If the adjacent data happens to contain `%` followed by a valid specifier, `printf` might try to read additional arguments from the stack, potentially causing a crash or information leak.
|
||||
|
||||
4. **The "DHT11 read failed" message uses puts instead of printf. Why would the compiler choose puts over printf for this particular string?**
|
||||
The compiler (with optimizations enabled) recognizes that `printf("DHT11 read failed\r\n")` has **no format specifiers** — it's a plain string with no `%d`, `%s`, `%f`, etc. Since no formatting is needed, the compiler optimizes it to `puts("DHT11 read failed")` (which automatically appends a newline). This is a common GCC optimization (`-fprintf-return-value`) because `puts` is simpler and faster than `printf` — it doesn't need to parse the format string looking for specifiers. The `\r\n` may be handled slightly differently depending on the implementation, but the key insight is that the compiler automatically selects the more efficient function.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 9
|
||||
Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics
|
||||
|
||||
### Exercise 4: Find All printf Format Strings
|
||||
### Non-Credit Practice Exercise 4: Find All printf Format Strings
|
||||
|
||||
#### Objective
|
||||
Systematically search through the `0x001a_operators` binary using GDB and a hex editor to locate every `printf` format string, catalog their addresses, file offsets, contents, and purposes, and gain experience identifying data structures in compiled binaries.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 11
|
||||
Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Add a Fourth LED
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Struct Layout (simple_led_ctrl_t)
|
||||
|
||||
| Offset | Field | Size | Original Value | Hex |
|
||||
|--------|------------|--------|----------------|------|
|
||||
| 0 | led1_pin | 1 byte | GPIO 16 | 0x10 |
|
||||
| 1 | led2_pin | 1 byte | GPIO 17 | 0x11 |
|
||||
| 2 | led3_pin | 1 byte | GPIO 18 | 0x12 |
|
||||
| 3 | led1_state | 1 byte | false (0) | 0x00 |
|
||||
| 4 | led2_state | 1 byte | false (0) | 0x00 |
|
||||
| 5 | led3_state | 1 byte | false (0) | 0x00 |
|
||||
|
||||
Total struct size: **6 bytes** (3 pin bytes + 3 state bytes).
|
||||
|
||||
##### Assembly Initialization Pattern
|
||||
|
||||
```asm
|
||||
movs r0, #0x10 ; led1_pin = 16
|
||||
strb r0, [r4, #0] ; struct offset 0
|
||||
movs r0, #0x11 ; led2_pin = 17
|
||||
strb r0, [r4, #1] ; struct offset 1
|
||||
movs r0, #0x12 ; led3_pin = 18
|
||||
strb r0, [r4, #2] ; struct offset 2
|
||||
```
|
||||
|
||||
##### Patch: Add GPIO 19 at Struct Offset 3
|
||||
|
||||
Writing `0x13` to offset 3 **overwrites led1_state**:
|
||||
|
||||
| Offset | Before | After | Impact |
|
||||
|--------|-------------|-------------|----------------------|
|
||||
| 3 | 0x00 (led1_state = false) | 0x13 (led4_pin = GPIO 19) | led1_state corrupted |
|
||||
|
||||
##### GDB Verification
|
||||
|
||||
```gdb
|
||||
(gdb) b *0x10000280
|
||||
(gdb) c
|
||||
(gdb) x/8bx <struct_address>
|
||||
```
|
||||
|
||||
Before patch: `10 11 12 00 00 00`
|
||||
After patch: `10 11 12 13 00 00`
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **The original struct has 6 members (3 pins + 3 states) in 6 bytes. If you add a fourth pin at offset 3, you overwrite led1_state. What is the practical impact on LED 1 behavior?**
|
||||
The byte `0x13` (decimal 19) is written to offset 3, which the program reads as `led1_state`. Since `bool` in C treats any non-zero value as `true`, `led1_state` would be interpreted as `true` (on) immediately after the struct is initialized. LED 1 would appear to be in the "on" state from the start, regardless of whether the user pressed button 1. The `leds_all_off` function may reset it to 0, but every time the struct is re-initialized on the stack (each loop iteration), the corrupted state returns. The fourth LED at GPIO 19 would need additional `gpio_init` and `gpio_set_dir` calls to actually function — just writing the pin number into the struct doesn't configure the GPIO hardware.
|
||||
|
||||
2. **How would you verify the exact struct layout and offsets using GDB's memory examination commands?**
|
||||
Set a breakpoint after struct initialization (`b *0x10000280`), then `x/6bx <struct_base>` to see all 6 bytes. Verify: offsets 0–2 should show `10 11 12` (pin values), offsets 3–5 should show `00 00 00` (state values). Use `x/1bx <struct_base+N>` for individual fields. To find the struct base, examine `r4` at the breakpoint since the `strb r0, [r4, #N]` instructions use r4 as the base. You can also use `p/x $r4` to get the base address, then `x/6bx $r4` for the complete layout.
|
||||
|
||||
3. **If the get_led_pin function uses a bounds check (e.g., if led_num > 3 return 0), what additional patch would you need?**
|
||||
You would need to find the comparison instruction in `get_led_pin` (at approximately `0x100002a0`) — likely a `cmp rN, #3` followed by a conditional branch. Patch the immediate from `#3` to `#4` so the bounds check allows led_num = 4. For example, if the check is `cmp r1, #3; bhi default`, change `03` to `04` in the `cmp` instruction's immediate byte. Without this patch, passing led_num=4 would fail the bounds check and return 0 (no pin), so the fourth LED would never be addressed.
|
||||
|
||||
4. **Could you extend the struct without overwriting existing fields by finding free space elsewhere in the binary? What challenges would that introduce?**
|
||||
You could find unused space (padding, NOP sleds, or unused data) and place the extended struct there. However, this introduces major challenges: (1) Every instruction that references the original struct address via `r4` would need to be redirected to the new location. (2) All `strb`/`ldrb` offsets would need updating. (3) Stack-allocated structs are recreated each loop iteration — you'd need to change the stack frame size (`sub sp, sp, #N`). (4) Functions that receive the struct pointer as an argument would need their call sites updated. In practice, relocating a struct in a compiled binary is extremely complex and error-prone — overwriting adjacent fields is the pragmatic (if destructive) approach.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 11
|
||||
Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics
|
||||
|
||||
### Exercise 1: Add a Fourth LED
|
||||
### Non-Credit Practice Exercise 1: Add a Fourth LED
|
||||
|
||||
#### Objective
|
||||
Find the struct initialization pattern in the `0x0026_functions` binary using GDB where `led1_pin` (0x10), `led2_pin` (0x11), and `led3_pin` (0x12) are stored, locate an unused byte in the struct memory region, and patch it to include a fourth LED on GPIO 19 (0x13) by extending the struct data and modifying the `ir_to_led_number` function to handle a fourth button mapping.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 11
|
||||
Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Change Blink Count
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Blink Count Parameter
|
||||
|
||||
| Parameter | Original | Patched |
|
||||
|-------------|---------|---------|
|
||||
| Blink count | 3 | 5 |
|
||||
| Hex | 0x03 | 0x05 |
|
||||
| Register | r1 | r1 |
|
||||
| Instruction | movs r1, #3 | movs r1, #5 |
|
||||
|
||||
##### Assembly Context (blink_led Call)
|
||||
|
||||
```asm
|
||||
movs r0, <pin> ; r0 = GPIO pin number
|
||||
movs r1, #3 ; r1 = blink count ← PATCH THIS
|
||||
movs r2, #0x32 ; r2 = delay (50ms)
|
||||
bl blink_led ; blink_led(pin, 3, 50)
|
||||
```
|
||||
|
||||
##### Patch
|
||||
|
||||
The immediate byte in `movs r1, #3` is the first byte of the 2-byte Thumb instruction:
|
||||
|
||||
```
|
||||
Before: 03 21 (movs r1, #3)
|
||||
After: 05 21 (movs r1, #5)
|
||||
```
|
||||
|
||||
File offset = instruction address - 0x10000000.
|
||||
|
||||
##### Behavior After Patch
|
||||
|
||||
| Button | LED | Original | Patched |
|
||||
|--------|--------|-------------------|---------------------|
|
||||
| 1 | Red | Blinks 3×, stays on | Blinks 5×, stays on |
|
||||
| 2 | Green | Blinks 3×, stays on | Blinks 5×, stays on |
|
||||
| 3 | Yellow | Blinks 3×, stays on | Blinks 5×, stays on |
|
||||
|
||||
Total blink time at 50ms delay: 5 × (50 + 50) = **500ms** (was 300ms).
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **movs rN, #imm8 can encode values 0–255. What is the maximum blink count with a single byte patch?**
|
||||
The maximum is **255** (`0xFF`). The `movs Rd, #imm8` Thumb instruction uses a full 8-bit immediate field, giving an unsigned range of 0–255. Setting the blink count to 255 would make each LED blink 255 times per button press — at 50ms on + 50ms off per blink, that's 255 × 100ms = **25.5 seconds** of blinking before the LED stays on. A count of 0 would skip the blink loop entirely (LED turns on immediately with no blinking).
|
||||
|
||||
2. **Why is blink count in r1 and not r0? What does r0 hold at this point?**
|
||||
The ARM calling convention (AAPCS) passes the first four function arguments in registers `r0`, `r1`, `r2`, `r3` in order. The `blink_led` function signature is `blink_led(uint8_t pin, uint8_t count, uint32_t delay_ms)`. So `r0` = pin (the GPIO number of the LED to blink), `r1` = count (how many times to blink), and `r2` = delay_ms (the delay in milliseconds between on/off transitions). The blink count is the second parameter, hence `r1`.
|
||||
|
||||
3. **If you wanted a blink count larger than 255 (e.g., 1000), what instruction sequence would the compiler generate instead of movs?**
|
||||
For values exceeding 255, the compiler would use a 32-bit Thumb-2 `movw r1, #imm16` instruction, which can encode 0–65535. For example, `movw r1, #1000` would be 4 bytes: `40 F2 E8 31` (encoding `movw r1, #0x3E8`). For values exceeding 65535, the compiler would add `movt r1, #imm16` to set the upper 16 bits, or use a literal pool load (`ldr r1, [pc, #offset]`). The function parameter type (`uint8_t`) would still truncate to 0–255, so a count of 1000 would wrap to 232 (1000 mod 256) unless the function uses a wider type internally.
|
||||
|
||||
4. **Is there one shared movs r1, #3 instruction for all three LEDs, or does each blink_led call have its own? How can you tell?**
|
||||
Each `blink_led` call likely has its **own** `movs r1, #3` instruction. The compiler generates separate parameter setup sequences for each `bl blink_led` call site — the `movs r0, <pin>` instruction before each call loads a different GPIO pin. You can verify by disassembling the full range (`disassemble 0x10000234,+300`) and counting how many `movs r1, #3` instructions appear before `bl blink_led` calls. If there are three separate call sites with three separate `movs r1, #3` instructions, you need to **patch all three** to change the blink count for every LED. If only one is patched, only that LED's blink count changes.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 11
|
||||
Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics
|
||||
|
||||
### Exercise 2: Change Blink Count
|
||||
### Non-Credit Practice Exercise 2: Change Blink Count
|
||||
|
||||
#### Objective
|
||||
Find the `blink_led(pin, 3, 50)` call in the `0x0026_functions` binary using GDB, identify the immediate value `#3` being loaded into `r1` (the blink count parameter), calculate the file offset, and patch it to `#5` so that each LED blinks 5 times instead of 3 when activated by the IR remote.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 11
|
||||
Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Swap All Three LEDs
|
||||
|
||||
#### Answers
|
||||
|
||||
##### GPIO Rotation Patch
|
||||
|
||||
| Struct Member | Original | Patched | Effect |
|
||||
|--------------|-----------------|-----------------|---------------------|
|
||||
| led1_pin | 0x10 (GPIO 16 Red) | 0x11 (GPIO 17 Green) | Button 1 → Green |
|
||||
| led2_pin | 0x11 (GPIO 17 Green) | 0x12 (GPIO 18 Yellow) | Button 2 → Yellow |
|
||||
| led3_pin | 0x12 (GPIO 18 Yellow) | 0x10 (GPIO 16 Red) | Button 3 → Red |
|
||||
|
||||
##### Assembly Patches
|
||||
|
||||
Three single-byte patches in `movs` immediate fields:
|
||||
|
||||
```
|
||||
Patch 1 (led1_pin): 10 → 11
|
||||
Before: 10 20 (movs r0, #0x10)
|
||||
After: 11 20 (movs r0, #0x11)
|
||||
|
||||
Patch 2 (led2_pin): 11 → 12
|
||||
Before: 11 20 (movs r0, #0x11)
|
||||
After: 12 20 (movs r0, #0x12)
|
||||
|
||||
Patch 3 (led3_pin): 12 → 10
|
||||
Before: 12 20 (movs r0, #0x12)
|
||||
After: 10 20 (movs r0, #0x10)
|
||||
```
|
||||
|
||||
##### GDB Verification
|
||||
|
||||
```gdb
|
||||
(gdb) b *0x10000280
|
||||
(gdb) c
|
||||
(gdb) x/6bx <struct_address>
|
||||
```
|
||||
|
||||
Before patch: `10 11 12 00 00 00`
|
||||
After patch: `11 12 10 00 00 00`
|
||||
|
||||
##### Behavior After Patch
|
||||
|
||||
| Button (IR) | NEC Code | Terminal Log | Actual LED |
|
||||
|------------|----------|---------------------------|-----------|
|
||||
| Button 1 | 0x0C | "LED 1 activated on GPIO 16" | Green (GPIO 17) |
|
||||
| Button 2 | 0x18 | "LED 2 activated on GPIO 17" | Yellow (GPIO 18) |
|
||||
| Button 3 | 0x5E | "LED 3 activated on GPIO 18" | Red (GPIO 16) |
|
||||
|
||||
The terminal logs are **desynchronized** from actual behavior.
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Terminal log still says "LED 1 activated on GPIO 16" even though GPIO 17 (Green) is actually blinking. Why don't the logs update automatically?**
|
||||
The `printf` format strings and their arguments are separate from the struct pin assignments. The log message "LED 1 activated on GPIO 16" is generated from hardcoded format strings or from reading the **original** pin value before our patch takes effect. The GPIO number in the log comes from a different code path — likely a format string like `"LED %d activated on GPIO %d\r\n"` where the GPIO value was loaded from the struct at a different point or is computed independently. Since we only patched the `movs` instructions that store pin values into the struct, the logging code still uses whatever values it computes independently.
|
||||
|
||||
2. **If the struct initialization used ldr from a literal pool instead of movs immediates, how would the patching differ?**
|
||||
With literal pool loads, the pin values would be stored as 32-bit words in a data area near the function code. You would need to: (1) find the `ldr r0, [pc, #offset]` instruction, (2) calculate the PC-relative offset to locate the literal pool entry, (3) navigate to the pool address in the hex editor, and (4) modify the 4-byte value there. For example, GPIO 16 would be `10 00 00 00` (little-endian) in the pool. This is more work than patching a 1-byte `movs` immediate, and you'd need to verify no other code shares the same pool entry. The `movs` approach is simpler because the value is encoded directly in the instruction.
|
||||
|
||||
3. **Could you achieve the same LED rotation by patching gpio_init/gpio_put calls instead of the struct initialization? Which approach is cleaner?**
|
||||
Patching `gpio_init` and `gpio_put` calls would require finding every call site that references each GPIO pin and modifying the pin argument. This is scattered throughout multiple functions (`process_ir_led_command`, `blink_led`, `leds_all_off`). The struct initialization approach is **far cleaner** — three adjacent `movs` instructions in one location control the entire mapping. By patching the struct data at its source, every function that reads from the struct automatically gets the new values. This demonstrates the power of data-driven design: changing the data at one point affects all code that uses it.
|
||||
|
||||
4. **In a real attack, why is log desynchronization (display says one thing, hardware does another) dangerous for forensic analysis?**
|
||||
Log desynchronization is dangerous because forensic investigators rely on logs to reconstruct what happened. If logs show "LED 1 on GPIO 16" but the hardware actually activated GPIO 17, investigators would draw incorrect conclusions about which physical device was controlled. In industrial systems, this could mask sabotage — operators see "normal" readings while equipment is being misused. In security systems, tampered firmware could log "door locked" while actually unlocking it. The logs become actively misleading, not just incomplete. This is a form of **anti-forensics** that makes post-incident analysis unreliable and can delay or prevent discovery of the actual attack.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 11
|
||||
Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics
|
||||
|
||||
### Exercise 3: Swap All Three LEDs
|
||||
### Non-Credit Practice Exercise 3: Swap All Three LEDs
|
||||
|
||||
#### Objective
|
||||
Find the struct initialization instructions where `led1_pin` = 0x10 (GPIO 16, Red), `led2_pin` = 0x11 (GPIO 17, Green), and `led3_pin` = 0x12 (GPIO 18, Yellow) are written in the `0x0026_functions` binary using GDB, calculate the file offsets, and rotate the GPIO values so that button 1→Green (0x11), button 2→Yellow (0x12), and button 3→Red (0x10), then verify on hardware that the LED mapping has shifted.
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 11
|
||||
Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Change Blink Speed
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Delay Parameter
|
||||
|
||||
| Parameter | Original | Patched |
|
||||
|----------|-------------|--------------|
|
||||
| Delay | 50ms | 25ms |
|
||||
| Hex | 0x32 | 0x19 |
|
||||
| Register | r2 | r2 |
|
||||
| Instruction | movs r2, #0x32 | movs r2, #0x19 |
|
||||
|
||||
##### Assembly Context
|
||||
|
||||
```asm
|
||||
movs r0, <pin> ; r0 = GPIO pin
|
||||
movs r1, #3 ; r1 = blink count
|
||||
movs r2, #0x32 ; r2 = delay 50ms ← PATCH THIS
|
||||
bl blink_led ; blink_led(pin, 3, 50)
|
||||
```
|
||||
|
||||
##### Patch
|
||||
|
||||
```
|
||||
Before: 32 22 (movs r2, #0x32 = 50ms)
|
||||
After: 19 22 (movs r2, #0x19 = 25ms)
|
||||
```
|
||||
|
||||
**Warning:** The byte `0x32` is also ASCII '2'. Verify you're patching the correct `movs r2` instruction by checking surrounding bytes — `movs r1, #3` (`03 21`) should appear immediately before, and `bl blink_led` immediately after.
|
||||
|
||||
##### Timing Comparison
|
||||
|
||||
| Metric | Original (50ms) | Patched (25ms) |
|
||||
|-------------------|-----------------|----------------|
|
||||
| On-time per blink | 50ms | 25ms |
|
||||
| Off-time per blink| 50ms | 25ms |
|
||||
| One blink cycle | 100ms | 50ms |
|
||||
| 3 blinks total | 300ms | 150ms |
|
||||
| Perceived speed | Normal | 2× faster |
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **If delay = 1ms (0x01), would you still see the LED blink, or would it appear constantly on?**
|
||||
At 1ms on/off (2ms per cycle, 500Hz flicker), the LED would appear **constantly on** to the human eye. Human flicker fusion threshold is approximately 60Hz — anything above that appears as a steady light. At 500Hz, the LED is switching far too fast for the eye to perceive individual blinks. The LED would look like it's at roughly 50% brightness (since it's on half the time) compared to being fully on. The 3 blinks would complete in just 6ms total, appearing as a brief flash rather than distinct blinks.
|
||||
|
||||
2. **0x32 appears as both the delay value (50ms) and potentially ASCII '2'. How would you systematically find ALL occurrences of 0x32 and determine which to patch?**
|
||||
Search the binary for all `0x32` bytes, then examine the **context** of each occurrence: (1) Check the byte following `0x32` — if it's `0x22`, this is `movs r2, #0x32` (the delay parameter). If it's `0x2C`, it's `cmp r4, #0x32` (comparing against ASCII '2'). (2) Examine surrounding instructions: the delay `0x32` will be preceded by `movs r1, #3` (blink count) and followed by `bl blink_led`. A comparison `0x32` will be near `beq`/`bne` branches. (3) Use GDB to disassemble the region (`x/10i <addr-4>`) and read the instruction mnemonic. (4) Cross-reference with the function structure — delay patches are in `blink_led` call setup, comparisons are in `ir_to_led_number` or similar dispatcher functions.
|
||||
|
||||
3. **For a delay of 500ms (0x1F4), the value won't fit in a movs immediate (max 255). How would the compiler handle it?**
|
||||
For 500 (`0x1F4`), the compiler would use either: (1) A 32-bit `movw r2, #0x1F4` Thumb-2 instruction (4 bytes), which can encode any 16-bit immediate (0–65535). (2) A literal pool load: `ldr r2, [pc, #offset]` that reads `0x000001F4` from a nearby data word. The `movw` approach is preferred for values 256–65535 because it's a single instruction with no data dependency. For values exceeding 65535, a literal pool or `movw`+`movt` pair would be necessary.
|
||||
|
||||
4. **The blink function uses the delay for both on-time and off-time (symmetrical blink). Could you make the LED stay on longer than off by patching only one instruction?**
|
||||
Not with a single patch to the `movs r2` instruction, because `blink_led` uses the same delay parameter for both the on-phase and off-phase `sleep_ms` calls internally. To create asymmetric blink timing, you would need to patch **inside** the `blink_led` function itself — find the two `sleep_ms` calls within the blink loop and modify their delay arguments independently. For example, find the `ldr`/`movs` that sets up `r0` before each `bl sleep_ms` inside `blink_led`, and patch one to a different value. This would require disassembling `blink_led` to locate both `sleep_ms` call sites.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 11
|
||||
Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics
|
||||
|
||||
### Exercise 4: Change Blink Speed
|
||||
### Non-Credit Practice Exercise 4: Change Blink Speed
|
||||
|
||||
#### Objective
|
||||
Find the `blink_led(pin, 3, 50)` call in the `0x0026_functions` binary using GDB, identify the immediate value `0x32` (50) being loaded into `r2` (the delay parameter), calculate the file offset, and patch it to `0x19` (25) so that each LED blinks at double speed when activated by the IR remote.
|
||||
|
||||
Reference in New Issue
Block a user