Updated WEEK04

This commit is contained in:
Kevin Thomas
2026-05-09 11:42:33 -04:00
parent 005fd08646
commit ee664b6733
165 changed files with 3952 additions and 13308 deletions
@@ -1,5 +1,5 @@
/**
* @file 0x0008_unitialized-variables-a.c
* @file 0x0008_uninitialized-variables-a.c
* @brief Blink LED using SDK gpio functions (no UART)
* @author Kevin Thomas
* @date 2025
@@ -28,30 +28,30 @@ set(PICO_BOARD pico2 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(0x0008_unitialized-variables-a C CXX ASM)
project(0x0008_uninitialized-variables-a C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(0x0008_unitialized-variables-a 0x0008_unitialized-variables-a.c )
add_executable(0x0008_uninitialized-variables-a 0x0008_uninitialized-variables-a.c )
pico_set_program_name(0x0008_unitialized-variables-a "0x0008_unitialized-variables-a")
pico_set_program_version(0x0008_unitialized-variables-a "0.1")
pico_set_program_name(0x0008_uninitialized-variables-a "0x0008_uninitialized-variables-a")
pico_set_program_version(0x0008_uninitialized-variables-a "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(0x0008_unitialized-variables-a 0)
pico_enable_stdio_usb(0x0008_unitialized-variables-a 0)
pico_enable_stdio_uart(0x0008_uninitialized-variables-a 0)
pico_enable_stdio_usb(0x0008_uninitialized-variables-a 0)
# Add the standard library to the build
target_link_libraries(0x0008_unitialized-variables-a
target_link_libraries(0x0008_uninitialized-variables-a
pico_stdlib)
# Add the standard include files to the build
target_include_directories(0x0008_unitialized-variables-a PRIVATE
target_include_directories(0x0008_uninitialized-variables-a PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(0x0008_unitialized-variables-a)
pico_add_extra_outputs(0x0008_uninitialized-variables-a)
@@ -1,5 +1,5 @@
/**
* @file 0x0008_unitialized-variables-b.c
* @file 0x0008_uninitialized-variables-b.c
* @brief Blink LED using gpioc coprocessor bit-level functions
* @author Kevin Thomas
* @date 2025
@@ -28,30 +28,30 @@ set(PICO_BOARD pico2 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(0x0008_unitialized-variables-d C CXX ASM)
project(0x0008_uninitialized-variables-b C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(0x0008_unitialized-variables-d 0x0008_unitialized-variables-d.c )
add_executable(0x0008_uninitialized-variables-b 0x0008_uninitialized-variables-b.c )
pico_set_program_name(0x0008_unitialized-variables-d "0x0008_unitialized-variables-d")
pico_set_program_version(0x0008_unitialized-variables-d "0.1")
pico_set_program_name(0x0008_uninitialized-variables-b "0x0008_uninitialized-variables-b")
pico_set_program_version(0x0008_uninitialized-variables-b "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(0x0008_unitialized-variables-d 0)
pico_enable_stdio_usb(0x0008_unitialized-variables-d 0)
pico_enable_stdio_uart(0x0008_uninitialized-variables-b 0)
pico_enable_stdio_usb(0x0008_uninitialized-variables-b 0)
# Add the standard library to the build
target_link_libraries(0x0008_unitialized-variables-d
target_link_libraries(0x0008_uninitialized-variables-b
pico_stdlib)
# Add the standard include files to the build
target_include_directories(0x0008_unitialized-variables-d PRIVATE
target_include_directories(0x0008_uninitialized-variables-b PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(0x0008_unitialized-variables-d)
pico_add_extra_outputs(0x0008_uninitialized-variables-b)
@@ -1,5 +1,5 @@
/**
* @file 0x0008_unitialized-variables-c.c
* @file 0x0008_uninitialized-variables-c.c
* @brief Blink LED using direct register-level pad and IO bank configuration
* @author Kevin Thomas
* @date 2025
@@ -28,30 +28,30 @@ set(PICO_BOARD pico2 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(0x0008_unitialized-variables-b C CXX ASM)
project(0x0008_uninitialized-variables-c C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(0x0008_unitialized-variables-b 0x0008_unitialized-variables-b.c )
add_executable(0x0008_uninitialized-variables-c 0x0008_uninitialized-variables-c.c )
pico_set_program_name(0x0008_unitialized-variables-b "0x0008_unitialized-variables-b")
pico_set_program_version(0x0008_unitialized-variables-b "0.1")
pico_set_program_name(0x0008_uninitialized-variables-c "0x0008_uninitialized-variables-c")
pico_set_program_version(0x0008_uninitialized-variables-c "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(0x0008_unitialized-variables-b 0)
pico_enable_stdio_usb(0x0008_unitialized-variables-b 0)
pico_enable_stdio_uart(0x0008_uninitialized-variables-c 0)
pico_enable_stdio_usb(0x0008_uninitialized-variables-c 0)
# Add the standard library to the build
target_link_libraries(0x0008_unitialized-variables-b
target_link_libraries(0x0008_uninitialized-variables-c
pico_stdlib)
# Add the standard include files to the build
target_include_directories(0x0008_unitialized-variables-b PRIVATE
target_include_directories(0x0008_uninitialized-variables-c PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(0x0008_unitialized-variables-b)
pico_add_extra_outputs(0x0008_uninitialized-variables-c)
@@ -1,5 +1,5 @@
/**
* @file 0x0008_unitialized-variables-d.c
* @file 0x0008_uninitialized-variables-d.c
* @brief Blink LED using pico_default_asm_volatile coprocessor instructions
* @author Kevin Thomas
* @date 2025
@@ -0,0 +1,57 @@
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico2 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(0x0008_uninitialized-variables-d C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(0x0008_uninitialized-variables-d 0x0008_uninitialized-variables-d.c )
pico_set_program_name(0x0008_uninitialized-variables-d "0x0008_uninitialized-variables-d")
pico_set_program_version(0x0008_uninitialized-variables-d "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(0x0008_uninitialized-variables-d 0)
pico_enable_stdio_usb(0x0008_uninitialized-variables-d 0)
# Add the standard library to the build
target_link_libraries(0x0008_uninitialized-variables-d
pico_stdlib)
# Add the standard include files to the build
target_include_directories(0x0008_uninitialized-variables-d PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(0x0008_uninitialized-variables-d)
@@ -1,5 +1,5 @@
/**
* @file 0x0008_unitialized-variables-e.c
* @file 0x0008_uninitialized-variables-e.c
* @brief Blink LED using pure inline ARM assembly with direct register addresses
* @author Kevin Thomas
* @date 2025
@@ -0,0 +1,57 @@
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico2 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(0x0008_uninitialized-variables-e C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(0x0008_uninitialized-variables-e 0x0008_uninitialized-variables-e.c )
pico_set_program_name(0x0008_uninitialized-variables-e "0x0008_uninitialized-variables-e")
pico_set_program_version(0x0008_uninitialized-variables-e "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(0x0008_uninitialized-variables-e 0)
pico_enable_stdio_usb(0x0008_uninitialized-variables-e 0)
# Add the standard library to the build
target_link_libraries(0x0008_uninitialized-variables-e
pico_stdlib)
# Add the standard include files to the build
target_include_directories(0x0008_uninitialized-variables-e PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(0x0008_uninitialized-variables-e)
@@ -1,5 +1,5 @@
/**
* @file 0x0008_unitialized-variables.c
* @file 0x0008_uninitialized-variables.c
* @brief Uninitialized variables: demonstrate undefined behavior with printf
* @author Kevin Thomas
* @date 2025
@@ -28,30 +28,30 @@ set(PICO_BOARD pico2 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(0x0008_unitialized-variables-c C CXX ASM)
project(0x0008_uninitialized-variables C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(0x0008_unitialized-variables-c 0x0008_unitialized-variables-c.c )
add_executable(0x0008_uninitialized-variables 0x0008_uninitialized-variables.c )
pico_set_program_name(0x0008_unitialized-variables-c "0x0008_unitialized-variables-c")
pico_set_program_version(0x0008_unitialized-variables-c "0.1")
pico_set_program_name(0x0008_uninitialized-variables "0x0008_uninitialized-variables")
pico_set_program_version(0x0008_uninitialized-variables "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(0x0008_unitialized-variables-c 0)
pico_enable_stdio_usb(0x0008_unitialized-variables-c 0)
pico_enable_stdio_uart(0x0008_uninitialized-variables 1)
pico_enable_stdio_usb(0x0008_uninitialized-variables 0)
# Add the standard library to the build
target_link_libraries(0x0008_unitialized-variables-c
target_link_libraries(0x0008_uninitialized-variables
pico_stdlib)
# Add the standard include files to the build
target_include_directories(0x0008_unitialized-variables-c PRIVATE
target_include_directories(0x0008_uninitialized-variables PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(0x0008_unitialized-variables-c)
pico_add_extra_outputs(0x0008_uninitialized-variables)
@@ -1,57 +0,0 @@
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico2 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(0x0008_unitialized-variables-e C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(0x0008_unitialized-variables-e 0x0008_unitialized-variables-e.c )
pico_set_program_name(0x0008_unitialized-variables-e "0x0008_unitialized-variables-e")
pico_set_program_version(0x0008_unitialized-variables-e "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(0x0008_unitialized-variables-e 0)
pico_enable_stdio_usb(0x0008_unitialized-variables-e 0)
# Add the standard library to the build
target_link_libraries(0x0008_unitialized-variables-e
pico_stdlib)
# Add the standard include files to the build
target_include_directories(0x0008_unitialized-variables-e PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(0x0008_unitialized-variables-e)
@@ -1,57 +0,0 @@
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico2 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(0x0008_unitialized-variables C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(0x0008_unitialized-variables 0x0008_unitialized-variables.c )
pico_set_program_name(0x0008_unitialized-variables "0x0008_unitialized-variables")
pico_set_program_version(0x0008_unitialized-variables "0.1")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(0x0008_unitialized-variables 1)
pico_enable_stdio_usb(0x0008_unitialized-variables 0)
# Add the standard library to the build
target_link_libraries(0x0008_unitialized-variables
pico_stdlib)
# Add the standard include files to the build
target_include_directories(0x0008_unitialized-variables PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(0x0008_unitialized-variables)
-150
View File
@@ -64,21 +64,6 @@ 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)
#### Non-Credit Practice Exercise 1: Explore in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-01.md)
#### Non-Credit Practice Exercise 1 Solution: Explore in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-01-S.md)
#### Non-Credit Practice Exercise 2: Find Strings in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-02.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.
@@ -97,21 +82,6 @@ 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)
#### Non-Credit Practice Exercise 1: Change the Message [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-01.md)
#### Non-Credit Practice Exercise 1 Solution: Change the Message [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-01-S.md)
#### Non-Credit Practice Exercise 2: Use a Different SRAM Address [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-02.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.
@@ -125,21 +95,6 @@ Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive
### Week 3 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03.md)
#### Non-Credit Practice Exercise 1: Trace a Reset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-01.md)
#### Non-Credit Practice Exercise 1 Solution: Trace a Reset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-01-S.md)
#### Non-Credit Practice Exercise 2: Find the Stack Size [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-02.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.
@@ -153,21 +108,6 @@ 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)
#### Non-Credit Practice 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 Solution: Analyze Variable Storage in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-01-S.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)
#### 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.
@@ -206,21 +146,6 @@ 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)
#### Non-Credit Practice 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 Solution: Analyze the Float Binary in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-01-S.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)
#### 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.
@@ -274,21 +199,6 @@ 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)
#### 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)
#### 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)
#### 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)
#### 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.
@@ -312,21 +222,6 @@ 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)
#### Non-Credit Practice Exercise 1: Change Both LCD Lines [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-01.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)
#### 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)
#### 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.
@@ -353,21 +248,6 @@ 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)
#### Non-Credit Practice Exercise 1: Change the Sleep Duration [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-01.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)
#### Non-Credit Practice Exercise 2: Invert the Temperature Reading [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-02.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.
@@ -391,21 +271,6 @@ 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)
#### Non-Credit Practice Exercise 1: Change Servo Angle Range [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-01.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)
#### Non-Credit Practice Exercise 2: Add a Third Command [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-02.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.
@@ -444,21 +309,6 @@ 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)
#### Non-Credit Practice Exercise 1: Add a Fourth LED [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-01.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)
#### Non-Credit Practice Exercise 2: Change Blink Count [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-02.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.
-41
View File
@@ -1,41 +0,0 @@
# 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`. The function signature is `_Bool stdio_init_all(void)`.
##### Question 2: What parameters does it take?
None. The signature uses `(void)`, which means zero parameters.
##### Question 3: What functions does it call?
In this build, it calls `stdio_uart_init()` to initialize serial output.
##### Question 4: What's the purpose?
Its purpose is to initialize standard I/O so `printf()`/`puts()` output can be transmitted over UART.
##### Expected Output
```
stdio_init_all() returns: _Bool
It takes 0 parameters
It calls the following functions: UART init
Based on these calls, I believe it initializes: Standard I/O for UART serial communication
```
#### Reflection Answers
1. **Why would we need to initialize standard I/O before using `printf()`?**
Without initialization, there is no configured output path. `printf()` needs a destination (UART in this exercise) to transmit characters.
2. **Can you find other functions in the Symbol Tree that might be related to I/O?**
Yes - `stdio_uart_init`, `__wrap_puts`, and related low-level serial/output helpers are I/O-related.
3. **How does this function support the `printf("hello, world\r\n")` call in main?**
It configures the UART output path so when `printf()` (optimized to `__wrap_puts`) runs, the string is sent over serial.
-122
View File
@@ -1,122 +0,0 @@
# 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: Explore in Ghidra
#### Objective
Learn how to navigate Ghidra's Symbol Tree to find and analyze functions, specifically examining the `stdio_init_all` function.
#### Prerequisites
- Ghidra installed and running
- `0x0001_hello-world` project already created and imported in Ghidra
- The `0x0001_hello-world.elf` file already imported and analyzed
#### Task Description
Your goal is to explore the `stdio_init_all` function in Ghidra and understand what it does based on:
1. Its decompiled code
2. The functions it calls
3. The variables it accesses
#### Step-by-Step Instructions
##### Step 1: Open Your Ghidra Project
1. Launch **Ghidra** on your computer
2. In the Ghidra Project Manager window, you should see your `0x0001_hello-world` project
3. If you don't see it, create a new project or open an existing one
4. **Double-click** on the project to open it
##### Step 2: Access the Symbol Tree
In the CodeBrowser window that opens:
- Look at the left side panel - you should see several tabs
- Find and click on the **Symbol Tree** tab (it might be labeled "Symbol Tree" or showing a tree icon)
- If you don't see it, go to **Window → Symbol Tree** in the menu
##### Step 3: Expand the Functions List
1. In the Symbol Tree, look for a folder or section labeled **Functions**
2. **Click the arrow/triangle** next to "Functions" to expand it
3. This will show you a list of all the functions that Ghidra identified in the binary
##### Step 4: Find the stdio_init_all Function
1. In the expanded Functions list, scroll through to find `stdio_init_all`
2. **Alternative method**: If the list is long, you can use **Search → For Address or Label** from the menu and type `stdio_init_all` to jump directly to it
3. Once you find it, **click on it** to navigate to that function in the CodeBrowser
##### Step 5: Examine the Decompiled Code
Once you've navigated to `stdio_init_all`:
- On the **right side** of the window, you should see the **Decompile** view
- This shows the C-like code that Ghidra has reconstructed from the assembly
- Read through the decompiled code carefully
##### Step 6: Answer These Questions
Based on what you see in the decompiled code, answer the following:
###### Question 1: What does the function return?
Look at the return type at the top of the function. Is it `void`, `int`, `_Bool`, or something else?
###### Question 2: What parameters does it take?
Look at the function signature. Does it take any parameters? (Hint: Look for anything inside the parentheses)
###### Question 3: What functions does it call?
Look for function calls within `stdio_init_all`. What other functions does it call? List them:
- Function 1: ________________
- Function 2: ________________
- Function 3: ________________
(There may be more or fewer)
###### Question 4: What's the purpose?
Based on the functions it calls and the overall structure, what do you think `stdio_init_all()` is setting up? Think about what "stdio" stands for:
- **std** = Standard
- **io** = Input/Output
What types of I/O might be getting initialized?
##### Step 7: Explore Called Functions (Optional Challenge)
If you want to go deeper:
1. In the Decompile view, **click on one of the functions** that `stdio_init_all` calls
2. Ghidra will navigate to that function
3. Look at what **that** function does
4. Can you build a picture of what's being initialized?
#### Expected Output
You should be able to write a brief summary like:
```
stdio_init_all() returns: [your answer]
It takes [number] parameters
It calls the following functions: [list them]
Based on these calls, I believe it initializes: [your analysis]
```
#### Questions for Reflection
1. Why would we need to initialize standard I/O before using `printf()`?
2. Can you find other functions in the Symbol Tree that might be related to I/O?
3. How does this function support the `printf("hello, world\r\n")` call in main?
#### Tips and Hints
- If you see a function name you don't recognize, you can right-click on it to see more options
- The Decompile view is your best friend - it shows you what code is doing in an almost-C format
- Don't worry if some variable names are automatic (like `local_4` or `param_1`) - that's normal when symbols aren't available
- You can collapse/expand sections in the Decompile view by clicking the arrows next to braces `{}`
#### Next Steps
After completing this exercise, you'll have a better understanding of:
- How to navigate Ghidra's interface
- How to find functions using the Symbol Tree
- How to read decompiled code
- How initialization functions work in embedded systems
-56
View File
@@ -1,56 +0,0 @@
# 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 is it referenced, and by which function(s)?
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.
-162
View File
@@ -1,162 +0,0 @@
# 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: 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.
#### Prerequisites
- Ghidra installed with `0x0001_hello-world` project open
- Basic familiarity with Ghidra's interface (from Exercise 1)
- CodeBrowser window open with the binary loaded
#### Task Description
In this exercise, you'll find the "hello, world" string in the binary and determine:
1. **Where** it's located in memory (its address)
2. **How** it's used by the program
3. **What** format it's stored in
#### Step-by-Step Instructions
##### Step 1: Open the Defined Strings Window
1. In the CodeBrowser menu, go to **Window** (top menu bar)
2. Look for and click on **Defined Strings**
3. A new window should appear showing all strings Ghidra found in the binary
##### Step 2: Understand the Strings Window
The Defined Strings window shows:
- **Address**: The memory location where the string starts
- **String**: The actual text content
- **Length**: How many bytes the string uses
- **Defined**: Whether Ghidra has marked it as data
##### Step 3: Search for "hello, world"
1. In the Defined Strings window, look through the list to find `"hello, world"`
2. **Search method**: If the window has a search box at the top, you can type to filter. Otherwise, use **Ctrl+F** to open the search function
3. Once you find it, **click on it** to highlight the entry
##### Step 4: Record the Address
When you find `"hello, world"`, note down:
**String Address**: ________________
**Actual String Content**: ________________
**String Length**: ________________ bytes
##### Step 5: Double-Click to Navigate
1. **Double-click** on the `"hello, world"` entry in the Defined Strings window
2. Ghidra will automatically navigate you to that address in the CodeBrowser
3. You should see the string displayed in the **Listing** view (center panel)
##### Step 6: Examine the Listing View
Now that you're at the string's location:
1. Look at the **Listing view** (center panel) where the string is shown
2. You'll see the string in **hex/ASCII** format
3. Notice how it appears in memory - each character takes one byte
4. Look for the string content: `hello, world\r\n`
5. What comes after the string? (Ghidra may show other data nearby)
##### Step 7: Look at the Cross-References
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**
3. A dialog should appear showing which functions/instructions reference this string
4. This tells you which parts of the code use this string
##### Step 8: Answer These Questions
Based on what you found:
###### Question 1: What is the address, and is it Flash or RAM?
- What is the address of the "hello, world" string? __________
- Is it in Flash memory (starts with `0x100...`) or RAM (starts with `0x200...`)? __________
###### Question 2: How many bytes does the string take?
- How many bytes does the string take in memory? __________
- Can you count the characters? (h-e-l-l-o-,-space-w-o-r-l-d-\r-\n)
###### Question 3: How many times is it referenced, and by which function(s)?
- How many times is this string referenced in the code? __________
- Which function(s) reference it? (Hint: Look at the cross-references)
###### Question 4: How is the string encoded?
- How is the string encoded in memory?
- Is each character one byte or more? __________
- What does `\r` and `\n` represent? (Hint: `\r` = carriage return, `\n` = newline)
#### Expected Output
You should be able to fill in a summary like:
```
String Found: "hello, world\r\n"
Address: 0x________
Located in: [Flash / RAM]
Total Size: ________ bytes
Referenced by: [Function names]
Used in: [How the program uses it]
```
#### Deeper Exploration (Optional Challenge)
##### Challenge 1: Follow the String Usage
1. From the cross-references you found, click on the instruction that uses the string
2. You should navigate to the `ldr` (load) instruction that loads the string's address into register `r0`
3. This is how the `printf` function gets the pointer to the string!
##### Challenge 2: Find Other Strings
1. Go back to the Defined Strings window
2. Look for other strings in the binary
3. Are there any other text strings besides "hello, world"?
4. If yes, where are they and what are they used for?
##### Challenge 3: Understand Little-Endian
1. When Ghidra shows the string address in the `ldr` instruction, it's showing a number
2. Look at the raw bytes of that address value
3. Notice how the bytes are stored in "backwards" order? That's little-endian!
4. Can you convert the hex bytes to the actual address?
#### Questions for Reflection
1. **Why is the string stored in Flash instead of RAM?**
2. **What would happen if you tried to modify this string at runtime?**
3. **How does the Listing view help you understand string storage?**
#### Tips and Hints
- Strings in compiled binaries are often stored in read-only memory (Flash) to save RAM
- The `\r` and `\n` characters are special: they're single bytes (0x0D and 0x0A in hex)
- When you see a string in Ghidra's listing, the ASCII representation is shown on the right side
- You can scroll left/right in the Listing view to see different representations (hex, ASCII, disassembly)
#### Real-World Application
Understanding where strings are stored is crucial for:
- **Firmware modification**: Finding text messages to modify
- **Reverse engineering**: Understanding what a program does by finding its strings
- **Vulnerability analysis**: Finding format string bugs or hardcoded credentials
- **Localization**: Finding where text needs to be translated
#### Summary
By completing this exercise, you've learned:
1. How to find strings in a binary using Ghidra's Defined Strings window
2. How to determine the memory address of a string
3. How to follow cross-references to see where strings are used
4. How strings are stored in memory and referenced in code
5. The relationship between C code (`printf()`) and assembly (`ldr`)
-60
View File
@@ -1,60 +0,0 @@
# 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`?
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).
##### Complete the 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.
-190
View File
@@ -1,190 +0,0 @@
# 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: 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.
#### Prerequisites
- Ghidra installed with `0x0001_hello-world` project open
- Completed Exercise 2 (Find Strings) - you should know where the "hello, world" string is located
- CodeBrowser window open with the binary loaded
#### Task Description
In this exercise, you'll:
1. Navigate to a specific data reference in the `main` function
2. Find where a particular data item (`DAT_...`) is used
3. Trace back to see which functions access this data
4. Understand how data flows from memory to the CPU and then to functions
#### 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
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**)
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)
##### Step 2: Locate the `ldr` Instruction
In the main function's disassembly, look for an `ldr` (load register) instruction. It should look something like:
```
ldr r0, [DAT_10000244]
```
or similar. This instruction:
- **`ldr`** = load register (read data from memory)
- **`r0`** = put the data into register `r0`
- **`[DAT_10000244]`** = read from the address stored at location `DAT_10000244`
##### Step 3: Understand the Notation
In Ghidra's decompiler notation:
- **`DAT_10000244`** = a data item (not code) at address `0x10000244`
- **`[...]`** = the address of; accessing memory at that location
- The actual value is the address of the "hello, world" string in Flash memory
##### Step 4: Right-Click on the Data Reference
1. In the Listing view, find the `ldr` instruction that loads the string address
2. **Right-click** on the `DAT_...` part (the data reference)
3. A context menu should appear
##### Step 5: Select "References" Option
In the context menu:
1. Look for an option that says **References**
2. Click on it to see a submenu
3. Select **Show References to** (this shows "where is this data used?")
##### Step 6: Review the References Window
A new window should appear showing all the locations where `DAT_10000244` (or whatever the address is) is referenced:
**Expected output might look like:**
```
DAT_10000244 (1 xref):
main:10000236 (read)
```
This means:
- The data at `DAT_10000244` is used in 1 place
- That place is in the `main` function at instruction `10000236`
- It's a **read** operation (the code is reading this data)
##### Step 7: Answer These Questions
###### Question 1: What is the address of the data reference?
- What is the address of the data reference you found? (e.g., `DAT_10000244`)
- __________
###### Question 2: How many places reference this data?
- How many places reference this data?
- __________
- Which function(s) use it?
- __________
###### Question 3: Is it a read or write operation? Why?
- Is it a read or write operation?
- __________
- Why? (What's the program doing with this data?)
- __________
###### Question 4: What happens next after the `ldr`?
- The `ldr` instruction loads an address into `r0`
- What happens next? (Hint: Look at the next instruction after the `ldr`)
- __________
- Is there a function call? If so, which one?
- __________
###### Complete the Data Flow Chain
- **`DAT_10000244`** contains the address of the "hello, world" string
- The `ldr` loads that address into `r0`
- Then a function (probably `printf` or `puts`) is called with `r0` as the argument
- Can you trace this complete flow?
#### Deeper Analysis (Optional Challenge)
##### Challenge 1: Find the Actual String Address
1. Navigate to the `DAT_10000244` location
2. Look at the value stored there
3. Can you decode the hex bytes and find the actual address of "hello, world"?
4. Hint: The RP2350 uses little-endian encoding, so the bytes are "backwards"
**Example:**
If you see bytes: `CC 19 00 10`
Read backwards: `10 00 19 CC` = `0x100019CC`
##### Challenge 2: Understand the Indirection
1. In C, if we want to load an address, we do: `char *ptr = &some_string;`
2. Then to use it: `printf(ptr);`
3. In assembly, this becomes:
- Load the pointer: `ldr r0, [DAT_...]`
- Call the function: `bl printf`
4. Can you see this pattern in the assembly?
##### Challenge 3: Follow Multiple References
1. Try this with different data items in the binary
2. Find a data reference that has **multiple** cross-references
3. What data is used in more than one place?
#### Questions for Reflection
1. **Why does the compiler use an indirect pointer reference here?**
2. **What is a literal pool?**
3. **How does cross-referencing help in reverse engineering?**
#### 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
- 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
#### Real-World Applications
Understanding cross-references is crucial for:
- **Vulnerability hunting**: Finding where user input flows through the code
- **Firmware patching**: Changing constants, strings, or data values
- **Malware analysis**: Tracking command-and-control server addresses or encryption keys
- **Reverse engineering**: Understanding program logic by following data dependencies
#### Summary
By completing this exercise, you've learned:
1. How to find and interpret cross-references in Ghidra
2. How to trace data from its definition to where it's used
3. How the `ldr` (load) instruction works to pass data to functions
4. The relationship between high-level C code and assembly-level data flow
5. How addresses are indirectly referenced in position-independent code
#### Expected Final Understanding
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
```
-72
View File
@@ -1,72 +0,0 @@
# 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
##### Step 3: Answer Exactly
- **Was GDB able to connect to OpenOCD?**
- Yes, via `target extended-remote localhost:3333`.
- **Did the program stop at the `main` breakpoint?**
- Yes, at `Breakpoint 1, main () at ../0x0001_hello-world.c:4`.
- **What is the address of `main`'s first instruction, and is it Flash or RAM?**
- `0x10000234`, and it is in **Flash** (`0x100...` XIP region).
- **What is the `sp` value at `main`, and is it Flash or RAM?**
- `0x20082000`, and it is in **RAM** (`0x200...` SRAM region).
- **What is the first instruction in `main`, and what does it do?**
- `push {r3, lr}`; it saves `r3` and `lr` on the stack and keeps 8-byte stack alignment for ABI-compliant calls.
- **Does GDB match what Ghidra shows?**
- Yes. The disassembly and flow match the Ghidra listing.
##### Step 4: Capture Register Values (`pc`, `sp`, `lr`, `r0-r3`)
| 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 |
##### Reference Disassembly (for verification)
```
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) disas main
(gdb) i r
```
#### Step 5: 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.
-360
View File
@@ -1,360 +0,0 @@
# 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: 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.
#### Prerequisites
- Raspberry Pi Pico 2 with "hello-world" binary already flashed
- OpenOCD installed and working
- GDB (arm-none-eabi-gdb) installed
- Your Pico 2 connected to your computer via USB CMSIS-DAP interface
- CMake build artifacts available (`.elf` file from compilation)
#### Task Description
In this exercise, you'll:
1. Start OpenOCD to provide a debug server
2. Connect GDB to the Pico 2 via OpenOCD
3. Set a breakpoint at the main function
4. Examine registers and memory while the program is running
5. Verify that your dynamic debugging setup works
#### Important Setup Notes
Before you start, make sure:
- Your Pico 2 is **powered on** and connected to your computer
- You have **OpenOCD** installed for ARM debugging
- You have **GDB** (specifically `arm-none-eabi-gdb`) installed
- Your binary file (`0x0001_hello-world.elf`) is available in the `build/` directory
#### Step-by-Step Instructions
##### Step 1: Start OpenOCD in Terminal 1
Open a **new terminal window** (PowerShell, Command Prompt, or WSL):
**On Windows (PowerShell/Command Prompt):**
```
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
```
**Expected Output:**
```
Open On-Chip Debugger 0.12.0+dev
...
Info : CMSIS-DAP: SWD detected
Info : RP2350 (dual core) detected
Info : Using JTAG interface
...
Info : accepting 'gdb' connection on tcp/3333
```
##### Step 2: Start GDB in Terminal 2
Open a **second terminal window** and navigate to your project directory:
```
arm-none-eabi-gdb build\0x0001_hello-world.elf
```
**Expected Output:**
```
Reading symbols from build\0x0001_hello-world.elf...
(gdb)
```
##### Step 3: Connect GDB to OpenOCD
At the GDB prompt `(gdb)`, type:
```gdb
target extended-remote localhost:3333
```
**Expected Output:**
```
Remote debugging using localhost:3333
(gdb)
```
(The warning is normal - you already loaded the .elf file, so it doesn't matter)
##### Step 4: Reset and Halt the Target
To reset the Pico 2 and prepare for debugging, type:
```gdb
monitor reset halt
```
**Expected Output:**
```
(gdb)
```
(This resets the processor and halts it, preventing execution until you tell it to run)
##### Step 5: Set a Breakpoint at main
To stop execution at the beginning of the `main` function:
```gdb
b main
```
**Expected Output:**
```
Breakpoint 1 at 0x10000234: file ../0x0001_hello-world.c, line 4.
(gdb)
```
**What this means:**
- Breakpoint 1 is set at address `0x10000234`
- That's in the file `../0x0001_hello-world.c` at line 4
- The breakpoint is at the `main` function
##### Step 6: Continue Execution to the Breakpoint
Now let the program run until it hits your breakpoint:
```gdb
c
```
**Expected Output:**
```
Continuing.
Breakpoint 1, main () at ../0x0001_hello-world.c:4
4 stdio_init_all();
(gdb)
```
**Great!** Your program is now halted at the beginning of `main()`.
##### Step 7: Examine the Assembly with `disas`
To see the assembly language of the current function:
```gdb
disas
```
**Expected Output:**
```
Dump of assembler code for function main:
=> 0x10000234 <+0>: push {r3, lr}
0x10000236 <+2>: bl 0x1000156c <stdio_init_all>
0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 <main+16>)
0x1000023c <+8>: bl 0x100015fc <__wrap_puts>
0x10000240 <+12>: b.n 0x1000023a <main+6>
0x10000242 <+14>: nop
0x10000244 <+16>: adds r4, r1, r7
0x10000246 <+18>: asrs r0, r0, #32
End of assembler dump.
(gdb)
```
**Interpretation:**
- The `=>` arrow shows where we're currently stopped (at `0x10000234`)
- We can see the `push`, `bl` (branch and link), `ldr`, and `b.n` (branch) instructions
- This is the exact code you analyzed in the Ghidra exercises!
##### Step 8: View All Registers with `i r`
To see the current state of all CPU registers:
```gdb
i r
```
**Expected Output:**
```
r0 0x0 0
r1 0x10000235 268436021
r2 0x80808080 -2139062144
r3 0xe000ed08 -536810232
r4 0x100001d0 268435920
r5 0x88526891 -2007865199
r6 0x4f54710 83183376
r7 0x400e0014 1074659348
r8 0x43280035 1126694965
r9 0x0 0
r10 0x10000000 268435456
r11 0x62707361 1651536737
r12 0xed07f600 -318245376
sp 0x20082000 0x20082000
lr 0x1000018f 268435855
pc 0x10000234 0x10000234 <main>
xpsr 0x69000000 1761607680
```
**Key Registers to Understand:**
| Register | Value | Meaning |
| -------- | ------------ | ------------------------------------------------- |
| `pc` | `0x10000234` | Program Counter - we're at the start of `main` |
| `sp` | `0x20082000` | Stack Pointer - top of our stack in RAM |
| `lr` | `0x1000018f` | Link Register - where we return from `main` |
| `r0-r3` | Various | Will hold function arguments and return values |
##### Step 9: Step Into the First Instruction
To execute one assembly instruction:
```gdb
si
```
**Expected Output:**
```
0x10000236 in main () at ../0x0001_hello-world.c:5
5 stdio_init_all();
(gdb)
```
The `pc` should now be at `0x10000236`, which is the next instruction.
##### Step 10: Answer These Questions
Based on what you've observed:
###### Question 1: Was GDB able to connect to OpenOCD?
- Was GDB able to connect to OpenOCD? (Yes/No)
- Did the program stop at your breakpoint? (Yes/No)
- __________
###### Question 2: What is the address of `main`'s first instruction, and is it Flash or RAM?
- What is the memory address of the `main` function's first instruction?
- __________
- Is this in Flash memory (0x100...) or RAM (0x200...)?
- __________
###### Question 3: What is the `sp` value at `main`, and is it Flash or RAM?
- What is the value of the Stack Pointer (sp) when you're at `main`?
- __________
- Is this in Flash or RAM?
- __________
###### Question 4: What is the first instruction in `main`, and what does it do?
- What is the first instruction in `main`?
- __________
- What does it do? (Hint: `push` = save to stack)
- __________
###### Question 5: Does GDB match what Ghidra shows?
- Look at the disassembly from GDB (Step 7)
- Compare it to the disassembly from Ghidra (Exercise 1)
- Are they the same?
- __________
#### Deeper Exploration (Optional Challenge)
##### Challenge 1: Step Through stdio_init_all
1. Continue stepping: `si` (step into) or `ni` (next instruction)
2. Eventually, you'll reach `bl 0x1000156c <stdio_init_all>`
3. Use `si` to step **into** that function
4. What instructions do you see?
5. What registers are being modified?
##### Challenge 2: View Specific Registers
Instead of viewing all registers, you can view just a few:
```gdb
i r pc sp lr r0 r1 r2
```
This shows only the registers you care about.
##### Challenge 3: Examine Memory
To examine memory at a specific address (e.g., where the string is):
```gdb
x/16b 0x100019cc
```
This displays 16 bytes (`b` = byte) starting at address `0x100019cc`. Can you see the "hello, world" string?
##### Challenge 4: Set a Conditional Breakpoint
Set a breakpoint that only triggers after a certain condition:
```gdb
b *0x1000023a if $r0 != 0
```
This is useful when you want to break on a condition rather than every time.
#### Questions for Reflection
1. **Why does the stack pointer start where it does?**
2. **Why does `push {r3, lr}` include `r3`?**
3. **How does the infinite loop work in assembly?**
#### Important GDB Commands Reference
| Command | Short Form | What It Does |
| ---------------------- | ---------- | ------------------------------------ |
| `target extended-remote localhost:3333` | | Connect to OpenOCD |
| `monitor reset halt` | | Reset and halt the processor |
| `break main` | `b main` | Set a breakpoint at main function |
| `continue` | `c` | Continue until breakpoint |
| `step instruction` | `si` | Step one instruction (into calls) |
| `next instruction` | `ni` | Step one instruction (over calls) |
| `disassemble` | `disas` | Show assembly for current function |
| `info registers` | `i r` | Show all register values |
| `x/Nxy ADDRESS` | `x` | Examine memory (N=count, x=format, y=size) |
| `quit` | `q` | Exit GDB |
**Examples for `x` command:**
- `x/10i $pc` - examine 10 instructions at program counter
- `x/16b 0x20000000` - examine 16 bytes starting at RAM address
- `x/4w 0x10000000` - examine 4 words (4-byte values) starting at Flash address
#### Troubleshooting
##### Problem: "OpenOCD not found"
**Solution:** Make sure OpenOCD is in your PATH or use the full path to the executable
##### Problem: "Target not responding"
**Solution:**
- Check that your Pico 2 is properly connected
- Make sure OpenOCD is running and shows "accepting 'gdb' connection"
- Restart both OpenOCD and GDB
##### Problem: "Cannot find breakpoint at main"
**Solution:**
- Make sure you compiled with debug symbols
- The .elf file must include symbol information
- Try breaking at an address instead: `b *0x10000234`
##### Problem: GDB shows "No source available"
**Solution:**
- This happens with stripped binaries
- You can still see assembly with `disas`
- You can still examine memory and registers
#### 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
You're now ready for Week 2, where you'll:
- Step through code line by line
- Watch variables and memory change
- Understand program flow in detail
- Use this knowledge to modify running code
#### Next Steps
1. **Close GDB**: Type `quit` or `q` to exit
2. **Close OpenOCD**: Type `Ctrl+C` in the OpenOCD terminal
3. **Review**: Go back to the Ghidra exercises and compare static vs. dynamic analysis
4. **Prepare**: Read through Week 2 materials to understand what's coming next
+53 -132
View File
@@ -13,7 +13,7 @@ By the end of this week, you will be able to:
---
## 📚 Part 1: Understanding the Basics
## Part 1: Understanding the Basics
### What is a Microcontroller?
@@ -32,7 +32,7 @@ Reverse engineering is like being a detective for code. Instead of writing code
---
## 📚 Part 2: Understanding Processor Registers
## Part 2: Understanding Processor Registers
### What is a Register?
@@ -70,13 +70,13 @@ The two Arm ABI documents we verified give the formal proof for these rules. In
```
Higher Memory Address (0x20082000)
┌──────────────────┐
← Stack starts here (empty)
├──────────────────┤
Pushed Item 1 ← SP points here after 1 push
├──────────────────┤
Pushed Item 2 ← SP points here after 2 pushes
└──────────────────┘
+------------------+
| | ← Stack starts here (empty)
+------------------+
| Pushed Item 1 | ← SP points here after 1 push
+------------------+
| Pushed Item 2 | ← SP points here after 2 pushes
+------------------+
Lower Memory Address (0x20081FF8)
```
@@ -103,7 +103,7 @@ The Program Counter always points to the **next instruction** the processor will
---
## 📚 Part 3: Understanding Memory Layout
## Part 3: Understanding Memory Layout
### XIP - Execute In Place
@@ -116,15 +116,15 @@ This is where your program code starts in flash memory. Remember this address -
### Memory Map Overview
```
┌─────────────────────────────────────┐
Flash Memory (XIP)
Starts at: 0x10000000
Contains: Your program code
├─────────────────────────────────────┤
RAM
Starts at: 0x20000000
Contains: Stack, Heap, Variables
└─────────────────────────────────────┘
+-------------------------------------+
| Flash Memory (XIP) |
| Starts at: 0x10000000 |
| Contains: Your program code |
+-------------------------------------+
| RAM |
| Starts at: 0x20000000 |
| Contains: Stack, Heap, Variables |
+-------------------------------------+
```
### Stack vs Heap
@@ -139,7 +139,7 @@ This is where your program code starts in flash memory. Remember this address -
---
## 📚 Part 3.5: Reviewing Our Hello World Code
## Part 3.5: Reviewing Our Hello World Code
Before we start debugging, let's understand the code we'll be working with. Here's our `0x0001_hello-world.c` program:
@@ -197,7 +197,7 @@ while (true)
- **`while (true)`** - This creates an infinite loop. The program will keep running forever (or until you reset/power off the Pico).
- **`printf("hello, world\r\n")`** - This prints the text "hello, world" followed by a carriage return (`\r`) and newline (`\n`).
> 💡 **Why `\r\n` instead of just `\n`?**
> Tip: **Why `\r\n` instead of just `\n`?**
>
> In embedded systems, we often use both carriage return (`\r`) and newline (`\n`) together. The `\r` moves the cursor back to the beginning of the line, and `\n` moves to the next line. This ensures proper display across different terminal programs.
@@ -239,7 +239,7 @@ To flash new code to your Pico 2, you need to put it into **BOOTSEL mode**:
When done correctly, your Pico 2 will appear as a USB mass storage device (like a flash drive) on your computer. This means it's ready to receive new firmware!
> 💡 **Tip:** You'll see a drive called "RP2350" appear in your file explorer when the Pico 2 is in flash loading mode.
> Tip: **Tip:** You'll see a drive called "RP2350" appear in your file explorer when the Pico 2 is in flash loading mode.
##### Step 3: Flash and Run
@@ -251,7 +251,7 @@ Once flashed, your Pico 2 will immediately start executing the hello-world progr
---
## 📚 Part 4: Dynamic Analysis with GDB
## Part 4: Dynamic Analysis with GDB
### Prerequisites
@@ -267,7 +267,7 @@ Open a terminal and start OpenOCD:
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-s "C:\Users\assem.KEVINTHOMAS\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
@@ -318,10 +318,6 @@ Breakpoint 1, main () at ../0x0001_hello-world.c:5
The program has stopped right at the beginning of `main`!
##### Disassembling with `disas`
The `disas` (disassemble) command shows us the assembly instructions for the current function:
@@ -352,9 +348,9 @@ To see how the ELF is laid out in memory, use:
```gdb
(gdb) info files
Symbols from "C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\build\0x0001_hello-world.elf".
Symbols from "C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0001_hello-world\build\0x0001_hello-world.elf".
Extended remote target using gdb-specific protocol:
`C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm.
`C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm.
Entry point: 0x1000014c
0x10000000 - 0x100019cc is .text
0x100019cc - 0x10001b18 is .rodata
@@ -370,7 +366,7 @@ Extended remote target using gdb-specific protocol:
0x10001ce8 - 0x10001cfc is .flash_end
While running this, GDB does not access memory from...
Local exec file:
`C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm.
`C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm.
Entry point: 0x1000014c
0x10000000 - 0x100019cc is .text
0x100019cc - 0x10001b18 is .rodata
@@ -385,7 +381,7 @@ Local exec file:
0x20081000 - 0x20081800 is .stack_dummy
0x10001ce8 - 0x10001cfc is .flash_end
(gdb) maintenance info sections
Exec file: `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm.
Exec file: `C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm.
[0] 0x10000000->0x100019cc at 0x00001000: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
[1] 0x100019cc->0x10001b18 at 0x000029cc: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
[2] 0x10001b18->0x10001b20 at 0x00002b18: .ARM.exidx ALLOC LOAD READONLY DATA HAS_CONTENTS
@@ -445,7 +441,7 @@ Exec file: `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\b
| `.debug_loclists` | Variable location lists (where variables live over PC ranges). |
| `.debug_line_str` | Extra string pool used by `.debug_line` data. |
> 💡 **Practical rule:** For reverse engineering runtime behavior, focus first on `.text`, `.rodata`, `.data`, `.bss`, heap/stack regions, and the vector table. Debug sections are for source-level mapping and symbol intelligence.
> Tip: **Practical rule:** For reverse engineering runtime behavior, focus first on `.text`, `.rodata`, `.data`, `.bss`, heap/stack regions, and the vector table. Debug sections are for source-level mapping and symbol intelligence.
**Fast interpretation checklist (use this every time):**
@@ -493,7 +489,7 @@ xpsr 0x69000000 1761607680
| `lr` | `0x100002d5` | Link Register - where we return after `main` |
| `r0-r3` | Various | Will hold function arguments and return values |
> 💡 **Tip:** You can also use `i r pc sp lr` to show only specific registers you care about.
> Tip: **Tip:** You can also use `i r pc sp lr` to show only specific registers you care about.
### Quick Reference: Essential GDB Commands
@@ -528,7 +524,7 @@ Notice the difference between inspecting memory at `$sp` and inspecting `$lr`.
0x1000018f <platform_entry+8>: 0x00478849
```
> 💡 **What's Next?** In Week 2, we'll put these GDB commands to work with hands-on debugging exercises! We'll step through code, examine the stack, watch registers change, and ultimately use these skills to modify a running program. The commands you learned here are the foundation for everything that follows.
> Tip: **What's Next?** In Week 2, we'll put these GDB commands to work with hands-on debugging exercises! We'll step through code, examine the stack, watch registers change, and ultimately use these skills to modify a running program. The commands you learned here are the foundation for everything that follows.
---
@@ -541,7 +537,7 @@ Before we dive into GDB debugging, let's set up Ghidra to analyze our hello-worl
##### Step 1: Create a New Project
1. Launch Ghidra
2. A window will appear - select **File New Project**
2. A window will appear - select **File -> New Project**
3. Choose **Non-Shared Project** and click **Next**
4. Enter the Project Name: `0x0001_hello-world`
5. Click **Finish**
@@ -555,7 +551,7 @@ Before we dive into GDB debugging, let's set up Ghidra to analyze our hello-worl
In the small window that appears, you will see the file identified as an **ELF** (Executable and Linkable Format).
> 💡 **What is an ELF file?**
> Tip: **What is an ELF file?**
>
> ELF stands for **Executable and Linkable Format**. This format includes **symbols** - human-readable names for functions and variables. These symbols make reverse engineering much easier because you can see function names like `main` and `printf` instead of just memory addresses.
>
@@ -575,7 +571,7 @@ Ghidra will now process the binary, identifying functions, strings, and cross-re
Once analysis is complete, let's find our `main` function:
1. In the **Symbol Tree** panel on the left, expand **Functions**
2. Look for `main` in the list (you can also use **Search For Address or Label** and type "main")
2. Look for `main` in the list (you can also use **Search -> For Address or Label** and type "main")
3. Click on `main` to navigate to it
##### What You'll See
@@ -677,104 +673,27 @@ In future weeks, we'll work with `.bin` files that have been stripped of symbols
### The Program Flow
```
┌─────────────────────────────────────────────────────┐
1. push {r3, lr}
Save registers to stack
├─────────────────────────────────────────────────────┤
2. bl stdio_init_all
Initialize standard I/O
├─────────────────────────────────────────────────────┤
3. ldr r0, [pc, #8] ────────────────┐
Load address of "hello, world" into r0
├─────────────────────────────────────────────────────┤
4. bl __wrap_puts
Print the string
├─────────────────────────────────────────────────────┤
5. b.n (back to step 3) ────────────────┘
Infinite loop!
└─────────────────────────────────────────────────────┘
+-----------------------------------------------------+
| 1. push {r3, lr} |
| Save registers to stack |
+-----------------------------------------------------+
| 2. bl stdio_init_all |
| Initialize standard I/O |
+-----------------------------------------------------+
| 3. ldr r0, [pc, #8] ----------------+ |
| Load address of "hello, world" into r0| |
+-----------------------------------------------------+
| 4. bl __wrap_puts | |
| Print the string | |
+-----------------------------------------------------+
| 5. b.n (back to step 3) ----------------+ |
| Infinite loop! |
+-----------------------------------------------------+
```
---
## ✅ Practice Exercises
Try these on your own to reinforce what you learned:
These prompts are intentionally aligned 1:1 with the four Week 1 solution files:
- `WEEK01-01-S.md`
- `WEEK01-02-S.md`
- `WEEK01-03-S.md`
- `WEEK01-04-S.md`
### Exercise 1: Analyze `stdio_init_all` in Ghidra
1. Open your `0x0001_hello-world` project in Ghidra.
2. Find `stdio_init_all` in the Symbol Tree.
3. Answer exactly:
- What does the function return?
- What parameters does it take?
- What functions does it call?
- What is its purpose?
4. Reflection:
- Why would we need to initialize standard I/O before using `printf()`?
- Can you find other functions in the Symbol Tree that might be related to I/O?
- How does this function support the `printf("hello, world\r\n")` call in `main`?
### Exercise 2: Locate and Characterize the String
1. In Ghidra, go to **Window → Defined Strings**.
2. Find `"hello, world\r\n"` and record its address.
3. Answer exactly:
- What is the address, and is it Flash or RAM?
- How many bytes does the string take?
- How many times is it referenced, and by which function(s)?
- How is the string encoded?
4. Reflection:
- Why is the string stored in Flash instead of RAM?
- What would happen if you tried to modify this string at runtime?
- How does the Listing view help you understand string storage?
### Exercise 3: Trace Cross-References and Data Flow
1. In `main`, locate `DAT_10000244` and open its references.
2. Fill in:
- Data reference address
- Number of references
- Reference type (read or write)
- Function using it
- Next instruction after `ldr`
3. Answer exactly:
- What is the address of the data reference?
- How many places reference this data?
- Is it a read or write operation? Why?
- What happens next after the `ldr`?
4. Complete the data flow chain from string storage to print call.
5. Reflection:
- Why does the compiler use an indirect pointer reference here?
- What is a literal pool?
- How does cross-referencing help in reverse engineering?
### Exercise 4: Verify Runtime View in GDB
1. Start OpenOCD and connect GDB as shown in Part 4.
2. Break at `main` and continue to the breakpoint.
3. Answer exactly:
- Was GDB able to connect to OpenOCD?
- Did the program stop at the `main` breakpoint?
- What is the address of `main`'s first instruction, and is it Flash or RAM?
- What is the `sp` value at `main`, and is it Flash or RAM?
- What is the first instruction in `main`, and what does it do?
- Does GDB match what Ghidra shows?
4. Capture register values for `pc`, `sp`, `lr`, and `r0-r3`.
5. Reflection:
- Why does the stack pointer start where it does?
- Why does `push {r3, lr}` include `r3`?
- How does the infinite loop work in assembly?
Use these solution keys after attempting the exercises:
- `WEEK01-01-S.md`
- `WEEK01-02-S.md`
- `WEEK01-03-S.md`
- `WEEK01-04-S.md`
> 💡 **Note:** The detailed hands-on GDB debugging (stepping through code, watching the stack, examining memory) will be covered in Week 2!
> **Note:** The detailed hands-on GDB debugging (stepping through code, watching the stack, examining memory) will be covered in Week 2!
---
@@ -807,3 +726,5 @@ Use these solution keys after attempting the exercises:
| **Stack** | Memory region for temporary storage during function calls |
| **Stack Pointer** | Register that points to the top of the stack |
| **XIP** | Execute In Place - running code directly from flash |
-41
View File
@@ -1,41 +0,0 @@
# 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 `0x20040000` 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[12]} 0x20040000 = {'Y','o','u','r',' ','N','a','m','e','!','\r','\0'}
(gdb) set $r0 = 0x20040000 # Redirect r0 to injected message
(gdb) c # Resume - serial shows custom message
```
##### Verification
```gdb
(gdb) x/s 0x20040000 # 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 is read-write, so we place our replacement string at the safe runtime address `0x20040000`.
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 `0x20040000` (our SRAM string), we redirect what `puts()` prints.
-104
View File
@@ -1,104 +0,0 @@
# 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: Change the Message
#### Objective
Write your own message into SRAM and redirect `r0` so the running program prints it without changing the source code.
#### Prerequisites
- Raspberry Pi Pico 2 with debug probe connected
- 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 (0a0e) completed: OpenOCD, serial monitor, and GDB ready
#### Task Description
You will create a custom string in SRAM at `0x20040000`, point `r0` at it just before `puts()` runs, and watch the live output change to your message.
#### Step-by-Step Instructions
##### Step 1: Start OpenOCD
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
```
##### Step 2: Start the Serial Monitor
- Open PuTTY (Serial), choose the correct COM port, set speed to `115200`, then click **Open**.
##### Step 3: Launch GDB
```powershell
arm-none-eabi-gdb build\0x0001_hello-world.elf
```
##### Step 4: Connect and Halt
```gdb
(gdb) target extended-remote :3333
(gdb) monitor reset halt
```
##### Step 5: Break Before `puts()`
```gdb
(gdb) b *0x1000023c
```
##### Step 6: Run to the Breakpoint
```gdb
(gdb) c
```
##### Step 7: Inject Your Message into SRAM
Replace the characters with your name as needed.
```gdb
(gdb) set {char[12]} 0x20040000 = {'Y','o','u','r',' ','N','a','m','e','!','\r','\0'}
```
##### Step 8: Point `r0` to Your Message
```gdb
(gdb) set $r0 = 0x20040000
```
##### Step 9: Resume and Observe
```gdb
(gdb) c
```
Check PuTTY for your custom string replacing "hello, world".
#### Expected Output
- GDB stops at `0x1000023c` before `__wrap_puts`.
- `x/s 0x20040000` shows your injected message.
- PuTTY displays your custom message after you continue execution.
#### Questions for Reflection
###### Question 1: Why does the string have to live in SRAM instead of flash during runtime?
###### Question 2: What would happen if you forgot the null terminator in your injected string?
###### Question 3: How does changing `r0` alter the behavior of `puts()` without touching source code?
#### Tips and Hints
- Match the array length to your payload exactly (characters + `\0`). The example uses `char[12]`.
- If you miss the breakpoint, confirm OpenOCD is running and the address matches `Week 2` disassembly.
- Use `x/s $r0` to confirm the register points to the intended address before continuing.
#### Next Steps
- Repeat the exercise with different messages to verify repeatability.
- Try smaller or larger buffers (still within SRAM) to see how size affects safety.
- Move on to Exercise 2 to practice using alternate SRAM addresses.
-38
View File
@@ -1,38 +0,0 @@
# 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 `0x20041000` instead of `0x20040000` to demonstrate that multiple safe SRAM locations can be used for injection.
##### GDB Commands
```gdb
(gdb) b *0x1000023c
(gdb) c
(gdb) set {char[14]} 0x20041000 = {'h','a','c','k','e','d','!','!','!','\r','\0'}
(gdb) set $r0 = 0x20041000
(gdb) c
```
##### Verification
```gdb
(gdb) x/s 0x20041000 # Shows "hacked!!!\r"
```
#### Reflection Answers
1. **How can you ensure `0x20041000` does not collide with stack usage?**
The stack pointer was observed at `0x20082000` (top of stack) and grows downward. Since `0x20041000` is well below the stack region, there is substantial separation. Use `info registers sp` in GDB to verify the current stack pointer is 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?**
Choose a region with clear separation from vectors, stack growth, and active data. In this simple program, `0x20040000` and `0x20041000` are practical safe offsets. In larger programs, inspect linker sections (`.data`, `.bss`, heap) and validate with runtime `sp` checks.
-91
View File
@@ -1,91 +0,0 @@
# 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: Use a Different SRAM Address
#### Objective
Practice writing to an alternate SRAM location and redirecting `r0` so your message prints from `0x20041000` instead of `0x20040000`.
#### Prerequisites
- Raspberry Pi Pico 2 with debug probe connected
- OpenOCD, `arm-none-eabi-gdb`, and a serial monitor ready (Week 2 steps 0a0e complete)
- `build\0x0001_hello-world.elf` flashed and running
- Comfortable setting breakpoints at `0x1000023c`
#### Task Description
You will inject a short string into `0x20041000`, point `r0` there, and verify the live output changes, demonstrating that any safe SRAM slot can host your payload.
#### Step-by-Step Instructions
##### Step 1: Start OpenOCD
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
```
##### Step 2: Start the Serial Monitor
- Open PuTTY (Serial) on the correct COM port at `115200` baud.
##### Step 3: Launch GDB and Halt
```gdb
(gdb) target extended-remote :3333
(gdb) monitor reset halt
```
##### Step 4: Break Before `puts()`
```gdb
(gdb) b *0x1000023c
(gdb) c
```
##### Step 5: Write a Payload at `0x20041000`
```gdb
(gdb) set {char[14]} 0x20041000 = {'h','a','c','k','e','d','!','!','!','\r','\0'}
```
##### Step 6: Redirect `r0`
```gdb
(gdb) set $r0 = 0x20041000
```
##### Step 7: Continue and Verify
```gdb
(gdb) c
```
Check PuTTY for the new output sourced from the alternate address.
#### Expected Output
- `x/s 0x20041000` shows `"hacked!!!\r"` (or your variant).
- PuTTY prints the injected message instead of the original string.
- The program continues looping with your modified output.
#### Questions for Reflection
###### Question 1: How can you ensure `0x20041000` does not collide with stack usage?
###### Question 2: What symptoms would indicate you overwrote an active stack frame?
###### Question 3: How would you pick a safe SRAM offset in a larger program with dynamic allocations?
#### Tips and Hints
- Keep payloads short; avoid overrunning the allocated bytes.
- If you see crashes, choose a lower SRAM address away from the stack top (stack grows downward).
- Use `info registers sp` and compare with your chosen address to gauge separation.
#### Next Steps
- Try other safe addresses (e.g., `0x20002000`) and verify stability.
- Map out stack usage by stepping deeper and watching `sp` move.
- Proceed to Exercise 3 to inspect memory around your payload.
-54
View File
@@ -1,54 +0,0 @@
# 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]} 0x20040000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'}
(gdb) x/20b 0x20040000
```
##### Byte Dump Output
```
0x20040000: 0x68 0x61 0x63 0x6b 0x79 0x2c 0x20 0x77
0x20040008: 0x6f 0x72 0x6c 0x64 0x0d 0x00 0x00 0x00
0x20040010: 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.
-82
View File
@@ -1,82 +0,0 @@
# 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: Examine Memory Around Your String
#### Objective
Inspect the byte-level layout of your injected string in SRAM and correlate bytes to characters.
#### Prerequisites
- Pico 2 connected with OpenOCD, GDB, and a serial monitor ready
- `build\0x0001_hello-world.elf` flashed and running
- Ability to break before `__wrap_puts` at `0x1000023c`
- A payload already written to SRAM (e.g., at `0x20040000` from Exercise 1)
#### Task Description
You will use `x/20b` to view the bytes surrounding your injected string, decode the characters, and confirm the presence of control characters and the null terminator.
#### Step-by-Step Instructions
##### Step 1: Connect and Halt
```gdb
(gdb) target extended-remote :3333
(gdb) monitor reset halt
```
##### Step 2: Break Before `puts()` and Run
```gdb
(gdb) b *0x1000023c
(gdb) c
```
##### Step 3: Ensure a String Exists in SRAM
If needed, re-inject a payload:
```gdb
(gdb) set {char[14]} 0x20040000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'}
(gdb) set $r0 = 0x20040000
```
##### Step 4: Examine Bytes Around the String
```gdb
(gdb) x/20b 0x20040000
```
##### Step 5: Decode the Output
- 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
```gdb
(gdb) c
```
#### Expected Output
- A byte dump where the sequence matches your string (`68 61 63 6b 79 2c 20 77 6f 72 6c 64 0d 00`).
- Confirmation of the carriage return (`0x0d`) and null terminator (`0x00`).
- Stable program output in PuTTY after resuming.
#### Questions for Reflection
###### Question 1: Which bytes mark the end of the printable string, and why are they needed?
###### Question 2: How would misaligned writes show up in the byte view?
###### Question 3: What risks arise if you overwrite bytes immediately after your string?
#### Tips and Hints
- Use `x/20bx` if you prefer hex with ASCII side-by-side.
- Keep the dump length modest (20 bytes) to avoid clutter while still seeing context.
- If the bytes look incorrect, re-run the injection command to reset the buffer.
#### Next Steps
- Try viewing a different address (e.g., `0x20041000`) to compare layouts.
- Experiment with longer strings and observe how the byte dump grows.
- Move on to Exercise 4 to automate the hack workflow.
-58
View File
@@ -1,58 +0,0 @@
# 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[13]} 0x20040000 = "hacky, world"
> set $r0 = 0x20040000
> 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 = 0x20040000`) 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[13]} 0x20040000 = "hacky, world"
> x/20b 0x20040000
> set $r0 = 0x20040000
> info registers r0
> c
> end
```
This dumps memory and registers before continuing, providing verification at each step.
-71
View File
@@ -1,71 +0,0 @@
# 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: Automate the Hack
#### Objective
Create a reusable GDB command that injects a string into SRAM, repoints `r0`, and resumes execution with a single call.
#### Prerequisites
- Pico 2 connected with OpenOCD, GDB, and serial monitor ready
- `build\0x0001_hello-world.elf` available
- Familiarity with breaking at `0x1000023c` and injecting strings from prior exercises
#### Task Description
You will define a custom GDB command `hack` that writes a payload to `0x20040000`, repoints `r0`, and continues execution automatically.
#### Step-by-Step Instructions
##### Step 1: Connect, Halt, and Break
```gdb
(gdb) target extended-remote :3333
(gdb) monitor reset halt
(gdb) b *0x1000023c
(gdb) c
```
##### Step 2: Define the `hack` Command
```gdb
(gdb) define hack
> set {char[13]} 0x20040000 = "hacky, world"
> set $r0 = 0x20040000
> c
> end
```
##### Step 3: Invoke the Command
```gdb
(gdb) hack
```
##### Step 4: Observe Output
- PuTTY should immediately show your injected string after the command runs.
- The breakpoint will be re-hit on the next loop iteration; rerun `hack` if you want to reapply after changes.
#### Expected Output
- `hack` executes without errors, writes the payload, updates `r0`, and resumes execution.
- Serial output reflects the injected message.
#### Questions for Reflection
###### 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 breakpointwill it still work as expected?
###### Question 3: How would you adapt this pattern for multi-step routines (e.g., patch, dump, continue)?
#### Tips and Hints
- Redefine `hack` any time you want a different payload; GDB will overwrite the prior definition.
- Keep the payload length aligned with the buffer size to avoid stray bytes.
- If the target keeps running past the breakpoint, ensure hardware breakpoints are available and set correctly.
#### Next Steps
- Create additional helper commands (e.g., `dumpstr`, `retarget`) to streamline experiments.
- Explore GDB scripting files (`.gdbinit`) to auto-load your helpers on startup.
- Try combining `hack` with watchpoints to observe memory changes live.
+54 -102
View File
@@ -23,7 +23,7 @@ This week builds directly on Week 1 concepts. You should already be comfortable
---
## 📚 Part 1: Understanding Live Hacking
## Part 1: Understanding Live Hacking
#### What is Live Hacking?
@@ -52,7 +52,7 @@ The techniques you'll learn today are *exactly* how this would be done. Understa
---
## 📚 Part 2: Review - Memory Layout (from Week 1)
## Part 2: Review - Memory Layout (from Week 1)
> 🔄 **REVIEW:** In Week 1, we learned about the RP2350's memory layout. This knowledge is essential for our hack!
@@ -84,17 +84,17 @@ Our goal: **Make it print something else WITHOUT changing the source code!**
#### Memory Map
```
┌─────────────────────────────────────────────────────┐
Flash Memory (XIP) - READ ONLY
Starts at: 0x10000000
Contains: Program code, constant strings
NOTE: We CANNOT write to flash during runtime!
├─────────────────────────────────────────────────────┤
SRAM - READ/WRITE
Starts at: 0x20000000
Contains: Stack, Heap, Variables
NOTE: We CAN write to SRAM during runtime!
└─────────────────────────────────────────────────────┘
+-----------------------------------------------------+
| Flash Memory (XIP) - READ ONLY |
| Starts at: 0x10000000 |
| Contains: Program code, constant strings |
| NOTE: We CANNOT write to flash during runtime! |
+-----------------------------------------------------+
| SRAM - READ/WRITE |
| Starts at: 0x20000000 |
| Contains: Stack, Heap, Variables |
| NOTE: We CAN write to SRAM during runtime! |
+-----------------------------------------------------+
```
> 🔄 **REVIEW:** In Week 1, we saw SP values in the `0x20081xxx` range (for example `0x20081fc8`) - that's in the SRAM region. In this run you may see values like `0x20081ff8` depending on where execution is paused. The stack "grows downward" from the top of SRAM.
@@ -107,32 +107,32 @@ But SRAM (starting at `0x20000000`) is **read-write**! This is where we'll creat
---
## 📚 Part 3: The Attack Plan
## Part 3: The Attack Plan
Here's our step-by-step attack strategy:
```
┌─────────────────────────────────────────────────────┐
STEP 1: Start the debug server (OpenOCD)
├─────────────────────────────────────────────────────┤
STEP 2: Connect with GDB and halt the program
├─────────────────────────────────────────────────────┤
STEP 3: Set a breakpoint right before puts()
├─────────────────────────────────────────────────────┤
STEP 4: When we hit the breakpoint, r0 contains
the address of "hello, world"
├─────────────────────────────────────────────────────┤
STEP 5: Create our malicious string in SRAM
├─────────────────────────────────────────────────────┤
STEP 6: Change r0 to point to OUR string
├─────────────────────────────────────────────────────┤
STEP 7: Continue execution - HACKED!
└─────────────────────────────────────────────────────┘
+-----------------------------------------------------+
| STEP 1: Start the debug server (OpenOCD) |
+-----------------------------------------------------+
| STEP 2: Connect with GDB and halt the program |
+-----------------------------------------------------+
| STEP 3: Set a breakpoint right before puts() |
+-----------------------------------------------------+
| STEP 4: When we hit the breakpoint, r0 contains |
| the address of "hello, world" |
+-----------------------------------------------------+
| STEP 5: Create our malicious string in SRAM |
+-----------------------------------------------------+
| STEP 6: Change r0 to point to OUR string |
+-----------------------------------------------------+
| STEP 7: Continue execution - HACKED! |
+-----------------------------------------------------+
```
---
## 📚 Part 4: Setting Up Your Environment
## Part 4: Setting Up Your Environment
#### Prerequisites
@@ -166,7 +166,7 @@ OpenOCD is the bridge between your computer and the Pico 2's debug interface. It
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-s "C:\Users\assem.KEVINTHOMAS\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
@@ -204,7 +204,7 @@ PuTTY will show us the output from our Pico 2. When we hack the program, we'll s
- **Speed**: Enter `115200`
3. Click **Open**
> 💡 **Finding your COM port:** Open Device Manager Ports (COM & LPT) Look for "USB Serial Device" or "Pico" - note the COM number.
> Tip: **Finding your COM port:** Open Device Manager -> Ports (COM & LPT) -> Look for "USB Serial Device" or "Pico" - note the COM number.
**You should see:**
@@ -257,7 +257,7 @@ Now we need to connect GDB to OpenOCD. OpenOCD is listening on port `3333`.
```
Remote debugging using :3333
main ()
at C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0001_hello-world/0x0001_hello-world.c:5
at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c:5
5 stdio_init_all();
```
@@ -531,7 +531,7 @@ Now let's see what the push instruction did to our stack:
**What changed:**
- The stack pointer moved from `0x20082000` to `0x20081ff8`
- That's 8 bytes lower (2 × 4-byte values)
- That's 8 bytes lower (2 * 4-byte values)
- Two new values appeared: `0xe000ed08` and `0x1000018f`
##### Step 9: Verify What Was Pushed
@@ -604,7 +604,7 @@ This is the value from `lr` (the return address), pushed second.
Before push {r3, lr}: After push {r3, lr}:
Address Value Address Value
───────────────────── ─────────────────────
--------------------- ---------------------
0x20082000 (empty) ← SP 0x20082000 (old SP location)
0x20081ffc 0x1000018f (lr)
0x20081ff8 0xe000ed08 (r3) ← SP
@@ -868,7 +868,7 @@ We want to stop the program RIGHT BEFORE it calls `puts()`. That's at address `0
**You should see:**
```
Breakpoint 2 at 0x1000023c: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0001_hello-world/0x0001_hello-world.c, line 8
Breakpoint 2 at 0x1000023c: file C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c, line 8
```
**What does "hardware breakpoints" mean?**
@@ -894,7 +894,7 @@ Now let's run the program until it hits our breakpoint:
Continuing.
Thread 1 "rp2350.cm0" hit Breakpoint 2, 0x1000023c in main ()
at C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0001_hello-world/0x0001_hello-world.c:8
at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c:8
8 printf("hello, world\r\n");
```
@@ -1170,7 +1170,7 @@ This is the moment of truth! Let's continue the program and watch our hack take
Continuing.
Thread 1 "rp2350.cm0" hit Breakpoint 2, 0x1000023c in main ()
at C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0001_hello-world/0x0001_hello-world.c:8
at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c:8
8 printf("hello, world\r\n");
```
@@ -1204,7 +1204,7 @@ Now that we've performed the hack dynamically with GDB, let's use Ghidra to unde
If you haven't already set up the Ghidra project from Week 1:
1. Launch Ghidra
2. Select **File New Project** **Non-Shared Project**
2. Select **File -> New Project** -> **Non-Shared Project**
3. Name it `0x0001_hello-world`
4. Drag and drop `0x0001_hello-world.elf` into the project
5. Double-click to open in CodeBrowser
@@ -1333,7 +1333,7 @@ Ghidra's cross-reference feature shows everywhere a value is used:
1. Navigate back to `main` (press **G**, type `main`, press Enter)
2. Click on `__wrap_puts` at address `0x1000023c`
3. Right-click and select **References Show References to __wrap_puts**
3. Right-click and select **References -> Show References to __wrap_puts**
This shows every place that calls `puts()`. In a larger program, you could find ALL the print statements and potentially modify any of them!
@@ -1430,9 +1430,9 @@ This step helps you understand the mechanics of modifying binary data. Once you'
| Task | GDB (Dynamic) | Ghidra (Static) |
| ---- | ------------- | --------------- |
| Find main address | `x/1000i 0x10000000` + search | Symbol Tree Functions main |
| Find main address | `x/1000i 0x10000000` + search | Symbol Tree -> Functions -> main |
| Find string address | Step through `ldr`, examine `$r0` | Click on `ldr` - shows `= 100019CCh` |
| See string content | `x/s $r0` | Double-click address see `ds "hello, world"` |
| See string content | `x/s $r0` | Double-click address -> see `ds "hello, world"` |
| Identify attack point | Set breakpoints, step, observe | Read decompiled code, find function calls |
| Verify memory type | Know address ranges | Check address prefix (`0x10...` vs `0x20...`) |
@@ -1448,7 +1448,7 @@ This step helps you understand the mechanics of modifying binary data. Once you'
2. **Follow Cross-References** - Find all places a function or variable is used
3. **Check Address Ranges** - Quickly identify Flash vs SRAM locations
4. **Add Comments** - Press `;` to annotate what you discover for later
5. **Rename Variables** - Right-click Rename to give meaningful names
5. **Rename Variables** - Right-click -> Rename to give meaningful names
---
@@ -1481,19 +1481,18 @@ We successfully performed a **live memory injection attack**:
```
BEFORE OUR HACK:
┌─────────────────┐ ┌──────────────────────────────┐
r0 = 0x100019cc│ ───> Flash: "hello, world\r"
└─────────────────┘ └──────────────────────────────┘
+-----------------+ +------------------------------+
| r0 = 0x100019cc| ---> | Flash: "hello, world\r" |
+-----------------+ +------------------------------+
|
puts() prints "hello, world"
AFTER OUR HACK:
┌─────────────────┐ ┌──────────────────────────────┐
r0 = 0x20040000│ ───> SRAM: "hacky, world"
└─────────────────┘ └──────────────────────────────┘
+-----------------+ +------------------------------+
| r0 = 0x20040000| ---> | SRAM: "hacky, world" |
+-----------------+ +------------------------------+
|
puts() prints "hacky, world"
```
@@ -1523,55 +1522,6 @@ AFTER OUR HACK:
---
## ✅ Practice Exercises
These prompts are intentionally aligned 1:1 with the Week 2 exercise and solution files:
- `WEEK02-01.md` and `WEEK02-01-S.md`
- `WEEK02-02.md` and `WEEK02-02-S.md`
- `WEEK02-03.md` and `WEEK02-03-S.md`
- `WEEK02-04.md` and `WEEK02-04-S.md`
#### Exercise 1: Change the Message
Try creating a different message! Write your name to SRAM and make the program print it:
```gdb
(gdb) set {char[12]} 0x20040000 = {'Y','o','u','r',' ','N','a','m','e','!','\r','\0'}
(gdb) set $r0 = 0x20040000
(gdb) c
```
#### Exercise 2: Use a Different SRAM Address
The SRAM region is large. Try writing your string to a different address:
```gdb
(gdb) set {char[14]} 0x20041000 = {'h','a','c','k','e','d','!','!','!','\r','\0'}
(gdb) set $r0 = 0x20041000
(gdb) c
```
#### Exercise 3: Examine Memory Around Your String
Look at the bytes around your injected string:
```gdb
(gdb) x/20b 0x20040000
```
What do you see? Can you identify each character?
#### Exercise 4: Automate the Hack
Create a GDB command sequence that does the full hack. You can use GDB's command feature:
```gdb
(gdb) define hack
> set {char[13]} 0x20040000 = "hacky, world"
> set $r0 = 0x20040000
> c
> end
(gdb) hack
```
Now you can just type `hack` each time!
---
## 🎓 Key Takeaways
@@ -1646,3 +1596,5 @@ Imagine an attacker with physical access to an industrial control system:
| **Stack Pointer** | Register that points to the top of the stack | Part 2 - Memory layout |
| **XIP** | Execute In Place - running code directly from flash | Part 2 - Why we can't write to flash |
| **Little-Endian** | Storing the least significant byte at the lowest address | Part 10 - String storage |
-47
View File
@@ -1,47 +0,0 @@
# 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 (r0r3) 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.
-130
View File
@@ -1,130 +0,0 @@
# 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: 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.
#### Prerequisites
- Raspberry Pi Pico 2 with debug probe connected
- OpenOCD and `arm-none-eabi-gdb` available in your PATH
- `build\0x0001_hello-world.elf` present and flashed to the board
- Week 3 environment setup completed (OpenOCD running, GDB connected)
#### Task Description
You will set a breakpoint at the reset handler (`0x1000015c`), trigger a reset, and step through each instruction one at a time while documenting what each instruction does.
#### Step-by-Step Instructions
##### Step 1: Start OpenOCD
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
```
##### Step 2: Launch GDB
```powershell
arm-none-eabi-gdb build\0x0001_hello-world.elf
```
##### Step 3: Connect to Target
```gdb
(gdb) target extended-remote :3333
```
##### Step 4: Set Breakpoint at Reset Handler
```gdb
(gdb) b *0x1000015c
```
**What this does:** Places a breakpoint at the very first instruction of the reset handler (the entry point after bootrom).
##### Step 5: Reset and Break
```gdb
(gdb) monitor reset halt
(gdb) c
```
**What this does:**
- `monitor reset halt` resets the chip and immediately halts it
- `c` continues execution until the breakpoint at the reset handler is hit
##### Step 6: Single-Step Through Instructions
Now step through the first 10 instructions, one at a time:
```gdb
(gdb) si
(gdb) disas $pc,+2
(gdb) info registers r0
```
Repeat `si` nine more times, examining each instruction.
**Example of what you'll see:**
**Instruction 1:**
```
0x1000015c <_reset_handler>: mov.w r0, #3489660928 @ 0xd0000000
```
**What it does:** Loads the SIO base address (0xd0000000) into r0
**Instruction 2:**
```
0x10000160 <_reset_handler+4>: ldr r0, [r0, #0]
```
**What it does:** Reads the CPUID register to determine which core is running
**Instruction 3:**
```
0x10000162 <_reset_handler+6>: cbz r0, 0x1000016a
```
**What it does:** If CPUID is 0 (Core 0), branch ahead to continue boot; otherwise handle Core 1
##### Step 7: Document Your Observations
For each of the 10 instructions:
1. Write down the address
2. Write down the assembly instruction
3. Explain what it does
4. Note any register changes using `info registers`
#### Expected Output
- You should see the reset handler check which core is running
- If you're on Core 0, you'll see it jump to the data copy section
- Register `r0` will contain CPUID value (should be 0)
- PC (program counter) advances with each `si` command
#### Questions for Reflection
###### Question 1: Why does the reset handler check the CPUID before doing anything else?
###### Question 2: What would happen if Core 1 tried to run the same initialization code as Core 0?
###### Question 3: Which registers are used in the first 10 instructions, and why those specific ones?
#### Tips and Hints
- 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!
#### Next Steps
- Try stepping all the way through to the data copy loop
- Set a breakpoint at `0x1000016c` (the data copy loop) and continue there directly
- Move on to Exercise 2 to calculate the stack size from the vector table
#### Additional Challenge
Set a breakpoint at `0x10000178` (the BSS clear phase) and continue execution to see how the reset handler transitions from data copying to BSS clearing.
-76
View File
@@ -1,76 +0,0 @@
# 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.
-167
View File
@@ -1,167 +0,0 @@
# 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: 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.
#### Prerequisites
- Raspberry Pi Pico 2 with debug probe connected
- OpenOCD and `arm-none-eabi-gdb` available
- `build\0x0001_hello-world.elf` flashed to the board
- Understanding of memory regions from Week 3 Part 5 (Linker Script)
#### Task Description
You will examine the initial stack pointer value from the vector table, identify the stack limit, calculate the total stack size in bytes and kilobytes, and verify your calculations.
#### Background Information
From the Week 3 lesson, we learned:
- The initial stack pointer is stored at `0x10000000` (first entry in vector table)
- The linker script defines: `SCRATCH_Y: ORIGIN = 0x20081000, LENGTH = 4k`
- Stack top is calculated as: `ORIGIN + LENGTH = 0x20082000`
- The stack grows downward from high addresses to low addresses
#### Step-by-Step Instructions
##### Step 1: Connect and Halt
```gdb
(gdb) target extended-remote :3333
(gdb) monitor reset halt
```
##### Step 2: Examine the Initial Stack Pointer
```gdb
(gdb) x/x 0x10000000
```
**Expected output:**
```
0x10000000 <__vectors>: 0x20082000
```
This is the **top of the stack** (where the stack starts before growing downward).
##### Step 3: Find the Stack Limit
The stack limit is defined in the linker script and can be found by examining stack-related symbols or calculating from memory regions.
From the Week 3 lesson, the stack limit is `0x20078000`.
You can verify this in GDB:
```gdb
(gdb) info symbol __StackLimit
```
or check registers after boot:
```gdb
(gdb) info registers
```
Look for stack limit values or calculate: The main RAM starts at `0x20000000`, and SCRATCH_Y starts at `0x20081000`.
##### Step 4: Calculate Stack Size in Bytes
**Formula:**
```
Stack Size = Stack Top - Stack Limit
Stack Size = 0x20082000 - 0x20078000
```
Let's convert to decimal:
- `0x20082000` = 537,108,480 decimal
- `0x20078000` = 537,067,520 decimal
- Difference = 40,960 bytes
**Alternative hex calculation:**
```
0x20082000
- 0x20078000
-----------
0x0000A000 = 40,960 bytes
```
##### Step 5: Convert to Kilobytes
```
Bytes to KB = 40,960 ÷ 1,024 = 40 KB
```
So the stack is **40 KB** in size.
##### Step 6: Verify Using Memory Regions
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
- **Stack range**: `0x20078000` - `0x20082000` (40 KB)
The stack extends from SCRATCH_Y down into the upper portion of main RAM.
##### Step 7: Examine Stack Usage at Runtime
You can see the current stack pointer value:
```gdb
(gdb) b main
(gdb) c
(gdb) info registers sp
```
**Expected output:**
```
sp 0x20081fc8 0x20081fc8
```
This shows the stack has used:
```
0x20082000 - 0x20081fc8 = 0x38 = 56 bytes
```
#### Expected Output
- Initial stack pointer: `0x20082000`
- Stack limit: `0x20078000`
- Stack size: **40,960 bytes** or **40 KB**
- Current stack usage (at main): approximately 56 bytes
#### Questions for Reflection
###### Question 1: Why is the stack 40 KB instead of just fitting in the 4 KB SCRATCH_Y region?
###### Question 2: What happens if the stack grows beyond 0x20078000?
###### Question 3: How would you detect a stack overflow during runtime?
###### Question 4: Why does the stack grow downward instead of upward?
#### Tips and Hints
- Use Windows Calculator in Programmer mode to convert hex to decimal
- Remember: 1 KB = 1,024 bytes (not 1,000)
- The stack pointer (SP) decreases as the stack grows (push operations)
- Use `bt` (backtrace) in GDB to see how much stack is currently in use
#### Next Steps
- Monitor the stack pointer as you step through functions to see it change
- Calculate stack usage for specific function calls
- Move on to Exercise 3 to examine all vector table entries
#### Additional Challenge
Write a GDB command to automatically calculate and display stack usage:
```gdb
(gdb) define stackusage
> set $used = 0x20082000 - $sp
> printf "Stack used: %d bytes (%d KB)\n", $used, $used/1024
> end
(gdb) stackusage
```
-78
View File
@@ -1,78 +0,0 @@
# 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.
-209
View File
@@ -1,209 +0,0 @@
# 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: 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.
#### Prerequisites
- Raspberry Pi Pico 2 with debug probe connected
- OpenOCD and `arm-none-eabi-gdb` available
- `build\0x0001_hello-world.elf` loaded
- Understanding of the vector table from Week 3 Part 4
- Knowledge of Thumb mode addressing (LSB = 1 indicates Thumb code)
#### Task Description
You will examine 16 consecutive 32-bit values from the vector table, decode each entry, determine if it's a valid code address, and identify which exception handler it points to.
#### Background Information
The ARM Cortex-M vector table structure:
| Offset | Vector # | Handler Name | Purpose |
|--------|----------|---------------------|---------|
| 0x00 | - | Initial SP | Stack pointer initialization |
| 0x04 | 1 | Reset | Power-on/reset entry point |
| 0x08 | 2 | NMI | Non-Maskable Interrupt |
| 0x0C | 3 | HardFault | Serious errors |
| 0x10 | 4 | MemManage | Memory protection fault |
| 0x14 | 5 | BusFault | Bus error |
| 0x18 | 6 | UsageFault | Undefined instruction, etc. |
| 0x1C-0x28 | 7-10 | Reserved | Not used |
| 0x2C | 11 | SVCall | Supervisor call |
| 0x30 | 12 | Debug Monitor | Debug events |
| 0x34 | 13 | Reserved | Not used |
| 0x38 | 14 | PendSV | Pendable service call |
| 0x3C | 15 | SysTick | System tick timer |
#### Step-by-Step Instructions
##### Step 1: Connect and Halt
```gdb
(gdb) target extended-remote :3333
(gdb) monitor reset halt
```
##### Step 2: Examine 16 Vector Table Entries
```gdb
(gdb) x/16x 0x10000000
```
**Expected output (example):**
```
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
```
##### Step 3: Analyze Each Entry
Create a table documenting each entry:
**Entry 1 (Offset 0x00):**
```
Address: 0x10000000
Value: 0x20082000
Valid Code Address? NO - This is the stack pointer (in RAM region 0x2xxxxxxx)
Handler: Initial Stack Pointer
```
**Entry 2 (Offset 0x04):**
```
Address: 0x10000004
Value: 0x1000015d
Valid Code Address? YES (starts with 0x1000...)
Thumb Mode? YES (LSB = 1, so actual address is 0x1000015c)
Handler: Reset Handler (_reset_handler)
```
**Entry 3 (Offset 0x08):**
```
Address: 0x10000008
Value: 0x1000011b
Valid Code Address? YES
Thumb Mode? YES (actual address: 0x1000011a)
Handler: NMI Handler (isr_nmi)
```
Continue this analysis for all 16 entries...
##### Step 4: Verify Handlers with Symbols
For each code address, check what function it points to:
```gdb
(gdb) info symbol 0x1000015c
```
**Expected output:**
```
_reset_handler in section .text
```
Repeat for other addresses:
```gdb
(gdb) info symbol 0x1000011a
(gdb) info symbol 0x1000011c
(gdb) info symbol 0x1000011e
```
##### Step 5: Examine Handler Code
Look at the actual code at each handler:
```gdb
(gdb) x/3i 0x1000011a
```
**Expected output for NMI handler:**
```
0x1000011a <isr_nmi>: bkpt 0x0000
0x1000011c <isr_hardfault>: bkpt 0x0000
0x1000011e <isr_svcall>: bkpt 0x0000
```
##### Step 6: Identify Reserved Entries
Note any entries with value `0x00000000`:
```
0x00000000 = Reserved/Unused vector
```
These slots are reserved by ARM and not used on Cortex-M33.
##### Step 7: Create a Complete Map
Document all 16 entries in this format:
| Offset | Value | Address Type | Actual Addr | Handler Name |
|--------|------------|--------------|-------------|------------------|
| 0x00 | 0x20082000 | Stack Ptr | N/A | __StackTop |
| 0x04 | 0x1000015d | Code (Thumb) | 0x1000015c | _reset_handler |
| 0x08 | 0x1000011b | Code (Thumb) | 0x1000011a | isr_nmi |
| 0x0C | 0x1000011d | Code (Thumb) | 0x1000011c | isr_hardfault |
| ... | ... | ... | ... | ... |
#### Expected Output
- First entry is the stack pointer in RAM (0x2xxxxxxx range)
- Entries 2-16 are mostly code addresses in flash (0x1000xxxx range)
- Code addresses have LSB = 1 (Thumb mode indicator)
- Reserved entries show 0x00000000
- Most handlers point to simple `bkpt` instructions (default handlers)
#### Questions for Reflection
###### Question 1: Why do all the code addresses end in odd numbers (LSB = 1)?
###### Question 2: What happens if an exception occurs for a reserved/null vector entry?
###### Question 3: Why do most exception handlers just contain `bkpt` instructions?
###### Question 4: How would you replace a default handler with your own custom handler?
#### Tips and Hints
- Use `x/32x 0x10000000` to see even more vectors (up to 48)
- Remember to subtract 1 from addresses before disassembling (remove Thumb bit)
- Use `info functions` to see all available handler symbols
- Compare GDB output with Ghidra's vector table view
#### Next Steps
- Set breakpoints at different exception handlers to see if they're ever called
- Trigger a fault intentionally to see which handler executes
- Move on to Exercise 4 to analyze your main function
#### Additional Challenge
Write a GDB script to automatically decode and display all vector table entries:
```gdb
(gdb) define vectors
> set $i = 0
> while $i < 16
> set $addr = 0x10000000 + ($i * 4)
> set $val = *(int*)$addr
> printf "[%2d] 0x%08x: 0x%08x", $i, $addr, $val
> if $i == 0
> printf " (Stack Pointer)\n"
> else
> if $val != 0
> if ($val & 0x10000000) == 0x10000000
> printf " -> 0x%08x\n", $val & 0xFFFFFFFE
> else
> printf " (Invalid/Reserved)\n"
> end
> else
> printf " (Reserved)\n"
> end
> end
> set $i = $i + 1
> end
> end
```
-94
View File
@@ -1,94 +0,0 @@
# 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.
-238
View File
@@ -1,238 +0,0 @@
# 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: 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.
#### Prerequisites
- Raspberry Pi Pico 2 with debug probe connected
- OpenOCD and `arm-none-eabi-gdb` available
- `build\0x0001_hello-world.elf` loaded
- Understanding of function calls and the link register (LR) from previous weeks
#### Task Description
You will use GDB to find `main()`, examine its disassembly, identify the initial function call (`stdio_init_all`), and use the link register to trace backward through the boot sequence.
#### Background Information
Key concepts:
- **Link Register (LR)**: Stores the return address when a function is called
- **Program Counter (PC)**: Points to the currently executing instruction
- **Function prologue**: The setup code at the start of every function
- **bl instruction**: "Branch with Link" - calls a function and stores return address in LR
#### Step-by-Step Instructions
##### Step 1: Connect and Halt
```gdb
(gdb) target extended-remote :3333
(gdb) monitor reset halt
```
##### Step 2: Find the Main Function
```gdb
(gdb) info functions main
```
**Expected output:**
```
All functions matching regular expression "main":
File 0x0001_hello-world.c:
0x10000234 int main(void);
Non-debugging symbols:
0x10000186 platform_entry_arm_a
...
```
Note the address of `main`: **`0x10000234`**
##### Step 3: Examine Instructions at Main
```gdb
(gdb) x/10i 0x10000234
```
**Expected output:**
```
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>
0x1000024c <runtime_init>: push {r3, r4, r5, r6, r7, lr}
```
##### Step 4: Identify the First Function Call
The first function call in `main()` is:
```
0x1000023a <main+6>: bl 0x100012c4 <stdio_init_all>
```
**What does this function do?**
```gdb
(gdb) info functions stdio_init_all
```
**Answer:** `stdio_init_all()` initializes all standard I/O systems (USB, UART, etc.) so `printf()` works.
##### Step 5: Set a Breakpoint at Main
```gdb
(gdb) b main
(gdb) c
```
**Expected output:**
```
Breakpoint 1, main () at 0x0001_hello-world.c:5
5 stdio_init_all();
```
##### Step 6: Examine the Link Register
When stopped at `main()`, check what's in the link register:
```gdb
(gdb) info registers lr
```
**Expected output:**
```
lr 0x1000018b 268435851
```
The LR contains the return address - where execution will go when `main()` returns.
##### Step 7: Disassemble the Caller
Subtract 1 to remove the Thumb bit and disassemble:
```gdb
(gdb) x/10i 0x1000018a
```
**Expected output:**
```
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
0x1000018e <platform_entry+8>: ldr r1, [pc, #80]
0x10000190 <platform_entry+10>: blx r1
0x10000192 <platform_entry+12>: bkpt 0x0000
```
##### Step 8: Understand the Call Chain
Working backward from `main()`:
```
platform_entry (0x10000186)
? calls (blx at +2)
runtime_init() (0x1000024c)
? calls (blx at +6)
main() (0x10000234) ? We are here
? will call (blx at +6)
stdio_init_all() (0x100012c4)
```
##### Step 9: Verify Platform Entry Calls Main
Look at what `platform_entry` loads before the `blx`:
```gdb
(gdb) x/x 0x100001dc
```
This is the address loaded into r1 before calling `blx`. It should point to `main()`.
**Expected output:**
```
0x100001dc <data_cpy_table+60>: 0x10000235
```
Note: `0x10000235` = `0x10000234` + 1 (Thumb bit), which is the address of `main()`!
##### Step 10: Complete the Boot Trace
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
?
9. stdio_init_all() (first line of main)
```
#### Expected Output
- `main()` is at address `0x10000234`
- First function call is `stdio_init_all()` at offset +6
- Link register points to `platform_entry+4` (0x1000018a)
- `platform_entry` makes three function calls: runtime_init, main, and exit
#### Questions for Reflection
###### Question 1: Why does the link register point 4 bytes after the `blx` instruction that called main?
###### Question 2: What would happen if `main()` tried to return (instead of looping forever)?
###### Question 3: How can you tell from the disassembly that main contains an infinite loop?
###### Question 4: Why is `stdio_init_all()` called before the printf loop?
#### Tips and Hints
- Use `bt` (backtrace) to see the call stack
- Remember to account for Thumb mode when reading addresses from LR
- Use `info frame` to see detailed information about the current stack frame
- The `push {r7, lr}` at the start of main saves the return address
#### Next Steps
- Set a breakpoint at `stdio_init_all()` and step through its initialization
- Examine what happens after `main()` by looking at `exit()` function
- Try Exercise 5 in Ghidra for static analysis of the boot sequence
#### Additional Challenge
Create a GDB command to automatically trace the call chain:
```gdb
(gdb) define calltrace
> set $depth = 0
> set $addr = $pc
> while $depth < 10
> printf "%d: ", $depth
> info symbol $addr
> set $addr = *(int*)($lr - 4)
> set $depth = $depth + 1
> end
> end
```
Then try stepping through functions and running `calltrace` at each level to build a complete call graph.
+198 -238
View File
@@ -22,7 +22,7 @@ This week builds on your GDB and Ghidra skills from previous weeks:
---
## 📚 The Code We're Analyzing
## The Code We're Analyzing
Throughout this week, we'll continue working with our `0x0001_hello-world.c` program:
@@ -42,7 +42,7 @@ But this week, we're going **deeper** - we'll understand everything that happens
---
## 📚 Part 1: Understanding the Boot Process
## Part 1: Understanding the Boot Process
### What Happens When You Power On?
@@ -60,49 +60,49 @@ Each of these steps has a corresponding piece of code. Let's explore them all!
### The RP2350 Boot Sequence Overview
```
┌─────────────────────────────────────────────────────────────────┐
STEP 1: Power On
- The Cortex-M33 core wakes up
- Execution begins at address 0x00000000 (Bootrom)
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| STEP 1: Power On |
| - The Cortex-M33 core wakes up |
| - Execution begins at address 0x00000000 (Bootrom) |
+-----------------------------------------------------------------+
┌─────────────────────────────────────────────────────────────────┐
STEP 2: Bootrom Executes (32KB on-chip ROM)
- This code is burned into the chip - can't be changed!
- It looks for valid firmware in flash memory
- It scans the first 4 kB of the image for a valid IMAGE_DEF
(Datasheet §4.1, p. 338: 32KB ROM; §5.9.5, p. 429: IMAGE_DEF)
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| STEP 2: Bootrom Executes (32KB on-chip ROM) |
| - This code is burned into the chip - can't be changed! |
| - It looks for valid firmware in flash memory |
| - It scans the first 4 kB of the image for a valid IMAGE_DEF |
| (Datasheet §4.1, p. 338: 32KB ROM; §5.9.5, p. 429: IMAGE_DEF) |
+-----------------------------------------------------------------+
┌─────────────────────────────────────────────────────────────────┐
STEP 3: Flash XIP Setup (bootrom-managed)
- The bootrom configures the flash interface automatically
- Sets up XIP (Execute In Place) mode
- NOTE: Unlike RP2040, there is NO separate boot2 in flash!
(Datasheet §5.2, p. 375: "removal of a boot2 in the first
256 bytes of the image")
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| STEP 3: Flash XIP Setup (bootrom-managed) |
| - The bootrom configures the flash interface automatically |
| - Sets up XIP (Execute In Place) mode |
| - NOTE: Unlike RP2040, there is NO separate boot2 in flash! |
| (Datasheet §5.2, p. 375: "removal of a boot2 in the first |
| 256 bytes of the image") |
+-----------------------------------------------------------------+
┌─────────────────────────────────────────────────────────────────┐
STEP 4: Vector Table & Reset Handler
- Bootrom reads the vector table at 0x10000000
- Gets the initial stack pointer from offset 0x00
- Gets the reset handler address from offset 0x04
- Jumps to the reset handler!
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| STEP 4: Vector Table & Reset Handler |
| - Bootrom reads the vector table at 0x10000000 |
| - Gets the initial stack pointer from offset 0x00 |
| - Gets the reset handler address from offset 0x04 |
| - Jumps to the reset handler! |
+-----------------------------------------------------------------+
┌─────────────────────────────────────────────────────────────────┐
STEP 5: C Runtime Startup (crt0.S)
- Copies initialized data from flash to RAM
- Zeros out the BSS section
- Calls runtime_init()
- Finally calls main()!
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| STEP 5: C Runtime Startup (crt0.S) |
| - Copies initialized data from flash to RAM |
| - Zeros out the BSS section |
| - Calls runtime_init() |
| - Finally calls main()! |
+-----------------------------------------------------------------+
```
---
## 📚 Part 2: The Bootrom - Where It All Begins
## Part 2: The Bootrom - Where It All Begins
### What is the Bootrom?
@@ -200,7 +200,7 @@ assuming a fixed address.
---
## 📚 Part 3: Understanding XIP (Execute In Place)
## Part 3: Understanding XIP (Execute In Place)
> 🔄 **REVIEW:** In Week 1, we learned that our code lives at `0x10000000` in flash memory. We used `x/1000i 0x10000000` to find our `main` function. Now we'll understand WHY code is at this address!
@@ -225,29 +225,29 @@ Think of it like reading a book:
The XIP flash region starts at address `0x10000000`. This is where your compiled code lives!
```
┌─────────────────────────────────────────────────────┐
Address: 0x10000000 (XIP Base)
│ ┌─────────────────────────────────────────────────┐│
Vector Table (first thing here!) ││
- Stack Pointer at offset 0x00 ││
- Reset Handler at offset 0x04 ││
- Other exception handlers... ││
│ ├─────────────────────────────────────────────────┤│
Your Code ││
- Reset handler ││
- main() function ││
- Other functions ││
│ ├─────────────────────────────────────────────────┤│
Read-Only Data ││
- Strings like "hello, world" ││
- Constant values ││
│ └─────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────┘
+-----------------------------------------------------+
| Address: 0x10000000 (XIP Base) |
| +-------------------------------------------------+|
| | Vector Table (first thing here!) ||
| | - Stack Pointer at offset 0x00 ||
| | - Reset Handler at offset 0x04 ||
| | - Other exception handlers... ||
| +-------------------------------------------------+|
| | Your Code ||
| | - Reset handler ||
| | - main() function ||
| | - Other functions ||
| +-------------------------------------------------+|
| | Read-Only Data ||
| | - Strings like "hello, world" ||
| | - Constant values ||
| +-------------------------------------------------+|
+-----------------------------------------------------+
```
---
## 📚 Part 4: The Vector Table - The CPU's Instruction Manual
## Part 4: The Vector Table - The CPU's Instruction Manual
### What is the Vector Table?
@@ -293,7 +293,7 @@ So `0x1000015d` means:
---
## 📚 Part 5: The Linker Script - Memory Mapping
## Part 5: The Linker Script - Memory Mapping
### What is a Linker Script?
@@ -344,7 +344,7 @@ This value (`0x20082000`) is what we see at offset `0x00` in the vector table!
---
## 📚 Part 6: Setting Up Your Environment (GDB - Dynamic Analysis)
## Part 6: Setting Up Your Environment (GDB - Dynamic Analysis)
> 🔄 **REVIEW:** This setup is identical to Weeks 1-2. If you need a refresher on OpenOCD and GDB connection, refer back to Week 1 Part 4 or Week 2 Part 5.
@@ -363,7 +363,7 @@ Before we start, make sure you have:
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-s "C:\Users\assem.KEVINTHOMAS\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
@@ -500,8 +500,8 @@ This is "Compare and Branch if Zero". If `r0` is `0` (meaning we're on Core 0),
The RP2350 has **two cores**, but only **Core 0** should run the startup code! If both cores tried to initialize the same memory and peripherals, chaos would ensue.
So the reset handler checks:
- **Core 0?** Continue with startup
- **Core 1?** Go back to the bootrom and wait
- **Core 0?** -> Continue with startup
- **Core 1?** -> Go back to the bootrom and wait
---
@@ -557,31 +557,31 @@ Let's look at more instructions to see the full picture:
The reset handler performs several phases:
```
┌─────────────────────────────────────────────────────────────────┐
PHASE 1: Core Check (0x1000015c - 0x10000168)
- Check CPUID to see which core we're on
- If not Core 0, go back to bootrom
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| PHASE 1: Core Check (0x1000015c - 0x10000168) |
| - Check CPUID to see which core we're on |
| - If not Core 0, go back to bootrom |
+-----------------------------------------------------------------+
┌─────────────────────────────────────────────────────────────────┐
PHASE 2: Data Copy Setup & Loop (0x1000016a - 0x10000176)
- Set up the data_cpy_table pointer and load each copy triplet
- Copy initialized variables from flash to RAM
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| PHASE 2: Data Copy Setup & Loop (0x1000016a - 0x10000176) |
| - Set up the data_cpy_table pointer and load each copy triplet |
| - Copy initialized variables from flash to RAM |
+-----------------------------------------------------------------+
┌─────────────────────────────────────────────────────────────────┐
PHASE 3: BSS Setup & Clear (0x10000178 - 0x10000184)
- Load the BSS start/end addresses into r1 and r2
- GDB labels those literals as `data_cpy_table+48/+52`
- Zero out all uninitialized global variables
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| PHASE 3: BSS Setup & Clear (0x10000178 - 0x10000184) |
| - Load the BSS start/end addresses into r1 and r2 |
| - GDB labels those literals as `data_cpy_table+48/+52` |
| - Zero out all uninitialized global variables |
+-----------------------------------------------------------------+
┌─────────────────────────────────────────────────────────────────┐
PHASE 4: Platform Entry Begins (0x10000186 - 0x10000188 shown)
- Load the runtime_init() pointer from the table
- Branch to runtime_init() with `blx r1`
- `main()` and `exit()` appear a few instructions later
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| PHASE 4: Platform Entry Begins (0x10000186 - 0x10000188 shown) |
| - Load the runtime_init() pointer from the table |
| - Branch to runtime_init() with `blx r1` |
| - `main()` and `exit()` appear a few instructions later |
+-----------------------------------------------------------------+
```
---
@@ -634,12 +634,12 @@ The table ends with an entry where the source address is `0x00000000` (which sig
The data copy loop works like this:
```
┌─────────────────────────────────────────────┐
1. Load source, dest, end from table
2. If source == 0, we're done
3. Otherwise, copy word by word
4. Go back to step 1 for next entry
└─────────────────────────────────────────────┘
+---------------------------------------------+
| 1. Load source, dest, end from table |
| 2. If source == 0, we're done |
| 3. Otherwise, copy word by word |
| 4. Go back to step 1 for next entry |
+---------------------------------------------+
```
The actual code (starting at **`0x1000016c`** in the reset handler):
@@ -655,7 +655,7 @@ bl 0x1000019a <data_cpy>
b.n 0x1000016c <hold_non_core0_in_bootrom+8>
```
> 💡 **Note:** You can see this code in **Step 6** earlier where we examined the reset handler with `x/20i 0x1000015c`.
> Tip: **Note:** You can see this code in **Step 6** earlier where we examined the reset handler with `x/20i 0x1000015c`.
---
@@ -699,16 +699,16 @@ The first two `ldr` instructions are still part of the **BSS clear setup**, even
### Understanding the Loop
```
┌─────────────────────────────────────────────┐
r1 = start of BSS section
r2 = end of BSS section
r0 = 0
│ │
LOOP:
Store 0 at address r1
Increment r1 by 4 bytes
If r1 != r2, repeat
└─────────────────────────────────────────────┘
+---------------------------------------------+
| r1 = start of BSS section |
| r2 = end of BSS section |
| r0 = 0 |
| |
| LOOP: |
| Store 0 at address r1 |
| Increment r1 by 4 bytes |
| If r1 != r2, repeat |
+---------------------------------------------+
```
---
@@ -817,7 +817,7 @@ Let's verify we understand the boot process by setting a breakpoint at main:
**You should see:**
```
Breakpoint 1 at 0x10000234: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0001_hello-world/0x0001_hello-world.c, line 5.
Breakpoint 1 at 0x10000234: file C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c, line 5.
Note: automatically using hardware breakpoints for read-only addresses.
```
@@ -833,7 +833,7 @@ Note: automatically using hardware breakpoints for read-only addresses.
Continuing.
Thread 1 "rp2350.cm0" hit Breakpoint 1, main ()
at C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0001_hello-world/0x0001_hello-world.c:5
at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c:5
5 stdio_init_all();
(gdb)
```
@@ -945,7 +945,7 @@ While GDB is excellent for dynamic analysis (watching code execute), Ghidra exce
...
```
> 💡 **Notice:** Ghidra shows the vector table data as individual bytes by default. You can see it has labeled the start as `__vectors`, `__flash_binary_start`, `__VECTOR_TABLE`, and `__logical_binary_start`. The arrows (like `? -> 1000015d`) show that Ghidra recognizes these bytes as pointers to code addresses! To see the data formatted as 32-bit addresses instead of bytes, you can right-click and retype the data.
> Tip: **Notice:** Ghidra shows the vector table data as individual bytes by default. You can see it has labeled the start as `__vectors`, `__flash_binary_start`, `__VECTOR_TABLE`, and `__logical_binary_start`. The arrows (like `? -> 1000015d`) show that Ghidra recognizes these bytes as pointers to code addresses! To see the data formatted as 32-bit addresses instead of bytes, you can right-click and retype the data.
### Step 17: Navigate to the Reset Handler
@@ -1005,7 +1005,7 @@ void _reset_handler(void)
Let's find how the boot code eventually calls `main()`:
1. In the Symbol Tree, find the `main` function
2. Right-click on `main` and select **References Show References to main**
2. Right-click on `main` and select **References -> Show References to main**
3. This shows everywhere `main` is called from!
**You should see:**
@@ -1048,7 +1048,7 @@ In Ghidra, look at `platform_entry`:
Ghidra can visualize the call flow:
1. With `_reset_handler` selected, go to **Window Function Call Graph**
1. With `_reset_handler` selected, go to **Window -> Function Call Graph**
2. This shows a visual graph of all function calls from the reset handler
3. You will see `_reset_handler` at the top with arrows going down to its four direct callees: `data_cpy`, `runtime_init`, `main`, and `exit`
@@ -1076,32 +1076,32 @@ Ghidra can visualize the call flow:
### The Complete Boot Sequence
```
┌─────────────────────────────────────────────────────────────────┐
1. POWER ON
Cortex-M33 begins at 0x00000000 (bootrom)
├─────────────────────────────────────────────────────────────────┤
2. BOOTROM
- Initializes hardware
- Configures flash XIP (no separate boot2 on RP2350)
- Finds IMAGE_DEF within first 4 kB of flash image
├─────────────────────────────────────────────────────────────────┤
3. VECTOR TABLE (0x10000000)
- Reads SP from offset 0x00 0x20082000
- Reads Reset Handler from offset 0x04 0x1000015d
├─────────────────────────────────────────────────────────────────┤
4. RESET HANDLER (0x1000015c)
- Checks CPUID (Core 0 continues, Core 1 waits)
- Copies .data from flash to RAM
- Zeros .bss section
├─────────────────────────────────────────────────────────────────┤
5. PLATFORM ENTRY (0x10000186)
- Calls runtime_init()
- Calls main()
- Calls exit() when main returns
├─────────────────────────────────────────────────────────────────┤
6. YOUR CODE RUNS!
main() at 0x10000234
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 1. POWER ON |
| Cortex-M33 begins at 0x00000000 (bootrom) |
+-----------------------------------------------------------------+
| 2. BOOTROM |
| - Initializes hardware |
| - Configures flash XIP (no separate boot2 on RP2350) |
| - Finds IMAGE_DEF within first 4 kB of flash image |
+-----------------------------------------------------------------+
| 3. VECTOR TABLE (0x10000000) |
| - Reads SP from offset 0x00 -> 0x20082000 |
| - Reads Reset Handler from offset 0x04 -> 0x1000015d |
+-----------------------------------------------------------------+
| 4. RESET HANDLER (0x1000015c) |
| - Checks CPUID (Core 0 continues, Core 1 waits) |
| - Copies .data from flash to RAM |
| - Zeros .bss section |
+-----------------------------------------------------------------+
| 5. PLATFORM ENTRY (0x10000186) |
| - Calls runtime_init() |
| - Calls main() |
| - Calls exit() when main returns |
+-----------------------------------------------------------------+
| 6. YOUR CODE RUNS! |
| main() at 0x10000234 |
+-----------------------------------------------------------------+
```
### Key Addresses to Remember
@@ -1157,56 +1157,14 @@ Ghidra can visualize the call flow:
| Action | How to Access | Purpose |
| ------ | ------------- | ------- |
| Go To Address | Navigation Go To... | Jump to specific memory address |
| Show References | Right-click References Show References to | Find all callers of a function |
| Function Call Graph | Window Function Call Graph | Visualize call flow |
| Go To Address | Navigation -> Go To... | Jump to specific memory address |
| Show References | Right-click -> References -> Show References to | Find all callers of a function |
| Function Call Graph | Window -> Function Call Graph | Visualize call flow |
| Add Comment | Press `;` | Document your analysis |
| Rename Symbol | Right-click Rename | Give meaningful names to functions |
| Rename Symbol | Right-click -> Rename | Give meaningful names to functions |
---
## ✅ Practice Exercises
### Exercise 1: Trace a Reset
1. Set a breakpoint at the reset handler: `b *0x1000015c`
2. Type `monitor reset halt` then `c`
3. Single-step through the first 10 instructions with `si`
4. For each instruction, explain what it does
### Exercise 2: Find the Stack Size
1. The stack starts at `0x20082000`
2. The stack limit is `0x20078000` (from register assignments)
3. Calculate: How many bytes is the stack?
4. How many kilobytes is that?
### Exercise 3: Examine All Vectors
1. Use `x/16x 0x10000000` to see the first 16 vector table entries
2. For each entry, determine:
- Is it a valid code address (starts with `0x1000...`)?
- What handler does it point to?
### Exercise 4: Find Your Main Function and Trace Back
1. Use `info functions main` to find main
2. Examine 10 instructions at that address
3. Identify the first function call in main
4. What does that function do?
5. When stopped at main, examine `$lr` (link register)
6. What address is stored there?
7. Disassemble that address - what function is it?
8. This shows you where main was called from!
### Exercise 5: Ghidra Boot Analysis
1. In Ghidra, navigate to `_reset_handler`
2. Use **Window → Function Call Graph** to visualize the call tree
3. Identify the path from `_reset_handler` to `main`
4. How many functions are called before `main` starts?
5. Add a comment in Ghidra explaining what each function does
---
## 🎓 Key Takeaways
@@ -1270,18 +1228,18 @@ Understanding the boot process is critical for both attackers and defenders. Kno
#### 1. Secure Boot Implementation
```
┌─────────────────────────────────────────────────────┐
SECURE BOOT FLOW
├─────────────────────────────────────────────────────┤
Bootrom (immutable)
Verify IMAGE_DEF signature
Verify application image signature
If all valid: Jump to reset handler
If any invalid: Refuse to boot
└─────────────────────────────────────────────────────┘
+-----------------------------------------------------+
| SECURE BOOT FLOW |
+-----------------------------------------------------+
| Bootrom (immutable) |
||
| Verify IMAGE_DEF signature |
||
| Verify application image signature |
||
| If all valid: Jump to reset handler |
| If any invalid: Refuse to boot |
+-----------------------------------------------------+
```
**Implementation:** Use cryptographic signatures to verify each boot stage before execution.
@@ -1386,7 +1344,7 @@ Understanding how an attacker would analyze and exploit the boot sequence is ess
---
## 📚 Additional Resources
## Additional Resources
### RP2350 Datasheet
@@ -1411,7 +1369,7 @@ https://github.com/raspberrypi/pico-bootrom-rp2350
## 🔬 Part 17: Proving the Boot Sequence with objdump
Everything we have learned about the boot sequence can be proven directly from the compiled ELF binary using `arm-none-eabi-objdump`. The bootrom is not in your ELF (it is mask ROM burned into the chip at `0x00000000`), but everything your firmware provides the vector table, the IMAGE_DEF, and the reset handler lives in your ELF starting at `0x10000000`.
Everything we have learned about the boot sequence can be proven directly from the compiled ELF binary using `arm-none-eabi-objdump`. The bootrom is not in your ELF (it is mask ROM burned into the chip at `0x00000000`), but everything your firmware provides - the vector table, the IMAGE_DEF, and the reset handler - lives in your ELF starting at `0x10000000`.
### Step 1: List All Sections
@@ -1429,9 +1387,9 @@ Idx Name Size VMA LMA
6 .data 0000019c 20000110 10001b4c
```
> 💡 The Pico SDK merges `.vectors`, `.embedded_block`, and `.reset` all into `.text` at `0x10000000`. They are not separate named ELF sections they are sub-regions inside `.text`.
> Tip: The Pico SDK merges `.vectors`, `.embedded_block`, and `.reset` all into `.text` at `0x10000000`. They are not separate named ELF sections - they are sub-regions inside `.text`.
### Step 2: Dump the First 0x150 Bytes of Flash One Command, Zero Skips
### Step 2: Dump the First 0x150 Bytes of Flash - One Command, Zero Skips
```bash
arm-none-eabi-objdump -s --start-address=0x10000000 --stop-address=0x10000150 build/0x0001_hello-world.elf
@@ -1465,30 +1423,30 @@ Raw output from that command:
Every address annotated, no skips:
#### 0x10000000 Vector Table, Mandatory Entries
#### 0x10000000 - Vector Table, Mandatory Entries
| Address | Raw Bytes (LE) | Decoded | What it is |
|---------|----------------|---------|------------|
| `0x10000000` | `00 20 08 20` | `0x20082000` | **Initial SP** top of SCRATCH_Y RAM. Bootrom loads MSP from here before doing anything else. |
| `0x10000004` | `5d 01 00 10` | `0x1000015d` | **Reset_Handler** address with Thumb bit set. Strip bit 0 real address `0x1000015c`. Bootrom jumps here. |
| `0x10000008` | `1b 01 00 10` | `0x1000011b` | **NMI** handler address (Thumb, `0x1000011a`). |
| `0x1000000c` | `1d 01 00 10` | `0x1000011d` | **HardFault** handler address (Thumb, `0x1000011c`). |
| `0x10000000` | `00 20 08 20` | `0x20082000` | **Initial SP** - top of SCRATCH_Y RAM. Bootrom loads MSP from here before doing anything else. |
| `0x10000004` | `5d 01 00 10` | `0x1000015d` | **Reset_Handler** address with Thumb bit set. Strip bit 0 -> real address `0x1000015c`. Bootrom jumps here. |
| `0x10000008` | `1b 01 00 10` | `0x1000011b` | **NMI** handler address (Thumb, -> `0x1000011a`). |
| `0x1000000c` | `1d 01 00 10` | `0x1000011d` | **HardFault** handler address (Thumb, -> `0x1000011c`). |
#### 0x100000100x1000010f Vector Table, IRQ Slots (all 52 external IRQs)
#### 0x10000010-0x1000010f - Vector Table, IRQ Slots (all 52 external IRQs)
```
10000010 11010010 11010010 ...(repeats through 0x1000010f)...
```
Every 4-byte word here is `11 01 00 10` = pointer `0x10000111`.
That is the **default IRQ handler** address with Thumb bit set ( `0x10000110`).
The RP2350 Cortex-M33 has 16 system vectors (offsets 0x000x3f) plus up to 52
external IRQ vectors (offsets 0x400xff = addresses `0x10000040``0x1000010f`).
That is the **default IRQ handler** address with Thumb bit set (-> `0x10000110`).
The RP2350 Cortex-M33 has 16 system vectors (offsets 0x00-0x3f) plus up to 52
external IRQ vectors (offsets 0x40-0xff = addresses `0x10000040`-`0x1000010f`).
Every IRQ the application does not register gets this default handler pointer.
This block is 240 bytes (`0x10000010` to `0x1000010f`) of nothing but that one
repeated pointer.
#### 0x100001100x10000127 Default IRQ Handler Code
#### 0x10000110-0x10000127 - Default IRQ Handler Code
```
10000110 eff30580 103800be 00be00be 00be00be
@@ -1498,14 +1456,14 @@ repeated pointer.
| Address | Bytes | ARM Thumb-2 Instruction | What it does |
|---------|-------|-------------------------|--------------|
| `0x10000110` | `ef f3 05 80` | `MRS r0, IPSR` | Read the Interrupt Program Status Register into r0. The low 9 bits = the active vector number. |
| `0x10000114` | `10 38` | `SUBS r0, #16` | Vector 16 = IRQ0, so subtract 16 to convert vector number IRQ index. |
| `0x10000114` | `10 38` | `SUBS r0, #16` | Vector 16 = IRQ0, so subtract 16 to convert vector number -> IRQ index. |
| `0x10000116` | `00 be` | `BKPT #0` | Software breakpoint. If a debugger is attached, it stops here and you can inspect r0 to see which IRQ fired. If no debugger is attached, the CPU enters a fault loop and the chip hangs. |
| `0x10000118``0x10000127` | `00 be` ×12 | `BKPT #0` repeating | Alignment padding to the next 4-byte boundary. |
| `0x10000118`-`0x10000127` | `00 be` *12 | `BKPT #0` repeating | Alignment padding to the next 4-byte boundary. |
This is the **entire** default IRQ handler. It is intentionally minimal: if your
code triggers an IRQ you did not register, it crashes visibly instead of silently.
#### 0x100001280x1000013b Binary Info Pointer Table
#### 0x10000128-0x1000013b - Binary Info Pointer Table
```
10000120 201b0010 4c1b0010
@@ -1517,12 +1475,12 @@ code triggers an IRQ you did not register, it crashes visibly instead of silentl
| `0x10000128` | `20 1b 00 10` | `0x10001b20` | Pointer to **start** of `.binary_info` data section in flash. |
| `0x1000012c` | `4c 1b 00 10` | `0x10001b4c` | Pointer to **end** of `.binary_info` data section in flash. |
| `0x10000130` | `a0 01 00 10` | `0x100001a0` | Pointer to `binary_info_callback` function. |
| `0x10000134` | `90 a3 1a e7` | (magic marker) | `BINARY_INFO_MARKER_END` marks the end of this pointer table. |
| `0x10000134` | `90 a3 1a e7` | (magic marker) | `BINARY_INFO_MARKER_END` - marks the end of this pointer table. |
`picotool` reads this table to extract the program name, version string, URL,
and GPIO pin map from any compiled binary without running it.
#### 0x100001380x1000014c IMAGE_DEF Block (this build)
#### 0x10000138-0x1000014c - IMAGE_DEF Block (this build)
```
10000130 a0010010 90a31ae7 d3deffff 42012110
@@ -1531,49 +1489,49 @@ and GPIO pin map from any compiled binary without running it.
| Address | Bytes | What it is |
|---------|-------|------------|
| `0x10000138` | `d3 de ff ff` | `PICOBIN_BLOCK_MARKER_START` the bootrom scans flash for this exact 4-byte sequence to locate the IMAGE_DEF. |
| `0x10000138` | `d3 de ff ff` | `PICOBIN_BLOCK_MARKER_START` - the bootrom scans flash for this exact 4-byte sequence to locate the IMAGE_DEF. |
| `0x1000013c` | `42 01 21 10` | IMAGE_DEF content (image type, flags, version). |
| `0x10000140` | `ff 01 00 00` | IMAGE_DEF content (continuation). |
| `0x10000144` | `b0 1b 00 00` | IMAGE_DEF content (continuation). |
| `0x10000148` | `79 35 12 ab` | `PICOBIN_BLOCK_MARKER_END` bootrom stops scanning here. |
| `0x10000148` | `79 35 12 ab` | `PICOBIN_BLOCK_MARKER_END` - bootrom stops scanning here. |
The IMAGE_DEF sits at `0x10000138``0x1000014b` in this build,
The IMAGE_DEF sits at `0x10000138`-`0x1000014b` in this build,
well within the 4 KB scan window the bootrom uses (Datasheet §5.9.5, p. 429).
#### Full Flash Map: 0x100000000x1000015c
#### Full Flash Map: 0x10000000-0x1000015c
```
0x100000000x1000000f Vector Table: mandatory entries (SP, Reset, NMI, HardFault)
0x100000100x1000010f Vector Table: 52 external IRQ slots all point to default handler
0x100001100x10000127 Default IRQ handler code (MRS / SUBS / BKPT)
0x100001280x10000137 Binary info pointer table (start / end / callback / magic end)
0x100001380x1000014b IMAGE_DEF block (d3 de ff ff ... 79 35 12 ab)
0x100001500x1000015b (padding / alignment)
0x10000000-0x1000000f Vector Table: mandatory entries (SP, Reset, NMI, HardFault)
0x10000010-0x1000010f Vector Table: 52 external IRQ slots -> all point to default handler
0x10000110-0x10000127 Default IRQ handler code (MRS / SUBS / BKPT)
0x10000128-0x10000137 Binary info pointer table (start / end / callback / magic end)
0x10000138-0x1000014b IMAGE_DEF block (d3 de ff ff ... 79 35 12 ab)
0x10000150-0x1000015b (padding / alignment)
0x1000015c Reset_Handler (_reset_handler in crt0.S) ← bootrom jumps here
```
### Step 4: Confirmed Boot Sequence (proven from ELF)
```
┌─────────────────────────────────────────────────────────────────┐
PROVEN BOOT SEQUENCE (0x0001_hello-world)
├─────────────────────────────────────────────────────────────────┤
1. Bootrom reads 0x10000000
SP = 0x20082000 (offset +0x00 of vector table)
RST = 0x1000015d (offset +0x04, Thumb 0x1000015c)
├─────────────────────────────────────────────────────────────────┤
2. Bootrom scans first 4 kB for IMAGE_DEF
Found at 0x10000138 (this build)
Start marker: d3 de ff ff
End marker: 79 35 12 ab
├─────────────────────────────────────────────────────────────────┤
3. Bootrom jumps to reset handler at 0x1000015c
_reset_handler (crt0.S) runs
Checks CPUID Core 1 sent back to bootrom
Core 0: .data copied, .bss zeroed, platform_entry called
├─────────────────────────────────────────────────────────────────┤
4. platform_entry calls runtime_init main exit
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| PROVEN BOOT SEQUENCE (0x0001_hello-world) |
+-----------------------------------------------------------------+
| 1. Bootrom reads 0x10000000 |
| -> SP = 0x20082000 (offset +0x00 of vector table) |
| -> RST = 0x1000015d (offset +0x04, Thumb -> 0x1000015c) |
+-----------------------------------------------------------------+
| 2. Bootrom scans first 4 kB for IMAGE_DEF |
| -> Found at 0x10000138 (this build) |
| -> Start marker: d3 de ff ff |
| -> End marker: 79 35 12 ab |
+-----------------------------------------------------------------+
| 3. Bootrom jumps to reset handler at 0x1000015c |
| -> _reset_handler (crt0.S) runs |
| -> Checks CPUID - Core 1 sent back to bootrom |
| -> Core 0: .data copied, .bss zeroed, platform_entry called |
+-----------------------------------------------------------------+
| 4. platform_entry calls runtime_init -> main -> exit |
+-----------------------------------------------------------------+
```
> 📖 **Datasheet References:**
@@ -1586,3 +1544,5 @@ well within the 4 KB scan window the bootrom uses (Datasheet §5.9.5, p. 429).
**Remember:** Understanding the boot process is fundamental to embedded systems work. Whether you're debugging a system that won't start, reverse engineering firmware, or building secure boot chains, this knowledge is essential!
Happy exploring! 🔍
-63
View File
@@ -1,63 +0,0 @@
# 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.
-241
View File
@@ -1,241 +0,0 @@
# 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: 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.
#### Prerequisites
- Ghidra installed and configured
- `0x0005_intro-to-variables.bin` binary available in your build directory
- Understanding of memory sections (`.data`, `.bss`, `.rodata`) from Week 4 Part 2
- Basic Ghidra navigation skills from Week 3
#### Task Description
You will import the binary into Ghidra, configure it for ARM Cortex-M33, analyze the code structure, resolve function names, and locate where the `age` variable is used in the compiled binary.
#### Step-by-Step Instructions
##### Step 1: Start Ghidra and Create New Project
```powershell
ghidraRun
```
1. Click **File** ? **New Project**
2. Select **Non-Shared Project**
3. Click **Next**
4. Enter Project Name: `week04-ex01-intro-to-variables`
5. Choose a project directory
6. Click **Finish**
##### Step 2: Import the Binary
1. Navigate to your file explorer
2. Find `Embedded-Hacking\0x0005_intro-to-variables\build\0x0005_intro-to-variables.bin`
3. **Drag and drop** the `.bin` file into Ghidra's project window
##### Step 3: Configure Import Settings
When the import dialog appears:
1. Click the three dots (**…**) next to **Language**
2. Search for: `Cortex`
3. Select: **ARM Cortex 32 little endian default**
4. Click **OK**
Now click **Options…** button:
1. Change **Block Name** to: `.text`
2. Change **Base Address** to: `10000000` (XIP flash base)
3. Click **OK**
Then click **OK** on the main import dialog.
##### Step 4: Analyze the Binary
1. Double-click the imported file in the project window
2. When prompted "Analyze now?" click **Yes**
3. Leave all default analysis options selected
4. Click **Analyze**
5. Wait for analysis to complete (watch bottom-right progress bar)
##### Step 5: Navigate to the Symbol Tree
Look at the left panel for the **Symbol Tree**. Expand **Functions** to see the auto-detected functions:
You should see function names like:
- `FUN_1000019a`
- `FUN_10000210`
- `FUN_10000234`
- Many more...
These are auto-generated names because we're analyzing a raw binary without debug symbols.
##### Step 6: Identify the Main Function
From Week 3, we know the typical boot sequence:
1. Reset handler copies data
2. `frame_dummy` runs
3. `main()` is called
Click on `FUN_10000234` - this should be our `main()` function.
**Look at the Decompile window:**
```c
void FUN_10000234(void)
{
FUN_100030cc();
do {
FUN_10003100("age: %d\r\n", 0x2b);
} while (true);
}
```
**Observations:**
- `FUN_100030cc()` is likely `stdio_init_all()`
- `FUN_10003100()` is likely `printf()`
- The magic value `0x2b` appears (what is this?)
##### Step 7: Convert 0x2b to Decimal
Let's figure out what `0x2b` means:
**Manual calculation:**
- `0x2b` in hexadecimal
- `2 × 16 + 11 = 32 + 11 = 43` in decimal
**In GDB (alternative method):**
```gdb
(gdb) p/d 0x2b
$1 = 43
```
So `0x2b = 43`! This matches our `age = 43` from the source code!
##### Step 8: Rename Functions for Clarity
Let's rename the functions to their actual names:
**Rename FUN_10000234 to main:**
1. Right-click on `FUN_10000234` in the Symbol Tree
2. Select **Rename Function**
3. Enter: `main`
4. Press **Enter**
**Update main's signature:**
1. In the Decompile window, right-click on `main`
2. Select **Edit Function Signature**
3. Change to: `int main(void)`
4. Click **OK**
**Rename FUN_100030cc to stdio_init_all:**
1. Click on `FUN_100030cc` in the decompile window
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**
3. Change name to: `printf`
4. Check the **Varargs** checkbox (printf accepts variable arguments)
5. Click **OK**
##### Step 9: Examine the Optimized Code
After renaming, the decompiled main should now look like:
```c
int main(void)
{
stdio_init_all();
do {
printf("age: %d\r\n", 0x2b);
} while (true);
}
```
**Critical observation:** Where did our `age` variable go?
Original source code:
```c
uint8_t age = 42;
age = 43;
```
The compiler **optimized it completely away**!
**Why?**
1. `age = 42` is immediately overwritten
2. The value `42` is never used
3. The compiler replaces `age` with the constant `43` (`0x2b`)
4. No variable allocation in memory is needed!
##### Step 10: Examine the Assembly Listing
Click on the **Listing** window (shows assembly code):
Find the instruction that loads `0x2b`:
```assembly
10000xxx movs r1, #0x2b
10000xxx ...
10000xxx bl printf
```
**What this does:**
- `movs r1, #0x2b` - Moves the immediate value 0x2b (43) into register r1
- `bl printf` - Branches to printf, which expects format args in r1+
##### Step 11: Document Your Findings
Create a table of your observations:
| 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 |
| Format string | "age: %d\r\n" | Located in .rodata section |
#### Expected Output
After completing this exercise, you should be able to:
- Successfully import and configure ARM binaries in Ghidra
- Navigate the Symbol Tree and identify functions
- Understand how compiler optimization removes unnecessary variables
- Convert hexadecimal values to decimal
- Rename functions for better code readability
#### Questions for Reflection
###### Question 1: Why did the compiler optimize away the `age` variable?
###### Question 2: In what memory section would `age` have been stored if it wasn't optimized away?
###### Question 3: Where is the string "age: %d\r\n" stored, and why can't it be in RAM?
###### Question 4: What would happen if we had used `age` in a calculation before reassigning it to 43?
#### Tips and Hints
- Use **CTRL+F** in Ghidra to search for specific values or strings
- 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
#### Next Steps
- Proceed to Exercise 2 to learn binary patching
- Try analyzing `0x0008_uninitialized-variables.bin` to see how uninitialized variables behave
- Explore the `.rodata` section to find string literals
#### Additional Challenge
Find the format string "age: %d\r\n" in Ghidra. What address is it stored at? How does the program reference this string in the assembly code? (Hint: Look for an `ldr` instruction that loads the string address into a register.)
-53
View File
@@ -1,53 +0,0 @@
# 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.
-220
View File
@@ -1,220 +0,0 @@
# 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: 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.
#### Prerequisites
- Completed Exercise 1 (binary imported and analyzed in Ghidra)
- Python installed for UF2 conversion
- `uf2conv.py` script available in your project directory
- Raspberry Pi Pico 2 connected via USB
- Serial monitor software (PuTTY, minicom, or screen)
#### Task Description
You will locate the instruction that loads the value 43 into a register, patch it to use a different value (70 in this example), export the modified binary, convert it to UF2 format, and flash it to verify the change.
#### Step-by-Step Instructions
##### Step 1: Open Your Ghidra Project
If you closed Ghidra from Exercise 1:
1. Launch `ghidraRun`
2. Open the project: `week04-ex01-intro-to-variables`
3. Double-click the binary file to open it
##### Step 2: Navigate to the Value Load Instruction
In Exercise 1, we found that `main()` calls `printf("age: %d\r\n", 0x2b)`.
**Find the assembly instruction:**
1. Click on the `main` function in the Symbol Tree
2. Look at the **Listing** window (assembly view)
3. Find the line with `movs r1, #0x2b`
The instruction should look like:
```assembly
10000xxx 21 2b movs r1, #0x2b
```
**What this instruction means:**
- Opcode: `21 2b` (encoded instruction bytes)
- Mnemonic: `movs r1, #0x2b`
- Operation: Move the immediate value 0x2b into register r1
- Register r1 will be used as the argument to printf
##### Step 3: Choose Your New Value
Let's change the value from `43` (0x2b) to `70` (0x46).
**Convert 70 to hexadecimal:**
- 70 ÷ 16 = 4 remainder 6
- Therefore: 70 decimal = 0x46 hexadecimal
You can verify this in Python:
```python
>>> hex(70)
'0x46'
>>> 0x46
70
```
##### Step 4: Patch the Instruction
Now we'll modify the binary:
1. **Right-click** on the instruction `movs r1, #0x2b`
2. Select **Patch Instruction**
3. A dialog appears showing the current instruction
4. Change `#0x2b` to `#0x46`
5. Press **Enter** or click **OK**
The instruction now reads:
```assembly
10000xxx 21 46 movs r1, #0x46
```
**Visual confirmation:**
- The patched instruction should be highlighted (usually in red or orange)
- The bytes should change from `21 2b` to `21 46`
- The decompiled view should update to show `printf("age: %d\r\n", 0x46);`
##### Step 5: Verify the Patch in Decompile Window
Click on the `main` function again and check the **Decompile** window:
```c
int main(void)
{
stdio_init_all();
do {
printf("age: %d\r\n", 0x46); // Changed from 0x2b!
} while (true);
}
```
Perfect! The decompiler recognized our patch.
##### Step 6: Export the Patched Binary
Now we need to save the modified binary:
1. Click **File** → **Export Program**
2. Set **Format** to: **Binary**
3. Navigate to your build directory:
- `Embedded-Hacking\0x0005_intro-to-variables\build\`
4. Set **Filename** to: `0x0005_intro-to-variables-h.bin`
- The `-h` suffix means "hacked"
5. Click **OK**
**Important:** Make sure you're exporting to the correct location!
##### Step 7: Convert to UF2 Format
The Pico 2 requires UF2 format. Open a terminal and run:
**Navigate to the project directory:**
```powershell
cd C:\path\to\Embedded-Hacking\0x0005_intro-to-variables
```
**Run the conversion:**
```powershell
python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
**Command breakdown:**
- `uf2conv.py` - The conversion script
- `--base 0x10000000` - XIP flash base address (where code runs from)
- `--family 0xe48bff59` - RP2350 family ID
- `--output build\hacked.uf2` - Output filename
**Expected output:**
```
Converting to uf2, output size: 57856, start address: 0x10000000
Wrote 57856 bytes to build\hacked.uf2
```
##### Step 8: Flash the Hacked Binary
**Enter bootloader mode:**
1. Disconnect your Pico 2 from USB
2. Hold down the **BOOTSEL** button
3. While holding BOOTSEL, plug in the USB cable
4. Release BOOTSEL
5. A drive called **RPI-RP2** appears
**Flash the binary:**
1. Open the RPI-RP2 drive
2. Drag and drop `build\hacked.uf2` onto the drive
3. The Pico will automatically reboot
##### Step 9: Verify the Changes
**Open your serial monitor:**
For PuTTY:
1. Select **Serial** connection type
2. Set the COM port (check Device Manager)
3. Set speed to **115200**
4. Click **Open**
For PowerShell:
```powershell
# Find the COM port
Get-PnpDevice -Class Ports | Where-Object {$_.FriendlyName -like "*USB Serial*"}
# Connect (replace COM3 with your port)
$port = new-Object System.IO.Ports.SerialPort COM3,115200,None,8,one
$port.Open()
while($true) { $port.ReadLine() }
```
**Expected output:**
```
age: 70
age: 70
age: 70
age: 70
...
```
🎉 **Success!** You've successfully patched a binary and changed its behavior!
#### Expected Output
After completing this exercise, you should:
- See `age: 70` printing instead of `age: 43`
- Have a patched binary file (`0x0005_intro-to-variables-h.bin`)
- Have a UF2 file (`hacked.uf2`)
- Understand the complete patching workflow
#### Questions for Reflection
###### Question 1: Why do we need to convert to UF2 format instead of flashing the raw .bin file?
###### Question 2: What is the significance of the base address 0x10000000 in the conversion command?
###### Question 3: What would happen if you patched the wrong instruction by mistake?
###### Question 4: How can you verify a patch was applied correctly before exporting?
#### Tips and Hints
- Always make a backup of the original binary before patching
- Use descriptive names like `-h` (hacked) or `-patch` for modified binaries
- Test your patches on hardware to ensure they work as expected
- If something goes wrong, you can always flash the original UF2 file
- Use `File → Export Program → Original File` to revert all patches
#### Next Steps
- Try patching to different values (100, 255, etc.)
- Proceed to Exercise 3 to learn about GPIO control
- Experiment with patching multiple values in the same binary
#### Additional Challenge
Instead of changing the value to 70, change it to 255 (0xFF) - the maximum value for a `uint8_t`. What do you observe? Now try changing it to 256 (0x100). What happens and why? (Hint: Think about the size limits of the instruction encoding.)
-97
View File
@@ -1,97 +0,0 @@
# 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.
-270
View File
@@ -1,270 +0,0 @@
# 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: 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.
#### Prerequisites
- Completed Exercises 1 and 2
- Understanding of GPIO basics from Week 4 Part 3
- Raspberry Pi Pico 2 with an LED connected to GPIO 16
- Basic knowledge of ARM Thumb-2 instruction set
#### Task Description
You will import a new binary that controls GPIO pins, identify the GPIO-related function calls, trace the initialization sequence, and understand how the Pico SDK controls hardware at the low level.
#### Step-by-Step Instructions
##### Step 1: Flash the Original Binary
Before analysis, let's see what the program does:
1. Hold **BOOTSEL** and plug in your Pico 2
2. Flash `0x0008_uninitialized-variables.uf2` to the RPI-RP2 drive
3. Open your serial monitor
**Expected output:**
```
age: 0
age: 0
age: 0
...
```
**Expected behavior:**
- The red LED on GPIO 16 blinks on/off every 500ms
- The value `0` is printed (uninitialized variable)
##### Step 2: Create a New Ghidra Project
1. Launch Ghidra: `ghidraRun`
2. Click **File** → **New Project**
3. Select **Non-Shared Project**
4. Project Name: `week04-ex03-gpio-analysis`
5. Click **Finish**
##### Step 3: Import the GPIO Binary
1. Drag and drop `0x0008_uninitialized-variables.bin` into Ghidra
2. Set Language: **ARM Cortex 32 little endian default**
3. Click **Options…**
- Block Name: `.text`
- Base Address: `10000000`
4. Click **OK** on all dialogs
5. Double-click the file and click **Yes** to analyze
##### Step 4: Identify the Main Function
Look for the main function (likely `FUN_10000234` or similar):
**In the Symbol Tree:**
1. Expand **Functions**
2. Look for a function that appears to be an entry point
3. Click on potential `main` candidates
**Look for these patterns in the decompile:**
- Call to `stdio_init_all()`
- Call to `gpio_init()`
- Infinite while loop with `gpio_put()` and `sleep_ms()`
##### Step 5: Rename the Main Function
Once you identify `main`:
1. Right-click on the function name
2. Select **Edit Function Signature**
3. Change to: `int main(void)`
4. Click **OK**
**Expected decompiled code structure:**
```c
int main(void)
{
// Some initial value
stdio_init_all();
gpio_init(0x10); // GPIO 16
// ... more GPIO setup
while (true) {
printf(...);
gpio_put(0x10, 1);
sleep_ms(0x1f4);
gpio_put(0x10, 0);
sleep_ms(0x1f4);
}
}
```
##### Step 6: Identify GPIO Function Calls
Look in the decompiled main for function calls. You should see several undefined functions.
**Find and rename these GPIO functions:**
| Auto-Generated Name | Actual Function | How to Identify |
| ------------------- | ----------------- | ------------------------------------------ |
| `FUN_xxxxx` | `gpio_init` | Takes one parameter (pin number) |
| `FUN_xxxxx` | `gpio_set_dir` | Takes two parameters (pin, direction) |
| `FUN_xxxxx` | `gpio_put` | Takes two parameters (pin, value) |
| `FUN_xxxxx` | `sleep_ms` | Takes one parameter (milliseconds) |
| `FUN_xxxxx` | `stdio_init_all` | Takes no parameters, called first |
| `FUN_xxxxx` | `printf` | Takes variable args, has format string |
**Example renaming gpio_init:**
1. Click on the function call in the decompile window
2. Right-click → **Edit Function Signature**
3. Change name to: `gpio_init`
4. Set signature to: `void gpio_init(uint gpio)`
5. Click **OK**
##### Step 7: Analyze GPIO Initialization Sequence
After renaming, your decompiled main should look clearer:
```c
int main(void)
{
stdio_init_all();
gpio_init(0x10); // Initialize GPIO 16
gpio_set_dir(0x10, 1); // Set as output (1 = GPIO_OUT)
while (true) {
printf("age: %d\r\n", 0);
gpio_put(0x10, 1); // LED ON
sleep_ms(0x1f4); // Wait 500ms (0x1f4 = 500)
gpio_put(0x10, 0); // LED OFF
sleep_ms(0x1f4); // Wait 500ms
}
}
```
**Key observations:**
- `0x10` is hexadecimal for 16 (GPIO 16 - red LED)
- `0x1f4` is hexadecimal for 500 (milliseconds)
- `1` means GPIO_OUT (output direction)
- The LED is controlled by toggling between 1 (on) and 0 (off)
##### Step 8: Examine gpio_init Assembly
Double-click on `gpio_init` to jump to its implementation.
**Look for these key operations in the assembly:**
```assembly
; Load GPIO pin number into register
movs r4, r0 ; Save pin number
; Calculate pad register address
; Base address: 0x40038000 (PADS_BANK0)
; Offset: pin * 4
ldr r3, =0x40038000
lsls r5, r4, #2 ; pin * 4
add r3, r5 ; Calculate address
; Configure pad (clear OD bit, set IE bit)
ldr r2, [r3] ; Read current config
bic r2, #0x80 ; Clear output disable
orr r2, #0x40 ; Set input enable
str r2, [r3] ; Write back
; Set GPIO function to SIO (0x05)
ldr r3, =0x40028000 ; IO_BANK0 base
add r3, r5 ; Add offset
movs r2, #5 ; FUNCSEL = SIO
str r2, [r3] ; Set function
```
**What this does:**
1. Configures the GPIO pad registers (physical pin properties)
2. Sets the GPIO function to SIO (Software I/O)
3. Prepares the pin for software control
##### Step 9: Examine gpio_put Assembly
Find the `gpio_put` function and examine its implementation.
**Look for the GPIO coprocessor instruction:**
```assembly
gpio_put:
movs r4, r0 ; GPIO pin number
movs r5, r1 ; Value (0 or 1)
; Use ARM coprocessor to control GPIO
mcrr p0, #4, r4, r5, c0
bx lr ; Return
```
**Critical instruction: `mcrr p0, #4, r4, r5, c0`**
- `mcrr` = Move to Coprocessor from two ARM Registers
- `p0` = Coprocessor 0 (GPIO coprocessor in RP2350)
- `#4` = Operation code
- `r4, r5` = Source registers (pin number, value)
- `c0` = Coprocessor register (GPIO output control)
This is a **single-cycle GPIO operation** - extremely fast!
##### Step 10: Document the GPIO Memory Map
Create a reference table of the addresses you found:
| Address | Register | Purpose |
| ------------ | ------------- | ------------------------------- |
| `0x40028000` | IO_BANK0 | GPIO function selection |
| `0x40038000` | PADS_BANK0 | GPIO pad configuration |
| `0xd0000000` | SIO | Single-cycle I/O (coprocessor) |
**GPIO 16 specific addresses:**
- Pad control: `0x40038000 + (16 * 4) = 0x40038040`
- Function select: `0x40028000 + (16 * 4) = 0x40028040`
##### Step 11: Trace the Blink Timing
Calculate the actual timing:
**sleep_ms(0x1f4):**
- Convert: 0x1f4 = (1 × 256) + (15 × 16) + 4 = 256 + 240 + 4 = 500 decimal
- So the LED is on for 500ms, off for 500ms
- Total cycle time: 1000ms = 1 second
- Blink rate: 1 Hz
#### Expected Output
After completing this exercise, you should understand:
- How GPIO initialization configures hardware registers
- The role of the GPIO coprocessor in the RP2350
- How `gpio_put()` uses a single ARM instruction for fast I/O
- The memory-mapped addresses for GPIO control
- How timing delays are implemented with `sleep_ms()`
#### Questions for Reflection
###### Question 1: Why does gpio_init() need to configure both PADS_BANK0 and IO_BANK0 registers?
###### Question 2: What is the advantage of using the GPIO coprocessor instruction (`mcrr`) instead of writing to memory-mapped registers?
###### Question 3: If you wanted to blink the LED at 10 Hz instead of 1 Hz, what value should `sleep_ms()` use?
###### Question 4: What would happen if you called `gpio_put()` on a pin that hasn't been initialized with `gpio_init()` first?
#### Tips and Hints
- Use Ghidra's **References** feature (right-click → Find References) to see where functions are called
- The **Display****Memory Map** shows all memory regions
- Look for bit manipulation instructions (`bic`, `orr`) to understand register configuration
- The ARM Architecture Reference Manual has complete documentation on coprocessor instructions
- Use hex-to-decimal converters online if you're unsure about conversions
#### Next Steps
- Proceed to Exercise 4 to patch the GPIO binary
- Try to identify other SDK functions like `gpio_get()` if they appear
- Explore the full GPIO initialization in the SDK source code
#### Additional Challenge
Find the `gpio_set_dir()` function in Ghidra. Does it also use a GPIO coprocessor instruction? What coprocessor register does it use (c0, c4, or something else)? Compare its implementation to `gpio_put()` and document the differences.
-102
View File
@@ -1,102 +0,0 @@
# 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 0255 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 065535), (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.
-345
View File
@@ -1,345 +0,0 @@
# 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: 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.
#### Prerequisites
- Completed Exercise 3 (GPIO binary analyzed in Ghidra)
- Understanding of how GPIO pin numbers are encoded
- Knowledge of hexadecimal-to-decimal conversion
- Pico 2 with ability to test multiple GPIO pins
#### Task Description
You will locate all instances where GPIO 16 is used, patch them to GPIO 17 (changing from red LED to green LED), modify the printed value from 0 to 66, and adjust the blink timing from 500ms to 100ms for faster blinking.
#### Step-by-Step Instructions
##### Step 1: Plan Your Patches
Before we start patching, let's identify what needs to change:
| Current Value | New Value | Description | Hex Conversion |
| ------------- | --------- | --------------------- | ------------------- |
| GPIO 16 | GPIO 17 | Change LED pin | 0x10 → 0x11 |
| age = 0 | age = 66 | Change printed value | 0x00 → 0x42 |
| 500ms | 100ms | Change blink timing | 0x1f4 → 0x64 |
**Verify the hex conversions:**
- 17 decimal = 0x11 hex ✓
- 66 decimal = 0x42 hex ✓
- 100 decimal = 0x64 hex ✓
##### Step 2: Open the GPIO Project in Ghidra
1. Launch Ghidra and open `week04-ex03-gpio-analysis`
2. Double-click the binary to open the CodeBrowser
3. Navigate to the `main` function
**Review the decompiled code:**
```c
int main(void)
{
stdio_init_all();
gpio_init(0x10);
gpio_set_dir(0x10, 1);
while (true) {
printf("age: %d\r\n", 0);
gpio_put(0x10, 1);
sleep_ms(0x1f4);
gpio_put(0x10, 0);
sleep_ms(0x1f4);
}
}
```
##### Step 3: Find and Patch gpio_init Parameter
Look at the **Listing** window (assembly view) for the main function.
**Find the gpio_init call:**
```assembly
1000023a 10 20 movs r0, #0x10
1000023c xx xx bl gpio_init
```
**Patch instruction:**
1. Right-click on `movs r0, #0x10`
2. Select **Patch Instruction**
3. Change `#0x10` to `#0x11`
4. Press **Enter**
**Result:**
```assembly
1000023a 11 20 movs r0, #0x11
```
The instruction bytes change from `10 20` to `11 20`.
##### Step 4: Find and Patch gpio_set_dir Parameter
Continue down the assembly listing:
```assembly
10000240 10 23 movs r3, #0x10
10000242 01 22 movs r2, #1
10000244 xx xx bl gpio_set_dir
```
**Patch the r3 load:**
1. Right-click on `movs r3, #0x10`
2. Select **Patch Instruction**
3. Change to `#0x11`
4. Press **Enter**
**Why r3 instead of r0?** The SDK might pass GPIO pin as the first parameter differently, or this could be due to register allocation. Trust the analysis!
##### Step 5: Find All gpio_put Calls
Inside the while loop, there are two `gpio_put` calls:
**First gpio_put (LED ON):**
```assembly
10000252 10 24 movs r4, #0x10
10000254 01 25 movs r5, #1
10000256 xx xx bl gpio_put
```
**Patch:**
1. Right-click on `movs r4, #0x10`
2. Change to `#0x11`
**Second gpio_put (LED OFF):**
```assembly
1000025e 10 24 movs r4, #0x10
10000260 00 25 movs r5, #0
10000262 xx xx bl gpio_put
```
**Patch:**
1. Right-click on `movs r4, #0x10`
2. Change to `#0x11`
**Note:** The exact addresses will vary, but the pattern is consistent.
##### Step 6: Patch the Printed Value
Find the printf call in the loop:
```assembly
1000024a 00 21 movs r1, #0x0
1000024c xx xx ldr r0, [pc, #offset]
1000024e xx xx bl printf
```
**Patch the value:**
1. Right-click on `movs r1, #0x0`
2. Select **Patch Instruction**
3. Change to `#0x42` (66 in decimal)
4. Press **Enter**
**Result:**
```assembly
1000024a 42 21 movs r1, #0x42
```
##### Step 7: Patch the Sleep Timing (First)
Find the first `sleep_ms(0x1f4)` call:
```assembly
10000258 f4 21 movs r1, #0xf4
1000025a 01 20 movs r0, #1
1000025c 00 04 lsls r0, r0, #16
1000025e 08 44 add r0, r1
10000260 xx xx bl sleep_ms
```
**Wait, this looks complex!** The value 0x1f4 (500) is being constructed:
- Load 1 into r0
- Shift left 16 bits: 1 << 16 = 0x10000
- Load 0xf4 into r1
- Add them: 0x10000 + 0xf4 = 0x1f4
**Alternative pattern (simpler):**
```assembly
10000xxx f4 20 movs r0, #0xf4
10000xxx 01 20 movs r1, #0x01
10000xxx ...
```
**For 100ms (0x64):**
Simply find where 0x1f4 is loaded and change it to 0x64.
**If the instruction is:**
```assembly
movs r0, #0x1f4
```
**Change to:**
```assembly
movs r0, #0x64
```
**Note:** The exact encoding depends on the instruction. For immediate values > 255, Thumb-2 uses different encodings.
##### Step 8: Handle Large Immediate Values
If `sleep_ms(500)` uses a multi-instruction sequence to load 0x1f4, you have two options:
**Option A: Patch both instructions**
If it's loading 0x1f4 as (0x100 + 0xf4):
1. Find where 0xf4 is loaded
2. Change it to 0x64
3. Find where 0x1 is loaded for the high byte
4. Change it to 0x0
**Option B: Simplify to single instruction**
Since 0x64 (100) fits in an 8-bit immediate, you can replace the multi-instruction sequence with:
```assembly
movs r0, #0x64
nop
nop
```
##### Step 9: Verify All Patches
Check the **Decompile** window to confirm changes:
```c
int main(void)
{
stdio_init_all();
gpio_init(0x11); // Changed from 0x10!
gpio_set_dir(0x11, 1);
while (true) {
printf("age: %d\r\n", 0x42); // Changed from 0!
gpio_put(0x11, 1); // Changed from 0x10!
sleep_ms(0x64); // Changed from 0x1f4!
gpio_put(0x11, 0); // Changed from 0x10!
sleep_ms(0x64); // Changed from 0x1f4!
}
}
```
Perfect! All changes are reflected.
##### Step 10: Export the Patched Binary
1. Click **File** → **Export Program**
2. Set Format: **Binary**
3. Navigate to: `Embedded-Hacking\0x0008_uninitialized-variables\build\`
4. Filename: `0x0008_uninitialized-variables-h.bin`
5. Click **OK**
##### Step 11: Convert to UF2
Open PowerShell and navigate to the project:
```powershell
cd C:\path\to\Embedded-Hacking\0x0008_uninitialized-variables
```
**Run conversion:**
```powershell
python ..\uf2conv.py build\0x0008_uninitialized-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
**Expected output:**
```
Converting to uf2, output size: 61952, start address: 0x10000000
Wrote 61952 bytes to build\hacked.uf2
```
##### Step 12: Flash and Test
**Enter bootloader:**
1. Hold **BOOTSEL** button
2. Plug in USB
3. Release BOOTSEL
**Flash:**
1. Drag `build\hacked.uf2` to RPI-RP2 drive
2. Pico reboots automatically
**Test with serial monitor:**
```
age: 66
age: 66
age: 66
...
```
**Hardware verification:**
- ✅ GREEN LED (GPIO 17) should be blinking
- ✅ RED LED (GPIO 16) should be off
- ✅ Blink rate should be much faster (10 Hz instead of 1 Hz)
- ✅ Serial output shows 66 instead of 0
🎉 **Triple success!** You've patched three different aspects of the program!
#### Expected Output
After completing this exercise, you should:
- See `age: 66` printing continuously
- Observe the green LED (GPIO 17) blinking rapidly
- Understand how to find and patch all instances of a value
- Know how to handle different immediate value encoding schemes
#### Questions for Reflection
###### Question 1: Why did we need to patch GPIO 16 in multiple places (gpio_init, gpio_set_dir, gpio_put)?
###### Question 2: What would happen if you forgot to patch one of the gpio_put calls?
###### Question 3: How does the instruction encoding differ for immediate values less than 256 vs. greater than 255?
###### Question 4: If you wanted to make the LED blink at exactly 5 Hz, what sleep_ms value would you use?
#### Tips and Hints
- Use Ghidra's **Search****For Scalars** to find all instances of a hex value
- Right-click in Listing → **Highlight****Highlight Instruction** helps track your patches
- Make notes of addresses you've patched to avoid confusion
- Test incrementally - patch one thing at a time if you're unsure
- Keep the original UF2 to revert if needed
#### Next Steps
- Try patching to use GPIO 18 (blue LED) instead
- Change the printf format string to display in hexadecimal
- Experiment with different timing patterns (e.g., 200ms on, 800ms off)
#### Additional Challenge
**Advanced Multi-LED Pattern:**
Patch the binary to create an alternating pattern:
- GPIO 16 (red) blinks for 100ms
- GPIO 17 (green) blinks for 100ms
- GPIO 18 (blue) blinks for 100ms
- Repeat
This requires:
1. Adding new gpio_init and gpio_set_dir calls for GPIO 18
2. Restructuring the while loop to have three gpio_put sequences
3. Finding space in the binary or replacing existing code
**Hint:** You might need to NOP out some instructions and carefully insert new ones. This is advanced patching!
#### Verification Checklist
Before moving on, confirm:
- [ ] GPIO 17 LED blinks (not GPIO 16)
- [ ] Blink rate is approximately 10 Hz (100ms on/off)
- [ ] Serial output shows "age: 66"
- [ ] You can explain each patch you made
- [ ] You understand why each patch was necessary
- [ ] You successfully converted and flashed the UF2
**Congratulations!** You've completed all Week 4 exercises and mastered variable analysis, binary patching, and GPIO manipulation!
Binary file not shown.
+905 -955
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More