diff --git a/0x0008_unitialized-variables-a/.gitignore b/0x0008_uninitialized-variables-a/.gitignore similarity index 100% rename from 0x0008_unitialized-variables-a/.gitignore rename to 0x0008_uninitialized-variables-a/.gitignore diff --git a/0x0008_unitialized-variables-a/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-a/.vscode/c_cpp_properties.json similarity index 100% rename from 0x0008_unitialized-variables-a/.vscode/c_cpp_properties.json rename to 0x0008_uninitialized-variables-a/.vscode/c_cpp_properties.json diff --git a/0x0008_unitialized-variables-a/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-a/.vscode/cmake-kits.json similarity index 100% rename from 0x0008_unitialized-variables-a/.vscode/cmake-kits.json rename to 0x0008_uninitialized-variables-a/.vscode/cmake-kits.json diff --git a/0x0008_unitialized-variables-a/.vscode/extensions.json b/0x0008_uninitialized-variables-a/.vscode/extensions.json similarity index 100% rename from 0x0008_unitialized-variables-a/.vscode/extensions.json rename to 0x0008_uninitialized-variables-a/.vscode/extensions.json diff --git a/0x0008_unitialized-variables-a/.vscode/launch.json b/0x0008_uninitialized-variables-a/.vscode/launch.json similarity index 100% rename from 0x0008_unitialized-variables-a/.vscode/launch.json rename to 0x0008_uninitialized-variables-a/.vscode/launch.json diff --git a/0x0008_unitialized-variables-a/.vscode/settings.json b/0x0008_uninitialized-variables-a/.vscode/settings.json similarity index 100% rename from 0x0008_unitialized-variables-a/.vscode/settings.json rename to 0x0008_uninitialized-variables-a/.vscode/settings.json diff --git a/0x0008_unitialized-variables-a/.vscode/tasks.json b/0x0008_uninitialized-variables-a/.vscode/tasks.json similarity index 100% rename from 0x0008_unitialized-variables-a/.vscode/tasks.json rename to 0x0008_uninitialized-variables-a/.vscode/tasks.json diff --git a/0x0008_unitialized-variables-a/0x0008_unitialized-variables-a.c b/0x0008_uninitialized-variables-a/0x0008_uninitialized-variables-a.c similarity index 97% rename from 0x0008_unitialized-variables-a/0x0008_unitialized-variables-a.c rename to 0x0008_uninitialized-variables-a/0x0008_uninitialized-variables-a.c index ba02410..9d2ddb3 100644 --- a/0x0008_unitialized-variables-a/0x0008_unitialized-variables-a.c +++ b/0x0008_uninitialized-variables-a/0x0008_uninitialized-variables-a.c @@ -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 diff --git a/0x0008_unitialized-variables-a/CMakeLists.txt b/0x0008_uninitialized-variables-a/CMakeLists.txt similarity index 67% rename from 0x0008_unitialized-variables-a/CMakeLists.txt rename to 0x0008_uninitialized-variables-a/CMakeLists.txt index 9512bd9..347375d 100644 --- a/0x0008_unitialized-variables-a/CMakeLists.txt +++ b/0x0008_uninitialized-variables-a/CMakeLists.txt @@ -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) diff --git a/0x0008_unitialized-variables-a/pico_sdk_import.cmake b/0x0008_uninitialized-variables-a/pico_sdk_import.cmake similarity index 100% rename from 0x0008_unitialized-variables-a/pico_sdk_import.cmake rename to 0x0008_uninitialized-variables-a/pico_sdk_import.cmake diff --git a/0x0008_unitialized-variables-b/.gitignore b/0x0008_uninitialized-variables-b/.gitignore similarity index 100% rename from 0x0008_unitialized-variables-b/.gitignore rename to 0x0008_uninitialized-variables-b/.gitignore diff --git a/0x0008_unitialized-variables-b/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-b/.vscode/c_cpp_properties.json similarity index 100% rename from 0x0008_unitialized-variables-b/.vscode/c_cpp_properties.json rename to 0x0008_uninitialized-variables-b/.vscode/c_cpp_properties.json diff --git a/0x0008_unitialized-variables-b/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-b/.vscode/cmake-kits.json similarity index 100% rename from 0x0008_unitialized-variables-b/.vscode/cmake-kits.json rename to 0x0008_uninitialized-variables-b/.vscode/cmake-kits.json diff --git a/0x0008_unitialized-variables-b/.vscode/extensions.json b/0x0008_uninitialized-variables-b/.vscode/extensions.json similarity index 100% rename from 0x0008_unitialized-variables-b/.vscode/extensions.json rename to 0x0008_uninitialized-variables-b/.vscode/extensions.json diff --git a/0x0008_unitialized-variables-b/.vscode/launch.json b/0x0008_uninitialized-variables-b/.vscode/launch.json similarity index 100% rename from 0x0008_unitialized-variables-b/.vscode/launch.json rename to 0x0008_uninitialized-variables-b/.vscode/launch.json diff --git a/0x0008_unitialized-variables-b/.vscode/settings.json b/0x0008_uninitialized-variables-b/.vscode/settings.json similarity index 100% rename from 0x0008_unitialized-variables-b/.vscode/settings.json rename to 0x0008_uninitialized-variables-b/.vscode/settings.json diff --git a/0x0008_unitialized-variables-b/.vscode/tasks.json b/0x0008_uninitialized-variables-b/.vscode/tasks.json similarity index 100% rename from 0x0008_unitialized-variables-b/.vscode/tasks.json rename to 0x0008_uninitialized-variables-b/.vscode/tasks.json diff --git a/0x0008_unitialized-variables-b/0x0008_unitialized-variables-b.c b/0x0008_uninitialized-variables-b/0x0008_uninitialized-variables-b.c similarity index 98% rename from 0x0008_unitialized-variables-b/0x0008_unitialized-variables-b.c rename to 0x0008_uninitialized-variables-b/0x0008_uninitialized-variables-b.c index ba676e4..b42c544 100644 --- a/0x0008_unitialized-variables-b/0x0008_unitialized-variables-b.c +++ b/0x0008_uninitialized-variables-b/0x0008_uninitialized-variables-b.c @@ -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 diff --git a/0x0008_unitialized-variables-d/CMakeLists.txt b/0x0008_uninitialized-variables-b/CMakeLists.txt similarity index 67% rename from 0x0008_unitialized-variables-d/CMakeLists.txt rename to 0x0008_uninitialized-variables-b/CMakeLists.txt index fa38995..24d559f 100644 --- a/0x0008_unitialized-variables-d/CMakeLists.txt +++ b/0x0008_uninitialized-variables-b/CMakeLists.txt @@ -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) diff --git a/0x0008_unitialized-variables-b/pico_sdk_import.cmake b/0x0008_uninitialized-variables-b/pico_sdk_import.cmake similarity index 100% rename from 0x0008_unitialized-variables-b/pico_sdk_import.cmake rename to 0x0008_uninitialized-variables-b/pico_sdk_import.cmake diff --git a/0x0008_unitialized-variables-c/.gitignore b/0x0008_uninitialized-variables-c/.gitignore similarity index 100% rename from 0x0008_unitialized-variables-c/.gitignore rename to 0x0008_uninitialized-variables-c/.gitignore diff --git a/0x0008_unitialized-variables-c/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-c/.vscode/c_cpp_properties.json similarity index 100% rename from 0x0008_unitialized-variables-c/.vscode/c_cpp_properties.json rename to 0x0008_uninitialized-variables-c/.vscode/c_cpp_properties.json diff --git a/0x0008_unitialized-variables-c/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-c/.vscode/cmake-kits.json similarity index 100% rename from 0x0008_unitialized-variables-c/.vscode/cmake-kits.json rename to 0x0008_uninitialized-variables-c/.vscode/cmake-kits.json diff --git a/0x0008_unitialized-variables-c/.vscode/extensions.json b/0x0008_uninitialized-variables-c/.vscode/extensions.json similarity index 100% rename from 0x0008_unitialized-variables-c/.vscode/extensions.json rename to 0x0008_uninitialized-variables-c/.vscode/extensions.json diff --git a/0x0008_unitialized-variables-c/.vscode/launch.json b/0x0008_uninitialized-variables-c/.vscode/launch.json similarity index 100% rename from 0x0008_unitialized-variables-c/.vscode/launch.json rename to 0x0008_uninitialized-variables-c/.vscode/launch.json diff --git a/0x0008_unitialized-variables-c/.vscode/settings.json b/0x0008_uninitialized-variables-c/.vscode/settings.json similarity index 100% rename from 0x0008_unitialized-variables-c/.vscode/settings.json rename to 0x0008_uninitialized-variables-c/.vscode/settings.json diff --git a/0x0008_unitialized-variables-c/.vscode/tasks.json b/0x0008_uninitialized-variables-c/.vscode/tasks.json similarity index 100% rename from 0x0008_unitialized-variables-c/.vscode/tasks.json rename to 0x0008_uninitialized-variables-c/.vscode/tasks.json diff --git a/0x0008_unitialized-variables-c/0x0008_unitialized-variables-c.c b/0x0008_uninitialized-variables-c/0x0008_uninitialized-variables-c.c similarity index 98% rename from 0x0008_unitialized-variables-c/0x0008_unitialized-variables-c.c rename to 0x0008_uninitialized-variables-c/0x0008_uninitialized-variables-c.c index 4c71b5b..40771de 100644 --- a/0x0008_unitialized-variables-c/0x0008_unitialized-variables-c.c +++ b/0x0008_uninitialized-variables-c/0x0008_uninitialized-variables-c.c @@ -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 diff --git a/0x0008_unitialized-variables-b/CMakeLists.txt b/0x0008_uninitialized-variables-c/CMakeLists.txt similarity index 67% rename from 0x0008_unitialized-variables-b/CMakeLists.txt rename to 0x0008_uninitialized-variables-c/CMakeLists.txt index 51897ba..b7e1991 100644 --- a/0x0008_unitialized-variables-b/CMakeLists.txt +++ b/0x0008_uninitialized-variables-c/CMakeLists.txt @@ -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) diff --git a/0x0008_unitialized-variables-c/pico_sdk_import.cmake b/0x0008_uninitialized-variables-c/pico_sdk_import.cmake similarity index 100% rename from 0x0008_unitialized-variables-c/pico_sdk_import.cmake rename to 0x0008_uninitialized-variables-c/pico_sdk_import.cmake diff --git a/0x0008_unitialized-variables-d/.gitignore b/0x0008_uninitialized-variables-d/.gitignore similarity index 100% rename from 0x0008_unitialized-variables-d/.gitignore rename to 0x0008_uninitialized-variables-d/.gitignore diff --git a/0x0008_unitialized-variables-d/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-d/.vscode/c_cpp_properties.json similarity index 100% rename from 0x0008_unitialized-variables-d/.vscode/c_cpp_properties.json rename to 0x0008_uninitialized-variables-d/.vscode/c_cpp_properties.json diff --git a/0x0008_unitialized-variables-d/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-d/.vscode/cmake-kits.json similarity index 100% rename from 0x0008_unitialized-variables-d/.vscode/cmake-kits.json rename to 0x0008_uninitialized-variables-d/.vscode/cmake-kits.json diff --git a/0x0008_unitialized-variables-d/.vscode/extensions.json b/0x0008_uninitialized-variables-d/.vscode/extensions.json similarity index 100% rename from 0x0008_unitialized-variables-d/.vscode/extensions.json rename to 0x0008_uninitialized-variables-d/.vscode/extensions.json diff --git a/0x0008_unitialized-variables-d/.vscode/launch.json b/0x0008_uninitialized-variables-d/.vscode/launch.json similarity index 100% rename from 0x0008_unitialized-variables-d/.vscode/launch.json rename to 0x0008_uninitialized-variables-d/.vscode/launch.json diff --git a/0x0008_unitialized-variables-d/.vscode/settings.json b/0x0008_uninitialized-variables-d/.vscode/settings.json similarity index 100% rename from 0x0008_unitialized-variables-d/.vscode/settings.json rename to 0x0008_uninitialized-variables-d/.vscode/settings.json diff --git a/0x0008_unitialized-variables-d/.vscode/tasks.json b/0x0008_uninitialized-variables-d/.vscode/tasks.json similarity index 100% rename from 0x0008_unitialized-variables-d/.vscode/tasks.json rename to 0x0008_uninitialized-variables-d/.vscode/tasks.json diff --git a/0x0008_unitialized-variables-d/0x0008_unitialized-variables-d.c b/0x0008_uninitialized-variables-d/0x0008_uninitialized-variables-d.c similarity index 99% rename from 0x0008_unitialized-variables-d/0x0008_unitialized-variables-d.c rename to 0x0008_uninitialized-variables-d/0x0008_uninitialized-variables-d.c index 2b7efe6..b834115 100644 --- a/0x0008_unitialized-variables-d/0x0008_unitialized-variables-d.c +++ b/0x0008_uninitialized-variables-d/0x0008_uninitialized-variables-d.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 diff --git a/0x0008_uninitialized-variables-d/CMakeLists.txt b/0x0008_uninitialized-variables-d/CMakeLists.txt new file mode 100644 index 0000000..9e88754 --- /dev/null +++ b/0x0008_uninitialized-variables-d/CMakeLists.txt @@ -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) + diff --git a/0x0008_unitialized-variables-d/pico_sdk_import.cmake b/0x0008_uninitialized-variables-d/pico_sdk_import.cmake similarity index 100% rename from 0x0008_unitialized-variables-d/pico_sdk_import.cmake rename to 0x0008_uninitialized-variables-d/pico_sdk_import.cmake diff --git a/0x0008_unitialized-variables-e/.gitignore b/0x0008_uninitialized-variables-e/.gitignore similarity index 100% rename from 0x0008_unitialized-variables-e/.gitignore rename to 0x0008_uninitialized-variables-e/.gitignore diff --git a/0x0008_unitialized-variables-e/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-e/.vscode/c_cpp_properties.json similarity index 100% rename from 0x0008_unitialized-variables-e/.vscode/c_cpp_properties.json rename to 0x0008_uninitialized-variables-e/.vscode/c_cpp_properties.json diff --git a/0x0008_unitialized-variables-e/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-e/.vscode/cmake-kits.json similarity index 100% rename from 0x0008_unitialized-variables-e/.vscode/cmake-kits.json rename to 0x0008_uninitialized-variables-e/.vscode/cmake-kits.json diff --git a/0x0008_unitialized-variables-e/.vscode/extensions.json b/0x0008_uninitialized-variables-e/.vscode/extensions.json similarity index 100% rename from 0x0008_unitialized-variables-e/.vscode/extensions.json rename to 0x0008_uninitialized-variables-e/.vscode/extensions.json diff --git a/0x0008_unitialized-variables-e/.vscode/launch.json b/0x0008_uninitialized-variables-e/.vscode/launch.json similarity index 100% rename from 0x0008_unitialized-variables-e/.vscode/launch.json rename to 0x0008_uninitialized-variables-e/.vscode/launch.json diff --git a/0x0008_unitialized-variables-e/.vscode/settings.json b/0x0008_uninitialized-variables-e/.vscode/settings.json similarity index 100% rename from 0x0008_unitialized-variables-e/.vscode/settings.json rename to 0x0008_uninitialized-variables-e/.vscode/settings.json diff --git a/0x0008_unitialized-variables-e/.vscode/tasks.json b/0x0008_uninitialized-variables-e/.vscode/tasks.json similarity index 100% rename from 0x0008_unitialized-variables-e/.vscode/tasks.json rename to 0x0008_uninitialized-variables-e/.vscode/tasks.json diff --git a/0x0008_unitialized-variables-e/0x0008_unitialized-variables-e.c b/0x0008_uninitialized-variables-e/0x0008_uninitialized-variables-e.c similarity index 99% rename from 0x0008_unitialized-variables-e/0x0008_unitialized-variables-e.c rename to 0x0008_uninitialized-variables-e/0x0008_uninitialized-variables-e.c index 42799c7..ac572c8 100644 --- a/0x0008_unitialized-variables-e/0x0008_unitialized-variables-e.c +++ b/0x0008_uninitialized-variables-e/0x0008_uninitialized-variables-e.c @@ -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 diff --git a/0x0008_uninitialized-variables-e/CMakeLists.txt b/0x0008_uninitialized-variables-e/CMakeLists.txt new file mode 100644 index 0000000..bfc1c28 --- /dev/null +++ b/0x0008_uninitialized-variables-e/CMakeLists.txt @@ -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) + diff --git a/0x0008_unitialized-variables-e/pico_sdk_import.cmake b/0x0008_uninitialized-variables-e/pico_sdk_import.cmake similarity index 100% rename from 0x0008_unitialized-variables-e/pico_sdk_import.cmake rename to 0x0008_uninitialized-variables-e/pico_sdk_import.cmake diff --git a/0x0008_unitialized-variables/.gitignore b/0x0008_uninitialized-variables/.gitignore similarity index 100% rename from 0x0008_unitialized-variables/.gitignore rename to 0x0008_uninitialized-variables/.gitignore diff --git a/0x0008_unitialized-variables/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables/.vscode/c_cpp_properties.json similarity index 100% rename from 0x0008_unitialized-variables/.vscode/c_cpp_properties.json rename to 0x0008_uninitialized-variables/.vscode/c_cpp_properties.json diff --git a/0x0008_unitialized-variables/.vscode/cmake-kits.json b/0x0008_uninitialized-variables/.vscode/cmake-kits.json similarity index 100% rename from 0x0008_unitialized-variables/.vscode/cmake-kits.json rename to 0x0008_uninitialized-variables/.vscode/cmake-kits.json diff --git a/0x0008_unitialized-variables/.vscode/extensions.json b/0x0008_uninitialized-variables/.vscode/extensions.json similarity index 100% rename from 0x0008_unitialized-variables/.vscode/extensions.json rename to 0x0008_uninitialized-variables/.vscode/extensions.json diff --git a/0x0008_unitialized-variables/.vscode/launch.json b/0x0008_uninitialized-variables/.vscode/launch.json similarity index 100% rename from 0x0008_unitialized-variables/.vscode/launch.json rename to 0x0008_uninitialized-variables/.vscode/launch.json diff --git a/0x0008_unitialized-variables/.vscode/settings.json b/0x0008_uninitialized-variables/.vscode/settings.json similarity index 100% rename from 0x0008_unitialized-variables/.vscode/settings.json rename to 0x0008_uninitialized-variables/.vscode/settings.json diff --git a/0x0008_unitialized-variables/.vscode/tasks.json b/0x0008_uninitialized-variables/.vscode/tasks.json similarity index 100% rename from 0x0008_unitialized-variables/.vscode/tasks.json rename to 0x0008_uninitialized-variables/.vscode/tasks.json diff --git a/0x0008_unitialized-variables/0x0008_unitialized-variables.c b/0x0008_uninitialized-variables/0x0008_uninitialized-variables.c similarity index 98% rename from 0x0008_unitialized-variables/0x0008_unitialized-variables.c rename to 0x0008_uninitialized-variables/0x0008_uninitialized-variables.c index 26bf440..8e9dc42 100644 --- a/0x0008_unitialized-variables/0x0008_unitialized-variables.c +++ b/0x0008_uninitialized-variables/0x0008_uninitialized-variables.c @@ -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 diff --git a/0x0008_unitialized-variables-c/CMakeLists.txt b/0x0008_uninitialized-variables/CMakeLists.txt similarity index 68% rename from 0x0008_unitialized-variables-c/CMakeLists.txt rename to 0x0008_uninitialized-variables/CMakeLists.txt index f5c50e3..c2ac591 100644 --- a/0x0008_unitialized-variables-c/CMakeLists.txt +++ b/0x0008_uninitialized-variables/CMakeLists.txt @@ -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) diff --git a/0x0008_unitialized-variables/pico_sdk_import.cmake b/0x0008_uninitialized-variables/pico_sdk_import.cmake similarity index 100% rename from 0x0008_unitialized-variables/pico_sdk_import.cmake rename to 0x0008_uninitialized-variables/pico_sdk_import.cmake diff --git a/0x0008_unitialized-variables-e/CMakeLists.txt b/0x0008_unitialized-variables-e/CMakeLists.txt deleted file mode 100644 index 7fa7d3a..0000000 --- a/0x0008_unitialized-variables-e/CMakeLists.txt +++ /dev/null @@ -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) - diff --git a/0x0008_unitialized-variables/CMakeLists.txt b/0x0008_unitialized-variables/CMakeLists.txt deleted file mode 100644 index 518d1cf..0000000 --- a/0x0008_unitialized-variables/CMakeLists.txt +++ /dev/null @@ -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) - diff --git a/README.md b/README.md index a2bcffa..d9ede99 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/WEEK01/WEEK01-01-S.md b/WEEK01/WEEK01-01-S.md deleted file mode 100644 index bcdb19c..0000000 --- a/WEEK01/WEEK01-01-S.md +++ /dev/null @@ -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. diff --git a/WEEK01/WEEK01-01.md b/WEEK01/WEEK01-01.md deleted file mode 100644 index abf913d..0000000 --- a/WEEK01/WEEK01-01.md +++ /dev/null @@ -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 diff --git a/WEEK01/WEEK01-02-S.md b/WEEK01/WEEK01-02-S.md deleted file mode 100644 index 90b2593..0000000 --- a/WEEK01/WEEK01-02-S.md +++ /dev/null @@ -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. diff --git a/WEEK01/WEEK01-02.md b/WEEK01/WEEK01-02.md deleted file mode 100644 index 88828a7..0000000 --- a/WEEK01/WEEK01-02.md +++ /dev/null @@ -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`) diff --git a/WEEK01/WEEK01-03-S.md b/WEEK01/WEEK01-03-S.md deleted file mode 100644 index bffaac3..0000000 --- a/WEEK01/WEEK01-03-S.md +++ /dev/null @@ -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. diff --git a/WEEK01/WEEK01-03.md b/WEEK01/WEEK01-03.md deleted file mode 100644 index 4c7389f..0000000 --- a/WEEK01/WEEK01-03.md +++ /dev/null @@ -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 -``` diff --git a/WEEK01/WEEK01-04-S.md b/WEEK01/WEEK01-04-S.md deleted file mode 100644 index b9814a7..0000000 --- a/WEEK01/WEEK01-04-S.md +++ /dev/null @@ -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 # 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 # 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. diff --git a/WEEK01/WEEK01-04.md b/WEEK01/WEEK01-04.md deleted file mode 100644 index 894e924..0000000 --- a/WEEK01/WEEK01-04.md +++ /dev/null @@ -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 - 0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 ) - 0x1000023c <+8>: bl 0x100015fc <__wrap_puts> - 0x10000240 <+12>: b.n 0x1000023a - 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
-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 ` -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 diff --git a/WEEK01/WEEK01.md b/WEEK01/WEEK01.md index 839fdfc..c827cfc 100644 --- a/WEEK01/WEEK01.md +++ b/WEEK01/WEEK01.md @@ -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 : 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 | + + diff --git a/WEEK02/WEEK02-01-S.md b/WEEK02/WEEK02-01-S.md deleted file mode 100644 index 4145a50..0000000 --- a/WEEK02/WEEK02-01-S.md +++ /dev/null @@ -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. diff --git a/WEEK02/WEEK02-01.md b/WEEK02/WEEK02-01.md deleted file mode 100644 index 1896f43..0000000 --- a/WEEK02/WEEK02-01.md +++ /dev/null @@ -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 (0a�0e) 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. diff --git a/WEEK02/WEEK02-02-S.md b/WEEK02/WEEK02-02-S.md deleted file mode 100644 index ffc051d..0000000 --- a/WEEK02/WEEK02-02-S.md +++ /dev/null @@ -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. diff --git a/WEEK02/WEEK02-02.md b/WEEK02/WEEK02-02.md deleted file mode 100644 index 806d683..0000000 --- a/WEEK02/WEEK02-02.md +++ /dev/null @@ -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 0a�0e 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. diff --git a/WEEK02/WEEK02-03-S.md b/WEEK02/WEEK02-03-S.md deleted file mode 100644 index 22fccc6..0000000 --- a/WEEK02/WEEK02-03-S.md +++ /dev/null @@ -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. diff --git a/WEEK02/WEEK02-03.md b/WEEK02/WEEK02-03.md deleted file mode 100644 index e3a9996..0000000 --- a/WEEK02/WEEK02-03.md +++ /dev/null @@ -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. diff --git a/WEEK02/WEEK02-04-S.md b/WEEK02/WEEK02-04-S.md deleted file mode 100644 index 30b2c7e..0000000 --- a/WEEK02/WEEK02-04-S.md +++ /dev/null @@ -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. diff --git a/WEEK02/WEEK02-04.md b/WEEK02/WEEK02-04.md deleted file mode 100644 index 4e2a8d6..0000000 --- a/WEEK02/WEEK02-04.md +++ /dev/null @@ -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 breakpoint�will 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. diff --git a/WEEK02/WEEK02.md b/WEEK02/WEEK02.md index 1dc20d4..626cd36 100644 --- a/WEEK02/WEEK02.md +++ b/WEEK02/WEEK02.md @@ -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 | + + diff --git a/WEEK03/WEEK03-01-S.md b/WEEK03/WEEK03-01-S.md deleted file mode 100644 index 9a5c66b..0000000 --- a/WEEK03/WEEK03-01-S.md +++ /dev/null @@ -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 (r0–r3) that don't need to be preserved across function calls. Since the reset handler is the very first code to run (no caller to preserve state for), using these low registers is both efficient (16-bit Thumb encodings) and safe. `r0` handles CPUID check and source pointer, `r1` is the destination pointer, `r2` is the end marker, and `r3` is the data transfer register. diff --git a/WEEK03/WEEK03-01.md b/WEEK03/WEEK03-01.md deleted file mode 100644 index 0a04ff6..0000000 --- a/WEEK03/WEEK03-01.md +++ /dev/null @@ -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 gothis 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. diff --git a/WEEK03/WEEK03-02-S.md b/WEEK03/WEEK03-02-S.md deleted file mode 100644 index 4bf4e4e..0000000 --- a/WEEK03/WEEK03-02-S.md +++ /dev/null @@ -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. diff --git a/WEEK03/WEEK03-02.md b/WEEK03/WEEK03-02.md deleted file mode 100644 index e2e837e..0000000 --- a/WEEK03/WEEK03-02.md +++ /dev/null @@ -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 -``` diff --git a/WEEK03/WEEK03-03-S.md b/WEEK03/WEEK03-03-S.md deleted file mode 100644 index 3b496c2..0000000 --- a/WEEK03/WEEK03-03-S.md +++ /dev/null @@ -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 : bkpt 0x0000 -0x1000011c : bkpt 0x0000 -0x1000011e : 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. diff --git a/WEEK03/WEEK03-03.md b/WEEK03/WEEK03-03.md deleted file mode 100644 index c47c460..0000000 --- a/WEEK03/WEEK03-03.md +++ /dev/null @@ -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 : bkpt 0x0000 -0x1000011c : bkpt 0x0000 -0x1000011e : 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 -``` diff --git a/WEEK03/WEEK03-04-S.md b/WEEK03/WEEK03-04-S.md deleted file mode 100644 index 8645a21..0000000 --- a/WEEK03/WEEK03-04-S.md +++ /dev/null @@ -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
: push {r7, lr} -0x10000236 : sub sp, #8 -0x10000238 : add r7, sp, #0 -0x1000023a : bl 0x100012c4 -0x1000023e : movw r0, #404 @ 0x194 -0x10000242 : movt r0, #4096 @ 0x1000 -0x10000246 : bl 0x1000023c <__wrap_puts> -0x1000024a : b.n 0x1000023e -``` - -##### First Function Call - -The first function call is at offset +6: -``` -0x1000023a : bl 0x100012c4 -``` - -`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 : ldr r1, [pc, #80] -0x10000188 : blx r1 → calls runtime_init() -0x1000018a : ldr r1, [pc, #80] → LR points here (return from main) -0x1000018c : blx r1 → THIS called main() -0x1000018e : ldr r1, [pc, #80] -0x10000190 : blx r1 → calls exit() -0x10000192 : 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 `, 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. diff --git a/WEEK03/WEEK03-04.md b/WEEK03/WEEK03-04.md deleted file mode 100644 index 4d0d8d6..0000000 --- a/WEEK03/WEEK03-04.md +++ /dev/null @@ -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
: push {r7, lr} -0x10000236 : sub sp, #8 -0x10000238 : add r7, sp, #0 -0x1000023a : bl 0x100012c4 -0x1000023e : movw r0, #404 @ 0x194 -0x10000242 : movt r0, #4096 @ 0x1000 -0x10000246 : bl 0x1000023c <__wrap_puts> -0x1000024a : b.n 0x1000023e -0x1000024c : push {r3, r4, r5, r6, r7, lr} -``` - -##### Step 4: Identify the First Function Call - -The first function call in `main()` is: -``` -0x1000023a : bl 0x100012c4 -``` - -**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 : ldr r1, [pc, #80] -0x10000188 : blx r1 -0x1000018a : ldr r1, [pc, #80] ? LR points here -0x1000018c : blx r1 ? This called main -0x1000018e : ldr r1, [pc, #80] -0x10000190 : blx r1 -0x10000192 : 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 : 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. diff --git a/WEEK03/WEEK03.md b/WEEK03/WEEK03.md index 55634fe..d176951 100644 --- a/WEEK03/WEEK03.md +++ b/WEEK03/WEEK03.md @@ -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 b.n 0x1000016c ``` -> 💡 **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`). | -#### 0x10000010–0x1000010f — 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 0x00–0x3f) plus up to 52 -external IRQ vectors (offsets 0x40–0xff = 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. -#### 0x10000110–0x10000127 — 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. -#### 0x10000128–0x1000013b — 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. -#### 0x10000138–0x1000014c — 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: 0x10000000–0x1000015c +#### Full Flash Map: 0x10000000-0x1000015c ``` - 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) + 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! 🔍 + + diff --git a/WEEK04/WEEK04-01-S.md b/WEEK04/WEEK04-01-S.md deleted file mode 100644 index 9241b72..0000000 --- a/WEEK04/WEEK04-01-S.md +++ /dev/null @@ -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. diff --git a/WEEK04/WEEK04-01.md b/WEEK04/WEEK04-01.md deleted file mode 100644 index 3bb7c46..0000000 --- a/WEEK04/WEEK04-01.md +++ /dev/null @@ -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.) diff --git a/WEEK04/WEEK04-02-S.md b/WEEK04/WEEK04-02-S.md deleted file mode 100644 index 8b15db9..0000000 --- a/WEEK04/WEEK04-02-S.md +++ /dev/null @@ -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. diff --git a/WEEK04/WEEK04-02.md b/WEEK04/WEEK04-02.md deleted file mode 100644 index a820d55..0000000 --- a/WEEK04/WEEK04-02.md +++ /dev/null @@ -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.) diff --git a/WEEK04/WEEK04-03-S.md b/WEEK04/WEEK04-03-S.md deleted file mode 100644 index 1315c78..0000000 --- a/WEEK04/WEEK04-03-S.md +++ /dev/null @@ -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. diff --git a/WEEK04/WEEK04-03.md b/WEEK04/WEEK04-03.md deleted file mode 100644 index 3ec9408..0000000 --- a/WEEK04/WEEK04-03.md +++ /dev/null @@ -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. diff --git a/WEEK04/WEEK04-04-S.md b/WEEK04/WEEK04-04-S.md deleted file mode 100644 index 818e3bd..0000000 --- a/WEEK04/WEEK04-04-S.md +++ /dev/null @@ -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 0–255 in a single 2-byte instruction. For values > 255 (like 500 = 0x1f4), the compiler must use either: (a) a 32-bit Thumb-2 `movw Rd, #imm16` instruction (4 bytes, can encode 0–65535), (b) a multi-instruction sequence that constructs the value (e.g., `movs` + `lsls` + `add`), or (c) an `ldr Rd, [pc, #offset]` that loads the constant from a literal pool in flash. This is why patching `sleep_ms(500)` may be more complex than patching `gpio_put(16, 1)`. - -4. **If you wanted to make the LED blink at exactly 5 Hz, what sleep_ms value would you use?** - At 5 Hz, each complete cycle is `1000 / 5 = 200ms`. With two `sleep_ms()` calls per cycle (ON and OFF), each call should be `200 / 2 = 100ms`. In hex: `100 = 0x64`. So `sleep_ms(0x64)` for each call—which is exactly the value used in this exercise's patch. For a different duty cycle (e.g., 150ms on, 50ms off), you'd use different values for each call while keeping the total at 200ms. diff --git a/WEEK04/WEEK04-04.md b/WEEK04/WEEK04-04.md deleted file mode 100644 index 296c680..0000000 --- a/WEEK04/WEEK04-04.md +++ /dev/null @@ -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! diff --git a/WEEK04/WEEK04-SLIDES.pdf b/WEEK04/WEEK04-SLIDES.pdf index f1a6bdd..9846259 100644 Binary files a/WEEK04/WEEK04-SLIDES.pdf and b/WEEK04/WEEK04-SLIDES.pdf differ diff --git a/WEEK04/WEEK04.md b/WEEK04/WEEK04.md index 84024cc..a6e9edf 100644 --- a/WEEK04/WEEK04.md +++ b/WEEK04/WEEK04.md @@ -1,956 +1,906 @@ -# Week 4: Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics +# Week 4: Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics + +## 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand what variables are and how they're stored in memory +- Know the difference between initialized, uninitialized, and constant variables +- Use Ghidra to analyze binaries without debug symbols +- Patch binary files to change program behavior permanently +- Control GPIO pins to blink LEDs on the Pico 2 +- Convert patched binaries to UF2 format for flashing +- Understand the `.data`, `.bss`, and `.rodata` memory sections + +--- + +## Part 1: Understanding Variables + +### What is a Variable? + +A **variable** is like a labeled box where you can store information. Imagine you have a row of boxes numbered 0 to 9. Each box can hold one item. In programming: + +- The **boxes** are memory locations (addresses in SRAM) +- The **items** are the values you store +- The **labels** are the variable names you choose + +``` ++-----------------------------------------------------------------+ +| Memory (SRAM) - Like a row of numbered boxes | +| | +| Box 0 Box 1 Box 2 Box 3 Box 4 ... | +| +----+ +----+ +----+ +----+ +----+ | +| | 42 | | 17 | | 0 | |255 | | 99 | | +| +----+ +----+ +----+ +----+ +----+ | +| age score count max temp | +| | ++-----------------------------------------------------------------+ +``` + +### Declaration vs Definition + +When working with variables, there are two important concepts: + +| Concept | What It Does | Example | +| ------------------ | ------------------------------------ | -------------------------- | +| **Declaration** | Tells the compiler the name and type | `uint8_t age;` | +| **Definition** | Allocates memory for the variable | (happens with declaration) | +| **Initialization** | Assigns an initial value | `uint8_t age = 42;` | + +**Important Rule:** You must declare a variable BEFORE you use it! + +### Understanding Data Types + +The **data type** tells the compiler how much memory to allocate: + +| Type | Size | Range | Description | +| ---------- | ------- | ------------------------------- | ----------------------- | +| `uint8_t` | 1 byte | 0 to 255 | Unsigned 8-bit integer | +| `int8_t` | 1 byte | -128 to 127 | Signed 8-bit integer | +| `uint16_t` | 2 bytes | 0 to 65,535 | Unsigned 16-bit integer | +| `int16_t` | 2 bytes | -32,768 to 32,767 | Signed 16-bit integer | +| `uint32_t` | 4 bytes | 0 to 4,294,967,295 | Unsigned 32-bit integer | +| `int32_t` | 4 bytes | -2,147,483,648 to 2,147,483,647 | Signed 32-bit integer | + +### Anatomy of a Variable Declaration + +Let's break down this line of code: + +```c +uint8_t age = 42; +``` + +| Part | Meaning | +| --------- | ----------------------------------------------------- | +| `uint8_t` | Data type - unsigned 8-bit integer (1 byte) | +| `age` | Variable name - how we refer to this storage location | +| `=` | Assignment operator - puts a value into the variable | +| `42` | The initial value | +| `;` | Semicolon - tells compiler the statement is complete | + +--- + +## Part 2: Memory Sections - Where Variables Live + +### The Three Main Sections + +When your program is compiled, variables go to different places depending on how they're declared: + +``` ++-----------------------------------------------------------------+ +| .data Section (Flash -> copied to RAM at startup) | +| Contains: Initialized global/static variables | +| Example: int counter = 42; | ++-----------------------------------------------------------------+ +| .bss Section (RAM - zeroed at startup) | +| Contains: Uninitialized global/static variables | +| Example: int counter; (will be 0) | ++-----------------------------------------------------------------+ +| .rodata Section (Flash - read only) | +| Contains: Constants, string literals | +| Example: const int MAX = 100; | +| Example: "hello, world" | ++-----------------------------------------------------------------+ +``` + +### What Happens to Uninitialized Variables? + +In older C compilers, uninitialized variables could contain "garbage" - random leftover data. But modern compilers (including the Pico SDK) are smarter: + +1. Uninitialized global variables go into the `.bss` section +2. The `.bss` section is **NOT stored in the binary** (saves space!) +3. At boot, the startup code uses `memset` to **zero out** all of `.bss` +4. So uninitialized variables are always `0`! + +This is why in our code: +```c +uint8_t age; // This will be 0, not garbage! +``` + +--- + +## Part 3: Understanding GPIO (General Purpose Input/Output) + +### What is GPIO? + +**GPIO** stands for **General Purpose Input/Output**. These are pins on the microcontroller that you can control with software. Think of them as tiny switches you can turn on and off. + +``` ++-----------------------------------------------------------------+ +| Raspberry Pi Pico 2 | +| | +| GPIO 16 -------► Red LED | +| GPIO 17 -------► Green LED | +| GPIO 18 -------► Blue LED | +| ... | +| GPIO 25 -------► Onboard LED | ++-----------------------------------------------------------------+ +``` + +### GPIO Functions in the Pico SDK + +The Pico SDK provides simple functions to control GPIO pins: + +| Function | Purpose | +| ------------------------------ | ------------------------------- | +| `gpio_init(pin)` | Initialize a GPIO pin for use | +| `gpio_set_dir(pin, direction)` | Set pin as INPUT or OUTPUT | +| `gpio_put(pin, value)` | Set pin HIGH (1) or LOW (0) | +| `sleep_ms(ms)` | Wait for specified milliseconds | + +### What Happens Behind the Scenes? + +Each high-level function calls lower-level code. Let's trace `gpio_init()`: + +``` +gpio_init(LED_PIN) + ↓ +gpio_set_dir(LED_PIN, GPIO_IN) // Initially set as input + ↓ +gpio_put(LED_PIN, 0) // Set output value to 0 + ↓ +gpio_set_function(LED_PIN, GPIO_FUNC_SIO) // Connect to SIO block +``` + +The SIO (Single-cycle I/O) block is a special hardware unit in the RP2350 that provides fast GPIO control! + +--- + +## Part 4: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. Ghidra installed (for static analysis) +3. Python installed (for UF2 conversion) +4. The sample projects: + - `0x0005_intro-to-variables` + - `0x0008_uninitialized-variables` +5. A serial monitor (PuTTY, minicom, or screen) + +### Project Structure + +``` +Embedded-Hacking/ ++-- 0x0005_intro-to-variables/ +| +-- build/ +| | +-- 0x0005_intro-to-variables.uf2 +| | +-- 0x0005_intro-to-variables.bin +| +-- 0x0005_intro-to-variables.c ++-- 0x0008_uninitialized-variables/ +| +-- build/ +| | +-- 0x0008_uninitialized-variables.uf2 +| | +-- 0x0008_uninitialized-variables.bin +| +-- 0x0008_uninitialized-variables.c ++-- uf2conv.py +``` + +--- + +## 🔬 Part 5: Hands-On Tutorial - Analyzing Variables in Ghidra + +### Step 1: Review the Source Code + +First, let's look at the code we'll be analyzing: + +**File: `0x0005_intro-to-variables.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + uint8_t age = 42; + + age = 43; + + stdio_init_all(); + + while (true) + printf("age: %d\r\n", age); +} +``` + +**What this code does:** +1. Declares a variable `age` and initializes it to `42` +2. Changes `age` to `43` +3. Initializes the serial output +4. Prints `age` forever in a loop + +### Step 2: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x0005_intro-to-variables.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 3: Verify It's Working + +Open your serial monitor (PuTTY, minicom, or screen) and you should see: + +``` +age: 43 +age: 43 +age: 43 +... +``` + +The program is printing `43` because that's what we assigned after the initial `42`. + +--- + +## 🔬 Part 6: Setting Up Ghidra for Binary Analysis + +### Step 4: Start Ghidra + +**Open a terminal and type:** + +```powershell +ghidraRun +``` + +Ghidra will open. Now we need to create a new project. + +### Step 5: Create a New Project + +1. Click **File** -> **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x0005_intro-to-variables` +5. Click **Finish** + +### Step 6: Import the Binary + +1. Open your file explorer +2. Navigate to the `Embedded-Hacking` folder +3. Find `0x0005_intro-to-variables.bin` +4. Select Cortex M Little Endian 32 +5. Select Options and set up the .text and offset 10000000 +6. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 7: Configure the Binary Format + +A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). + +**Click the three dots (...) next to "Language" and:** +1. Search for "Cortex" +2. Select **ARM Cortex 32 little endian default** +3. Click **OK** + +**Click the "Options..." button and:** +1. Change **Block Name** to `.text` +2. Change **Base Address** to `10000000` (the XIP address!) +3. Click **OK** + +### Step 8: Open and Analyze + +1. Double-click on the file in the project window +2. A dialog asks "Analyze now?" - Click **Yes** +3. Use default analysis options and click **Analyze** + +Wait for analysis to complete (watch the progress bar in the bottom right). + +--- + +## 🔬 Part 7: Navigating and Resolving Functions + +### Step 9: Find the Functions + +Look at the **Symbol Tree** panel on the left. Expand **Functions**. + +You'll see function names like: +- `FUN_1000019a` +- `FUN_10000210` +- `FUN_10000234` + +These are auto-generated names because we imported a raw binary without symbols! + +### Step 10: Resolve Known Functions + +From our previous chapters, we know what some of these functions are: + +| Ghidra Name | Actual Name | How We Know | +| -------------- | ------------- | -------------------------- | +| `FUN_1000019a` | `data_cpy` | From Week 3 boot analysis | +| `FUN_10000210` | `frame_dummy` | From Week 3 boot analysis | +| `FUN_10000234` | `main` | This is where our code is! | + +### Step 11: Update Main's Signature + +For `main`, let's also fix the return type: + +1. Right-click on `main` in the Decompile window +2. Select **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +--- + +## 🔬 Part 8: Analyzing the Main Function + +### Step 12: Examine Main in Ghidra + +Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: + +You'll see something like: + +```c +void FUN_10000234(void) + +{ + FUN_10002f54(); + do { + FUN_100030e4(DAT_10000244,0x2b); + } while( true ); +} +``` + +### Step 13: Resolve stdio_init_all + +1. Click on `FUN_10002f54` +2. Right-click -> **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 14: Resolve printf + +1. Click on `FUN_100030e4` +2. Right-click -> **Edit Function Signature** +3. Change the name to `void printf (undefined4 param_1, ...)` +4. Check the **Varargs** checkbox (printf takes variable arguments!) +5. Click **OK** + +### Step 15: Understand the Optimization + +Look at the updated decompiled code. This will look different if you resolved your functions however do you notice something interesting? + +```c +int main(void) + +{ + stdio_init_all(); + do { + printf(DAT_10000244,0x2b); + } while( true ); +} +``` + +**Where's `uint8_t age = 42`?** It's gone! + +The compiler **optimized it out**! Here's what happened: + +1. Original code: `age = 42`, then `age = 43` +2. Compiler sees: "The `42` is never used, only `43` matters" +3. Compiler removes the unused `42` and just uses `43` directly + +**What is `0x2b`?** Let's check: +- `0x2b` in hexadecimal = `43` in decimal ✓ + +The compiler replaced our variable with the constant value! + +--- + +## 🔬 Part 9: Patching the Binary - Changing the Value + +### Step 16: Find the Value to Patch + +Look at the **Listing** window (assembly view). Find the instruction that loads `0x2b`: + +```assembly +1000023a 2b 21 movs r1,#0x2b +``` + +This instruction loads the value `0x2b` (43) into register `r1` before calling `printf`. + +### Step 17: Patch the Instruction + +We're going to change `0x2b` (43) to `0x46` (70)! + +1. Click on the instruction `movs r1,#0x2b` +2. Right-click and select **Patch Instruction** +3. Change `0x2b` to `0x46` +4. Press Enter + +The instruction now reads: +```assembly +1000023a 46 21 movs r1,#0x46 +``` + +### Step 18: Export the Patched Binary + +1. Click **File** -> **Export Program** +2. Set **Format** to **Raw Bytes** +3. Navigate to your build directory +4. Name the file `0x0005_intro-to-variables-h.bin` +5. Click **OK** + +--- + +## 🔬 Part 10: Converting and Flashing the Hacked Binary + +### Step 19: Convert to UF2 Format + +The Pico 2 expects UF2 files, not raw BIN files. We need to convert it! + +**Open a terminal and navigate to your project directory:** + +```powershell +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0005_intro-to-variables +``` + +**Run the conversion command:** + +```powershell +python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +**What this command means:** +- `uf2conv.py` = the conversion script +- `--base 0x10000000` = the XIP base address +- `--family 0xe48bff59` = the RP2350 family ID +- `--output build\hacked.uf2` = the output filename + +### Step 20: Flash the Hacked Binary + +1. Hold BOOTSEL and plug in your Pico 2 +2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive +3. Open your serial monitor + +**You should see:** + +``` +age: 70 +age: 70 +age: 70 +... +``` + +🎉 **BOOM! We hacked it!** The value changed from 43 to 70! + +--- + +## 🔬 Part 11: Uninitialized Variables and GPIO + +Now let's work with a more complex example that includes GPIO control. + +### Step 21: Review the Uninitialized Variables Code + +**File: `0x0008_uninitialized-variables.c`** + +```c +#include +#include "pico/stdlib.h" + +#define LED_PIN 16 + +int main(void) { + uint8_t age; // Uninitialized! + + stdio_init_all(); + + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + while (true) { + printf("age: %d\r\n", age); + + gpio_put(LED_PIN, 1); + sleep_ms(500); + + gpio_put(LED_PIN, 0); + sleep_ms(500); + } +} +``` + +**What this code does:** +1. Declares `age` without initializing it (will be 0 due to BSS zeroing) +2. Initializes GPIO 16 as an output +3. In a loop: prints age, blinks the LED + +### Step 22: Flash and Verify + +1. Flash `0x0008_uninitialized-variables.uf2` to your Pico 2 +2. Open your serial monitor + +**You should see:** + +``` +age: 0 +age: 0 +age: 0 +... +``` + +And the **red LED on GPIO 16 should be blinking**! + +The value is `0` because uninitialized variables in the `.bss` section are zeroed at startup. + +--- + +## 🔬 Part 12: Analyzing GPIO Code in Ghidra + +### Step 23: Set Up Ghidra for the New Binary + +1. Create a new project: `0x0008_uninitialized-variables` +2. Import `0x0008_uninitialized-variables.bin` +3. Set Language to **ARM Cortex 32 little endian** +4. Set Base Address to `.text` and `10000000` +5. Auto-analyze + +### Step 24: Resolve the Functions + +Find and rename these functions: + +| Ghidra Name | Actual Name | +| -------------- | ---------------- | +| `FUN_10000234` | `main` | +| `FUN_100030cc` | `stdio_init_all` | +| `FUN_100002b4` | `gpio_init` | +| `FUN_1000325c` | `printf` | + +For `gpio_init`, set the signature to: +```c +void gpio_init(uint gpio) +``` + +### Step 25: Examine the Main Function + +The decompiled main should look something like: + +```c +void FUN_10000234(void) + +{ + undefined4 extraout_r1; + undefined4 extraout_r2; + undefined4 in_cr0; + undefined4 in_cr4; + + FUN_100030cc(); + FUN_100002b4(0x10); + coprocessor_moveto2(0,4,0x10,1,in_cr4); + do { + FUN_1000325c(DAT_10000274,0); + coprocessor_moveto2(0,4,0x10,1,in_cr0); + FUN_10000d10(500); + coprocessor_moveto2(0,4,0x10,0,in_cr0); + FUN_10000d10(500,extraout_r1,extraout_r2,0); + } while( true ); +} +``` + +--- + +## 🔬 Part 13: Hacking GPIO - Changing the LED Pin + +### Step 26: Find the GPIO Pin Value + +Look in the assembly for instructions that use `0x10` (which is 16 in decimal - our LED pin): + +```assembly +1000023a 10 20 movs r0,#0x10 +``` + +This is where `gpio_init(LED_PIN)` is called with GPIO 16. + +### Step 27: Patch GPIO 16 to GPIO 17 + +We'll change the red LED (GPIO 16) to the green LED (GPIO 17)! + +1. Find the instruction `movs r0,#0x10` +2. Right-click -> **Patch Instruction** +3. Change `0x10` to `0x11` (17 in hex) +4. Click **OK** + +### Step 28: Find All GPIO 16 References + +There are more places that use GPIO 16. Look for: + +```assembly +10000244 10 23 movs r3,#0x10 +``` + +This is used in `gpio_set_dir`. Patch this to `0x11` as well. + +```assembly +10000252 10 24 movs r4,#0x10 +``` + +This is inside the loop for `gpio_put`. Patch this to `0x11` as well. + +### Step 29: Bonus - Change the Printed Value + +Let's also change the printed value from `0` to `0x42` (66 in decimal): + +```assembly +1000024a 00 21 movs r1,#0x0 +``` + +1. Right-click -> **Patch Instruction** +2. Change `0x0` to `0x42` +3. Click **OK** + +--- + +## 🔬 Part 14: Export and Test the Hacked GPIO + +### Step 30: Export the Patched Binary + +1. Click **File** -> **Export Program** +2. Format: **Raw Bytes** +3. Filename: `0x0008_uninitialized-variables-h.bin` +4. Click **OK** + +### Step 31: Convert to UF2 + +```powershell +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0008_uninitialized-variables +python ..\uf2conv.py build\0x0008_uninitialized-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 32: Flash and Verify + +1. Flash `hacked.uf2` to your Pico 2 +2. Check your serial monitor + +**You should see:** + +``` +age: 66 +age: 66 +age: 66 +... +``` + +And now the **GREEN LED on GPIO 17** should be blinking instead of the red one! + +🎉 **We successfully:** +1. Changed the printed value from 0 to 66 +2. Changed which LED blinks from red (GPIO 16) to green (GPIO 17) + +--- + +## Part 15: Deep Dive - GPIO at the Assembly Level + +### Understanding the GPIO Coprocessor + +The RP2350 has a special **GPIO coprocessor** that provides fast, single-cycle GPIO control. This is different from the RP2040! + +The coprocessor is accessed using special ARM instructions: + +```assembly +mcrr p0, #4, r4, r5, c0 ; GPIO output control +mcrr p0, #4, r4, r5, c4 ; GPIO direction control +``` + +**What this means:** +- `mcrr` = Move to Coprocessor from two ARM Registers +- `p0` = Coprocessor 0 (the GPIO coprocessor) +- `r4` = Contains the GPIO pin number +- `r5` = Contains the value (0 or 1) +- `c0` = Output value register +- `c4` = Output enable register + +### The Full GPIO Initialization Sequence + +When you call `gpio_init(16)`, here's what actually happens: + +``` +Step 1: Configure pad (address 0x40038044) ++-----------------------------------------------------------------+ +| - Clear OD bit (output disable) | +| - Set IE bit (input enable) | +| - Clear ISO bit (isolation) | ++-----------------------------------------------------------------+ + +Step 2: Set function (address 0x40028084) ++-----------------------------------------------------------------+ +| - Set FUNCSEL to 5 (SIO - Software I/O) | ++-----------------------------------------------------------------+ + +Step 3: Enable output (via coprocessor) ++-----------------------------------------------------------------+ +| - mcrr p0, #4, r4, r5, c4 (where r4=16, r5=1) | ++-----------------------------------------------------------------+ +``` + +### Raw Assembly LED Blink + +Here's what a completely hand-written assembly LED blink looks like: + +```assembly +; Initialize GPIO 16 as output +movs r4, #0x10 ; GPIO 16 +movs r5, #0x01 ; Enable +mcrr p0, #4, r4, r5, c4 ; Set as output + +; Configure pad registers +ldr r3, =0x40038044 ; Pad control for GPIO 16 +ldr r2, [r3] ; Load current config +bic r2, r2, #0x80 ; Clear OD (output disable) +orr r2, r2, #0x40 ; Set IE (input enable) +str r2, [r3] ; Store config + +; Set GPIO function to SIO +ldr r3, =0x40028084 ; IO bank control for GPIO 16 +movs r2, #5 ; FUNCSEL = SIO +str r2, [r3] ; Set function + +; Main loop +loop: + ; LED ON + movs r4, #0x10 ; GPIO 16 + movs r5, #0x01 ; High + mcrr p0, #4, r4, r5, c0 + + ; Delay + ldr r2, =0x17D7840 ; ~25 million iterations +delay1: + subs r2, r2, #1 + bne delay1 + + ; LED OFF + movs r4, #0x10 ; GPIO 16 + movs r5, #0x00 ; Low + mcrr p0, #4, r4, r5, c0 + + ; Delay + ldr r2, =0x17D7840 +delay2: + subs r2, r2, #1 + bne delay2 + + b loop ; Repeat forever +``` + +--- + +## 📊 Part 16: Summary and Review + +### What We Accomplished + +1. **Learned about variables** - How they're declared, initialized, and stored +2. **Understood memory sections** - `.data`, `.bss`, and `.rodata` +3. **Analyzed binaries in Ghidra** - Without debug symbols! +4. **Patched binaries** - Changed values directly in the binary +5. **Controlled GPIO** - Made LEDs blink +6. **Changed program behavior** - Different LED, different value + +### The Binary Patching Workflow + +``` ++-----------------------------------------------------------------+ +| 1. Import .bin file into Ghidra | +| - Set language to ARM Cortex | +| - Set base address to 0x10000000 | ++-----------------------------------------------------------------+ +| 2. Analyze and resolve functions | +| - Rename functions to meaningful names | +| - Fix function signatures | ++-----------------------------------------------------------------+ +| 3. Find the values/instructions to patch | +| - Look in the assembly listing | +| - Right-click -> Patch Instruction | ++-----------------------------------------------------------------+ +| 4. Export the patched binary | +| - File -> Export Program | +| - Format: Raw Bytes | ++-----------------------------------------------------------------+ +| 5. Convert to UF2 | +| - python uf2conv.py file.bin --base 0x10000000 | +| --family 0xe48bff59 --output hacked.uf2 | ++-----------------------------------------------------------------+ +| 6. Flash and verify | +| - Hold BOOTSEL, plug in, drag UF2 | +| - Check serial output and LED behavior | ++-----------------------------------------------------------------+ +``` + +### Key Memory Sections + +| Section | Location | Contains | Writable? | +| --------- | -------- | ------------------------------ | --------- | +| `.text` | Flash | Code | No | +| `.rodata` | Flash | Constants, strings | No | +| `.data` | RAM | Initialized globals | Yes | +| `.bss` | RAM | Uninitialized globals (zeroed) | Yes | + +### Important Ghidra Commands + +| Action | How To Do It | +| ----------------- | ------------------------------------- | +| Rename function | Right-click -> Edit Function Signature | +| Patch instruction | Right-click -> Patch Instruction | +| Export binary | File -> Export Program -> Raw Bytes | +| Go to address | Press 'G' and enter address | + +--- + +--- + +## 🎓 Key Takeaways + +1. **Variables are just memory locations** - The compiler assigns them addresses in SRAM. + +2. **Compilers optimize aggressively** - Unused code and values may be removed entirely. + +3. **Uninitialized doesn't mean random** - Modern compilers zero out the `.bss` section. + +4. **Ghidra works without symbols** - You can analyze any binary, even stripped ones. + +5. **Binary patching is powerful** - You can change behavior without source code. + +6. **UF2 conversion is required** - The Pico 2 needs UF2 format, not raw binaries. + +7. **GPIO is just memory-mapped I/O** - Writing to specific addresses controls hardware. + +--- + +## 📖 Glossary + +| Term | Definition | +| ------------------ | --------------------------------------------------------------------- | +| **BSS** | Block Started by Symbol - section for uninitialized global variables | +| **Declaration** | Telling the compiler a variable's name and type | +| **Definition** | Allocating memory for a variable | +| **GPIO** | General Purpose Input/Output - controllable pins on a microcontroller | +| **Initialization** | Assigning an initial value to a variable | +| **Linker** | Tool that combines compiled code and assigns memory addresses | +| **Optimization** | Compiler removing or simplifying code for efficiency | +| **Patching** | Modifying bytes directly in a binary file | +| **rodata** | Read-only data section for constants and string literals | +| **SIO** | Single-cycle I/O - fast GPIO control block in RP2350 | +| **UF2** | USB Flashing Format - file format for Pico 2 firmware | +| **Variable** | A named storage location in memory | + +--- + +## 🔗 Additional Resources + +### GPIO Coprocessor Reference + +The RP2350 GPIO coprocessor instructions: + +| Instruction | Description | +| -------------------------- | ---------------------------- | +| `mcrr p0, #4, Rt, Rt2, c0` | Set/clear GPIO output | +| `mcrr p0, #4, Rt, Rt2, c4` | Set/clear GPIO output enable | + +### RP2350 Memory Map Quick Reference + +| Address | Description | +| ------------ | ------------------------ | +| `0x10000000` | XIP Flash (code) | +| `0x20000000` | SRAM (data) | +| `0x40028000` | IO_BANK0 (GPIO control) | +| `0x40038000` | PADS_BANK0 (pad control) | +| `0xd0000000` | SIO (single-cycle I/O) | + +--- + +**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Practice makes perfect! + +Happy hacking! 🔧 -## 🎯 What You'll Learn This Week - -By the end of this tutorial, you will be able to: -- Understand what variables are and how they're stored in memory -- Know the difference between initialized, uninitialized, and constant variables -- Use Ghidra to analyze binaries without debug symbols -- Patch binary files to change program behavior permanently -- Control GPIO pins to blink LEDs on the Pico 2 -- Convert patched binaries to UF2 format for flashing -- Understand the `.data`, `.bss`, and `.rodata` memory sections - ---- - -## 📚 Part 1: Understanding Variables - -### What is a Variable? - -A **variable** is like a labeled box where you can store information. Imagine you have a row of boxes numbered 0 to 9. Each box can hold one item. In programming: - -- The **boxes** are memory locations (addresses in SRAM) -- The **items** are the values you store -- The **labels** are the variable names you choose - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Memory (SRAM) - Like a row of numbered boxes │ -│ │ -│ Box 0 Box 1 Box 2 Box 3 Box 4 ... │ -│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ -│ │ 42 │ │ 17 │ │ 0 │ │255 │ │ 99 │ │ -│ └────┘ └────┘ └────┘ └────┘ └────┘ │ -│ age score count max temp │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### Declaration vs Definition - -When working with variables, there are two important concepts: - -| Concept | What It Does | Example | -| ------------------ | ------------------------------------ | -------------------------- | -| **Declaration** | Tells the compiler the name and type | `uint8_t age;` | -| **Definition** | Allocates memory for the variable | (happens with declaration) | -| **Initialization** | Assigns an initial value | `uint8_t age = 42;` | - -**Important Rule:** You must declare a variable BEFORE you use it! - -### Understanding Data Types - -The **data type** tells the compiler how much memory to allocate: - -| Type | Size | Range | Description | -| ---------- | ------- | ------------------------------- | ----------------------- | -| `uint8_t` | 1 byte | 0 to 255 | Unsigned 8-bit integer | -| `int8_t` | 1 byte | -128 to 127 | Signed 8-bit integer | -| `uint16_t` | 2 bytes | 0 to 65,535 | Unsigned 16-bit integer | -| `int16_t` | 2 bytes | -32,768 to 32,767 | Signed 16-bit integer | -| `uint32_t` | 4 bytes | 0 to 4,294,967,295 | Unsigned 32-bit integer | -| `int32_t` | 4 bytes | -2,147,483,648 to 2,147,483,647 | Signed 32-bit integer | - -### Anatomy of a Variable Declaration - -Let's break down this line of code: - -```c -uint8_t age = 42; -``` - -| Part | Meaning | -| --------- | ----------------------------------------------------- | -| `uint8_t` | Data type - unsigned 8-bit integer (1 byte) | -| `age` | Variable name - how we refer to this storage location | -| `=` | Assignment operator - puts a value into the variable | -| `42` | The initial value | -| `;` | Semicolon - tells compiler the statement is complete | - ---- - -## 📚 Part 2: Memory Sections - Where Variables Live - -### The Three Main Sections - -When your program is compiled, variables go to different places depending on how they're declared: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ .data Section (Flash → copied to RAM at startup) │ -│ Contains: Initialized global/static variables │ -│ Example: int counter = 42; │ -├─────────────────────────────────────────────────────────────────┤ -│ .bss Section (RAM - zeroed at startup) │ -│ Contains: Uninitialized global/static variables │ -│ Example: int counter; (will be 0) │ -├─────────────────────────────────────────────────────────────────┤ -│ .rodata Section (Flash - read only) │ -│ Contains: Constants, string literals │ -│ Example: const int MAX = 100; │ -│ Example: "hello, world" │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### What Happens to Uninitialized Variables? - -In older C compilers, uninitialized variables could contain "garbage" - random leftover data. But modern compilers (including the Pico SDK) are smarter: - -1. Uninitialized global variables go into the `.bss` section -2. The `.bss` section is **NOT stored in the binary** (saves space!) -3. At boot, the startup code uses `memset` to **zero out** all of `.bss` -4. So uninitialized variables are always `0`! - -This is why in our code: -```c -uint8_t age; // This will be 0, not garbage! -``` - ---- - -## 📚 Part 3: Understanding GPIO (General Purpose Input/Output) - -### What is GPIO? - -**GPIO** stands for **General Purpose Input/Output**. These are pins on the microcontroller that you can control with software. Think of them as tiny switches you can turn on and off. - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Raspberry Pi Pico 2 │ -│ │ -│ GPIO 16 ───────► Red LED │ -│ GPIO 17 ───────► Green LED │ -│ GPIO 18 ───────► Blue LED │ -│ ... │ -│ GPIO 25 ───────► Onboard LED │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### GPIO Functions in the Pico SDK - -The Pico SDK provides simple functions to control GPIO pins: - -| Function | Purpose | -| ------------------------------ | ------------------------------- | -| `gpio_init(pin)` | Initialize a GPIO pin for use | -| `gpio_set_dir(pin, direction)` | Set pin as INPUT or OUTPUT | -| `gpio_put(pin, value)` | Set pin HIGH (1) or LOW (0) | -| `sleep_ms(ms)` | Wait for specified milliseconds | - -### Basic LED Blink Code - -```c -#include -#include "pico/stdlib.h" - -#define LED_PIN 16 - -int main(void) { - gpio_init(LED_PIN); // Initialize GPIO 16 - gpio_set_dir(LED_PIN, GPIO_OUT); // Set as output - - while (true) { - gpio_put(LED_PIN, 1); // LED ON - sleep_ms(500); // Wait 500ms - gpio_put(LED_PIN, 0); // LED OFF - sleep_ms(500); // Wait 500ms - } -} -``` - -### What Happens Behind the Scenes? - -Each high-level function calls lower-level code. Let's trace `gpio_init()`: - -``` -gpio_init(LED_PIN) - ↓ -gpio_set_dir(LED_PIN, GPIO_IN) // Initially set as input - ↓ -gpio_put(LED_PIN, 0) // Set output value to 0 - ↓ -gpio_set_function(LED_PIN, GPIO_FUNC_SIO) // Connect to SIO block -``` - -The SIO (Single-cycle I/O) block is a special hardware unit in the RP2350 that provides fast GPIO control! - ---- - -## 📚 Part 4: Setting Up Your Environment - -### Prerequisites - -Before we start, make sure you have: -1. A Raspberry Pi Pico 2 board -2. Ghidra installed (for static analysis) -3. Python installed (for UF2 conversion) -4. The sample projects: - - `0x0005_intro-to-variables` - - `0x0008_uninitialized-variables` -5. A serial monitor (PuTTY, minicom, or screen) - -### Project Structure - -``` -Embedded-Hacking/ -├── 0x0005_intro-to-variables/ -│ ├── build/ -│ │ ├── 0x0005_intro-to-variables.uf2 -│ │ └── 0x0005_intro-to-variables.bin -│ └── 0x0005_intro-to-variables.c -├── 0x0008_uninitialized-variables/ -│ ├── build/ -│ │ ├── 0x0008_uninitialized-variables.uf2 -│ │ └── 0x0008_uninitialized-variables.bin -│ └── 0x0008_uninitialized-variables.c -└── uf2conv.py -``` - ---- - -## 🔬 Part 5: Hands-On Tutorial - Analyzing Variables in Ghidra - -### Step 1: Review the Source Code - -First, let's look at the code we'll be analyzing: - -**File: `0x0005_intro-to-variables.c`** - -```c -#include -#include "pico/stdlib.h" - -int main(void) { - uint8_t age = 42; - - age = 43; - - stdio_init_all(); - - while (true) - printf("age: %d\r\n", age); -} -``` - -**What this code does:** -1. Declares a variable `age` and initializes it to `42` -2. Changes `age` to `43` -3. Initializes the serial output -4. Prints `age` forever in a loop - -### Step 2: Flash the Binary to Your Pico 2 - -1. Hold the BOOTSEL button on your Pico 2 -2. Plug in the USB cable (while holding BOOTSEL) -3. Release BOOTSEL - a drive called "RPI-RP2" appears -4. Drag and drop `0x0005_intro-to-variables.uf2` onto the drive -5. The Pico will reboot and start running! - -### Step 3: Verify It's Working - -Open your serial monitor (PuTTY, minicom, or screen) and you should see: - -``` -age: 43 -age: 43 -age: 43 -... -``` - -The program is printing `43` because that's what we assigned after the initial `42`. - ---- - -## 🔬 Part 6: Setting Up Ghidra for Binary Analysis - -### Step 4: Start Ghidra - -**Open a terminal and type:** - -```powershell -ghidraRun -``` - -Ghidra will open. Now we need to create a new project. - -### Step 5: Create a New Project - -1. Click **File** → **New Project** -2. Select **Non-Shared Project** -3. Click **Next** -4. Enter Project Name: `0x0005_intro-to-variables` -5. Click **Finish** - -### Step 6: Import the Binary - -1. Open your file explorer -2. Navigate to the `Embedded-Hacking` folder -3. Find `0x0005_intro-to-variables.bin` -4. Select Cortex M Little Endian 32 -5. Select Options and set up the .text and offset 10000000 -6. **Drag and drop** the `.bin` file into Ghidra's project window - -### Step 7: Configure the Binary Format - -A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). - -**Click the three dots (…) next to "Language" and:** -1. Search for "Cortex" -2. Select **ARM Cortex 32 little endian default** -3. Click **OK** - -**Click the "Options…" button and:** -1. Change **Block Name** to `.text` -2. Change **Base Address** to `10000000` (the XIP address!) -3. Click **OK** - -### Step 8: Open and Analyze - -1. Double-click on the file in the project window -2. A dialog asks "Analyze now?" - Click **Yes** -3. Use default analysis options and click **Analyze** - -Wait for analysis to complete (watch the progress bar in the bottom right). - ---- - -## 🔬 Part 7: Navigating and Resolving Functions - -### Step 9: Find the Functions - -Look at the **Symbol Tree** panel on the left. Expand **Functions**. - -You'll see function names like: -- `FUN_1000019a` -- `FUN_10000210` -- `FUN_10000234` - -These are auto-generated names because we imported a raw binary without symbols! - -### Step 10: Resolve Known Functions - -From our previous chapters, we know what some of these functions are: - -| Ghidra Name | Actual Name | How We Know | -| -------------- | ------------- | -------------------------- | -| `FUN_1000019a` | `data_cpy` | From Week 3 boot analysis | -| `FUN_10000210` | `frame_dummy` | From Week 3 boot analysis | -| `FUN_10000234` | `main` | This is where our code is! | - -**To rename `FUN_1000019a` to `data_cpy`:** -1. Click on `FUN_1000019a` in the Symbol Tree -2. In the Decompile window, right-click on the function name -3. Select **Edit Function Signature** -4. Change the name to `data_cpy` -5. Click **OK** - -**Repeat for the other functions:** -- Rename `FUN_10000210` to `frame_dummy` -- Rename `FUN_10000234` to `main` - -### Step 11: Update Main's Signature - -For `main`, let's also fix the return type: - -1. Right-click on `main` in the Decompile window -2. Select **Edit Function Signature** -3. Change to: `int main(void)` -4. Click **OK** - ---- - -## 🔬 Part 8: Analyzing the Main Function - -### Step 12: Examine Main in Ghidra - -Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: - -You'll see something like: - -```c -void FUN_10000234(void) -{ - FUN_10002f54(); - do { - FUN_100030e4(DAT_10000244,0x2b); - } while( true ); -} -``` - -### Step 13: Resolve stdio_init_all - -1. Click on `FUN_10002f54` -2. Right-click → **Edit Function Signature** -3. Change to: `bool stdio_init_all(void)` -4. Click **OK** - -### Step 14: Resolve printf - -1. Click on `FUN_100030e4` -2. Right-click → **Edit Function Signature** -3. Change the name to `printf` -4. Check the **Varargs** checkbox (printf takes variable arguments!) -5. Click **OK** - -### Step 15: Understand the Optimization - -Look at the decompiled code. This will look different if you resolved your functions however do you notice something interesting? - -```c -void FUN_10000234(void) -{ - FUN_10002f54(); - do { - FUN_100030e4(DAT_10000244,0x2b); - } while( true ); -} -``` - -**Where's `uint8_t age = 42`?** It's gone! - -The compiler **optimized it out**! Here's what happened: - -1. Original code: `age = 42`, then `age = 43` -2. Compiler sees: "The `42` is never used, only `43` matters" -3. Compiler removes the unused `42` and just uses `43` directly - -**What is `0x2b`?** Let's check: -- `0x2b` in hexadecimal = `43` in decimal ✓ - -The compiler replaced our variable with the constant value! - ---- - -## 🔬 Part 9: Patching the Binary - Changing the Value - -### Step 16: Find the Value to Patch - -Look at the **Listing** window (assembly view). Find the instruction that loads `0x2b`: - -```assembly -1000023a 2b 21 movs r1,#0x2b -``` - -This instruction loads the value `0x2b` (43) into register `r1` before calling `printf`. - -### Step 17: Patch the Instruction - -We're going to change `0x2b` (43) to `0x46` (70)! - -1. Click on the instruction `movs r1,#0x2b` -2. Right-click and select **Patch Instruction** -3. Change `0x2b` to `0x46` -4. Press Enter - -The instruction now reads: -```assembly -1000023a 46 21 movs r1,#0x46 -``` - -### Step 18: Export the Patched Binary - -1. Click **File** → **Export Program** -2. Set **Format** to **Raw Bytes** -3. Navigate to your build directory -4. Name the file `0x0005_intro-to-variables-h.bin` -5. Click **OK** - ---- - -## 🔬 Part 10: Converting and Flashing the Hacked Binary - -### Step 19: Convert to UF2 Format - -The Pico 2 expects UF2 files, not raw BIN files. We need to convert it! - -**Open a terminal and navigate to your project directory:** - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0005_intro-to-variables -``` - -**Run the conversion command:** - -```powershell -python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -**What this command means:** -- `uf2conv.py` = the conversion script -- `--base 0x10000000` = the XIP base address -- `--family 0xe48bff59` = the RP2350 family ID -- `--output build\hacked.uf2` = the output filename - -### Step 20: Flash the Hacked Binary - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive -3. Open your serial monitor - -**You should see:** - -``` -age: 70 -age: 70 -age: 70 -... -``` - -🎉 **BOOM! We hacked it!** The value changed from 43 to 70! - ---- - -## 🔬 Part 11: Uninitialized Variables and GPIO - -Now let's work with a more complex example that includes GPIO control. - -### Step 21: Review the Uninitialized Variables Code - -**File: `0x0008_uninitialized-variables.c`** - -```c -#include -#include "pico/stdlib.h" - -#define LED_PIN 16 - -int main(void) { - uint8_t age; // Uninitialized! - - stdio_init_all(); - - gpio_init(LED_PIN); - gpio_set_dir(LED_PIN, GPIO_OUT); - - while (true) { - printf("age: %d\r\n", age); - - gpio_put(LED_PIN, 1); - sleep_ms(500); - - gpio_put(LED_PIN, 0); - sleep_ms(500); - } -} -``` - -**What this code does:** -1. Declares `age` without initializing it (will be 0 due to BSS zeroing) -2. Initializes GPIO 16 as an output -3. In a loop: prints age, blinks the LED - -### Step 22: Flash and Verify - -1. Flash `0x0008_uninitialized-variables.uf2` to your Pico 2 -2. Open your serial monitor - -**You should see:** - -``` -age: 0 -age: 0 -age: 0 -... -``` - -And the **red LED on GPIO 16 should be blinking**! - -The value is `0` because uninitialized variables in the `.bss` section are zeroed at startup. - ---- - -## 🔬 Part 12: Analyzing GPIO Code in Ghidra - -### Step 23: Set Up Ghidra for the New Binary - -1. Create a new project: `0x0008_uninitialized-variables` -2. Import `0x0008_uninitialized-variables.bin` -3. Set Language to **ARM Cortex 32 little endian** -4. Set Base Address to `10000000` -5. Auto-analyze - -### Step 24: Resolve the Functions - -Find and rename these functions: - -| Ghidra Name | Actual Name | -| -------------- | ---------------- | -| `FUN_10000234` | `main` | -| `FUN_100030cc` | `stdio_init_all` | -| `FUN_100002b4` | `gpio_init` | -| `FUN_1000325c` | `printf` | - -For `gpio_init`, set the signature to: -```c -void gpio_init(uint gpio) -``` - -### Step 25: Examine the Main Function - -The decompiled main should look something like: - -```c -void FUN_10000234(void) -{ - undefined4 extraout_r1; - undefined4 extraout_r2; - undefined4 in_cr0; - undefined4 in_cr4; - - FUN_100030cc(); - FUN_100002b4(0x10); - coprocessor_moveto2(0,4,0x10,1,in_cr4); - do { - FUN_1000325c(DAT_10000274,0); - coprocessor_moveto2(0,4,0x10,1,in_cr0); - FUN_10000d10(500); - coprocessor_moveto2(0,4,0x10,0,in_cr0); - FUN_10000d10(500,extraout_r1,extraout_r2,0); - } while( true ); -} -``` - ---- - -## 🔬 Part 13: Hacking GPIO - Changing the LED Pin - -### Step 26: Find the GPIO Pin Value - -Look in the assembly for instructions that use `0x10` (which is 16 in decimal - our LED pin): - -```assembly -1000023a 10 20 movs r0,#0x10 -``` - -This is where `gpio_init(LED_PIN)` is called with GPIO 16. - -### Step 27: Patch GPIO 16 to GPIO 17 - -We'll change the red LED (GPIO 16) to the green LED (GPIO 17)! - -1. Find the instruction `movs r0,#0x10` -2. Right-click → **Patch Instruction** -3. Change `0x10` to `0x11` (17 in hex) -4. Click **OK** - -### Step 28: Find All GPIO 16 References - -There are more places that use GPIO 16. Look for: - -```assembly -10000244 10 23 movs r3,#0x10 -``` - -This is used in `gpio_set_dir`. Patch this to `0x11` as well. - -```assembly -10000252 10 24 movs r4,#0x10 -``` - -This is inside the loop for `gpio_put`. Patch this to `0x11` as well. - -### Step 29: Bonus - Change the Printed Value - -Let's also change the printed value from `0` to `0x42` (66 in decimal): - -```assembly -1000024a 00 21 movs r1,#0x0 -``` - -1. Right-click → **Patch Instruction** -2. Change `0x0` to `0x42` -3. Click **OK** - ---- - -## 🔬 Part 14: Export and Test the Hacked GPIO - -### Step 30: Export the Patched Binary - -1. Click **File** → **Export Program** -2. Format: **Raw Bytes** -3. Filename: `0x0008_uninitialized-variables-h.bin` -4. Click **OK** - -### Step 31: Convert to UF2 - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0008_uninitialized-variables -python ..\uf2conv.py build\0x0008_uninitialized-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -### Step 32: Flash and Verify - -1. Flash `hacked.uf2` to your Pico 2 -2. Check your serial monitor - -**You should see:** - -``` -age: 66 -age: 66 -age: 66 -... -``` - -And now the **GREEN LED on GPIO 17** should be blinking instead of the red one! - -🎉 **We successfully:** -1. Changed the printed value from 0 to 66 -2. Changed which LED blinks from red (GPIO 16) to green (GPIO 17) - ---- - -## 📚 Part 15: Deep Dive - GPIO at the Assembly Level - -### Understanding the GPIO Coprocessor - -The RP2350 has a special **GPIO coprocessor** that provides fast, single-cycle GPIO control. This is different from the RP2040! - -The coprocessor is accessed using special ARM instructions: - -```assembly -mcrr p0, #4, r4, r5, c0 ; GPIO output control -mcrr p0, #4, r4, r5, c4 ; GPIO direction control -``` - -**What this means:** -- `mcrr` = Move to Coprocessor from two ARM Registers -- `p0` = Coprocessor 0 (the GPIO coprocessor) -- `r4` = Contains the GPIO pin number -- `r5` = Contains the value (0 or 1) -- `c0` = Output value register -- `c4` = Output enable register - -### The Full GPIO Initialization Sequence - -When you call `gpio_init(16)`, here's what actually happens: - -``` -Step 1: Configure pad (address 0x40038044) -┌─────────────────────────────────────────────────────────────────┐ -│ - Clear OD bit (output disable) │ -│ - Set IE bit (input enable) │ -│ - Clear ISO bit (isolation) │ -└─────────────────────────────────────────────────────────────────┘ - -Step 2: Set function (address 0x40028084) -┌─────────────────────────────────────────────────────────────────┐ -│ - Set FUNCSEL to 5 (SIO - Software I/O) │ -└─────────────────────────────────────────────────────────────────┘ - -Step 3: Enable output (via coprocessor) -┌─────────────────────────────────────────────────────────────────┐ -│ - mcrr p0, #4, r4, r5, c4 (where r4=16, r5=1) │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### Raw Assembly LED Blink - -Here's what a completely hand-written assembly LED blink looks like: - -```assembly -; Initialize GPIO 16 as output -movs r4, #0x10 ; GPIO 16 -movs r5, #0x01 ; Enable -mcrr p0, #4, r4, r5, c4 ; Set as output - -; Configure pad registers -ldr r3, =0x40038044 ; Pad control for GPIO 16 -ldr r2, [r3] ; Load current config -bic r2, r2, #0x80 ; Clear OD (output disable) -orr r2, r2, #0x40 ; Set IE (input enable) -str r2, [r3] ; Store config - -; Set GPIO function to SIO -ldr r3, =0x40028084 ; IO bank control for GPIO 16 -movs r2, #5 ; FUNCSEL = SIO -str r2, [r3] ; Set function - -; Main loop -loop: - ; LED ON - movs r4, #0x10 ; GPIO 16 - movs r5, #0x01 ; High - mcrr p0, #4, r4, r5, c0 - - ; Delay - ldr r2, =0x17D7840 ; ~25 million iterations -delay1: - subs r2, r2, #1 - bne delay1 - - ; LED OFF - movs r4, #0x10 ; GPIO 16 - movs r5, #0x00 ; Low - mcrr p0, #4, r4, r5, c0 - - ; Delay - ldr r2, =0x17D7840 -delay2: - subs r2, r2, #1 - bne delay2 - - b loop ; Repeat forever -``` - ---- - -## 📊 Part 16: Summary and Review - -### What We Accomplished - -1. **Learned about variables** - How they're declared, initialized, and stored -2. **Understood memory sections** - `.data`, `.bss`, and `.rodata` -3. **Analyzed binaries in Ghidra** - Without debug symbols! -4. **Patched binaries** - Changed values directly in the binary -5. **Controlled GPIO** - Made LEDs blink -6. **Changed program behavior** - Different LED, different value - -### The Binary Patching Workflow - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 1. Import .bin file into Ghidra │ -│ - Set language to ARM Cortex │ -│ - Set base address to 0x10000000 │ -├─────────────────────────────────────────────────────────────────┤ -│ 2. Analyze and resolve functions │ -│ - Rename functions to meaningful names │ -│ - Fix function signatures │ -├─────────────────────────────────────────────────────────────────┤ -│ 3. Find the values/instructions to patch │ -│ - Look in the assembly listing │ -│ - Right-click → Patch Instruction │ -├─────────────────────────────────────────────────────────────────┤ -│ 4. Export the patched binary │ -│ - File → Export Program │ -│ - Format: Raw Bytes │ -├─────────────────────────────────────────────────────────────────┤ -│ 5. Convert to UF2 │ -│ - python uf2conv.py file.bin --base 0x10000000 │ -│ --family 0xe48bff59 --output hacked.uf2 │ -├─────────────────────────────────────────────────────────────────┤ -│ 6. Flash and verify │ -│ - Hold BOOTSEL, plug in, drag UF2 │ -│ - Check serial output and LED behavior │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### Key Memory Sections - -| Section | Location | Contains | Writable? | -| --------- | -------- | ------------------------------ | --------- | -| `.text` | Flash | Code | No | -| `.rodata` | Flash | Constants, strings | No | -| `.data` | RAM | Initialized globals | Yes | -| `.bss` | RAM | Uninitialized globals (zeroed) | Yes | - -### Important Ghidra Commands - -| Action | How To Do It | -| ----------------- | ------------------------------------- | -| Rename function | Right-click → Edit Function Signature | -| Patch instruction | Right-click → Patch Instruction | -| Export binary | File → Export Program → Raw Bytes | -| Go to address | Press 'G' and enter address | - ---- - -## ✅ Practice Exercises - -### Exercise 1: Change the Delay -The LED blinks every 500ms. Find the `sleep_ms(500)` calls in the binary and change them to `sleep_ms(100)` for faster blinking. - -**Hint:** Look for the value `0x1F4` (500 in hex) being loaded into a register. - -### Exercise 2: Reverse the LED -Instead of GPIO 16 → ON → OFF, make it GPIO 16 → OFF → ON (start with LED on). - -**Hint:** Find and swap the two `gpio_put` calls (the ones with values 0 and 1). - -### Exercise 3: Add a Second LED -Patch the binary so that BOTH GPIO 16 and GPIO 17 blink together. - -**Hint:** You'll need to find space for additional instructions or modify existing ones cleverly. - -### Exercise 4: Change the Format String -The program prints "age: %d\r\n". Can you find this string in Ghidra and figure out where it's stored? - -**Hint:** Look in the `.rodata` section around address `0x10001xxx`. - ---- - -## 🎓 Key Takeaways - -1. **Variables are just memory locations** - The compiler assigns them addresses in SRAM. - -2. **Compilers optimize aggressively** - Unused code and values may be removed entirely. - -3. **Uninitialized doesn't mean random** - Modern compilers zero out the `.bss` section. - -4. **Ghidra works without symbols** - You can analyze any binary, even stripped ones. - -5. **Binary patching is powerful** - You can change behavior without source code. - -6. **UF2 conversion is required** - The Pico 2 needs UF2 format, not raw binaries. - -7. **GPIO is just memory-mapped I/O** - Writing to specific addresses controls hardware. - ---- - -## 📖 Glossary - -| Term | Definition | -| ------------------ | --------------------------------------------------------------------- | -| **BSS** | Block Started by Symbol - section for uninitialized global variables | -| **Declaration** | Telling the compiler a variable's name and type | -| **Definition** | Allocating memory for a variable | -| **GPIO** | General Purpose Input/Output - controllable pins on a microcontroller | -| **Initialization** | Assigning an initial value to a variable | -| **Linker** | Tool that combines compiled code and assigns memory addresses | -| **Optimization** | Compiler removing or simplifying code for efficiency | -| **Patching** | Modifying bytes directly in a binary file | -| **rodata** | Read-only data section for constants and string literals | -| **SIO** | Single-cycle I/O - fast GPIO control block in RP2350 | -| **UF2** | USB Flashing Format - file format for Pico 2 firmware | -| **Variable** | A named storage location in memory | - ---- - -## 🔗 Additional Resources - -### GPIO Coprocessor Reference - -The RP2350 GPIO coprocessor instructions: - -| Instruction | Description | -| -------------------------- | ---------------------------- | -| `mcrr p0, #4, Rt, Rt2, c0` | Set/clear GPIO output | -| `mcrr p0, #4, Rt, Rt2, c4` | Set/clear GPIO output enable | - -### RP2350 Memory Map Quick Reference - -| Address | Description | -| ------------ | ------------------------ | -| `0x10000000` | XIP Flash (code) | -| `0x20000000` | SRAM (data) | -| `0x40028000` | IO_BANK0 (GPIO control) | -| `0x40038000` | PADS_BANK0 (pad control) | -| `0xd0000000` | SIO (single-cycle I/O) | - ---- - -**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Practice makes perfect! - -Happy hacking! 🔧 diff --git a/WEEK04/slides/WEEK04-IMG00.svg b/WEEK04/slides/WEEK04-IMG00.svg new file mode 100644 index 0000000..2b2aae1 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 04 + + +Variables in Embedded Systems: +Debugging and Hacking Variables +w/ GPIO Output Basics + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK04/slides/WEEK04-IMG01.svg b/WEEK04/slides/WEEK04-IMG01.svg new file mode 100644 index 0000000..5331adf --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG01.svg @@ -0,0 +1,96 @@ + + + + + +What is a Variable? +Labeled Boxes in Memory (SRAM) + + + +Memory — A Row of Numbered Boxes + + + +42 +age +Box 0 + + + +17 +score +Box 1 + + + +0 +count +Box 2 + + + +255 +max +Box 3 + + + +99 +temp +Box 4 + + + +Anatomy of a Declaration + + +uint8_t age = 42; + +uint8_t +Data type (1 byte) + +age +Variable name (label) + += 42 +Initial value + +; +End of statement + + + +Key Concepts + + +Declaration +name + type + + +Definition +allocates memory + + +Initialization +assigns value + + + +Important Rule +You MUST declare a +variable BEFORE you +use it! +Compiler needs to know the type + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG02.svg b/WEEK04/slides/WEEK04-IMG02.svg new file mode 100644 index 0000000..8933406 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG02.svg @@ -0,0 +1,86 @@ + + + + + +Data Types & Sizes +How Much Memory Each Type Uses + + + + + + +Type +Size +Range +Description + + + +uint8_t +1 byte +0 — 255 +Unsigned 8-bit + + + +int8_t +1 byte +-128 — 127 +Signed 8-bit + + + +uint16_t +2 bytes +0 — 65,535 +Unsigned 16-bit + + + +int16_t +2 bytes +-32,768 — 32,767 +Signed 16-bit + + + +uint32_t +4 bytes +0 — 4,294,967,295 +Unsigned 32-bit + + + +int32_t +4 bytes +-2.1B — 2.1B +Signed 32-bit + + +Size Comparison + + +1B +uint8_t + + +2B +uint16_t + + +4 Bytes +uint32_t + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG03.svg b/WEEK04/slides/WEEK04-IMG03.svg new file mode 100644 index 0000000..0cbcf5a --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG03.svg @@ -0,0 +1,63 @@ + + + + + +Memory Sections +Where Variables Live After Compilation + + + + +.data +Flash -> copied to RAM at startup +Contains: Initialized global/static variables +int counter = 42; +Initial value stored in flash, copied to SRAM by data_cpy + + + + +.bss +RAM — zeroed at startup +Contains: Uninitialized global/static variables +int counter; +NOT stored in binary (saves space!) — memset to 0 at boot + + + + +.rodata +Flash — read only +Contains: Constants and string literals +const int MAX = 100; +Lives in flash permanently — cannot be modified at runtime + + + +.data +RAM +Writable +Initialized globals + +.bss +RAM +Writable +Uninitialized globals (zeroed) + +.rodata +Flash +Read-only +Constants & strings + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG04.svg b/WEEK04/slides/WEEK04-IMG04.svg new file mode 100644 index 0000000..82437d2 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG04.svg @@ -0,0 +1,79 @@ + + + + + +GPIO Basics +General Purpose Input/Output on RP2350 + + + +Pico 2 GPIO Pins + +GPIO 16 + +Red LED + +GPIO 17 + +Green LED + +GPIO 18 + +Blue LED + +GPIO 25 + +Onboard LED + +Software-controlled switches + + + +Pico SDK Functions + + +gpio_init(pin) +Init pin + + +gpio_set_dir(pin,d) +I/O dir + + +gpio_put(pin,val) +Set H/L + + +sleep_ms(ms) +Delay + + + +Basic LED Blink Code + + +#define LED_PIN 16 +int main(void) { +gpio_init(LED_PIN); +gpio_set_dir(LED_PIN, GPIO_OUT); +while (true) { +gpio_put(LED_PIN, 1); +// ON +sleep_ms(500); +gpio_put(LED_PIN, 0); +// OFF +sleep_ms(500); +}} + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG05.svg b/WEEK04/slides/WEEK04-IMG05.svg new file mode 100644 index 0000000..c6a0ef6 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG05.svg @@ -0,0 +1,79 @@ + + + + + +Ghidra Binary Analysis +Analyzing a Raw .bin Without Symbols + + + +1. Import + +File -> Import +Language: +ARM Cortex 32 LE +Block: +.text +Base: +10000000 +XIP address for RP2350 + + + +2. Analyze + +Auto-Analyze: Yes +Ghidra finds: +FUN_1000019a +FUN_10000210 +FUN_10000234 +Auto-generated names + + + +3. Resolve + +Edit Function Sig +Rename to: +data_cpy +frame_dummy +main +Fix signatures + + + +Decompiled main() in Ghidra + + +Before Resolving: +void FUN_10000234(void){ +FUN_10002f54(); +do { +FUN_100030e4( +DAT_10000244,0x2b); +} while(true); +} + + +After Resolving: +int main(void) { +stdio_init_all(); +do { +printf( +"age: %d\r\n" +, 0x2b); +} while(true); +} + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG06.svg b/WEEK04/slides/WEEK04-IMG06.svg new file mode 100644 index 0000000..156aeeb --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG06.svg @@ -0,0 +1,77 @@ + + + + + +Compiler Optimization +Why Your Variable Disappeared + + + +Source Code + + +int main(void) { +uint8_t age = 42; +age = 43; +stdio_init_all(); +while (true) +printf("age: %d", age); + + + +Compiler Thinks... + + +age = 42 is NEVER read + + +Dead store -> REMOVED + + +age = 43 -> constant fold + +Replaces variable with literal + + + +Resulting Assembly + + +1000023a +2b 21 +movs r1, #0x2b +; 0x2b = 43 +No age=42 instruction — compiler removed it + + + +Key Takeaway + + +Source Code +age = 42 +age = 43 + +-> + + +Binary +movs r1, #0x2b + + +Compiler +Optimizes dead +stores away! + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG07.svg b/WEEK04/slides/WEEK04-IMG07.svg new file mode 100644 index 0000000..2041444 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG07.svg @@ -0,0 +1,86 @@ + + + + + +Binary Patching +Changing Values in the Binary + + + +Before Patch + + +1000023a +2b 21 +movs r1,#0x2b + +0x2b = 43 decimal +Output: +age: 43 +Compiler-optimized constant + + + +After Patch + + +1000023a +46 21 +movs r1,#0x46 + +0x46 = 70 decimal +Output: +age: 70 +Changed program behavior! + + + +How to Patch in Ghidra + + +1. Find Instr + +-> + + +2. Rt-Click + +-> + + +3. Patch Val + +-> + + +Done! + +Patch Instruction: change operand + + + +Export Patched Binary + + +File: Export + + +Format: Raw Bytes + + +Save as *-h.bin + +Exported binary has your patches + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG08.svg b/WEEK04/slides/WEEK04-IMG08.svg new file mode 100644 index 0000000..eada6e7 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG08.svg @@ -0,0 +1,99 @@ + + + + + +GPIO Hacking +Patching GPIO 16 to GPIO 17 + + + +Original: GPIO 16 +Red LED on pin 16 + + +1000023a +10 20 +movs r0,#0x10 + + +10000244 +10 23 +movs r3,#0x10 + + +10000252 +10 24 +movs r4,#0x10 + +0x10 = 16, three locations + + + +Patched: GPIO 17 +Green LED on pin 17 + + +1000023a +11 20 +movs r0,#0x11 + + +10000244 +11 23 +movs r3,#0x11 + + +10000252 +11 24 +movs r4,#0x11 + +0x11 = 17, all patched! + + + +What Each Patch Controls + + +gpio_init +r0 + + +gpio_set_dir +r3 + + +gpio_put +r4 + +ALL pin refs must be patched + + + +Bonus: Change Print Value + + +00 21 +movs r1,#0x0 +age: 0 + +-> + + +42 21 +movs r1,#0x42 +age: 66 + +Changed value: 0 to 66 (0x42) + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG09.svg b/WEEK04/slides/WEEK04-IMG09.svg new file mode 100644 index 0000000..6743432 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG09.svg @@ -0,0 +1,78 @@ + + + + + +GPIO Coprocessor +RP2350 Single-Cycle I/O via mcrr + + + +mcrr Instruction Breakdown + + +mcrr p0, #4, r4, r5, c0 + +mcrr +Move to Coprocessor (2 regs) +p0 +Coprocessor 0 (GPIO) +r4 +GPIO pin number +r5 +Value (0=LOW, 1=HIGH) + + + +Output Value (c0) + + +mcrr p0,#4,r4,r5,c0 + +r4 = pin number +r5 = 0 or 1 +Controls GPIO output state + + +Output Enable (c4) + + +mcrr p0,#4,r4,r5,c4 + +r4 = pin number +r5 = 1 (enable output) +Sets pin direction to OUTPUT + + + +gpio_init(16) Sequence + + +Step 1: Config Pad +addr 0x40038044 + + +Step 2: Set Func +FUNCSEL = 5 (SIO) + + +Step 3: Enable Out +mcrr p0,#4,r4,r5,c4 + + + + +Pad: clear OD, set IE, clear ISO +SIO = fast single-cycle GPIO access + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG10.svg b/WEEK04/slides/WEEK04-IMG10.svg new file mode 100644 index 0000000..23741fe --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG10.svg @@ -0,0 +1,119 @@ + + + + + +Full Patching Pipeline +End-to-End Binary Hacking Workflow + + + + +1 +Import .bin +Ghidra: Import +ARM Cortex 32 LE +Base: 0x10000000 + + + + + + + +2 +Analyze +Auto-analyze +Rename functions +Fix signatures + + + + + + + +3 +Find Target +Listing window +Find movs rN,#val +Identify bytes to change + + + + +4 +Patch +Right-click: +Patch Instruction +Change operand value + + + + + + + +5 +Export +File: Export +Format: Raw Bytes +Save as *-h.bin + + + + + + + +6 +Convert UF2 +uf2conv.py +--family 0xe48bff59 +RP2350 family ID + + + +UF2 Command + +python uf2conv.py file.bin --base 0x10000000 -o hacked.uf2 + + + +Flash to Pico 2 +1. Hold BOOTSEL + USB +2. Drop hacked.uf2 +3. Pico reboots hacked +RPI-RP2 drive in BOOTSEL + + + +Key Sections + +.text +Flash +Code + +.rodata +Flash +Constants + +.data +RAM +Init globals + +.bss +RAM +Zeroed globals + \ No newline at end of file diff --git a/WEEK05/WEEK05-01-S.md b/WEEK05/WEEK05-01-S.md deleted file mode 100644 index 59eb48f..0000000 --- a/WEEK05/WEEK05-01-S.md +++ /dev/null @@ -1,63 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 5 -Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis - -### Non-Credit Practice Exercise 1 Solution: Analyze the Float Binary in Ghidra - -#### Answers - -##### Main Function Analysis - -| Item | Value | Notes | -|--------------------------|------------------------|------------------------------------| -| Main function address | 0x10000234 | Entry point of program | -| Float value (original) | 42.5 | Declared as `float` | -| Double hex encoding | 0x4045400000000000 | Promoted to double for printf | -| r3 (high word) | 0x40454000 | Sign + exponent + top mantissa | -| r2 (low word) | 0x00000000 | All zeros (clean fractional part) | -| Exponent (stored) | 1028 | Biased value | -| Exponent (real) | 5 | After subtracting bias 1023 | -| Format string | "fav_num: %f\r\n" | Located at 0x100034a8 | -| stdio_init_all address | 0x10002f5c | I/O initialization | -| printf address | 0x100030ec | Standard library function | - -##### IEEE 754 Decoding of 0x4045400000000000 - -``` -r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 -r2 = 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000 - -Sign bit (bit 63): 0 → Positive -Exponent (bits 62-52): 10000000100 = 1028 → 1028 - 1023 = 5 -Mantissa (bits 51-0): 0101010000...0 → 1.010101 (with implied 1) - -Value = 1.010101₂ × 2⁵ = 101010.1₂ = 32 + 8 + 2 + 0.5 = 42.5 ✓ -``` - -##### Decompiled main() After Renaming - -```c -int main(void) -{ - stdio_init_all(); - do { - __wrap_printf("fav_num: %f\r\n", /* r2:r3 = 0x4045400000000000 = 42.5 */); - } while (true); -} -``` - -#### Reflection Answers - -1. **Why does the compiler promote a `float` to a `double` when passing it to `printf`?** - The C standard (§6.5.2.2) specifies **default argument promotions** for variadic functions like `printf`. When a `float` is passed to a variadic parameter (the `...` part), it is automatically promoted to `double`. This is because historically, floating-point hardware and calling conventions operated more efficiently with double precision. The `printf` function with `%f` always expects a 64-bit `double` on the stack or in the register pair `r2:r3`, never a 32-bit `float`. - -2. **The low word (`r2`) is `0x00000000`. What does this tell you about the fractional part of `42.5`?** - It means the fractional part of 42.5 can be represented exactly with very few mantissa bits. The value 0.5 is exactly 2⁻¹ in binary—a single bit. After normalization, the mantissa is `010101000...` which only needs 6 significant bits. All remaining 46 bits (including the entire low 32-bit word) are zero. Values like 0.5, 0.25, 0.125 (negative powers of 2) and their sums always produce clean low words, while values like 0.1 or 0.3 produce repeating binary fractions that fill both words. - -3. **What is the purpose of the exponent bias (1023) in IEEE 754 double-precision?** - The bias allows the exponent field to represent both positive and negative exponents using only unsigned integers. The 11-bit exponent field stores values 0–2047. By subtracting the bias (1023), the actual exponent range is −1022 to +1023. This avoids needing a separate sign bit for the exponent and simplifies hardware comparison—doubles can be compared as unsigned integers (for positive values) because larger exponents produce larger bit patterns. The bias value 1023 = 2¹⁰ − 1 is chosen to center the range symmetrically. - -4. **If the sign bit (bit 63) were `1` instead of `0`, what value would the double represent?** - The value would be **−42.5**. The sign bit in IEEE 754 is independent of all other fields: flipping bit 63 from 0 to 1 simply negates the value. The hex encoding would change from `0x4045400000000000` to `0xC045400000000000`—only the most significant nibble changes from `4` (`0100`) to `C` (`1100`), with bit 31 of r3 changing from 0 to 1. diff --git a/WEEK05/WEEK05-01.md b/WEEK05/WEEK05-01.md deleted file mode 100644 index 1122c79..0000000 --- a/WEEK05/WEEK05-01.md +++ /dev/null @@ -1,271 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 5 -Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis - -### Non-Credit Practice Exercise 1: Analyze the Float Binary in Ghidra - -#### Objective -Import and analyze the `0x000e_floating-point-data-type.bin` binary in Ghidra to understand how the compiler handles floating-point variables, discover float-to-double promotion, and decode the IEEE 754 double-precision encoding of `42.5` from two 32-bit registers. - -#### Prerequisites -- Ghidra installed and configured -- `0x000e_floating-point-data-type.bin` binary available in your build directory -- Understanding of IEEE 754 encoding from Week 5 Part 2 -- Basic Ghidra navigation skills from Weeks 3 and 4 - -#### Task Description -You will import the float binary into Ghidra, configure it for ARM Cortex-M33, resolve function names, discover that the compiler promotes `float` to `double` when passing to `printf`, and manually decode the 64-bit double `0x4045400000000000` field by field to confirm it represents `42.5`. - -#### Step-by-Step Instructions - -##### Step 1: Start Ghidra and Create New Project - -```powershell -ghidraRun -``` - -1. Click **File** ? **New Project** -2. Select **Non-Shared Project** -3. Click **Next** -4. Enter Project Name: `week05-ex01-floating-point` -5. Choose a project directory -6. Click **Finish** - -##### Step 2: Import the Binary - -1. Navigate to your file explorer -2. Find `Embedded-Hacking\0x000e_floating-point-data-type\build\0x000e_floating-point-data-type.bin` -3. **Drag and drop** the `.bin` file into Ghidra's project window - -##### Step 3: Configure Import Settings - -When the import dialog appears: - -1. Click the three dots (****) next to **Language** -2. Search for: `Cortex` -3. Select: **ARM Cortex 32 little endian default** -4. Click **OK** - -Now click **Options** button: -1. Change **Block Name** to: `.text` -2. Change **Base Address** to: `10000000` (XIP flash base) -3. Click **OK** - -Then click **OK** on the main import dialog. - -##### Step 4: Analyze the Binary - -1. Double-click the imported file in the project window -2. When prompted "Analyze now?" click **Yes** -3. Leave all default analysis options selected -4. Click **Analyze** -5. Wait for analysis to complete (watch bottom-right progress bar) - -##### Step 5: Locate and Rename the Main Function - -Look at the **Symbol Tree** panel on the left. Expand **Functions**. - -From previous weeks, we know the boot sequence leads to `main()`: - -1. Click on `FUN_10000234` -2. Right-click ? **Edit Function Signature** -3. Change to: `int main(void)` -4. Click **OK** - -##### Step 6: Resolve stdio_init_all and printf - -**Rename stdio_init_all:** -1. Click on `FUN_10002f5c` in the decompile window -2. Right-click ? **Edit Function Signature** -3. Change to: `bool stdio_init_all(void)` -4. Click **OK** - -**Rename printf:** -1. Click on `FUN_100030ec` -2. Right-click ? **Edit Function Signature** -3. Change to: `int __wrap_printf(char *format,...)` -4. Check the **Varargs** checkbox -5. Click **OK** - -##### Step 7: Observe the Decompiled Code - -After resolving, the decompiled `main` should look like: - -```c -int main(void) -{ - undefined4 uVar1; - undefined4 extraout_r1; - undefined4 uVar2; - undefined4 extraout_r1_00; - - stdio_init_all(); - uVar1 = DAT_1000024c; - uVar2 = extraout_r1; - do { - __wrap_printf(DAT_10000250,uVar2,0,uVar1); - uVar2 = extraout_r1_00; - } while( true ); -} -``` - -**Critical observation:** Where is `float fav_num = 42.5`? The compiler optimized it into constants! - -##### Step 8: Identify the Register Pair - -Look at the **Listing** window (assembly view) for `main`: - -```assembly -1000023a 00 24 movs r4, #0x0 -1000023c 03 4d ldr r5, [DAT_1000024c] = 40454000h -``` - -Two values are being passed to `printf`: -- `r2 = 0x00000000` (low 32 bits) -- `r3 = 0x40454000` (high 32 bits) - -Together they form a 64-bit double: `0x4045400000000000` - -**Why a double?** The C standard requires that `float` arguments to variadic functions like `printf` are **promoted to `double`**. So even though our variable is declared as `float fav_num = 42.5`, `printf` always receives a 64-bit double. - -##### Step 9: Write Out the Binary Layout - -Convert both registers to binary: - -``` -r3 (high 32 bits): 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 -r2 (low 32 bits): 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000 -``` - -Map the 64-bit IEEE 754 fields: - -``` -Bit 63 (sign): 0 -Bits 6252 (exponent): 10000000100 -Bits 510 (mantissa): 0101010000000000...0000 -``` - -##### Step 10: Decode the Sign Bit - -Bit 63 of the double = bit 31 of r3: - -``` -r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 - ^ - bit 31 = 0 ? Positive number -``` - -IEEE 754 sign rule: `0` = Positive, `1` = Negative. - -##### Step 11: Decode the Exponent - -Extract bits 3020 from r3: - -``` -0x40454000: 0 10000000100 01010100000000000000 - ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ - sign exponent mantissa (top 20) -``` - -Exponent bits: `10000000100` = 2 + 2 = 1024 + 4 = **1028** - -Subtract the double-precision bias (1023): - -$$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ - -##### Step 12: Decode the Mantissa - -High 20 bits of mantissa (from r3 bits 190): -``` -0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -``` - -Low 32 bits of mantissa (from r2): -``` -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -``` - -With the implied leading `1`: -``` -1.010101 00000... -``` - -##### Step 13: Reconstruct the Final Value - -$$1.010101_2 \times 2^5 = 101010.1_2$$ - -Convert to decimal: - -| Bit | Power | Value | -|-----|-------|-------| -| 1 | 25 | 32 | -| 0 | 24 | 0 | -| 1 | 2 | 8 | -| 0 | 2 | 0 | -| 1 | 2 | 2 | -| 0 | 2 | 0 | -| 1 | 2? | 0.5 | - -$$32 + 8 + 2 + 0.5 = \mathbf{42.5} ?$$ - -##### Step 14: Find the Format String - -In the Listing view, click on the data reference to locate: - -``` -s_fav_num:_%f_100034a8 ds "fav_num: %f\r\n" -``` - -Note that the format specifier is `%f`, confirming this is a floating-point print call. - -##### Step 15: Document Your Findings - -Create a table of your observations: - -| Item | Value | Notes | -| ------------------------- | ------------------ | ---------------------------------- | -| Main function address | `0x10000234` | Entry point of program | -| Float value (original) | `42.5` | Declared as `float` | -| Double hex encoding | `0x4045400000000000` | Promoted to double for printf | -| r3 (high word) | `0x40454000` | Contains sign + exponent + mantissa top bits | -| r2 (low word) | `0x00000000` | All zeros clean fractional part | -| Exponent (stored) | 1028 | Biased value | -| Exponent (real) | 5 | After subtracting bias 1023 | -| Format string | `"fav_num: %f\r\n"` | Located at `0x100034a8` | -| Float-to-double promotion | Yes | C standard for variadic functions | - -#### Expected Output - -After completing this exercise, you should be able to: -- Import and configure ARM binaries in Ghidra for float analysis -- Explain why `printf` receives a `double` even when the variable is a `float` -- Identify the register pair `r2:r3` that holds a 64-bit double -- Manually decode an IEEE 754 double from hex to decimal -- Locate format strings in the binary - -#### Questions for Reflection - -###### Question 1: Why does the compiler promote a `float` to a `double` when passing it to `printf`? - -###### Question 2: The low word (`r2`) is `0x00000000`. What does this tell you about the fractional part of `42.5`? - -###### Question 3: What is the purpose of the exponent bias (1023) in IEEE 754 double-precision? - -###### Question 4: If the sign bit (bit 63) were `1` instead of `0`, what value would the double represent? - -#### Tips and Hints -- The **Listing** window shows raw assembly; the **Decompile** window shows reconstructed C -- Double-click on a `DAT_` reference to jump to the data constant -- Use Python to verify: `import struct; struct.pack('>d', 42.5).hex()` gives `4045400000000000` -- Remember: r3 = high 32 bits (sign + exponent + top mantissa), r2 = low 32 bits (bottom mantissa) -- The bias for doubles is always 1023; for floats it's 127 - -#### Next Steps -- Proceed to Exercise 2 to patch this float value in Ghidra -- Try computing the IEEE 754 encoding of other values like `3.14` or `100.0` by hand -- Compare the 32-bit float encoding `0x422A0000` with the 64-bit double encoding `0x4045400000000000` both represent `42.5` - -#### Additional Challenge -Find the data constant `DAT_1000024c` in the Listing view. What raw bytes are stored there? Remember that ARM is little-endian the bytes in memory are in reverse order. Write out the byte order as it appears in memory vs. as a 32-bit value. diff --git a/WEEK05/WEEK05-02-S.md b/WEEK05/WEEK05-02-S.md deleted file mode 100644 index deafe4b..0000000 --- a/WEEK05/WEEK05-02-S.md +++ /dev/null @@ -1,73 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 5 -Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis - -### Non-Credit Practice Exercise 2 Solution: Patch the Float Binary — Changing 42.5 to 99.0 - -#### Answers - -##### IEEE 754 Encoding of 99.0 - -``` -Integer: 99 = 1100011₂ -Fractional: .0 = .0₂ -Combined: 1100011.0₂ -Normalized: 1.100011₂ × 2⁶ - -Sign: 0 (positive) -Exponent: 6 + 1023 = 1029 = 10000000101₂ -Mantissa: 100011 followed by 46 zeros - -Full double: 0x4058C00000000000 -``` - -##### Patch Summary - -| Register | Old Value (42.5) | New Value (99.0) | Changed? | -|----------|-----------------|------------------|----------| -| r2 | 0x00000000 | 0x00000000 | No | -| r3 | 0x40454000 | 0x4058C000 | **Yes** | - -##### Ghidra Patch - -``` -DAT_1000024c: - Before (little-endian): 00 40 45 40 → 0x40454000 - After (little-endian): 00 C0 58 40 → 0x4058C000 -``` - -##### Serial Output - -``` -fav_num: 99.000000 -fav_num: 99.000000 -fav_num: 99.000000 -... -``` - -#### Reflection Answers - -1. **Why did we only need to patch r3 (the high word) and not r2 (the low word)?** - Both 42.5 and 99.0 have "clean" fractional parts that can be exactly represented with few mantissa bits. For 42.5, the mantissa is `010101000...0`; for 99.0, it's `100011000...0`. In both cases, all significant mantissa bits fit within the top 20 bits (stored in r3 bits 19–0), leaving the bottom 32 bits (r2) as all zeros. Only values with complex or repeating binary fractions (like 42.52525 or 99.99) need non-zero low words. - -2. **What would the high word be if we wanted to patch the value to `-99.0` instead?** - Flip bit 31 of r3 (the sign bit). The current r3 = `0x4058C000` = `0100 0000 0101 1000 1100...`. Setting bit 31 to 1: `0xC058C000` = `1100 0000 0101 1000 1100...`. The full double encoding of −99.0 is `0xC058C00000000000`. Only the most significant nibble changes from `4` to `C`. - -3. **Walk through the encoding of `100.0` as a double. What are the high and low words?** - ``` - 100 = 1100100₂ - 100.0 = 1100100.0₂ = 1.1001₂ × 2⁶ - Sign: 0 - Exponent: 6 + 1023 = 1029 = 10000000101₂ - Mantissa: 1001 followed by 48 zeros - - Full 64-bit: 0 10000000101 1001000000...0 - High word (r3): 0x40590000 - Low word (r2): 0x00000000 - ``` - Verification: `struct.pack('>d', 100.0).hex()` → `4059000000000000` ✓ - -4. **Why do we need the `--family 0xe48bff59` flag when converting to UF2?** - The `--family` flag specifies the target chip family in the UF2 file header. `0xe48bff59` is the registered family ID for the RP2350. The bootloader reads this field to verify the firmware is intended for the correct chip before flashing. If the family ID doesn't match (e.g., using the RP2040 ID `0xe48bff56`), the bootloader may reject the firmware or write it incorrectly. This prevents accidentally flashing RP2040 firmware onto an RP2350 (or vice versa), which could cause undefined behavior since the chips have different architectures (Cortex-M0+ vs Cortex-M33). diff --git a/WEEK05/WEEK05-02.md b/WEEK05/WEEK05-02.md deleted file mode 100644 index 83868cf..0000000 --- a/WEEK05/WEEK05-02.md +++ /dev/null @@ -1,173 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 5 -Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis - -### Non-Credit Practice Exercise 2: Patch the Float Binary — Changing 42.5 to 99.0 - -#### Objective -Calculate the IEEE 754 double-precision encoding of `99.0`, patch the float binary in Ghidra to change the printed value from `42.5` to `99.0`, export the patched binary, convert it to UF2 format, and flash it to the Pico 2 to verify the change. - -#### Prerequisites -- Completed Exercise 1 (float binary imported and analyzed in Ghidra) -- Understanding of IEEE 754 double-precision encoding from Week 5 Part 2.7 -- Python installed for UF2 conversion -- `uf2conv.py` script available in your project directory -- Raspberry Pi Pico 2 connected via USB -- Serial monitor software (PuTTY, minicom, or screen) - -#### Task Description -You will convert `99.0` to its IEEE 754 double-precision encoding by hand (integer-to-binary conversion, normalization, field extraction), determine which register words need to change, patch the data constant in Ghidra, and verify on hardware that the serial output now prints `99.000000`. - -#### Step-by-Step Instructions - -##### Step 1: Convert 99 to Binary - -Use repeated division by 2: - -| Division | Quotient | Remainder | -|----------|----------|-----------| -| 99 ÷ 2 | 49 | **1** | -| 49 ÷ 2 | 24 | **1** | -| 24 ÷ 2 | 12 | **0** | -| 12 ÷ 2 | 6 | **0** | -| 6 ÷ 2 | 3 | **0** | -| 3 ÷ 2 | 1 | **1** | -| 1 ÷ 2 | 0 | **1** | - -Read remainders bottom-to-top: $99_{10} = 1100011_2$ - -##### Step 2: Handle the Fractional Part - -The fractional part of `99.0` is `.0` — exactly zero. There are no fractional bits. - -$$99.0_{10} = 1100011.0_2$$ - -##### Step 3: Normalize to IEEE 754 Form - -Move the binary point so there is exactly one `1` before it: - -$$1100011.0_2 = 1.100011_2 \times 2^6$$ - -We shifted the binary point 6 places left, so the exponent is **6**. - -##### Step 4: Extract the IEEE 754 Fields - -1. **Sign:** `0` (positive) -2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$ -3. **Mantissa:** `100011` followed by 46 zeros (everything after the `1.`, padded to 52 bits) -4. **Full double:** `0x4058C00000000000` - -Split into register words: -- **r3 (high word):** `0x4058C000` -- **r2 (low word):** `0x00000000` - -##### Step 5: Determine What to Patch - -Compare old vs. new: - -| Register | Old Value | New Value | Changed? | -| -------- | ------------ | ------------ | -------- | -| `r2` | `0x00000000` | `0x00000000` | No | -| `r3` | `0x40454000` | `0x4058C000` | **Yes** | - -Since `r2` stays all zeros, we only need to patch the high word in `r3`. - -##### Step 6: Locate the Data Constant in Ghidra - -Open your Ghidra project from Exercise 1. In the Listing view, find the data constant that loads into `r3`: - -``` - DAT_1000024c -10000248 00 40 45 40 undefined4 40454000h -``` - -This is the 32-bit constant `0x40454000` — the high word of the double `42.5`. - -##### Step 7: Patch the Constant - -1. Click on **Window** → **Bytes** to open the Bytes Editor -2. Click the **Pencil Icon** to enable editing -3. Navigate to the data at `DAT_1000024c` -4. The bytes in memory (little-endian) read: `00 40 45 40` -5. Change them to: `00 C0 58 40` (which is `0x4058C000` in little-endian) -6. Press **Enter** - -**Verify** in the Listing view — the data should now show `4058C000h`. - -##### Step 8: Verify the Patch in the Decompile Window - -The decompiled code should now reference the new constant. The value loaded into `r3` should be `0x4058C000`. - -##### Step 9: Export the Patched Binary - -1. Click **File** → **Export Program** -2. Set **Format** to **Raw Bytes** -3. Navigate to your build directory -4. Name the file: `0x000e_floating-point-data-type-h.bin` -5. Click **OK** - -##### Step 10: Convert to UF2 Format - -Open a terminal and navigate to your project directory: - -```powershell -cd Embedded-Hacking-main\0x000e_floating-point-data-type -``` - -Run the conversion: - -```powershell -python ..\uf2conv.py build\0x000e_floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 11: Flash and Verify - -1. Hold **BOOTSEL** and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive -3. Open your serial monitor - -**Expected output:** - -``` -fav_num: 99.000000 -fav_num: 99.000000 -fav_num: 99.000000 -... -``` - -🎉 **Success!** The value changed from `42.5` to `99.0`! - -#### Expected Output - -After completing this exercise, you should: -- See `fav_num: 99.000000` printing instead of `fav_num: 42.500000` -- Have a patched binary file (`0x000e_floating-point-data-type-h.bin`) -- Have a UF2 file (`hacked.uf2`) -- Understand the complete IEEE 754 encoding and patching workflow for floats - -#### Questions for Reflection - -###### Question 1: Why did we only need to patch r3 (the high word) and not r2 (the low word)? - -###### Question 2: What would the high word be if we wanted to patch the value to `-99.0` instead? (Hint: which bit controls the sign?) - -###### Question 3: Walk through the encoding of `100.0` as a double. What are the high and low words? - -###### Question 4: Why do we need the `--family 0xe48bff59` flag when converting to UF2? - -#### Tips and Hints -- Always verify your encoding with Python: `import struct; struct.pack('>d', 99.0).hex()` -- Little-endian means the bytes in memory are reversed: `0x4058C000` is stored as `00 C0 58 40` -- If you patch the wrong bytes, use **File** → **Undo** in Ghidra (or re-import the original binary) -- The bias for double-precision is always 1023; for single-precision it's 127 -- A clean fractional part (like `.0` or `.5`) means the low word (`r2`) is all zeros - -#### Next Steps -- Proceed to Exercise 3 to analyze the double-precision binary -- Try patching to different values: `3.14`, `100.0`, `255.0` -- Compare how many register words change for clean vs. messy fractions - -#### Additional Challenge -Patch the float to `3.14` instead of `99.0`. Since `3.14` has a repeating binary fraction, the low word will **not** be zero. This means you need to modify the assembly to load a non-zero value into `r2` as well. Can you figure out where `r2` gets its zero value (`movs r4, #0x0`) and change it? diff --git a/WEEK05/WEEK05-03-S.md b/WEEK05/WEEK05-03-S.md deleted file mode 100644 index aa3fd6a..0000000 --- a/WEEK05/WEEK05-03-S.md +++ /dev/null @@ -1,70 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 5 -Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis - -### Non-Credit Practice Exercise 3 Solution: Analyze the Double Binary in Ghidra - -#### Answers - -##### Register Pair for 42.52525 - -| Register | Value | Role | -|----------|-------------|---------------| -| r2 | 0x645A1CAC | Low 32 bits | -| r3 | 0x4045433B | High 32 bits | - -Full double: **0x4045433B645A1CAC** - -##### IEEE 754 Decoding - -``` -r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 -r2 = 0x645A1CAC = 0110 0100 0101 1010 0001 1100 1010 1100 - -Sign bit (bit 63): 0 → Positive -Exponent (bits 62-52): 10000000100 = 1028 → 1028 - 1023 = 5 -Mantissa (bits 51-0): 0101010000110011101101100100010110100001110010101100 - -Value = 1.0101010000110011...₂ × 2⁵ = 101010.10000110011...₂ -Integer part: 101010₂ = 32 + 8 + 2 = 42 -Fractional part: .10000110011... ≈ 0.52525 -Result: 42.52525 ✓ -``` - -##### Float vs Double Comparison - -| Item | Float (42.5) | Double (42.52525) | -|--------------------------|---------------------|------------------------| -| r2 (low word) | 0x00000000 | 0x645A1CAC | -| r3 (high word) | 0x40454000 | 0x4045433B | -| Low word is zero? | Yes | **No** | -| Words to patch | 1 (r3 only) | **2 (both r2 and r3)** | -| Format specifier | %f | %lf | -| Assembly load instruction| movs + ldr | ldrd (load double) | - -##### Key Assembly - -```assembly -10000238 push {r3,r4,r5,lr} -1000023a adr r5, [0x10000254] -1000023c ldrd r4, r5, [r5, #0x0] ; r4 = 0x645A1CAC, r5 = 0x4045433B -10000240 bl stdio_init_all -10000244 mov r2, r4 ; r2 = low word -10000246 mov r3, r5 ; r3 = high word -``` - -#### Reflection Answers - -1. **Why does `42.5` have a zero low word but `42.52525` does not?** - The fractional part determines whether the low word is zero. 0.5 in binary is exactly 2⁻¹ = `0.1₂`—a single bit. After normalization, the mantissa for 42.5 is `010101000...0`, needing only 6 significant bits, all fitting in the top 20 mantissa bits within r3. In contrast, 0.52525 is a **repeating binary fraction** that cannot be represented exactly—it requires all 52 mantissa bits to approximate as closely as possible. The lower 32 bits in r2 (`0x645A1CAC`) carry the additional precision needed for this approximation. - -2. **The assembly uses `ldrd r4, r5, [r5, #0x0]` instead of two separate `ldr` instructions. What is the advantage?** - `ldrd` (Load Register Double) loads two consecutive 32-bit words from memory in a single instruction, completing in one memory access cycle (or two back-to-back aligned accesses on the bus). Using two separate `ldr` instructions would require two instruction fetches, two decode cycles, and two memory accesses. `ldrd` reduces code size by 4 bytes (one 4-byte instruction vs. two) and improves performance by allowing the memory controller to pipeline both loads. For 64-bit doubles that are always loaded in pairs, `ldrd` is the optimal choice. - -3. **Both the float and double programs have the same exponent (stored as 1028, real exponent 5). Why?** - Both 42.5 and 42.52525 fall in the same range: between 32 (2⁵) and 64 (2⁶). Normalization produces `1.xxx × 2⁵` for both values. The exponent is determined solely by the magnitude (which power of 2 the number falls between), not by the fractional precision. Any number from 32.0 to 63.999... would have real exponent 5 (stored as 1028). The mantissa captures the differences—42.5 has mantissa `010101000...` while 42.52525 has `0101010000110011...`. - -4. **If you were patching this double, how many data constants would you need to modify compared to the float exercise?** - **Two** data constants—both `DAT_10000254` (low word, r2) and `DAT_10000258` (high word, r3). In the float exercise (42.5), only one constant needed patching because r2 was zero and stayed zero when patching to 99.0. For the double 42.52525, since the low word is already non-zero (`0x645A1CAC`), any new value with a different repeating fraction will require changing both words. The only exception would be patching to a value whose low word happens to also be `0x645A1CAC` (virtually impossible for an arbitrary target value). diff --git a/WEEK05/WEEK05-03.md b/WEEK05/WEEK05-03.md deleted file mode 100644 index 4b99d37..0000000 --- a/WEEK05/WEEK05-03.md +++ /dev/null @@ -1,277 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 5 -Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis - -### Non-Credit Practice Exercise 3: Analyze the Double Binary in Ghidra - -#### Objective -Import and analyze the `0x0011_double-floating-point-data-type.bin` binary in Ghidra to understand how doubles differ from floats at the binary level, observe that **both** register words carry non-zero data when the fractional part is complex, and decode the IEEE 754 double-precision encoding of `42.52525` from two 32-bit registers. - -#### Prerequisites -- Completed Exercises 1 and 2 (float analysis and patching) -- Understanding of IEEE 754 double-precision format from Week 5 Part 3 -- Understanding of register pairs (`r2:r3`) from Exercise 1 -- Basic Ghidra navigation skills - -#### Task Description -You will import the double binary into Ghidra, resolve function names, identify that `42.52525` requires non-zero data in **both** r2 and r3 (unlike `42.5` which had r2 = 0), and decode the full 64-bit double `0x4045433B645A1CAC` field by field to confirm it represents `42.52525`. - -#### Step-by-Step Instructions - -##### Step 1: Flash the Original Binary - -Before analysis, verify the program works: - -1. Hold **BOOTSEL** and plug in your Pico 2 -2. Flash `0x0011_double-floating-point-data-type.uf2` to the RPI-RP2 drive -3. Open your serial monitor - -**Expected output:** -``` -fav_num: 42.525250 -fav_num: 42.525250 -fav_num: 42.525250 -... -``` - -##### Step 2: Create a New Ghidra Project - -1. Launch Ghidra: `ghidraRun` -2. Click **File** → **New Project** -3. Select **Non-Shared Project** -4. Project Name: `week05-ex03-double-analysis` -5. Click **Finish** - -##### Step 3: Import and Configure the Binary - -1. Drag and drop `0x0011_double-floating-point-data-type.bin` into Ghidra -2. Set Language: **ARM Cortex 32 little endian default** -3. Click **Options…** - - Block Name: `.text` - - Base Address: `10000000` -4. Click **OK** on all dialogs -5. Double-click the file and click **Yes** to analyze - -##### Step 4: Locate and Rename Functions - -Identify the main function and standard library calls: - -**Rename main:** -1. Click on `FUN_10000238` -2. Right-click → **Edit Function Signature** -3. Change to: `int main(void)` -4. Click **OK** - -**Rename stdio_init_all:** -1. Click on `FUN_10002f64` -2. Right-click → **Edit Function Signature** -3. Change to: `bool stdio_init_all(void)` -4. Click **OK** - -**Rename printf:** -1. Click on `FUN_100030f4` -2. Right-click → **Edit Function Signature** -3. Change to: `int printf(char *format,...)` -4. Check the **Varargs** checkbox -5. Click **OK** - -##### Step 5: Observe the Decompiled Code - -After renaming, the decompiled `main` should look like: - -```c -int main(void) -{ - undefined4 uVar1; - undefined4 uVar2; - undefined4 extraout_r1; - undefined4 uVar3; - undefined4 extraout_r1_00; - - uVar2 = DAT_10000258; - uVar1 = DAT_10000254; - stdio_init_all(); - uVar3 = extraout_r1; - do { - printf(DAT_10000250,uVar3,uVar1,uVar2); - uVar3 = extraout_r1_00; - } while( true ); -} -``` - -**Critical observation:** There are now **two** non-zero data constants — `DAT_10000254` and `DAT_10000258`. This is the key difference from the float exercise! - -##### Step 6: Examine the Assembly Listing - -Click on the **Listing** window and find the main function assembly: - -```assembly -10000238 push {r3,r4,r5,lr} -1000023a adr r5, [0x10000254] -1000023c ldrd r4, r5, [r5, #0x0] = 645A1CACh / 4045433Bh -10000240 bl stdio_init_all -``` - -**Key instruction: `ldrd r4, r5, [r5, #0x0]`** - -This is a **load register double** instruction — it loads two consecutive 32-bit words in a single instruction: -- `r4` gets the low word: `0x645A1CAC` -- `r5` gets the high word: `0x4045433B` - -Later in the loop: -```assembly -10000244 mov r2, r4 ; r2 = 0x645A1CAC (low) -10000246 mov r3, r5 ; r3 = 0x4045433B (high) -``` - -##### Step 7: Identify the Register Pair - -| Register | Value | Role | -| -------- | ------------ | ------------ | -| `r2` | `0x645A1CAC` | Low 32 bits | -| `r3` | `0x4045433B` | High 32 bits | - -Together: `0x4045433B645A1CAC` - -**Compare to the float exercise:** -- Float `42.5`: r2 = `0x00000000`, r3 = `0x40454000` — low word is all zeros -- Double `42.52525`: r2 = `0x645A1CAC`, r3 = `0x4045433B` — **both words are non-zero!** - -This happens because `42.52525` has a repeating binary fraction that needs all 52 mantissa bits. - -##### Step 8: Decode the Sign Bit - -Convert r3 to binary and check bit 31: - -``` -r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 - ^ - bit 31 = 0 → Positive number ✓ -``` - -##### Step 9: Decode the Exponent - -Extract bits 30–20 from r3: - -``` -0x4045433B: 0 10000000100 01010100001100111011 - ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ - sign exponent mantissa (top 20) -``` - -Exponent bits: `10000000100` = 2¹⁰ + 2² = 1024 + 4 = **1028** - -$$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ - -##### Step 10: Decode the Mantissa - -**High 20 bits** (r3 bits 19–0): -``` -0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 -``` - -**Low 32 bits** (all of r2 = `0x645A1CAC`): -``` -0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 -``` - -With the implied leading `1`: -``` -1.0101010000110011101101100100010110100001110010101100 -``` - -##### Step 11: Reconstruct the Integer Part - -$$1.0101010000110011..._2 \times 2^5$$ - -Shift the binary point 5 places right → `101010.1000011001110110...` - -Integer part `101010`: - -| Bit | Power | Value | -|-----|-------|-------| -| 1 | 2⁵ | 32 | -| 0 | 2⁴ | 0 | -| 1 | 2³ | 8 | -| 0 | 2² | 0 | -| 1 | 2¹ | 2 | -| 0 | 2⁰ | 0 | - -$$32 + 8 + 2 = \mathbf{42}$$ - -##### Step 12: Approximate the Fractional Part - -The first few fractional bits `.10000110011...`: - -| Bit | Power | Decimal | -|------|--------|---------------| -| 1 | 2⁻¹ | 0.5 | -| 0 | 2⁻² | 0 | -| 0 | 2⁻³ | 0 | -| 0 | 2⁻⁴ | 0 | -| 0 | 2⁻⁵ | 0 | -| 1 | 2⁻⁶ | 0.015625 | -| 1 | 2⁻⁷ | 0.0078125 | -| 0 | 2⁻⁸ | 0 | -| 0 | 2⁻⁹ | 0 | -| 1 | 2⁻¹⁰ | 0.0009765625 | -| 1 | 2⁻¹¹ | 0.00048828125 | - -First 11 bits sum ≈ 0.5249. The remaining 36 fractional bits refine this to ≈ 0.52525. - -$$42 + 0.52525 = \mathbf{42.52525} ✓$$ - -##### Step 13: Find the Format String - -In the Listing view, locate: - -``` -s_fav_num:_%lf_100034b0 ds "fav_num: %lf\r\n" -``` - -Note the `%lf` format specifier — this program explicitly uses `double`, unlike the float program which used `%f`. - -##### Step 14: Document Your Findings - -| Item | Float (`42.5`) | Double (`42.52525`) | -| ------------------------- | -------------------- | ------------------------ | -| r2 (low word) | `0x00000000` | `0x645A1CAC` | -| r3 (high word) | `0x40454000` | `0x4045433B` | -| Low word is zero? | Yes | **No** | -| Words to patch | 1 (r3 only) | **2 (both r2 and r3)** | -| Format specifier | `%f` | `%lf` | -| Assembly load instruction | `movs` + `ldr` | `ldrd` (load double) | - -#### Expected Output - -After completing this exercise, you should understand: -- How the compiler loads a 64-bit double using `ldrd` (load register double) -- Why `42.52525` requires non-zero data in both registers while `42.5` does not -- How to decode a complex IEEE 754 double with a repeating binary fraction -- The differences between float and double handling at the assembly level - -#### Questions for Reflection - -###### Question 1: Why does `42.5` have a zero low word but `42.52525` does not? - -###### Question 2: The assembly uses `ldrd r4, r5, [r5, #0x0]` instead of two separate `ldr` instructions. What is the advantage? - -###### Question 3: Both the float and double programs have the same exponent (stored as 1028, real exponent 5). Why? - -###### Question 4: If you were patching this double, how many data constants would you need to modify compared to the float exercise? - -#### Tips and Hints -- Use Python to verify: `import struct; struct.pack('>d', 42.52525).hex()` gives `4045433b645a1cac` -- The `ldrd` instruction always loads the lower-addressed word into the first register -- A repeating binary fraction (like `0.52525`) can never be represented exactly — double precision uses the closest 52-bit approximation -- Compare data addresses: the float binary has one `DAT_` constant; the double binary has two consecutive ones - -#### Next Steps -- Proceed to Exercise 4 to patch both register words -- Compare the mantissa of `42.5` (clean: `010101 000...`) vs. `42.52525` (complex: `0101010000110011...`) -- Think about what values would have a zero low word (hint: powers of 2, halves, quarters) - -#### Additional Challenge -Using the 52-bit mantissa `0101010000110011101101100100010110100001110010101100`, manually sum the first 20 fractional bits to see how close you get to `0.52525`. How many bits of precision does it take to get within 0.001 of the true value? diff --git a/WEEK05/WEEK05-04-S.md b/WEEK05/WEEK05-04-S.md deleted file mode 100644 index df3491f..0000000 --- a/WEEK05/WEEK05-04-S.md +++ /dev/null @@ -1,70 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 5 -Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis - -### Non-Credit Practice Exercise 4 Solution: Patch the Double Binary — Changing 42.52525 to 99.99 - -#### Answers - -##### IEEE 754 Encoding of 99.99 - -``` -Integer: 99 = 1100011₂ -Fractional: .99 ≈ .111111010111...₂ (repeating) -Combined: 1100011.111111010111...₂ -Normalized: 1.100011111111010111...₂ × 2⁶ - -Sign: 0 (positive) -Exponent: 6 + 1023 = 1029 = 10000000101₂ -Mantissa: 1000111111110101 11000010100011110101 11000010100011110...₂ (52 bits) - -Full double: 0x4058FF5C28F5C28F -``` - -Python verification: `struct.pack('>d', 99.99).hex()` → `4058ff5c28f5c28f` ✓ - -##### Patch Summary - -| Register | Old Value (42.52525) | New Value (99.99) | Changed? | -|----------|---------------------|-------------------|----------| -| r2 | 0x645A1CAC | 0x28F5C28F | **Yes** | -| r3 | 0x4045433B | 0x4058FF5C | **Yes** | - -##### Ghidra Patches - -**Low word (DAT_10000254):** -``` -Before (little-endian): AC 1C 5A 64 → 0x645A1CAC -After (little-endian): 8F C2 F5 28 → 0x28F5C28F -``` - -**High word (DAT_10000258):** -``` -Before (little-endian): 3B 43 45 40 → 0x4045433B -After (little-endian): 5C FF 58 40 → 0x4058FF5C -``` - -##### Serial Output - -``` -fav_num: 99.990000 -fav_num: 99.990000 -fav_num: 99.990000 -... -``` - -#### Reflection Answers - -1. **Why did both r2 and r3 change when patching 42.52525 → 99.99, but only r3 changed when patching 42.5 → 99.0?** - Both 42.5 and 99.0 have "clean" fractional parts (0.5 and 0.0 respectively) that are exact in binary—they need very few mantissa bits, all fitting in the top 20 bits of r3. The low word (r2) remains `0x00000000` for both. In contrast, 42.52525 and 99.99 both have repeating binary fractions (0.52525 and 0.99 respectively) that require all 52 mantissa bits to approximate. Since the low 32 bits of the mantissa live in r2, changing from one repeating fraction to another necessarily changes both r2 and r3. - -2. **The multiply-by-2 method for 0.99 produces a repeating pattern. What does this mean for the precision of the stored value?** - It means 99.99 **cannot** be represented exactly as an IEEE 754 double. The binary fraction 0.111111010111... repeats indefinitely, but the mantissa only has 52 bits. The stored value is the closest 52-bit approximation, which is 99.98999999999999... (off by approximately 10⁻¹⁴). This is a fundamental limitation of binary floating-point: decimal fractions that aren't sums of negative powers of 2 always produce repeating binary expansions. The `printf` output rounds to `99.990000` because the default `%lf` precision (6 decimal places) hides the tiny error. - -3. **If you wanted to patch the double to `100.0` instead of `99.99`, how many data constants would need to change?** - **Both** would need to change—but for the opposite reason. Currently r2 = `0x645A1CAC` (non-zero). For 100.0: `struct.pack('>d', 100.0).hex()` = `4059000000000000`, so r3 = `0x40590000` and r2 = `0x00000000`. The r2 constant must be patched from `0x645A1CAC` to `0x00000000`, and r3 from `0x4045433B` to `0x40590000`. Even though the low word becomes zero, you still need to patch it because it was previously non-zero. - -4. **Compare the Ghidra Listing for the float binary (Exercise 1) and the double binary (Exercise 3). How does the compiler load the double differently?** - The float binary uses separate instructions: `movs r4, #0x0` (loads zero into r4 for the low word) and `ldr r5, [DAT_1000024c]` (loads the high word from a literal pool). The double binary uses a single `ldrd r4, r5, [r5, #0x0]` instruction that loads both words from consecutive memory addresses in one operation. The `ldrd` approach is more efficient (fewer instructions, single memory transaction) and is preferred when both words carry meaningful data. The float's approach works fine because one word is a trivially loaded zero. diff --git a/WEEK05/WEEK05-04.md b/WEEK05/WEEK05-04.md deleted file mode 100644 index 462b882..0000000 --- a/WEEK05/WEEK05-04.md +++ /dev/null @@ -1,217 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 5 -Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis - -### Non-Credit Practice Exercise 4: Patch the Double Binary — Changing 42.52525 to 99.99 - -#### Objective -Calculate the IEEE 754 double-precision encoding of `99.99`, patch **both** register words in the double binary in Ghidra, export the patched binary, convert it to UF2 format, and flash it to the Pico 2 to verify the change. - -#### Prerequisites -- Completed Exercise 3 (double binary imported and analyzed in Ghidra) -- Understanding of IEEE 754 double-precision encoding from Week 5 Parts 2.7 and 3.7 -- Knowledge of integer-to-binary conversion and the multiply-by-2 method for fractions -- Python installed for UF2 conversion and verification -- Raspberry Pi Pico 2 connected via USB - -#### Task Description -You will derive the IEEE 754 encoding of `99.99` step by step (integer part, fractional part, normalization, field extraction), patch both the low word and high word data constants in Ghidra, and verify on hardware that the serial output now prints `99.990000`. Unlike Exercise 2 where only one word changed, this exercise requires patching **both** registers. - -#### Step-by-Step Instructions - -##### Step 1: Convert the Integer Part (99) to Binary - -Use repeated division by 2: - -| Division | Quotient | Remainder | -|----------|----------|-----------| -| 99 ÷ 2 | 49 | **1** | -| 49 ÷ 2 | 24 | **1** | -| 24 ÷ 2 | 12 | **0** | -| 12 ÷ 2 | 6 | **0** | -| 6 ÷ 2 | 3 | **0** | -| 3 ÷ 2 | 1 | **1** | -| 1 ÷ 2 | 0 | **1** | - -Read remainders bottom-to-top: $99_{10} = 1100011_2$ - -##### Step 2: Convert the Fractional Part (.99) to Binary - -Use the multiply-by-2 method: - -| Multiply | Result | Integer part | Remaining fraction | -|----------------|--------|--------------|-------------------| -| 0.99 × 2 | 1.98 | **1** | 0.98 | -| 0.98 × 2 | 1.96 | **1** | 0.96 | -| 0.96 × 2 | 1.92 | **1** | 0.92 | -| 0.92 × 2 | 1.84 | **1** | 0.84 | -| 0.84 × 2 | 1.68 | **1** | 0.68 | -| 0.68 × 2 | 1.36 | **1** | 0.36 | -| 0.36 × 2 | 0.72 | **0** | 0.72 | -| 0.72 × 2 | 1.44 | **1** | 0.44 | -| 0.44 × 2 | 0.88 | **0** | 0.88 | -| 0.88 × 2 | 1.76 | **1** | 0.76 | -| 0.76 × 2 | 1.52 | **1** | 0.52 | -| 0.52 × 2 | 1.04 | **1** | 0.04 | -| ... | ... | ... | *(continues — repeating fraction)* | - -Reading the integer parts top-to-bottom: $0.99_{10} \approx 0.111111010111..._2$ - -This is a **repeating fraction** — it never terminates in binary. - -##### Step 3: Combine Integer and Fractional Parts - -$$99.99_{10} = 1100011.111111010111..._2$$ - -##### Step 4: Normalize to IEEE 754 Form - -Move the binary point so there is exactly one `1` before it: - -$$1100011.111111010111..._2 = 1.100011111111010111..._2 \times 2^6$$ - -We shifted the binary point 6 places left, so the exponent is **6**. - -##### Step 5: Extract the IEEE 754 Fields - -1. **Sign:** `0` (positive) -2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$ -3. **Mantissa:** `1000111111110101110000101000111101011100001010001111...` (52 bits after the `1.`) -4. **Full double:** `0x4058FF5C28F5C28F` - -**Verify with Python:** -```python ->>> import struct ->>> struct.pack('>d', 99.99).hex() -'4058ff5c28f5c28f' -``` - -##### Step 6: Split into Register Words - -| Register | Old Value | New Value | Changed? | -| -------- | ------------ | ------------ | -------- | -| `r2` | `0x645A1CAC` | `0x28F5C28F` | **Yes** | -| `r3` | `0x4045433B` | `0x4058FF5C` | **Yes** | - -**Both registers change!** This is the key difference from Exercise 2 where only r3 changed. - -##### Step 7: Locate the Data Constants in Ghidra - -Open your Ghidra project from Exercise 3. In the Listing view, find the two data constants: - -**Low word (loaded into r2):** -``` - DAT_10000254 -10000254 ac 1c 5a 64 undefined4 645A1CACh -``` - -**High word (loaded into r3):** -``` - DAT_10000258 -10000258 3b 43 45 40 undefined4 4045433Bh -``` - -##### Step 8: Patch the Low Word - -1. Click on **Window** → **Bytes** to open the Bytes Editor -2. Click the **Pencil Icon** to enable editing -3. Navigate to address `10000254` -4. The bytes read: `AC 1C 5A 64` (little-endian for `0x645A1CAC`) -5. Change to: `8F C2 F5 28` (little-endian for `0x28F5C28F`) -6. Press **Enter** - -##### Step 9: Patch the High Word - -1. Navigate to address `10000258` -2. The bytes read: `3B 43 45 40` (little-endian for `0x4045433B`) -3. Change to: `5C FF 58 40` (little-endian for `0x4058FF5C`) -4. Press **Enter** - -**Verify** in the Listing view: -- `DAT_10000254` should show `28F5C28Fh` -- `DAT_10000258` should show `4058FF5Ch` - -Together: `0x4058FF5C28F5C28F` = `99.99` as a double ✓ - -##### Step 10: Export the Patched Binary - -1. Click **File** → **Export Program** -2. Set **Format** to **Raw Bytes** -3. Navigate to your build directory -4. Name the file: `0x0011_double-floating-point-data-type-h.bin` -5. Click **OK** - -##### Step 11: Convert to UF2 Format - -Open a terminal: - -```powershell -cd Embedded-Hacking-main\0x0011_double-floating-point-data-type -``` - -```powershell -python ..\uf2conv.py build\0x0011_double-floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 12: Flash and Verify - -1. Hold **BOOTSEL** and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive -3. Open your serial monitor - -**Expected output:** - -``` -fav_num: 99.990000 -fav_num: 99.990000 -fav_num: 99.990000 -... -``` - -🎉 **Success!** The value changed from `42.52525` to `99.99`! - -#### Expected Output - -After completing this exercise, you should: -- See `fav_num: 99.990000` printing instead of `fav_num: 42.525250` -- Have a patched binary file (`0x0011_double-floating-point-data-type-h.bin`) -- Have a UF2 file (`hacked.uf2`) -- Understand that patching doubles with repeating fractions requires modifying **both** register words - -#### Questions for Reflection - -###### Question 1: Why did both r2 and r3 change when patching 42.52525 → 99.99, but only r3 changed when patching 42.5 → 99.0? - -###### Question 2: The multiply-by-2 method for 0.99 produces a repeating pattern. What does this mean for the precision of the stored value? - -###### Question 3: If you wanted to patch the double to `100.0` instead of `99.99`, how many data constants would need to change? - -###### Question 4: Compare the Ghidra Listing for the float binary (Exercise 1) and the double binary (Exercise 3). How does the compiler load the double differently? - -#### Tips and Hints -- Always verify your encoding with Python before patching -- Little-endian byte order: `0x28F5C28F` is stored as `8F C2 F5 28` in memory -- Use Ghidra's **Bytes** window (Window → Bytes) for precise hex editing -- If `r2` was zero before and needs to be non-zero after, you need to patch the data constant — not the `movs r4, #0x0` instruction -- The `ldrd` instruction loads r4 and r5 from two consecutive memory addresses — both must be correct - -#### Next Steps -- Review the complete patching workflow diagram in Week 5 Part 3.95 -- Try patching to `100.0` — since it has a zero low word, you'll need to change `r2` from non-zero to zero -- Attempt the practice exercises at the end of Week 5 - -#### Additional Challenge -Patch the double to `3.14159265358979` (pi). This requires extreme precision in all 52 mantissa bits. Use Python to get the exact encoding, then patch both words. Verify the output prints at least 6 correct decimal places. What happens to the precision if you only patch the high word and leave the low word as `0x645A1CAC`? - -#### Verification Checklist - -Before moving on, confirm: -- [ ] Serial output shows `fav_num: 99.990000` -- [ ] Both data constants were patched (low word and high word) -- [ ] You can derive the IEEE 754 encoding of `99.99` from scratch -- [ ] You understand why messy fractions require patching both register words -- [ ] You can explain the difference between the float and double patching workflows -- [ ] You successfully converted and flashed the UF2 - -**Congratulations!** You've completed all Week 5 exercises and mastered floating-point analysis, IEEE 754 decoding, and double-precision binary patching! diff --git a/WEEK05/WEEK05.md b/WEEK05/WEEK05.md index 2b75625..059411c 100644 --- a/WEEK05/WEEK05.md +++ b/WEEK05/WEEK05.md @@ -1,6 +1,6 @@ -# Week 5: Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis +?# Week 5: Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis -## 🎯 What You'll Learn This Week +## ? What You'll Learn This Week By the end of this tutorial, you will be able to: - Understand how integers and floating-point numbers are stored in memory @@ -13,7 +13,7 @@ By the end of this tutorial, you will be able to: - Reconstruct 64-bit doubles from two 32-bit registers --- -## 📚 Part 1: Understanding Integer Data Types +## Part 1: Understanding Integer Data Types ### What is an Integer? @@ -22,15 +22,15 @@ An **integer** is a whole number without any decimal point. Think of it like cou In C programming for embedded systems, we have special integer types that tell the compiler exactly how much memory to use: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Integer Types - Different Sizes for Different Needs │ -│ │ -│ uint8_t: 1 byte (0 to 255) - like a small box │ -│ int8_t: 1 byte (-128 to 127) - can hold negatives! │ -│ uint16_t: 2 bytes (0 to 65,535) - medium box │ -│ uint32_t: 4 bytes (0 to 4 billion) - big box │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Integer Types - Different Sizes for Different Needs | +| | +| uint8_t: 1 byte (0 to 255) - like a small box | +| int8_t: 1 byte (-128 to 127) - can hold negatives! | +| uint16_t: 2 bytes (0 to 65,535) - medium box | +| uint32_t: 4 bytes (0 to 4 billion) - big box | +| | ++-----------------------------------------------------------------+ ``` ### Signed vs Unsigned Integers @@ -125,7 +125,7 @@ uint8_t age = 43; int8_t range = -42; ``` -The variable `age` is a `uint8_t` — an **unsigned** 8-bit integer that can only hold values from `0` to `255`. Since age is always a positive number, unsigned is the right choice. The variable `range` is an `int8_t` — a **signed** 8-bit integer that can hold values from `-128` to `127`. The signed type allows it to represent negative numbers like `-42`. Under the hood, negative values are stored using **two's complement** encoding: the CPU flips all the bits of `42` (`0x2A`) and adds `1`, producing `0xD6`, which is how `-42` lives in a single byte of memory. +The variable `age` is a `uint8_t` - an **unsigned** 8-bit integer that can only hold values from `0` to `255`. Since age is always a positive number, unsigned is the right choice. The variable `range` is an `int8_t` - a **signed** 8-bit integer that can hold values from `-128` to `127`. The signed type allows it to represent negative numbers like `-42`. Under the hood, negative values are stored using **two's complement** encoding: the CPU flips all the bits of `42` (`0x2A`) and adds `1`, producing `0xD6`, which is how `-42` lives in a single byte of memory. #### GPIO Initialization with Inline Assembly @@ -133,11 +133,11 @@ Instead of using the Pico SDK's `gpio_init()`, `gpio_set_dir()`, and `gpio_set_f The initialization loop configures GPIO pins 16 through 19 (our red, green, blue, and yellow LEDs) in three steps per pin: -**Step 1 — Configure the pad.** Each GPIO pin has a pad control register in `PADS_BANK0` starting at base address `0x40038000`. The code calculates the offset as `pin * 4`, loads the current register value, clears the **OD** (output disable) and **ISO** (isolation) bits with `bic r5, r5, #0x180`, and sets the **IE** (input enable) bit with `orr r5, r5, #0x40`. This ensures the pad is electrically active and ready to drive output. +**Step 1 - Configure the pad.** Each GPIO pin has a pad control register in `PADS_BANK0` starting at base address `0x40038000`. The code calculates the offset as `pin * 4`, loads the current register value, clears the **OD** (output disable) and **ISO** (isolation) bits with `bic r5, r5, #0x180`, and sets the **IE** (input enable) bit with `orr r5, r5, #0x40`. This ensures the pad is electrically active and ready to drive output. -**Step 2 — Set the pin function.** Each GPIO pin has a control register in `IO_BANK0` starting at `0x40028004`. The offset is `pin * 8` because each pin's control block is 8 bytes wide. The code clears the `FUNCSEL` field (bits `[4:0]`) and sets it to `5`, which selects the **SIO** (Single-cycle I/O) function. SIO is the mode that lets software directly control pin state through the GPIO coprocessor. +**Step 2 - Set the pin function.** Each GPIO pin has a control register in `IO_BANK0` starting at `0x40028004`. The offset is `pin * 8` because each pin's control block is 8 bytes wide. The code clears the `FUNCSEL` field (bits `[4:0]`) and sets it to `5`, which selects the **SIO** (Single-cycle I/O) function. SIO is the mode that lets software directly control pin state through the GPIO coprocessor. -**Step 3 — Enable the output driver.** The instruction `mcrr p0, #4, r4, r5, c4` writes to the RP2350's GPIO coprocessor. Coprocessor register `c4` controls the **output enable** — with `r4` holding the pin number and `r5` set to `1`, this tells the hardware "this pin is an output." The `mcrr` (Move to Coprocessor from two ARM Registers) instruction is how the Cortex-M33 on the RP2350 talks to its dedicated GPIO coprocessor, bypassing the normal memory-mapped I/O path for single-cycle pin control. +**Step 3 - Enable the output driver.** The instruction `mcrr p0, #4, r4, r5, c4` writes to the RP2350's GPIO coprocessor. Coprocessor register `c4` controls the **output enable** - with `r4` holding the pin number and `r5` set to `1`, this tells the hardware "this pin is an output." The `mcrr` (Move to Coprocessor from two ARM Registers) instruction is how the Cortex-M33 on the RP2350 talks to its dedicated GPIO coprocessor, bypassing the normal memory-mapped I/O path for single-cycle pin control. #### The Blink Loop with Inline Assembly @@ -147,7 +147,7 @@ Inside the `while (1)` loop, the program uses two inline assembly blocks to togg "mcrr p0, #4, r4, r5, c0\n" // gpioc_bit_out_put(pin, 1) ``` -This time the coprocessor register is `c0` instead of `c4`. Register `c0` controls the **output value** — setting `r5 = 1` drives the pin HIGH (LED on), and `r5 = 0` drives it LOW (LED off). Each toggle is followed by `sleep_ms(500)` for a half-second delay, creating a visible blink. +This time the coprocessor register is `c0` instead of `c4`. Register `c0` controls the **output value** - setting `r5 = 1` drives the pin HIGH (LED on), and `r5 = 0` drives it LOW (LED off). Each toggle is followed by `sleep_ms(500)` for a half-second delay, creating a visible blink. The GCC extended assembly syntax `"r"(pin)` tells the compiler to load the C variable `pin` into a general-purpose register and make it available as `%0` inside the assembly block. The clobber list `"r4","r5"` warns the compiler that those registers are modified, so it won't store anything important there. @@ -160,42 +160,42 @@ pin++; if (pin > 18) pin = 16; ``` -This cycles through GPIO 16, 17, and 18 — red, green, and blue LEDs — creating a rotating blink pattern. Finally, `printf` prints both integer variables over UART so we can observe their values on the serial terminal: +This cycles through GPIO 16, 17, and 18 - red, green, and blue LEDs - creating a rotating blink pattern. Finally, `printf` prints both integer variables over UART so we can observe their values on the serial terminal: ``` age: 43 range: -42 ``` -> 💡 **Why use inline assembly instead of the SDK?** This program is designed to teach you what happens *beneath* the SDK. When you call `gpio_put(16, 1)` in normal Pico code, the SDK ultimately does the same coprocessor write — `mcrr p0, #4, r4, r5, c0`. By writing the assembly directly, you can see exactly how the RP2350 hardware is controlled, which is essential knowledge for reverse engineering and binary patching. +> Tip: **Why use inline assembly instead of the SDK?** This program is designed to teach you what happens *beneath* the SDK. When you call `gpio_put(16, 1)` in normal Pico code, the SDK ultimately does the same coprocessor write - `mcrr p0, #4, r4, r5, c0`. By writing the assembly directly, you can see exactly how the RP2350 hardware is controlled, which is essential knowledge for reverse engineering and binary patching. --- -## 📚 Part 2: Understanding Floating-Point Data Types +## Part 2: Understanding Floating-Point Data Types ### What is a Float? A **float** is a number that can have a decimal point. Unlike integers which can only hold whole numbers like `42`, a float can hold values like `42.5`, `3.14`, or `-0.001`. In C, the `float` type uses **32 bits (4 bytes)** to store a number using the **IEEE 754** standard. ``` -┌─────────────────────────────────────────────────────────────────┐ -│ IEEE 754 Single-Precision (32-bit float) │ -│ │ -│ ┌──────┬──────────┬───────────────────────────┐ │ -│ │ Sign │ Exponent │ Mantissa (Fraction) │ │ -│ │ 1bit │ 8 bits │ 23 bits │ │ -│ └──────┴──────────┴───────────────────────────┘ │ -│ │ -│ Value = (-1)^sign × 2^(exponent-127) × 1.mantissa │ -│ │ -│ Example: 42.5 │ -│ Sign: 0 (positive) │ -│ Exponent: 10000100 (132 - 127 = 5) │ -│ Mantissa: 01010100000000000000000 │ -│ Full: 0 10000100 01010100000000000000000 │ -│ Hex: 0x422A0000 │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| IEEE 754 Single-Precision (32-bit float) | +| | +| +------+----------+---------------------------+ | +| | Sign | Exponent | Mantissa (Fraction) | | +| | 1bit | 8 bits | 23 bits | | +| +------+----------+---------------------------+ | +| | +| Value = (-1)^sign * 2^(exponent-127) * 1.mantissa | +| | +| Example: 42.5 | +| Sign: 0 (positive) | +| Exponent: 10000100 (132 - 127 = 5) | +| Mantissa: 01010100000000000000000 | +| Full: 0 10000100 01010100000000000000000 | +| Hex: 0x422A0000 | +| | ++-----------------------------------------------------------------+ ``` ### Float vs Integer - Key Differences @@ -204,7 +204,7 @@ A **float** is a number that can have a decimal point. Unlike integers which can | -------------- | ---------------------- | --------------------------- | | **Size** | 1 byte | 4 bytes | | **Precision** | Exact | ~7 decimal digits | -| **Range** | 0 to 255 | ±3.4 × 10³⁸ | +| **Range** | 0 to 255 | ?3.4 * 10++ | | **Encoding** | Direct binary | IEEE 754 (sign/exp/mantissa)| | **printf** | `%d` | `%f` | @@ -233,7 +233,7 @@ int main(void) { 2. Initializes the serial output 3. Prints `fav_num` forever in a loop using the `%f` format specifier -> 💡 **Why `%f` instead of `%d`?** The `%d` format specifier tells `printf` to expect an integer. The `%f` specifier tells it to expect a floating-point number. Using the wrong one would print garbage! +> Tip: **Why `%f` instead of `%d`?** The `%d` format specifier tells `printf` to expect an integer. The `%f` specifier tells it to expect a floating-point number. Using the wrong one would print garbage! ### Step 1: Flash the Binary to Your Pico 2 @@ -260,7 +260,7 @@ The program is printing `42.500000` because `printf` with `%f` defaults to 6 dec --- -## 🔬 Part 2.5: Setting Up Ghidra for Float Analysis +## ? Part 2.5: Setting Up Ghidra for Float Analysis ### Step 3: Start Ghidra @@ -274,7 +274,7 @@ Ghidra will open. Now we need to create a new project. ### Step 4: Create a New Project -1. Click **File** → **New Project** +1. Click **File** -> **New Project** 2. Select **Non-Shared Project** 3. Click **Next** 4. Enter Project Name: `0x000e_floating-point-data-type` @@ -293,12 +293,12 @@ Ghidra will open. Now we need to create a new project. A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). -**Click the three dots (…) next to "Language" and:** +**Click the three dots (...) next to "Language" and:** 1. Search for "Cortex" 2. Select **ARM Cortex 32 little endian default** 3. Click **OK** -**Click the "Options…" button and:** +**Click the "Options..." button and:** 1. Change **Block Name** to `.text` 2. Change **Base Address** to `10000000` (the XIP address!) 3. Click **OK** @@ -313,7 +313,7 @@ Wait for analysis to complete (watch the progress bar in the bottom right). --- -## 🔬 Part 2.6: Navigating and Resolving Functions +## ? Part 2.6: Navigating and Resolving Functions ### Step 8: Find the Functions @@ -347,7 +347,7 @@ For `main`, let's also fix the return type: --- -## 🔬 Part 2.7: Analyzing the Main Function +## ? Part 2.7: Analyzing the Main Function ### Step 11: Examine Main in Ghidra @@ -376,14 +376,14 @@ int main(void) ### Step 12: Resolve stdio_init_all 1. Click on `FUN_10002f5c` -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `bool stdio_init_all(void)` 4. Click **OK** ### Step 13: Resolve printf 1. Click on `FUN_100030ec` -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `int __wrap_printf(char *format,...)` 4. Check the **Varargs** checkbox (printf takes variable arguments!) 5. Click **OK** @@ -412,7 +412,7 @@ int main(void) **Where's `float fav_num = 42.5`?** It's been optimized into an immediate value! -The compiler replaced our float variable with constants passed directly to `printf`. But wait — we see **two** values: `0x0`, in `r2` and `DAT_1000024c` or `0x40454000`, in `r3`. That's because `printf` with `%f` always receives a **double** (64-bit), not a `float` (32-bit). The C standard requires that `float` arguments to variadic functions like `printf` are **promoted to `double`**. +The compiler replaced our float variable with constants passed directly to `printf`. But wait - we see **two** values: `0x0`, in `r2` and `DAT_1000024c` or `0x40454000`, in `r3`. That's because `printf` with `%f` always receives a **double** (64-bit), not a `float` (32-bit). The C standard requires that `float` arguments to variadic functions like `printf` are **promoted to `double`**. A 64-bit double is passed in two 32-bit registers: @@ -421,7 +421,7 @@ A 64-bit double is passed in two 32-bit registers: | `r2` | `0x00000000` | Low 32 bits | | `r3` | `0x40454000` | High 32 bits | -Together they form `0x40454000_00000000` — the IEEE 754 **double-precision** encoding of `42.5`. +Together they form `0x40454000_00000000` - the IEEE 754 **double-precision** encoding of `42.5`. ### Step 15: Verify the Double Encoding @@ -436,11 +436,11 @@ Laid out as a single 64-bit value with every bit numbered: ``` Bit: 63 62-52 (11 bits) 51-32 (20 bits) 31-0 (32 bits) - ┌───┬───────────────────────┬──────────────────────────────────────────┬──────────────────────────────────┐ - │ 0 │ 1 0 0 0 0 0 0 0 1 0 0 │ 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ 00000000000000000000000000000000 │ - └───┴───────────────────────┴──────────────────────────────────────────┴──────────────────────────────────┘ + +---+-----------------------+------------------------------------------+----------------------------------+ + | 0 | 1 0 0 0 0 0 0 0 1 0 0 | 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 00000000000000000000000000000000 | + +---+-----------------------+------------------------------------------+----------------------------------+ Sign Exponent (11) Mantissa high 20 bits Mantissa low 32 bits - (from r3 bits 19–0) (from r2, all zero) + (from r3 bits 19-0) (from r2, all zero) ``` **Step-by-step field extraction:** @@ -455,11 +455,11 @@ In IEEE 754, the **sign bit** is the very first (leftmost) bit of the 64-bit dou sign bit ``` -But we don't have a single 64-bit register — we have **two** 32-bit registers. The high register `r3` holds bits 63–32 of the double. So bit 63 of the double is the same physical bit as **bit 31 of r3** (the topmost bit of r3): +But we don't have a single 64-bit register - we have **two** 32-bit registers. The high register `r3` holds bits 63-32 of the double. So bit 63 of the double is the same physical bit as **bit 31 of r3** (the topmost bit of r3): ``` -r3 holds bits 63–32 of the double -r2 holds bits 31–0 of the double +r3 holds bits 63-32 of the double +r2 holds bits 31-0 of the double ``` Now let's check it. IEEE 754 uses a simple rule for the sign bit: @@ -472,14 +472,14 @@ Now let's check it. IEEE 754 uses a simple rule for the sign bit: ``` r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 ^ - r3 bit 31 = 0 → sign = 0 → Positive number + r3 bit 31 = 0 -> sign = 0 -> Positive number ``` The topmost bit of r3 is `0`, so the number is **positive**. If that bit were `1` instead (e.g. `0xC0454000`), the number would be negative (`-42.5`). -**2. Exponent — bits 62–52 of the 64-bit value = bits 30–20 of r3** +**2. Exponent - bits 62-52 of the 64-bit value = bits 30-20 of r3** -Extract bits 30–20 from `0x40454000`: +Extract bits 30-20 from `0x40454000`: ``` 0x40454000 in binary: 0 10000000100 01010100000000000000 @@ -490,9 +490,9 @@ Exponent bits: `10000000100` Convert to decimal: $2^{10} + 2^{2} = 1024 + 4 = 1028$ -But `1028` is **not** the actual power of 2 yet. IEEE 754 stores exponents with a **bias** — a fixed number that gets added during encoding so that the stored value is always positive (no sign bit needed for the exponent). For doubles, the bias is **1023**. +But `1028` is **not** the actual power of 2 yet. IEEE 754 stores exponents with a **bias** - a fixed number that gets added during encoding so that the stored value is always positive (no sign bit needed for the exponent). For doubles, the bias is **1023**. -> 💡 **Why 1023?** The exponent field is 11 bits wide, giving $2^{11} = 2048$ total values. Half of that range should represent negative exponents and half positive. The midpoint is $(2^{11} / 2) - 1 = 1023$. So a stored exponent of `1023` means a real exponent of **0**, values below `1023` are negative exponents, and values above `1023` are positive exponents. +> Tip: **Why 1023?** The exponent field is 11 bits wide, giving $2^{11} = 2048$ total values. Half of that range should represent negative exponents and half positive. The midpoint is $(2^{11} / 2) - 1 = 1023$. So a stored exponent of `1023` means a real exponent of **0**, values below `1023` are negative exponents, and values above `1023` are positive exponents. To recover the real exponent, we subtract the bias: @@ -502,25 +502,25 @@ $$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ This means the number is scaled by $2^5 = 32$. In other words, the mantissa gets shifted left by 5 binary places. -**3. Mantissa — bits 51–0 of the 64-bit value** +**3. Mantissa - bits 51-0 of the 64-bit value** -- **High 20 bits of mantissa** (bits 51–32) = bits 19–0 of r3: +- **High 20 bits of mantissa** (bits 51-32) = bits 19-0 of r3: ``` -r3 bits 19–0: 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +r3 bits 19-0: 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ``` -- **Low 32 bits of mantissa** (bits 31–0) = all of r2: +- **Low 32 bits of mantissa** (bits 31-0) = all of r2: ``` -r2 = 0x00000000 → 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +r2 = 0x00000000 -> 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ``` Full 52-bit mantissa: ``` 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - ← top 20 bits from r3 → ← bottom 32 bits from r2 (all zero) → + ?? top 20 bits from r3 -> ?? bottom 32 bits from r2 (all zero) -> ``` IEEE 754 always prepends an **implied leading `1`**, so the actual value represented is: @@ -547,9 +547,9 @@ Now convert each bit position to decimal: | `0` (bit 2) | $2^2$ | 0 | | `1` (bit 1) | $2^1$ | 2 | | `0` (bit 0) | $2^0$ | 0 | -| `1` (bit −1) | $2^{-1}$ | 0.5 | +| `1` (bit ?1) | $2^{-1}$ | 0.5 | -$$32 + 8 + 2 + 0.5 = \mathbf{42.5} ✓$$ +$$32 + 8 + 2 + 0.5 = \mathbf{42.5} ?$$ ### Step 16: Examine the Assembly @@ -583,7 +583,7 @@ Look at the **Listing** window (assembly view). Find the main function: ``` -> 🎯 **Key Insight:** The `mov.w r2, #0x0` loads the low 32 bits (all zeros) and `ldr r3, [DAT_...]` loads the high 32 bits (`0x40454000`) of the double. Together, `r2:r3` = `0x40454000_00000000` = `42.5` as a double. +> ? **Key Insight:** The `mov.w r2, #0x0` loads the low 32 bits (all zeros) and `ldr r3, [DAT_...]` loads the high 32 bits (`0x40454000`) of the double. Together, `r2:r3` = `0x40454000_00000000` = `42.5` as a double. ### Step 17: Find the Format String @@ -601,41 +601,41 @@ This confirms `printf` is called with the format string `"fav_num: %f\r\n"` and --- -## 🔬 Part 2.8: Patching the Float - Changing 42.5 to 99.0 +## ? Part 2.8: Patching the Float - Changing 42.5 to 99.0 ### Step 18: Calculate the New IEEE 754 Encoding We want to change `42.5` to `99.0`. First, we need to figure out the double-precision encoding of `99.0`: -**Step A — Convert the integer part (99) to binary:** +**Step A - Convert the integer part (99) to binary:** | Division | Quotient | Remainder | |---------------|----------|-----------| -| 99 ÷ 2 | 49 | **1** | -| 49 ÷ 2 | 24 | **1** | -| 24 ÷ 2 | 12 | **0** | -| 12 ÷ 2 | 6 | **0** | -| 6 ÷ 2 | 3 | **0** | -| 3 ÷ 2 | 1 | **1** | -| 1 ÷ 2 | 0 | **1** | +| 99 ? 2 | 49 | **1** | +| 49 ? 2 | 24 | **1** | +| 24 ? 2 | 12 | **0** | +| 12 ? 2 | 6 | **0** | +| 6 ? 2 | 3 | **0** | +| 3 ? 2 | 1 | **1** | +| 1 ? 2 | 0 | **1** | Read remainders bottom-to-top: $99_{10} = 1100011_2$ -**Step B — Convert the fractional part (.0) to binary:** +**Step B - Convert the fractional part (.0) to binary:** -There is no fractional part — `.0` is exactly zero, so the fractional binary is just `0`. +There is no fractional part - `.0` is exactly zero, so the fractional binary is just `0`. -**Step C — Combine:** +**Step C - Combine:** $$99.0_{10} = 1100011.0_2$$ -**Step D — Normalize to IEEE 754 form** (move the binary point so there's exactly one `1` before it): +**Step D - Normalize to IEEE 754 form** (move the binary point so there's exactly one `1` before it): $$1100011.0_2 = 1.100011_2 \times 2^6$$ We shifted the binary point 6 places left, so the exponent is **6**. -**Step E — Extract the IEEE 754 fields:** +**Step E - Extract the IEEE 754 fields:** 1. **Sign:** `0` (positive) 2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$ @@ -657,7 +657,7 @@ Look in the Listing view for the data that loads the high word of the double: 10000248 00 40 45 40 undefined4 40454000h ``` -This is the 32-bit constant that gets loaded into `r3` — the high word of our double `42.5`. +This is the 32-bit constant that gets loaded into `r3` - the high word of our double `42.5`. ### Step 20: Patch the Constant @@ -671,11 +671,11 @@ This changes the high word from `0x40454000` (42.5 as double) to `0x4058C000` (9 --- -## 🔬 Part 2.9: Export and Test the Hacked Binary +## ? Part 2.9: Export and Test the Hacked Binary ### Step 21: Export the Patched Binary -1. Click **File** → **Export Program** +1. Click **File** -> **Export Program** 2. Set **Format** to **Raw Bytes** 3. Navigate to your build directory 4. Name the file `0x000e_floating-point-data-type-h.bin` @@ -686,7 +686,7 @@ This changes the high word from `0x40454000` (42.5 as double) to `0x4058C000` (9 **Open a terminal and navigate to your project directory:** ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x000e_floating-point-data-type +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x000e_floating-point-data-type ``` **Run the conversion command:** @@ -710,34 +710,34 @@ fav_num: 99.000000 ... ``` -🎉 **BOOM! We hacked the float!** The value changed from `42.5` to `99.0`! +? **BOOM! We hacked the float!** The value changed from `42.5` to `99.0`! --- -## 📚 Part 3: Understanding Double-Precision Floating-Point Data Types +## Part 3: Understanding Double-Precision Floating-Point Data Types ### What is a Double? -A **double** (short for "double-precision floating-point") is like a `float` but with **twice the precision**. While a `float` uses 32 bits, a `double` uses **64 bits (8 bytes)**, giving it roughly **15–16 significant decimal digits** of precision compared to a float's ~7. +A **double** (short for "double-precision floating-point") is like a `float` but with **twice the precision**. While a `float` uses 32 bits, a `double` uses **64 bits (8 bytes)**, giving it roughly **15-16 significant decimal digits** of precision compared to a float's ~7. ``` -┌─────────────────────────────────────────────────────────────────┐ -│ IEEE 754 Double-Precision (64-bit double) │ -│ │ -│ ┌──────┬───────────┬──────────────────────────────────────┐ │ -│ │ Sign │ Exponent │ Mantissa (Fraction) │ │ -│ │ 1bit │ 11 bits │ 52 bits │ │ -│ └──────┴───────────┴──────────────────────────────────────┘ │ -│ │ -│ Value = (-1)^sign × 2^(exponent-1023) × 1.mantissa │ -│ │ -│ Example: 42.52525 │ -│ Sign: 0 (positive) │ -│ Exponent: 10000000100 (1028 - 1023 = 5) │ -│ Mantissa: 0101010000110011101101100100010110100001110010101100 │ -│ Hex: 0x4045433B645A1CAC │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| IEEE 754 Double-Precision (64-bit double) | +| | +| +------+-----------+--------------------------------------+ | +| | Sign | Exponent | Mantissa (Fraction) | | +| | 1bit | 11 bits | 52 bits | | +| +------+-----------+--------------------------------------+ | +| | +| Value = (-1)^sign * 2^(exponent-1023) * 1.mantissa | +| | +| Example: 42.52525 | +| Sign: 0 (positive) | +| Exponent: 10000000100 (1028 - 1023 = 5) | +| Mantissa: 0101010000110011101101100100010110100001110010101100 | +| Hex: 0x4045433B645A1CAC | +| | ++-----------------------------------------------------------------+ ``` ### Float vs Double - Key Differences @@ -748,11 +748,11 @@ A **double** (short for "double-precision floating-point") is like a `float` but | **Precision** | ~7 decimal digits | ~15 decimal digits | | **Exponent** | 8 bits (bias 127) | 11 bits (bias 1023) | | **Mantissa** | 23 bits | 52 bits | -| **Range** | ±3.4 × 10³⁸ | ±1.8 × 10³⁰⁸ | +| **Range** | ?3.4 * 10++ | ?1.8 * 10+++? | | **printf** | `%f` | `%lf` | | **ARM passing** | Promoted to double | Native in `r2:r3` | -> 💡 **Why does precision matter?** With a `float`, the value `42.52525` might be stored as `42.525249` due to rounding. A `double` can represent it as `42.525250` with much higher fidelity. For scientific or financial applications, that extra precision is critical! +> Tip: **Why does precision matter?** With a `float`, the value `42.52525` might be stored as `42.525249` due to rounding. A `double` can represent it as `42.525250` with much higher fidelity. For scientific or financial applications, that extra precision is critical! ### Our Double-Precision Program @@ -779,7 +779,7 @@ int main(void) { 2. Initializes the serial output 3. Prints `fav_num` forever in a loop using the `%lf` format specifier -> 💡 **`%lf` vs `%f`:** While `printf` actually treats `%f` and `%lf` identically (both expect a `double`), using `%lf` makes your intent clear — you're explicitly working with a `double`, not a `float`. It's good practice to match the format specifier to your variable type. +> Tip: **`%lf` vs `%f`:** While `printf` actually treats `%f` and `%lf` identically (both expect a `double`), using `%lf` makes your intent clear - you're explicitly working with a `double`, not a `float`. It's good practice to match the format specifier to your variable type. ### Step 1: Flash the Binary to Your Pico 2 @@ -806,7 +806,7 @@ The program is printing `42.525250` because `printf` with `%lf` defaults to 6 de --- -## 🔬 Part 3.5: Setting Up Ghidra for Double Analysis +## ? Part 3.5: Setting Up Ghidra for Double Analysis ### Step 3: Start Ghidra @@ -820,7 +820,7 @@ Ghidra will open. Now we need to create a new project. ### Step 4: Create a New Project -1. Click **File** → **New Project** +1. Click **File** -> **New Project** 2. Select **Non-Shared Project** 3. Click **Next** 4. Enter Project Name: `0x000A_intro-to-doubles` @@ -839,12 +839,12 @@ Ghidra will open. Now we need to create a new project. A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). -**Click the three dots (…) next to "Language" and:** +**Click the three dots (...) next to "Language" and:** 1. Search for "Cortex" 2. Select **ARM Cortex 32 little endian default** 3. Click **OK** -**Click the "Options…" button and:** +**Click the "Options..." button and:** 1. Change **Block Name** to `.text` 2. Change **Base Address** to `10000000` (the XIP address!) 3. Click **OK** @@ -859,7 +859,7 @@ Wait for analysis to complete (watch the progress bar in the bottom right). --- -## 🔬 Part 3.6: Navigating and Resolving Functions +## ? Part 3.6: Navigating and Resolving Functions ### Step 8: Find the Functions @@ -893,7 +893,7 @@ For `main`, let's also fix the return type: --- -## 🔬 Part 3.7: Analyzing the Main Function +## ? Part 3.7: Analyzing the Main Function ### Step 11: Examine Main in Ghidra @@ -925,14 +925,14 @@ void FUN_10000238(void) ### Step 12: Resolve stdio_init_all 1. Click on `FUN_10002f64` -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `bool stdio_init_all(void)` 4. Click **OK** ### Step 13: Resolve printf 1. Click on `FUN_100030f4` -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change the name to `printf` 4. Check the **Varargs** checkbox (printf takes variable arguments!) 5. Click **OK** @@ -963,7 +963,7 @@ int main(void) **Where's `double fav_num = 42.52525`?** It's been optimized into immediate values! -This time we see **two** non-zero values: `0x645a1cac` and `0x4045433b`. Unlike the float example where the low word was `0x0`, a double with a fractional part like `42.52525` needs **all 52 mantissa bits** — so both halves carry data. +This time we see **two** non-zero values: `0x645a1cac` and `0x4045433b`. Unlike the float example where the low word was `0x0`, a double with a fractional part like `42.52525` needs **all 52 mantissa bits** - so both halves carry data. A 64-bit double is passed in two 32-bit registers: @@ -972,9 +972,9 @@ A 64-bit double is passed in two 32-bit registers: | `r2` | `0x645A1CAC` | Low 32 bits | | `r3` | `0x4045433B` | High 32 bits | -Together they form `0x4045433B645A1CAC` — the IEEE 754 **double-precision** encoding of `42.52525`. +Together they form `0x4045433B645A1CAC` - the IEEE 754 **double-precision** encoding of `42.52525`. -> 🎯 **Key Difference from Float:** In the float example, `r2` was `0x00000000` because `42.5` has a clean fractional part. But `42.52525` has a repeating binary fraction, so the low 32 bits are non-zero (`0x645A1CAC`). This means **both** registers matter when patching doubles with complex fractional values! +> ? **Key Difference from Float:** In the float example, `r2` was `0x00000000` because `42.5` has a clean fractional part. But `42.52525` has a repeating binary fraction, so the low 32 bits are non-zero (`0x645A1CAC`). This means **both** registers matter when patching doubles with complex fractional values! ### Step 15: Verify the Double Encoding @@ -989,30 +989,30 @@ Laid out as a single 64-bit value with every bit numbered: ``` Bit: 63 62-52 (11 bits) 51-32 (20 bits) 31-0 (32 bits) - ┌───┬───────────────────────┬──────────────────────────────────────────┬──────────────────────────────────────────┐ - │ 0 │ 1 0 0 0 0 0 0 0 1 0 0 │ 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 │ 01100100010110100001110010101100 │ - └───┴───────────────────────┴──────────────────────────────────────────┴──────────────────────────────────────────┘ + +---+-----------------------+------------------------------------------+------------------------------------------+ + | 0 | 1 0 0 0 0 0 0 0 1 0 0 | 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 | 01100100010110100001110010101100 | + +---+-----------------------+------------------------------------------+------------------------------------------+ Sign Exponent (11) Mantissa high 20 bits Mantissa low 32 bits - (from r3 bits 19–0) (from r2) + (from r3 bits 19-0) (from r2) ``` -> 🎯 **Key Difference from 42.5:** In the `42.5` example, r2 was `0x00000000` because `42.5` has a clean fractional part (`.5` = exactly one binary digit). But `42.52525` has a repeating binary fraction, so the low 32 bits are **non-zero** (`0x645A1CAC`). Every bit of both registers matters here! +> ? **Key Difference from 42.5:** In the `42.5` example, r2 was `0x00000000` because `42.5` has a clean fractional part (`.5` = exactly one binary digit). But `42.52525` has a repeating binary fraction, so the low 32 bits are **non-zero** (`0x645A1CAC`). Every bit of both registers matters here! **Step-by-step field extraction:** **1. Sign bit** -The sign bit is bit 63 of the 64-bit double, which is bit 31 of r3 (the high register holds bits 63–32): +The sign bit is bit 63 of the 64-bit double, which is bit 31 of r3 (the high register holds bits 63-32): ``` r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 ^ - r3 bit 31 = 0 → sign = 0 → Positive number ✓ + r3 bit 31 = 0 -> sign = 0 -> Positive number ? ``` -**2. Exponent — bits 62–52 = bits 30–20 of r3** +**2. Exponent - bits 62-52 = bits 30-20 of r3** -Extract bits 30–20 from `0x4045433B`: +Extract bits 30-20 from `0x4045433B`: ``` 0x4045433B in binary: 0 10000000100 01010100001100111011 @@ -1023,33 +1023,33 @@ Exponent bits: `10000000100` Convert to decimal: $2^{10} + 2^{2} = 1024 + 4 = 1028$ -Subtract the bias (same formula as Part 2 — the bias is 1023 for all doubles): +Subtract the bias (same formula as Part 2 - the bias is 1023 for all doubles): $$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ This means the mantissa gets shifted left by 5 binary places (i.e. multiplied by $2^5 = 32$). -**3. Mantissa — bits 51–0** +**3. Mantissa - bits 51-0** Unlike the `42.5` example where r2 was all zeros, **both registers contribute non-zero bits** here: -- **High 20 bits of mantissa** (bits 51–32) = bits 19–0 of r3: +- **High 20 bits of mantissa** (bits 51-32) = bits 19-0 of r3: ``` -r3 bits 19–0: 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 +r3 bits 19-0: 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 ``` -- **Low 32 bits of mantissa** (bits 31–0) = all of r2: +- **Low 32 bits of mantissa** (bits 31-0) = all of r2: ``` -r2 = 0x645A1CAC → 0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 +r2 = 0x645A1CAC -> 0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 ``` Full 52-bit mantissa: ``` 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 | 0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 - ← top 20 bits from r3 → ← bottom 32 bits from r2 → + ?? top 20 bits from r3 -> ?? bottom 32 bits from r2 -> ``` IEEE 754 always prepends an **implied leading `1`**, so the actual value represented is: @@ -1083,25 +1083,25 @@ $$32 + 8 + 2 = \mathbf{42}$$ | Bit position | Power of 2 | Decimal value | |---|---|---| -| `1` (bit −1) | $2^{-1}$ | 0.5 | -| `0` (bit −2) | $2^{-2}$ | 0 | -| `0` (bit −3) | $2^{-3}$ | 0 | -| `0` (bit −4) | $2^{-4}$ | 0 | -| `0` (bit −5) | $2^{-5}$ | 0 | -| `1` (bit −6) | $2^{-6}$ | 0.015625 | -| `1` (bit −7) | $2^{-7}$ | 0.0078125 | -| `0` (bit −8) | $2^{-8}$ | 0 | -| `0` (bit −9) | $2^{-9}$ | 0 | -| `1` (bit −10) | $2^{-10}$ | 0.0009765625 | -| `1` (bit −11) | $2^{-11}$ | 0.00048828125 | -| `1` (bit −12) | $2^{-12}$ | 0.000244140625 | +| `1` (bit ?1) | $2^{-1}$ | 0.5 | +| `0` (bit ?2) | $2^{-2}$ | 0 | +| `0` (bit ?3) | $2^{-3}$ | 0 | +| `0` (bit ?4) | $2^{-4}$ | 0 | +| `0` (bit ?5) | $2^{-5}$ | 0 | +| `1` (bit ?6) | $2^{-6}$ | 0.015625 | +| `1` (bit ?7) | $2^{-7}$ | 0.0078125 | +| `0` (bit ?8) | $2^{-8}$ | 0 | +| `0` (bit ?9) | $2^{-9}$ | 0 | +| `1` (bit ?10) | $2^{-10}$ | 0.0009765625 | +| `1` (bit ?11) | $2^{-11}$ | 0.00048828125 | +| `1` (bit ?12) | $2^{-12}$ | 0.000244140625 | | ... | ... | *(remaining 35 bits add smaller and smaller fractions)* | First 12 fractional bits sum: $0.5 + 0.015625 + 0.0078125 + 0.0009765625 + 0.00048828125 + 0.000244140625 \approx 0.5251$ -The remaining 35 fractional bits refine this to $\approx 0.52525$. This is because `0.52525` is a **repeating fraction** in binary — it can never be represented with a finite number of bits, so double precision stores the closest possible 52-bit approximation. +The remaining 35 fractional bits refine this to $\approx 0.52525$. This is because `0.52525` is a **repeating fraction** in binary - it can never be represented with a finite number of bits, so double precision stores the closest possible 52-bit approximation. -$$42 + 0.52525 = \mathbf{42.52525} ✓$$ +$$42 + 0.52525 = \mathbf{42.52525} ?$$ ### Step 16: Examine the Assembly @@ -1135,7 +1135,7 @@ Look at the **Listing** window (assembly view). Find the main function: 10000258 3b 43 45 40 undefine 4045433Bh ``` -> 🎯 **Key Insight:** Notice that **both** `r2` and `r3` are loaded from data constants using `ldr`. Compare this to the float example where `r2` was loaded with `mov.w r2, #0x0`. Because `42.52525` requires all 52 mantissa bits, neither word can be zero — the compiler must store both halves as separate data constants. +> ? **Key Insight:** Notice that **both** `r2` and `r3` are loaded from data constants using `ldr`. Compare this to the float example where `r2` was loaded with `mov.w r2, #0x0`. Because `42.52525` requires all 52 mantissa bits, neither word can be zero - the compiler must store both halves as separate data constants. ### Step 17: Find the Format String @@ -1153,7 +1153,7 @@ This confirms `printf` is called with the format string `"fav_num: %lf\r\n"` and --- -## 🔬 Part 3.8: Patching the Double - Changing 42.52525 to 99.99 +## ? Part 3.8: Patching the Double - Changing 42.52525 to 99.99 ### Step 18: Calculate the New IEEE 754 Encoding @@ -1202,15 +1202,15 @@ Look in the Listing view for the two data constants: This changes the full 64-bit double from `0x4045433B645A1CAC` (42.52525) to `0x4058FF5C28F5C28F` (99.99). -> 🎯 **Key Difference from Float Patching:** When we patched the float `42.5`, we only needed to change one word (the high word in `r3`) because the low word was all zeros. With `42.52525 → 99.99`, **both** words change. Always check whether the low word is non-zero before patching! +> ? **Key Difference from Float Patching:** When we patched the float `42.5`, we only needed to change one word (the high word in `r3`) because the low word was all zeros. With `42.52525 -> 99.99`, **both** words change. Always check whether the low word is non-zero before patching! --- -## 🔬 Part 3.9: Export and Test the Hacked Binary +## ? Part 3.9: Export and Test the Hacked Binary ### Step 21: Export the Patched Binary -1. Click **File** → **Export Program** +1. Click **File** -> **Export Program** 2. Set **Format** to **Raw Bytes** 3. Navigate to your build directory 4. Name the file `0x0011_double-floating-point-data-type-h.bin` @@ -1221,7 +1221,7 @@ This changes the full 64-bit double from `0x4045433B645A1CAC` (42.52525) to `0x4 **Open a terminal and navigate to your project directory:** ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0011_double-floating-point-data-type +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0011_double-floating-point-data-type ``` **Run the conversion command:** @@ -1245,11 +1245,11 @@ fav_num: 99.990000 ... ``` -🎉 **BOOM! We hacked the double!** The value changed from `42.52525` to `99.99`! +? **BOOM! We hacked the double!** The value changed from `42.52525` to `99.99`! --- -## 📊 Part 3.95: Summary - Float and Double Analysis +## ? Part 3.95: Summary - Float and Double Analysis ### What We Accomplished @@ -1276,62 +1276,40 @@ fav_num: 99.990000 ### The Float/Double Patching Workflow ``` -┌─────────────────────────────────────────────────────────────────┐ -│ 1. Identify the float/double value in the decompiled view │ -│ - Look for hex constants like 0x40454000 or 0x4045433B │ -├─────────────────────────────────────────────────────────────────┤ -│ 2. Determine if it's float (32-bit) or double (64-bit) │ -│ - printf promotes floats to doubles! │ -│ - Check if value spans r2:r3 (double) or just r0 (float) │ -├─────────────────────────────────────────────────────────────────┤ -│ 3. Check if the low word (r2) is zero or non-zero │ -│ - Zero low word = only patch the high word │ -│ - Non-zero low word = patch BOTH words │ -├─────────────────────────────────────────────────────────────────┤ -│ 4. Calculate the new IEEE 754 encoding │ -│ - Convert your desired value to IEEE 754 │ -│ - Split into high/low words │ -├─────────────────────────────────────────────────────────────────┤ -│ 5. Patch the constant(s) in Ghidra │ -│ - Right-click → Patch Data │ -│ - Replace the old encoding with the new one │ -├─────────────────────────────────────────────────────────────────┤ -│ 6. Export → Convert to UF2 → Flash → Verify │ -│ - Same workflow as integer patching │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| 1. Identify the float/double value in the decompiled view | +| - Look for hex constants like 0x40454000 or 0x4045433B | ++-----------------------------------------------------------------+ +| 2. Determine if it's float (32-bit) or double (64-bit) | +| - printf promotes floats to doubles! | +| - Check if value spans r2:r3 (double) or just r0 (float) | ++-----------------------------------------------------------------+ +| 3. Check if the low word (r2) is zero or non-zero | +| - Zero low word = only patch the high word | +| - Non-zero low word = patch BOTH words | ++-----------------------------------------------------------------+ +| 4. Calculate the new IEEE 754 encoding | +| - Convert your desired value to IEEE 754 | +| - Split into high/low words | ++-----------------------------------------------------------------+ +| 5. Patch the constant(s) in Ghidra | +| - Right-click -> Patch Data | +| - Replace the old encoding with the new one | ++-----------------------------------------------------------------+ +| 6. Export -> Convert to UF2 -> Flash -> Verify | +| - Same workflow as integer patching | ++-----------------------------------------------------------------+ ``` -> 💡 **Key takeaway:** Hacking doubles is the same process as hacking floats — find the IEEE 754 constant, calculate the new encoding, patch it. The only extra step is checking whether the **low word** (`r2`) is also non-zero. Clean values like `42.5` only need one patch; messy fractions like `42.52525` need two! +> Tip: **Key takeaway:** Hacking doubles is the same process as hacking floats - find the IEEE 754 constant, calculate the new encoding, patch it. The only extra step is checking whether the **low word** (`r2`) is also non-zero. Clean values like `42.5` only need one patch; messy fractions like `42.52525` need two! --- -## ✅ Practice Exercises - -### Exercise 1: Patch the Float to Pi -The float program stores `42.5`. Patch it in Ghidra so the serial output prints `3.14` instead. - -**Hint:** `3.14` as a double is `0x40091EB851EB851F` — the high word is `0x40091EB8` and the low word is `0x51EB851F`. You'll need to patch **both** words since the low word is non-zero! - -### Exercise 2: Patch the Double to a Whole Number -The double program stores `42.52525`. Instead of patching it to `99.99` (as we did in the chapter), patch it to `100.0`. - -**Hint:** `100.0` as a double is `0x4059000000000000` — high word `0x40590000`, low word `0x00000000`. Notice the low word is all zeros this time — but the original low word (`0x645A1CAC`) is non-zero, so you still need to patch it to `0x00000000`! - -### Exercise 3: Change the Blink Speed -The LED blinks every 500ms. Find the `sleep_ms(500)` calls in the binary and change them to `sleep_ms(100)` for faster blinking. - -**Hint:** Look for the value `0x1F4` (500 in hex) being loaded into a register before the delay call. - -### Exercise 4: Find the Format Strings -Both programs use format strings (`"fav_num: %f\r\n"` and `"fav_num: %lf\r\n"`). Can you find these strings in Ghidra and determine where they're stored? - -**Hint:** Look in the `.rodata` section. Try pressing `S` in Ghidra to search for strings, or navigate to addresses near `0x10003xxx`. - --- -## 🎓 Key Takeaways +## ? Key Takeaways -1. **Integers have fixed sizes** - `uint8_t` is 1 byte (0–255), `int8_t` is 1 byte (-128 to 127). The `u` prefix means unsigned. +1. **Integers have fixed sizes** - `uint8_t` is 1 byte (0-255), `int8_t` is 1 byte (-128 to 127). The `u` prefix means unsigned. 2. **IEEE 754 encodes floats in binary** - Sign bit, exponent (with bias), and mantissa form the encoding for both 32-bit floats and 64-bit doubles. @@ -1347,7 +1325,7 @@ Both programs use format strings (`"fav_num: %f\r\n"` and `"fav_num: %lf\r\n"`). --- -## 📖 Glossary +## ? Glossary | Term | Definition | | ----------------------- | ------------------------------------------------------------------------------ | @@ -1364,14 +1342,14 @@ Both programs use format strings (`"fav_num: %f\r\n"` and `"fav_num: %lf\r\n"`). | **Mantissa** | Fractional part of IEEE 754 encoding (23 bits for float, 52 bits for double) | | **mcrr** | ARM coprocessor register transfer instruction used for GPIO control | | **PADS_BANK0** | Register block at `0x40038000` that controls GPIO pad electrical properties | -| **Promotion** | Automatic conversion of a smaller type to a larger type (float → double) | +| **Promotion** | Automatic conversion of a smaller type to a larger type (float -> double) | | **Register Pair** | Two 32-bit registers (r2:r3) used together to hold a 64-bit value | | **UF2** | USB Flashing Format - file format for Pico 2 firmware | | **uint8_t** | Unsigned 8-bit integer type (0 to 255) | --- -## 🔗 Additional Resources +## ? Additional Resources ### GPIO Coprocessor Reference @@ -1395,19 +1373,21 @@ The RP2350 GPIO coprocessor instructions: ### IEEE 754 Encoding Formula ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Float (32-bit): [1 sign] [8 exponent] [23 mantissa] │ -│ Double (64-bit): [1 sign] [11 exponent] [52 mantissa] │ -│ │ -│ Value = (-1)^sign × 2^(exponent - bias) × (1 + mantissa) │ -│ │ -│ Float bias: 127 │ -│ Double bias: 1023 │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Float (32-bit): [1 sign] [8 exponent] [23 mantissa] | +| Double (64-bit): [1 sign] [11 exponent] [52 mantissa] | +| | +| Value = (-1)^sign * 2^(exponent - bias) * (1 + mantissa) | +| | +| Float bias: 127 | +| Double bias: 1023 | ++-----------------------------------------------------------------+ ``` --- -**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Whether it's an integer, a float, or a double — it's all just bits waiting to be decoded. Practice makes perfect! +**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Whether it's an integer, a float, or a double - it's all just bits waiting to be decoded. Practice makes perfect! + +Happy hacking! ? + -Happy hacking! 🔧 diff --git a/WEEK06/WEEK06-01-S.md b/WEEK06/WEEK06-01-S.md deleted file mode 100644 index 27c771b..0000000 --- a/WEEK06/WEEK06-01-S.md +++ /dev/null @@ -1,53 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 6 -Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics - -### Non-Credit Practice Exercise 1 Solution: Change the Static Variable Initial Value from 42 to 100 - -#### Answers - -##### Static Variable Location - -| Item | Value | Notes | -|---------------------------|-------------|--------------------------------| -| RAM address (runtime) | 0x200005a8 | Where variable lives at runtime | -| Flash address (init value)| Calculated | In .data section of flash | -| Original value (hex) | 0x2A | 42 decimal | -| Patched value (hex) | 0x64 | 100 decimal | -| File offset | flash_addr - 0x10000000 | Binary base subtraction | - -##### GDB Session - -```gdb -(gdb) x/1db 0x200005a8 -0x200005a8: 42 -(gdb) find /b 0x10000000, 0x10010000, 0x2a -``` - -##### Serial Output After Patch - -``` -regular_fav_num: 42 -static_fav_num: 100 -regular_fav_num: 42 -static_fav_num: 101 -regular_fav_num: 42 -static_fav_num: 102 -... -``` - -#### Reflection Answers - -1. **Why does the initial value live in flash AND get copied to RAM? Why not just use flash directly?** - Static variables need to be **modifiable** at runtime—the program increments `static_fav_num` each iteration. Flash memory is read-only during normal execution (it requires a special erase/program sequence to modify). So the initial value is stored in flash as a template, and the startup code (`crt0.S`) copies the entire `.data` section from flash to RAM before `main()` runs. This gives the variable its correct starting value (42) in writable RAM where subsequent `adds` and `strb` instructions can modify it freely. - -2. **The static variable wraps around at 255 (since it's `uint8_t`). After patching the initial value to 100, after how many iterations will it overflow back to 0?** - A `uint8_t` overflows from 255 to 0. Starting at 100 and incrementing by 1: it takes `255 - 100 = 155` increments to reach 255, then one more to wrap to 0. So it overflows after **156 iterations**. Compare to the original: starting at 42, it takes `255 - 42 + 1 = 214` iterations. - -3. **If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not?** - No. `regular_fav_num` is a **local variable** that the compiler optimized to an immediate constant (`movs r1, #0x2a`), just like Week 4's `age` variable. It's encoded directly in the instruction opcode in the `.text` section, not in the `.data` section. You would need to find the `movs r1, #0x2a` instruction in the code and patch the immediate byte from `0x2a` to your desired value. The `.data` section only contains initialized static/global variables. - -4. **What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?** - Yes. The linker places all initialized static variables contiguously in the `.data` section. Their initial values are stored in the same order in flash, packed adjacent to each other (possibly with alignment padding). The startup code performs a single `memcpy`-like loop that copies the entire `.data` block from flash to RAM. So if you had `static uint8_t a = 42;` and `static uint8_t b = 99;`, the bytes `0x2A` and `0x63` would be adjacent (or nearly so) in flash, and both would be copied to their respective RAM addresses during boot. diff --git a/WEEK06/WEEK06-01.md b/WEEK06/WEEK06-01.md deleted file mode 100644 index 5043b7b..0000000 --- a/WEEK06/WEEK06-01.md +++ /dev/null @@ -1,157 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 6 -Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics - -### Non-Credit Practice Exercise 1: Change the Static Variable Initial Value from 42 to 100 - -#### Objective -Use GDB to locate the static variable `static_fav_num` in the `.data` section of the `0x0014_static-variables` binary, calculate the corresponding file offset, patch the initial value from `42` (`0x2A`) to `100` (`0x64`) using a hex editor, convert the patched binary to UF2 format, and flash it to the Pico 2 to verify the change. - -#### Prerequisites -- Completed Week 6 tutorial (GDB section) -- `0x0014_static-variables.bin` binary available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 connected via USB -- Serial monitor software (PuTTY, minicom, or screen) - -#### Task Description -You will use GDB to examine the static variable at its known RAM address (`0x200005a8`), trace back to where the initial value is stored in the `.data` section of flash, calculate the file offset in the `.bin` file, patch the byte from `0x2A` to `0x64` with a hex editor, and verify on hardware that the serial output now starts counting from `100` instead of `42`. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0014_static-variables.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Examine the Static Variable in RAM - -We know from the tutorial that the static variable lives at `0x200005a8`. Examine its current value: - -```gdb -(gdb) x/1db 0x200005a8 -``` - -You should see the current value (probably `42` or a count if the program has been running). - -##### Step 3: Find Where the Initial Value Lives in Flash - -Static variables that are initialized get their starting values from the `.data` section in flash. The startup code copies these values from flash to RAM before `main()` runs. - -Examine the disassembly to find data copy references. The initial value `42` (`0x2A`) is stored somewhere in flash. Use GDB to search: - -```gdb -(gdb) find /b 0x10000000, 0x10010000, 0x2a -``` - -This searches the flash region for the byte `0x2A`. You may get multiple hits look for one that is in the data initialization area (typically near the end of the code section). - -##### Step 4: Confirm the Address - -Once you identify a candidate address, verify it by examining the surrounding bytes: - -```gdb -(gdb) x/8xb -``` - -The `0x2A` byte at this address should be the initialization value for our static variable. - -##### Step 5: Calculate the File Offset - -The binary is loaded at base address `0x10000000`. To find the file offset: - -``` -file_offset = address - 0x10000000 -``` - -For example, if the initial value is at `0x10004xxx`: -- File offset = `0x10004xxx` - `0x10000000` = `0x4xxx` - -##### Step 6: Patch the Value with a Hex Editor - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` -2. Press **Ctrl+G** (Go to offset) -3. Enter the calculated offset -4. You should see the byte `2A` at this position -5. Change `2A` to `64` (100 in decimal) -6. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory) - -##### Step 7: Convert to UF2 and Flash - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables -python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive -3. Open your serial monitor - -##### Step 8: Verify the Hack - -**Expected serial output:** -``` -regular_fav_num: 42 -static_fav_num: 100 ? Starts at 100 now! -regular_fav_num: 42 -static_fav_num: 101 -regular_fav_num: 42 -static_fav_num: 102 -... -``` - -The static variable now starts counting from 100 instead of 42! - -#### Expected Output - -After completing this exercise, you should be able to: -- Use GDB `find` command to search for byte patterns in flash -- Distinguish between RAM addresses (where the variable lives at runtime) and flash addresses (where the initial value is stored) -- Calculate file offsets from memory addresses -- Patch initialization values with a hex editor -- Understand the `.data` section copy mechanism at startup - -#### Questions for Reflection - -###### Question 1: Why does the initial value live in flash AND get copied to RAM? Why not just use flash directly? - -###### Question 2: The static variable wraps around at 255 (since it's `uint8_t`). After patching the initial value to 100, after how many iterations will it overflow back to 0? - -###### Question 3: If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not? - -###### Question 4: What would happen if the `.data` section had TWO static variables would their initial values be adjacent in flash? - -#### Tips and Hints -- The `find` command in GDB can search for bytes, halfwords, or words in any memory range -- Static variables with initial values are in `.data`; without initial values they're in `.bss` -- The startup code (`crt0`) copies the entire `.data` section from flash to RAM before calling `main()` -- HxD shows both hex and ASCII the value `0x2A` is the ASCII character `*` - -#### Next Steps -- Proceed to Exercise 2 to try a more complex hack (reversing GPIO logic) -- Try patching the static variable to `0xFF` (255) and observe immediate overflow behavior -- Look at the startup code in GDB to find the memcpy that copies `.data` from flash to RAM diff --git a/WEEK06/WEEK06-02-S.md b/WEEK06/WEEK06-02-S.md deleted file mode 100644 index 113ff3e..0000000 --- a/WEEK06/WEEK06-02-S.md +++ /dev/null @@ -1,63 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 6 -Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics - -### Non-Credit Practice Exercise 2 Solution: Reverse Engineer gpio_set_pulls with GDB - -#### Answers - -##### Function Arguments - -```gdb -(gdb) b *0x100002d8 -(gdb) c -(gdb) info registers r0 r1 r2 -r0 = 15 ; GPIO pin -r1 = 1 ; pull-up enable (true) -r2 = 0 ; pull-down disable (false) -``` - -##### PADS_BANK0 Address Calculation - -``` -Base: 0x40038000 (PADS_BANK0) -GPIO 0 offset: 0x04 (first pad register) -GPIO N offset: 0x04 + (N × 4) -GPIO 15: 0x40038000 + 0x04 + (15 × 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040 -``` - -##### Function Behavior Summary - -| Step | Instruction(s) | Effect | -|------------------------|---------------------|-----------------------------------| -| Load PADS base address | `ldr rX, =0x40038000` | rX = PADS_BANK0 base | -| Calculate pad offset | `adds`, `lsls` | offset = 0x04 + pin × 4 | -| Read current config | `ldr rX, [addr]` | Read existing pad register value | -| Clear pull bits | `bic rX, rX, #0xC` | Clear bits 2 (PDE) and 3 (PUE) | -| Set pull-up bit | `orr rX, rX, #0x8` | Set bit 3 (PUE = pull-up enable) | -| Write back | `str rX, [addr]` | Write updated config to hardware | - -##### Pad Register Bits - -| Bit | Name | Value | Meaning | -|-----|----------|-------|-------------------------| -| 3 | PUE | 1 | Pull-up enable | -| 2 | PDE | 0 | Pull-down disable | -| 1 | SCHMITT | 1 | Schmitt trigger enabled | -| 0 | SLEWFAST | 0 | Slow slew rate | - -#### Reflection Answers - -1. **Why does the function read-modify-write the register instead of just writing a new value? What would happen if it just wrote without reading first?** - The pad register contains multiple configuration fields (drive strength, slew rate, Schmitt trigger, input enable, output disable, pull-up, pull-down). If the function wrote a new value without reading first, it would overwrite all other fields with zeros or arbitrary values, potentially disabling the Schmitt trigger, changing drive strength, or disabling input/output. The read-modify-write pattern uses `bic` to clear only the pull bits (2 and 3) and `orr` to set the desired pull configuration, leaving all other bits untouched. - -2. **The pad register for GPIO 15 is at offset `0x40` from the PADS base. How is this offset calculated? What would the offset be for GPIO 0?** - The formula is: offset = `0x04 + (GPIO_number × 4)`. For GPIO 15: `0x04 + (15 × 4) = 0x04 + 0x3C = 0x40`. The `0x04` initial offset exists because offset `0x00` is the VOLTAGE_SELECT register, not a pad register. For GPIO 0: offset = `0x04 + (0 × 4) = 0x04`. So GPIO 0's pad register is at `0x40038004`. - -3. **What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)?** - Enabling both creates a **resistive voltage divider** between VDD and GND through the internal pull resistors. On the RP2350, both pull resistors are typically ~50kΩ. The pin voltage would settle at approximately VDD/2 (1.65V for 3.3V supply), which is in the undefined region between logic HIGH and LOW thresholds. This makes the digital input unreliable—the Schmitt trigger may oscillate or read randomly. While not damaging to the hardware, it wastes power and produces unpredictable input reads. The SDK intentionally never sets both bits simultaneously. - -4. **The compiler inlines `gpio_pull_up` into `gpio_set_pulls`. What does this tell you about the compiler's optimization level? How does inlining affect binary analysis?** - This indicates at least `-O1` or higher optimization (the Pico SDK defaults to `-O2`). The `gpio_pull_up` function is declared `static inline` in the SDK header, and the compiler eliminates the function call overhead by inserting `gpio_set_pulls(pin, true, false)` directly. For binary analysis, inlining means: (a) the original function name `gpio_pull_up` doesn't appear in the symbol table, (b) you see `gpio_set_pulls` called directly with hardcoded arguments, making it harder to identify the programmer's original intent, and (c) multiple calls to `gpio_pull_up` with different pins each become separate `gpio_set_pulls` calls rather than referencing a single function body. diff --git a/WEEK06/WEEK06-02.md b/WEEK06/WEEK06-02.md deleted file mode 100644 index 81f781c..0000000 --- a/WEEK06/WEEK06-02.md +++ /dev/null @@ -1,187 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 6 -Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics - -### Non-Credit Practice Exercise 2: Reverse Engineer gpio_set_pulls with GDB - -#### Objective -Use GDB to disassemble the `gpio_set_pulls` function, trace its register writes to identify the PADS_BANK0 hardware register it modifies, and document how the Pico 2 configures internal pull-up and pull-down resistors at the hardware level. - -#### Prerequisites -- Completed Week 6 tutorial (GDB section) -- `0x0014_static-variables.elf` binary available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- Understanding of GPIO pull-up resistors from Week 6 Part 2 -- Understanding of function inlining from Week 6 Part 3 - -#### Task Description -You will use GDB to locate the `gpio_set_pulls` function (remember: `gpio_pull_up` is inlined and becomes `gpio_set_pulls`), disassemble it, step through it instruction by instruction, and identify the PADS_BANK0 register address it writes to. You will document the bit fields being set and explain how the hardware implements pull-up and pull-down resistors. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0014_static-variables.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find Where gpio_set_pulls is Called - -From the tutorial, we know `gpio_set_pulls` is called at `0x1000024e` with arguments for GPIO 15: - -```gdb -(gdb) x/5i 0x10000240 -``` - -You should see: -``` -0x10000240: movs r0, #15 ; GPIO pin 15 -0x10000242: mov.w r3, #0 ; GPIO_IN (direction) -0x10000246: mcrr 0, 4, r0, r3, cr4 ; gpio_set_dir via coprocessor -0x1000024a: movs r2, #0 ; down = false -0x1000024c: movs r1, #1 ; up = true -0x1000024e: bl 0x100002d8 ; gpio_set_pulls -``` - -##### Step 3: Disassemble gpio_set_pulls - -Now examine the function itself: - -```gdb -(gdb) x/30i 0x100002d8 -``` - -Study the disassembly carefully. Look for: -- Address calculations (base address of PADS_BANK0) -- Register loads and stores -- Bit manipulation instructions (AND, OR, BIC) -- The write to the hardware register - -##### Step 4: Set a Breakpoint at gpio_set_pulls - -```gdb -(gdb) b *0x100002d8 -(gdb) monitor reset halt -(gdb) c -``` - -When the breakpoint hits, examine the arguments: - -```gdb -(gdb) info registers r0 r1 r2 -``` - -You should see: -- `r0 = 15` (GPIO pin) -- `r1 = 1` (pull-up enable) -- `r2 = 0` (pull-down disable) - -##### Step 5: Step Through the Function - -Use `si` (step instruction) to execute one instruction at a time: - -```gdb -(gdb) si -(gdb) info registers -``` - -Repeat, watching the registers change. Pay attention to: - -1. **Address calculation**: The function computes the PADS_BANK0 register address for GPIO 15. The base address is `0x40038000`, and each GPIO pad has a 4-byte register. GPIO 0 starts at offset `0x04`, so GPIO 15 is at: - -$$0x40038000 + 0x04 + (15 \times 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040$$ - -2. **Read the current register value**: The function reads the existing pad configuration -3. **Modify the pull bits**: It sets or clears the pull-up (bit 3) and pull-down (bit 2) bits -4. **Write back**: It stores the modified value - -##### Step 6: Examine the PADS_BANK0 Register - -After the function completes, examine the result: - -```gdb -(gdb) x/1wx 0x40038040 -``` - -Document the value you see. The relevant bits are: - -| Bit | Name | Value | Meaning | -| --- | -------- | ----- | ----------------------- | -| 3 | PUE | 1 | Pull-up enable | -| 2 | PDE | 0 | Pull-down enable | -| 1 | SCHMITT | 1 | Schmitt trigger enabled | -| 0 | SLEWFAST | 0 | Slow slew rate | - -##### Step 7: Compare with gpio_set_pulls for the LED Pin - -Continue execution until gpio_set_pulls is called again (if it is). Or, examine the pad register for GPIO 16 (the LED pin): - -```gdb -(gdb) x/1wx 0x40038044 -``` - -Compare the values. The LED pin (output) should NOT have pull-up enabled. - -##### Step 8: Document Your Findings - -Create a table documenting the function's behavior: - -| Step | Instruction(s) | Register Changes | -| ------------------------ | ------------------ | --------------------------- | -| Load PADS base address | `ldr rX, =...` | rX = `0x40038000` | -| Calculate pad offset | `adds`, `lsls` | offset = `0x04 + pin * 4` | -| Read current pad config | `ldr rX, [addr]` | rX = current register value | -| Clear pull bits | `bic rX, rX, #0xC` | Clear bits 2 and 3 | -| Set pull-up bit | `orr rX, rX, #0x8` | Set bit 3 (PUE) | -| Write back | `str rX, [addr]` | Updated pad register | - -#### Expected Output - -After completing this exercise, you should be able to: -- Disassemble and trace through a hardware configuration function -- Identify PADS_BANK0 register addresses for any GPIO pin -- Understand how pull-up and pull-down resistors are controlled at the register level -- Explain why `gpio_pull_up(pin)` becomes `gpio_set_pulls(pin, true, false)` in the binary - -#### Questions for Reflection - -###### Question 1: Why does the function read-modify-write the register instead of just writing a new value? What would happen if it just wrote without reading first? - -###### Question 2: The pad register for GPIO 15 is at offset `0x40` from the PADS base. How is this offset calculated? What would the offset be for GPIO 0? - -###### Question 3: What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)? - -###### Question 4: The compiler inlines `gpio_pull_up` into `gpio_set_pulls`. What does this tell you about the compiler's optimization level? How does inlining affect binary analysis? - -#### Tips and Hints -- PADS_BANK0 base address on RP2350 is `0x40038000` -- Each GPIO has a 4-byte pad control register starting at offset `0x04` -- The `bic` instruction clears bits (Bit Clear); `orr` sets bits (OR) -- Use `x/1wx` to examine a 32-bit word; use `x/1tb` to see individual bits -- The RP2350 datasheet section on PADS_BANK0 has the full register bit map - -#### Next Steps -- Proceed to Exercise 3 to reverse engineer the `eor` (XOR) instruction in the GPIO logic -- Try enabling pull-down instead of pull-up by modifying the `gpio_set_pulls` arguments in GDB: `set $r1 = 0` and `set $r2 = 1` before the function call -- Examine the PADS registers for all GPIOs to see which pins have pull-ups enabled after boot diff --git a/WEEK06/WEEK06-03-S.md b/WEEK06/WEEK06-03-S.md deleted file mode 100644 index ed4a6d2..0000000 --- a/WEEK06/WEEK06-03-S.md +++ /dev/null @@ -1,57 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 6 -Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics - -### Non-Credit Practice Exercise 3 Solution: Make the Overflow Happen Faster - -#### Answers - -##### Patch Details - -| Item | Original | Patched | -|--------------------|-----------------|-----------------| -| Instruction | adds r3, #0x1 | adds r3, #0xa | -| Address | 0x1000027c | 0x1000027c | -| Hex bytes | 01 33 | 0A 33 | -| Increment value | 1 | 10 | -| File offset | 0x27c | 0x27c | - -##### Instruction Encoding - -``` -Thumb adds rD, #imm8: - Byte 0: immediate value (0x01 → 0x0A) - Byte 1: opcode + register (0x33 = adds r3) -``` - -##### Serial Output After Patch - -``` -regular_fav_num: 42 -static_fav_num: 42 -regular_fav_num: 42 -static_fav_num: 52 -regular_fav_num: 42 -static_fav_num: 62 -... -regular_fav_num: 42 -static_fav_num: 252 -regular_fav_num: 42 -static_fav_num: 6 ← Overflow! 252 + 10 = 262 mod 256 = 6 -``` - -#### Reflection Answers - -1. **The overflow now wraps to 6 instead of 0. Explain why, using the modular arithmetic of a `uint8_t` (range 0-255).** - A `uint8_t` stores values modulo 256. Starting at 42 and incrementing by 10: the sequence passes through 42, 52, 62, ..., 242, 252. The next value is 252 + 10 = 262. Since `uint8_t` can only hold 0–255: $262 \bmod 256 = 6$. The wrap value is non-zero because the increment (10) does not evenly divide into 256. With increment 1, the value hits exactly 255, and $255 + 1 = 256 \bmod 256 = 0$. With increment 10, it skips from 252 directly to 262, bypassing 0 and landing on 6. - -2. **What is the maximum value you could change the increment to while still using `adds r3, #imm8`? What would happen if you needed an increment larger than 255?** - The maximum is **255** (`0xFF`). The `adds rD, #imm8` Thumb encoding has an 8-bit immediate field, so valid values are 0–255. For an increment larger than 255, you would need to: (a) use a 32-bit Thumb-2 `adds.w` instruction which supports a wider range of modified immediates, (b) use a `movs` + `adds` two-instruction sequence to load a larger value, or (c) load the value from a literal pool with `ldr` then use a register-register `add`. Each approach requires different instruction sizes, so patching in a hex editor becomes more complex—you may need to shift code or use NOP padding. - -3. **If you changed the increment to 128 (`0x80`), how many iterations would it take to wrap, and what value would it wrap to?** - Starting at 42, incrementing by 128: 42 → 170 → 42 → 170 → ... Wait, let's compute: $42 + 128 = 170$ (first iteration), $170 + 128 = 298 \bmod 256 = 42$ (second iteration). It wraps after **2 iterations** back to 42. The variable alternates between 42 and 170 forever because $2 \times 128 = 256$, and $42 + 256 = 42 \bmod 256$. The value never reaches 0—it cycles between exactly two values. - -4. **Could you achieve the same speedup by changing the `strb` (store byte) to `strh` (store halfword)? Why or why not?** - No. Changing `strb` to `strh` would store 16 bits instead of 8, which means the variable would be treated as a `uint16_t` (range 0–65535). This would actually **slow down** overflow—it would take 65,535 − 42 = 65,493 iterations to wrap instead of 213. Additionally, `strh` writes 2 bytes to RAM, potentially corrupting the adjacent byte at `0x200005a9`. The proper way to speed up overflow is to increase the increment value, not change the storage width. The `strb` truncation to 8 bits is what enforces the `uint8_t` modular arithmetic. diff --git a/WEEK06/WEEK06-03.md b/WEEK06/WEEK06-03.md deleted file mode 100644 index d770f2d..0000000 --- a/WEEK06/WEEK06-03.md +++ /dev/null @@ -1,183 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 6 -Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics - -### Non-Credit Practice Exercise 3: Make the Overflow Happen Faster - -#### Objective -Patch the `adds r3, #0x1` instruction which increments `static_fav_num` by 1 each loop iteration to `adds r3, #0xa` so the variable increments by 10 instead. Use GDB to locate the instruction, calculate the hex editor file offset, patch the binary, and verify on hardware that the `uint8_t` overflow occurs roughly 10 times sooner. - -#### Prerequisites -- Completed Week 6 tutorial (GDB and hex editor sections) -- `0x0014_static-variables.bin` binary available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 connected via USB -- Serial monitor software (PuTTY, minicom, or screen) - -#### Task Description -The static variable `static_fav_num` is a `uint8_t` that counts from 42 to 255 before wrapping to 0. Currently it increments by 1 each iteration, so it takes 214 steps to overflow. You will change the increment value from 1 to 10 so that it overflows after only ~22 steps. This exercise teaches you how Thumb immediate encoding works for small arithmetic instructions and demonstrates the real-world impact of patching arithmetic operations. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0014_static-variables.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Locate the Increment Instruction - -From the tutorial, we know the static variable operations are in the loop body starting at `0x10000274`. Disassemble the loop body: - -```gdb -(gdb) x/20i 0x10000274 -``` - -Look for this sequence: - -``` -0x10000278: ldrb r3, [r4, #0] ; Load static_fav_num from RAM -0x1000027a: movs r2, #16 ; LED GPIO pin number -0x1000027c: adds r3, #1 ; Increment by 1 ? THIS IS OUR TARGET -0x1000027e: strb r3, [r4, #0] ; Store back to RAM -``` - -The `adds r3, #1` instruction is at address `0x1000027c`. - -##### Step 3: Examine the Instruction Encoding - -Look at the raw bytes of the instruction: - -```gdb -(gdb) x/2bx 0x1000027c -``` - -You should see: - -``` -01 33 -``` - -**Thumb encoding breakdown:** -- `01` = the immediate value `0x01` (decimal 1) -- `33` = the opcode for `adds r3, #imm8` - -##### Step 4: Test the Change in GDB First - -Before making a permanent patch, test the change in RAM: - -```gdb -(gdb) b *0x1000027c -(gdb) c -``` - -When the breakpoint hits: - -```gdb -(gdb) x/1db 0x200005a8 -``` - -Note the current value of `static_fav_num`. Now continue a few iterations and check how it increments. - -##### Step 5: Calculate the File Offset - -``` -file_offset = address - 0x10000000 -``` - -For the `adds r3, #0x1` instruction at its address, calculate the offset. - -##### Step 6: Patch with the Hex Editor - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` -2. Press **Ctrl+G** (Go to offset) and enter the calculated offset -3. You should see the byte `01` followed by `33` -4. Change `01` to `0A` (10 in decimal) -5. Verify: the bytes should now read `0A 33` encoding `adds r3, #0xa` -6. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory) - -> ?? **Why this works:** In Thumb `adds rD, #imm8` encoding, the immediate value is stored in the first byte. The `#imm8` field accepts values 0-255, so changing 1 to 10 is safe. - -##### Step 7: Convert to UF2 and Flash - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables -python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive -3. Open your serial monitor - -##### Step 8: Verify the Hack - -**Expected serial output:** -``` -regular_fav_num: 42 -static_fav_num: 42 -regular_fav_num: 42 -static_fav_num: 52 ? Jumped by 10! -regular_fav_num: 42 -static_fav_num: 62 -... -regular_fav_num: 42 -static_fav_num: 242 -regular_fav_num: 42 -static_fav_num: 252 -regular_fav_num: 42 -static_fav_num: 6 ? Overflow! 252 + 10 = 262, but uint8_t wraps: 262 - 256 = 6 -``` - -Notice the overflow now happens much sooner, and the wrap value is no longer 0 it's 6 because `252 + 10 = 262` which wraps to `262 mod 256 = 6`. - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate arithmetic instructions in disassembled code -- Understand Thumb `adds rD, #imm8` encoding -- Patch an immediate operand in a hex editor -- Predict the effects of changing an increment value on overflow behavior - -#### Questions for Reflection - -###### Question 1: The overflow now wraps to 6 instead of 0. Explain why, using the modular arithmetic of a `uint8_t` (range 0-255). - -###### Question 2: What is the maximum value you could change the increment to while still using `adds r3, #imm8`? What would happen if you needed an increment larger than 255? - -###### Question 3: If you changed the increment to 128 (`0x80`), how many iterations would it take to wrap, and what value would it wrap to? - -###### Question 4: Could you achieve the same speedup by changing the `strb` (store byte) to `strh` (store halfword)? Why or why not? - -#### Tips and Hints -- In Thumb encoding, `adds rD, #imm8` has the immediate in the first byte and opcode in the second -- The `imm8` field is 8 bits, so valid ranges are `0x00` to `0xFF` (0-255) -- Overflow for `uint8_t` is $(value) \bmod 256$ -- Use GDB to verify: `set *(unsigned char*)0x200005a8 = 250` then continue to watch the wrap quickly - -#### Next Steps -- Proceed to Exercise 4 to learn about inverting button logic with XOR -- Try changing the increment to other values (2, 5, 50, 128) and predict the wrap behavior before flashing -- Consider: what would happen if you changed `adds` to `subs` (subtract)? diff --git a/WEEK06/WEEK06-04-S.md b/WEEK06/WEEK06-04-S.md deleted file mode 100644 index 9464c45..0000000 --- a/WEEK06/WEEK06-04-S.md +++ /dev/null @@ -1,60 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 6 -Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics - -### Non-Credit Practice Exercise 4 Solution: Invert the Button Logic with XOR - -#### Answers - -##### Patch Details - -| Item | Original | Patched | -|--------------------|-----------------------|-----------------------| -| Instruction | eor.w r3, r3, #1 | eor.w r3, r3, #0 | -| Address | 0x10000286 | 0x10000286 | -| Hex bytes | 83 F0 01 03 | 83 F0 00 03 | -| Patched byte offset| 0x288 (3rd byte) | 01 → 00 | - -##### Logic Table Comparison - -**Original (EOR #1):** - -| Button State | GPIO 15 | After UBFX | After EOR #1 | LED (GPIO 16) | -|-------------|---------|------------|-------------|---------------| -| Released | 1 (HIGH)| 1 | 0 | OFF | -| Pressed | 0 (LOW) | 0 | 1 | ON | - -**Patched (EOR #0):** - -| Button State | GPIO 15 | After UBFX | After EOR #0 | LED (GPIO 16) | -|-------------|---------|------------|-------------|---------------| -| Released | 1 (HIGH)| 1 | 1 | **ON** | -| Pressed | 0 (LOW) | 0 | 0 | **OFF** | - -##### Hardware Result - -- Button NOT pressed: LED **ON** (was OFF) -- Button PRESSED: LED **OFF** (was ON) -- Behavior completely reversed by changing a single byte (01 → 00) - -#### Reflection Answers - -1. **Why does XOR with 1 act as a NOT for single-bit values? Write out the truth table for `x XOR 1` and `x XOR 0` where x is 0 or 1.** - - | x | x XOR 1 | x XOR 0 | - |---|---------|---------| - | 0 | 1 | 0 | - | 1 | 0 | 1 | - - `x XOR 1` always flips the bit (acts as NOT): 0→1, 1→0. `x XOR 0` always preserves the bit (acts as identity): 0→0, 1→1. This works because XOR returns 1 when inputs differ and 0 when they match. XOR with 1 forces a difference; XOR with 0 forces a match. This property only applies to the single affected bit—for multi-bit values, each bit is XORed independently. - -2. **Instead of changing `eor.w r3, r3, #1` to `eor.w r3, r3, #0`, could you achieve the same result by NOPing (removing) the instruction entirely? What bytes encode a NOP in Thumb?** - Yes. Removing the EOR instruction entirely would have the same effect as EOR #0—the value passes through unchanged. A Thumb NOP is encoded as `00 BF` (2 bytes). Since `eor.w` is a 32-bit Thumb-2 instruction (4 bytes), you would need **two** NOPs to replace it: `00 BF 00 BF`. In the hex editor, replace bytes at offset 0x286–0x289 from `83 F0 01 03` to `00 BF 00 BF`. Both approaches yield identical behavior, but the EOR #0 patch is "cleaner" because it preserves the instruction structure—making the modification more obvious during reverse engineering. - -3. **The pull-up resistor means "pressed = LOW." If you removed the pull-up (changed `gpio_pull_up` to no pull), would the button still work? Why or why not?** - It would be unreliable. Without a pull-up or pull-down resistor, the GPIO input is **floating** when the button is not pressed—there's no defined voltage on the pin. The input would pick up electrical noise, stray capacitance, and electromagnetic interference, causing random readings (0 or 1 unpredictably). When the button IS pressed, it connects to ground (LOW), which works. But when released, the pin has no path to any voltage, so `gpio_get(15)` returns garbage. The pull-up provides a defined HIGH state when the button circuit is open. - -4. **The `ubfx r3, r3, #0xf, #0x1` instruction extracts bit 15. If you changed `#0xf` to `#0x10` (bit 16), what GPIO pin would you be reading? What value would you get if nothing is connected to that pin?** - You would be reading **GPIO 16**, which is the LED output pin. Since GPIO 16 is configured as an output (not input), reading its input register returns the current output state—either 0 or 1 depending on whether the LED is currently on or off. This would create a **feedback loop**: the LED's current state determines its next state (after the XOR), causing unpredictable oscillation or a stuck state. If GPIO 16 had nothing connected and was unconfigured, the floating input would return random values, similar to Q3's scenario. diff --git a/WEEK06/WEEK06-04.md b/WEEK06/WEEK06-04.md deleted file mode 100644 index 211d41d..0000000 --- a/WEEK06/WEEK06-04.md +++ /dev/null @@ -1,193 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 6 -Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics - -### Non-Credit Practice Exercise 4: Invert the Button Logic with XOR - -#### Objective -Find the `eor.w r3, r3, #1` instruction that implements the ternary operator's button inversion, patch it to `eor.w r3, r3, #0` using a hex editor to reverse the LED behavior, and verify that the LED is now ON when the button is pressed and OFF when released the opposite of the original behavior. - -#### Prerequisites -- Completed Week 6 tutorial (all GDB and hex editor sections) -- `0x0014_static-variables.bin` binary available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with button on GP15 and LED on GP16 - -#### Task Description -The original program uses `gpio_put(LED_GPIO, !gpio_get(BUTTON_GPIO))` which the compiler implements as an XOR (`eor.w r3, r3, #1`) to invert the button state. With the pull-up resistor, button released = HIGH, so `HIGH XOR 1 = 0` (LED off). You will patch the XOR operand from `#1` to `#0`, which effectively removes the inversion: `HIGH XOR 0 = 1` (LED on when released). This exercise demonstrates how a single-byte binary patch can completely reverse hardware behavior. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0014_static-variables.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Locate the GPIO Logic - -From the tutorial, the GPIO input/output logic is near address `0x10000274`. Disassemble: - -```gdb -(gdb) x/10i 0x10000274 -``` - -Look for this sequence: - -``` -0x10000274: mov.w r1, #0xd0000000 ; SIO base address -0x10000280: ldr r3, [r1, #4] ; Read GPIO input register -0x10000282: ubfx r3, r3, #15, #1 ; Extract bit 15 (button state) -0x10000286: eor.w r3, r3, #1 ; XOR with 1 INVERT ? OUR TARGET -0x1000028a: mcrr 0, 4, r2, r3, cr0 ; Write to GPIO output -``` - -The `eor.w r3, r3, #1` instruction is at address `0x10000286`. - -##### Step 3: Understand the Current Logic - -Trace the logic with the pull-up resistor: - -| Button State | GPIO 15 Input | After UBFX | After EOR #1 | LED (GPIO 16) | -| ------------ | ------------- | ---------- | ------------ | -------------- | -| Released | 1 (HIGH) | 1 | 0 | OFF | -| Pressed | 0 (LOW) | 0 | 1 | ON | - -The `eor.w #1` flips the bit, implementing the `!` (NOT) from the C code. - -##### Step 4: Verify with GDB - -Set a breakpoint at the `eor.w` instruction: - -```gdb -(gdb) b *0x10000286 -(gdb) c -``` - -When it hits, check what value is about to be XORed: - -```gdb -(gdb) info registers r3 -``` - -- If button is **released**: `r3 = 1` ? after EOR: `r3 = 0` -- If button is **pressed**: `r3 = 0` ? after EOR: `r3 = 1` - -##### Step 5: Test the Patch in GDB - -Modify the EOR operand in RAM to see the effect live: - -```gdb -(gdb) set $r3 = 0 -(gdb) si -(gdb) info registers r3 -``` - -Or skip the EOR entirely by advancing the PC past it, then observe the LED behavior. - -##### Step 6: Examine the Instruction Encoding - -Look at the raw bytes: - -```gdb -(gdb) x/4bx 0x10000286 -0x10000286 : 0x83 0xf0 0x01 0x03 -``` - -The `eor.w` instruction is a 32-bit Thumb-2 encoding. The 4 bytes break down as: -- `0x83 0xF0` opcode + source register (r3) -- `0x01` **the immediate value (`#1`)** ? this is what we change -- `0x03` destination register (r3) - -##### Step 7: Patch with the Hex Editor - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` -2. The instruction starts at file offset: `0x10000286 - 0x10000000 = 0x286` -3. The immediate byte is the 3rd byte: offset `0x286 + 2 = 0x288` -4. Press **Ctrl+G** and enter offset: `288` -5. You should see `01` change it to `00` -6. Verify the surrounding bytes (`83 F0` before and `03` after) are unchanged -7. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory) - -> ?? **Why offset `0x288`?** The 4-byte instruction starts at `0x286`, but the immediate value `#1` is in the **third byte** (index 2), so it's at `0x286 + 2 = 0x288`. - -##### Step 8: Predict the New Behavior - -After patching, the logic changes: - -| Button State | GPIO 15 Input | After UBFX | After EOR #0 | LED (GPIO 16) | -| ------------ | ------------- | ---------- | ------------ | -------------- | -| Released | 1 (HIGH) | 1 | 1 | **ON** | -| Pressed | 0 (LOW) | 0 | 0 | **OFF** | - -The LED behavior is now **inverted** from the original! - -##### Step 9: Convert to UF2 and Flash - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables -python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -##### Step 10: Verify the Hack - -Test the button: -- **Button NOT pressed**: LED should now be **ON** (was OFF before patching) -- **Button PRESSED**: LED should now be **OFF** (was ON before patching) - -The LED behavior is completely reversed by changing a single byte! - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate XOR / EOR instructions in disassembled GPIO logic -- Understand how XOR implements logical NOT for single-bit values -- Patch a Thumb-2 encoded immediate operand -- Predict hardware behavior changes from binary patches - -#### Questions for Reflection - -###### Question 1: Why does XOR with 1 act as a NOT for single-bit values? Write out the truth table for `x XOR 1` and `x XOR 0` where x is 0 or 1. - -###### Question 2: Instead of changing `eor.w r3, r3, #1` to `eor.w r3, r3, #0`, could you achieve the same result by NOPing (removing) the instruction entirely? What bytes encode a NOP in Thumb? - -###### Question 3: The pull-up resistor means "pressed = LOW." If you removed the pull-up (changed `gpio_pull_up` to no pull), would the button still work? Why or why not? - -###### Question 4: The `ubfx r3, r3, #0xf, #0x1` instruction extracts bit 15. If you changed `#0xf` to `#0x10` (bit 16), what GPIO pin would you be reading? What value would you get if nothing is connected to that pin? - -#### Tips and Hints -- `eor.w r3, r3, #1` is a 32-bit Thumb-2 instruction (4 bytes), not a 16-bit Thumb instruction -- A Thumb NOP is `00 bf` (2 bytes) you would need two NOPs to replace a 4-byte instruction -- Use GDB `x/1tw` to view a word in binary format, making bit manipulation easier to see -- The SIO base address `0xd0000000` provides single-cycle access to GPIO it's separate from the IO_BANK0 registers at `0x40028000` - -#### Next Steps -- Review all four exercises and verify you can patch any part of the binary: data values, arithmetic operations, and logic operations -- Try combining multiple hacks in a single binary: change the initial value, speed up the overflow, AND invert the button logic -- Compare your patched binary with the original using `fc /b original.bin patched.bin` in the command prompt to see all changed bytes diff --git a/WEEK06/WEEK06.md b/WEEK06/WEEK06.md index 71caf77..f8bc966 100644 --- a/WEEK06/WEEK06.md +++ b/WEEK06/WEEK06.md @@ -1,6 +1,6 @@ -# Week 6: Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics +?# Week 6: Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics -## 🎯 What You'll Learn This Week +## ? What You'll Learn This Week By the end of this tutorial, you will be able to: - Understand the difference between regular (automatic) variables and static variables @@ -14,7 +14,7 @@ By the end of this tutorial, you will be able to: --- -## 📚 Part 1: Understanding Static Variables +## Part 1: Understanding Static Variables ### What is a Static Variable? @@ -25,26 +25,26 @@ Think of it like this: - **Static variable:** Like writing in a notebook that you keep forever ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Regular vs Static Variables │ -│ │ -│ REGULAR (automatic): │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ Loop 1: Create → Set to 42 → Increment to 43 → Destroy │ │ -│ │ Loop 2: Create → Set to 42 → Increment to 43 → Destroy │ │ -│ │ Loop 3: Create → Set to 42 → Increment to 43 → Destroy │ │ -│ │ Result: Always appears as 42! │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ STATIC: │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ Loop 1: Already exists → Read 42 → Increment → Store 43 │ │ -│ │ Loop 2: Already exists → Read 43 → Increment → Store 44 │ │ -│ │ Loop 3: Already exists → Read 44 → Increment → Store 45 │ │ -│ │ Result: Keeps incrementing! │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Regular vs Static Variables | +| | +| REGULAR (automatic): | +| +------------------------------------------------------------+ | +| | Loop 1: Create -> Set to 42 -> Increment to 43 -> Destroy | | +| | Loop 2: Create -> Set to 42 -> Increment to 43 -> Destroy | | +| | Loop 3: Create -> Set to 42 -> Increment to 43 -> Destroy | | +| | Result: Always appears as 42! | | +| +------------------------------------------------------------+ | +| | +| STATIC: | +| +------------------------------------------------------------+ | +| | Loop 1: Already exists -> Read 42 -> Increment -> Store 43 | | +| | Loop 2: Already exists -> Read 43 -> Increment -> Store 44 | | +| | Loop 3: Already exists -> Read 44 -> Increment -> Store 45 | | +| | Result: Keeps incrementing! | | +| +------------------------------------------------------------+ | +| | ++-----------------------------------------------------------------+ ``` ### The `static` Keyword @@ -70,26 +70,26 @@ Different types of variables are stored in different memory locations: ### Stack vs Static Storage vs Heap ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Memory Layout │ -│ │ -│ ┌───────────────────┐ High Address (0x20082000) │ -│ │ STACK │ ← Automatic/local variables │ -│ │ (grows down) │ Created/destroyed per function │ -│ ├───────────────────┤ │ -│ │ │ │ -│ │ (free space) │ │ -│ │ │ │ -│ ├───────────────────┤ │ -│ │ HEAP │ ← Dynamic allocation (malloc/free) │ -│ │ (grows up) │ │ -│ ├───────────────────┤ │ -│ │ .bss section │ ← Uninitialized static/global vars │ -│ ├───────────────────┤ │ -│ │ .data section │ ← Initialized static/global vars │ -│ └───────────────────┘ Low Address (0x20000000) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Memory Layout | +| | +| +-------------------+ High Address (0x20082000) | +| | STACK | ?? Automatic/local variables | +| | (grows down) | Created/destroyed per function | +| +-------------------+ | +| | | | +| | (free space) | | +| | | | +| +-------------------+ | +| | HEAP | ?? Dynamic allocation (malloc/free) | +| | (grows up) | | +| +-------------------+ | +| | .bss section | ?? Uninitialized static/global vars | +| +-------------------+ | +| | .data section | ?? Initialized static/global vars | +| +-------------------+ Low Address (0x20000000) | +| | ++-----------------------------------------------------------------+ ``` **Key Point:** Static variables are NOT on the heap! They live in a fixed location in the `.data` section (if initialized) or `.bss` section (if uninitialized). This is different from heap memory which is dynamically allocated at runtime. @@ -108,29 +108,29 @@ This is called **overflow** or **wrap-around**. The value "wraps" back to 0 and --- -## 📚 Part 2: Understanding GPIO Inputs +## Part 2: Understanding GPIO Inputs ### Input vs Output So far, we've used GPIO pins as **outputs** to control LEDs. Now we'll learn to use them as **inputs** to read button states! ``` -┌─────────────────────────────────────────────────────────────────┐ -│ GPIO Direction │ -│ │ -│ OUTPUT (what we've done before): │ -│ ┌─────────┐ │ -│ │ Pico │ ───────► LED │ -│ │ GPIO 16 │ (We control the LED) │ -│ └─────────┘ │ -│ │ -│ INPUT (new this week): │ -│ ┌─────────┐ │ -│ │ Pico │ ◄─────── Button │ -│ │ GPIO 15 │ (We read the button state) │ -│ └─────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| GPIO Direction | +| | +| OUTPUT (what we've done before): | +| +---------+ | +| | Pico | -------? LED | +| | GPIO 16 | (We control the LED) | +| +---------+ | +| | +| INPUT (new this week): | +| +---------+ | +| | Pico | ?------- Button | +| | GPIO 15 | (We read the button state) | +| +---------+ | +| | ++-----------------------------------------------------------------+ ``` ### The Floating Input Problem @@ -138,18 +138,18 @@ So far, we've used GPIO pins as **outputs** to control LEDs. Now we'll learn to When a GPIO pin is set as an input but nothing is connected, it's called a **floating input**. The voltage on the pin is undefined and can randomly read as HIGH (1) or LOW (0) due to electrical noise. ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Floating Input = Random Values! │ -│ │ -│ GPIO Pin (no connection): │ -│ Reading 1: HIGH │ -│ Reading 2: LOW │ -│ Reading 3: HIGH │ -│ Reading 4: HIGH │ -│ Reading 5: LOW │ -│ (Completely unpredictable!) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Floating Input = Random Values! | +| | +| GPIO Pin (no connection): | +| Reading 1: HIGH | +| Reading 2: LOW | +| Reading 3: HIGH | +| Reading 4: HIGH | +| Reading 5: LOW | +| (Completely unpredictable!) | +| | ++-----------------------------------------------------------------+ ``` ### Pull-Up and Pull-Down Resistors @@ -164,25 +164,25 @@ To solve the floating input problem, we use **pull resistors**: The Pico 2 has **internal** pull resistors that you can enable with software - no external components needed! ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Pull-Up Resistor (what we're using) │ -│ │ -│ 3.3V │ -│ │ │ -│ ┴ (internal pull-up resistor) │ -│ │ │ -│ ├──────► GPIO 15 (reads HIGH normally) │ -│ │ │ -│ ┌─┴─┐ │ -│ │BTN│ ← Button connects GPIO to GND when pressed │ -│ └─┬─┘ │ -│ │ │ -│ GND │ -│ │ -│ Button NOT pressed: GPIO reads 1 (HIGH) │ -│ Button PRESSED: GPIO reads 0 (LOW) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Pull-Up Resistor (what we're using) | +| | +| 3.3V | +| | | +| + (internal pull-up resistor) | +| | | +| +------? GPIO 15 (reads HIGH normally) | +| | | +| +-+-+ | +| |BTN| ?? Button connects GPIO to GND when pressed | +| +-+-+ | +| | | +| GND | +| | +| Button NOT pressed: GPIO reads 1 (HIGH) | +| Button PRESSED: GPIO reads 0 (LOW) | +| | ++-----------------------------------------------------------------+ ``` ### GPIO Input Functions @@ -208,8 +208,8 @@ This is a compact if-else statement: - If `pressed` is **false (0)**: output `1` (LED ON) **Why is it inverted?** Because of the pull-up resistor! -- Button **released** → GPIO reads `1` → `pressed = 1` → output `0` → LED OFF -- Button **pressed** → GPIO reads `0` → `pressed = 0` → output `1` → LED ON +- Button **released** -> GPIO reads `1` -> `pressed = 1` -> output `0` -> LED OFF +- Button **pressed** -> GPIO reads `0` -> `pressed = 0` -> output `1` -> LED ON A clearer way to write this: ```c @@ -218,7 +218,7 @@ gpio_put(LED_GPIO, !gpio_get(BUTTON_GPIO)); --- -## 📚 Part 3: Understanding Compiler Optimizations +## Part 3: Understanding Compiler Optimizations ### Why Does Code Disappear? @@ -254,7 +254,7 @@ This is why when you look for `gpio_pull_up` in the binary, you might find `gpio --- -## 📚 Part 4: Setting Up Your Environment +## Part 4: Setting Up Your Environment ### Prerequisites @@ -273,46 +273,46 @@ Before we start, make sure you have: ### Hardware Setup Connect your button like this: -- One side of button → GPIO 15 -- Other side of button → GND +- One side of button -> GPIO 15 +- Other side of button -> GND The internal pull-up resistor provides the 3.3V connection, so you only need to connect to GND! ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Breadboard Wiring │ -│ │ -│ Pico 2 │ -│ ┌──────────┐ │ -│ │ │ │ -│ │ GPIO 15 │────────┐ │ -│ │ │ │ │ -│ │ GPIO 16 │────────┼───► LED (with resistor to GND) │ -│ │ │ │ │ -│ │ GND │────────┼───┐ │ -│ │ │ │ │ │ -│ └──────────┘ ┌─┴─┐ │ │ -│ │BTN│─┘ │ -│ └───┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Breadboard Wiring | +| | +| Pico 2 | +| +----------+ | +| | | | +| | GPIO 15 |--------+ | +| | | | | +| | GPIO 16 |--------+---? LED (with resistor to GND) | +| | | | | +| | GND |--------+---+ | +| | | | | | +| +----------+ +-+-+ | | +| |BTN|-+ | +| +---+ | +| | ++-----------------------------------------------------------------+ ``` ### Project Structure ``` Embedded-Hacking/ -├── 0x0014_static-variables/ -│ ├── build/ -│ │ ├── 0x0014_static-variables.uf2 -│ │ └── 0x0014_static-variables.elf -│ └── 0x0014_static-variables.c -└── uf2conv.py ++-- 0x0014_static-variables/ +| +-- build/ +| | +-- 0x0014_static-variables.uf2 +| | +-- 0x0014_static-variables.elf +| +-- 0x0014_static-variables.c ++-- uf2conv.py ``` --- -## 🔬 Part 5: Hands-On Tutorial - Static Variables and GPIO Input +## ? Part 5: Hands-On Tutorial - Static Variables and GPIO Input ### Step 1: Review the Source Code @@ -412,7 +412,7 @@ Keep the program running and watch `static_fav_num`. After 255, you'll see: ``` static_fav_num: 254 static_fav_num: 255 -static_fav_num: 0 ← Wrapped around! +static_fav_num: 0 ?? Wrapped around! static_fav_num: 1 static_fav_num: 2 ... @@ -422,9 +422,9 @@ This demonstrates unsigned integer overflow! --- -## 🔬 Part 6: Debugging with GDB (Dynamic Analysis) +## ? Part 6: Debugging with GDB (Dynamic Analysis) -> 🔄 **REVIEW:** This setup is identical to previous weeks. If you need a refresher on OpenOCD and GDB connection, refer back to Week 3 Part 6. +> ? **REVIEW:** This setup is identical to previous weeks. If you need a refresher on OpenOCD and GDB connection, refer back to Week 3 Part 6. ### Starting the Debug Session @@ -432,7 +432,7 @@ This demonstrates unsigned integer overflow! ```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" @@ -511,17 +511,17 @@ c GDB responds: ``` -Breakpoint 1 at 0x10000234: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0014_static-variables/0x0014_static-variables.c, line 5. +Breakpoint 1 at 0x10000234: file C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0014_static-variables/0x0014_static-variables.c, line 5. Note: automatically using hardware breakpoints for read-only addresses. (gdb) c Continuing. Thread 1 "rp2350.cm0" hit Breakpoint 1, main () - at C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0014_static-variables/0x0014_static-variables.c:5 + at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0014_static-variables/0x0014_static-variables.c:5 5 stdio_init_all(); ``` -> ⚠️ **Note:** If GDB says `The program is not being run.` when you type `c`, the target hasn't been started yet. Use `monitor reset halt` first, then `c` to continue to your breakpoint. +> **Note:** If GDB says `The program is not being run.` when you type `c`, the target hasn't been started yet. Use `monitor reset halt` first, then `c` to continue to your breakpoint. ### Step 8: Examine the Static Variable Location @@ -531,16 +531,16 @@ Static variables live at fixed RAM addresses. But how do we find that address? L 0x10000262: ldr r4, [pc, #44] @ (0x10000290 ) ``` -This loads `r4` from the **literal pool** at address `0x10000290`. The literal pool stores constants that are too large for immediate encoding — in this case, a 32-bit RAM address. Let's examine what's stored there: +This loads `r4` from the **literal pool** at address `0x10000290`. The literal pool stores constants that are too large for immediate encoding - in this case, a 32-bit RAM address. Let's examine what's stored there: ```gdb (gdb) x/1wx 0x10000290 0x10000290 : 0x200005a8 ``` -That's `0x200005a8` — the RAM address of `static_fav_num`! The compiler placed this address in the literal pool because it can't encode a full 32-bit address in a single Thumb instruction. +That's `0x200005a8` - the RAM address of `static_fav_num`! The compiler placed this address in the literal pool because it can't encode a full 32-bit address in a single Thumb instruction. -> 💡 **Why did the disassembly at `0x10000290` show `lsls r0, r5, #22` instead?** Because `x/i` (disassemble) interprets raw data as instructions. The bytes `A8 05 00 20` at that address are the little-endian encoding of `0x200005A8`, but GDB's disassembler doesn't know it's data — it tries to decode it as a Thumb instruction. Using `x/wx` (examine as word) shows the actual value. +> Tip: **Why did the disassembly at `0x10000290` show `lsls r0, r5, #22` instead?** Because `x/i` (disassemble) interprets raw data as instructions. The bytes `A8 05 00 20` at that address are the little-endian encoding of `0x200005A8`, but GDB's disassembler doesn't know it's data - it tries to decode it as a Thumb instruction. Using `x/wx` (examine as word) shows the actual value. ### Step 9: Step Through the Loop @@ -568,10 +568,10 @@ After stepping to `0x10000262` or later, check the registers: ``` Pay attention to: -- `r4` — Should hold `0x200005a8` (static variable's RAM address, loaded from literal pool) -- `r1` — Used for `printf` arguments (holds `42` or the static variable value) -- `r3` — Used for load/increment/store of the static variable -- `pc` — Program counter (current instruction address) +- `r4` - Should hold `0x200005a8` (static variable's RAM address, loaded from literal pool) +- `r1` - Used for `printf` arguments (holds `42` or the static variable value) +- `r3` - Used for load/increment/store of the static variable +- `pc` - Program counter (current instruction address) ### Step 11: Watch the Static Variable Change @@ -613,7 +613,7 @@ TRY IT! --- -## 🔬 Part 7: Understanding the Assembly +## ? Part 7: Understanding the Assembly Now that we've explored the binary in GDB, let's make sense of the key patterns. @@ -658,9 +658,9 @@ Look for the load-increment-store pattern using `r4` (which holds the static var 0x1000028e : b.n 0x10000264 ``` -Note that `r4` was loaded earlier at `0x10000262` via `ldr r4, [pc, #44]` — this pulled the static variable's RAM address (`0x200005a8`) from the literal pool at `0x10000290`. +Note that `r4` was loaded earlier at `0x10000262` via `ldr r4, [pc, #44]` - this pulled the static variable's RAM address (`0x200005a8`) from the literal pool at `0x10000290`. -**Key insight:** The static variable lives at a **fixed RAM address** (`0x200005a8`). It's loaded, incremented, and stored back — unlike the regular variable which was optimized away! +**Key insight:** The static variable lives at a **fixed RAM address** (`0x200005a8`). It's loaded, incremented, and stored back - unlike the regular variable which was optimized away! Verify the static variable value which should be `43`: @@ -706,7 +706,7 @@ Look for this sequence: | `0x1000028a` | `mcrr 0, 4, r2, r3, cr0` | Write `r3` (button) and `r2` (pin 16) to GPIO output | | `0x1000028e` | `b.n 0x10000264` | Loop back to start (`while (true)`) | -> 💡 **Notice how the compiler interleaves the static variable increment with the GPIO logic.** It loads the SIO base address (`r1`) *before* doing the increment, and sets up `r2 = 16` (LED pin) in between. This is called **instruction scheduling** — the compiler reorders instructions to avoid pipeline stalls while waiting for memory reads. +> Tip: **Notice how the compiler interleaves the static variable increment with the GPIO logic.** It loads the SIO base address (`r1`) *before* doing the increment, and sets up `r2 = 16` (LED pin) in between. This is called **instruction scheduling** - the compiler reorders instructions to avoid pipeline stalls while waiting for memory reads. ### Step 16: Find the Infinite Loop @@ -716,21 +716,21 @@ The last instruction at `0x1000028e` is already covered in the table above: 0x1000028e: b.n 0x10000264 ``` -This is an **unconditional branch** back to `0x10000264` (the `movs r1, #42` at the top of the loop) — this is the `while (true)` in our code! There is no `pop` or `bx lr` to return from `main` because the loop never exits. +This is an **unconditional branch** back to `0x10000264` (the `movs r1, #42` at the top of the loop) - this is the `while (true)` in our code! There is no `pop` or `bx lr` to return from `main` because the loop never exits. --- -## 🔬 Part 8: Hacking the Binary with a Hex Editor +## ? Part 8: Hacking the Binary with a Hex Editor -Now for the fun part — we'll patch the `.bin` file directly using a hex editor! +Now for the fun part - we'll patch the `.bin` file directly using a hex editor! -> 💡 **Why a hex editor?** GDB **cannot write to flash memory** — the `0x10000000+` address range where program instructions live. Trying `set *(char *)0x10000264 = 0x2b` in GDB gives `Writing to flash memory forbidden in this context`. To make **permanent** patches that survive a power cycle, we edit the `.bin` file directly with a hex editor and re-flash it. +> Tip: **Why a hex editor?** GDB **cannot write to flash memory** - the `0x10000000+` address range where program instructions live. Trying `set *(char *)0x10000264 = 0x2b` in GDB gives `Writing to flash memory forbidden in this context`. To make **permanent** patches that survive a power cycle, we edit the `.bin` file directly with a hex editor and re-flash it. ### Step 17: Open the Binary in a Hex Editor 1. Open **HxD** (or your preferred hex editor: ImHex, 010 Editor, etc.) -2. Click **File** → **Open** -3. Navigate to `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\` +2. Click **File** -> **Open** +3. Navigate to `C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0014_static-variables\build\` 4. Open `0x0014_static-variables.bin` ### Step 18: Calculate the File Offset @@ -742,29 +742,29 @@ file_offset = address - 0x10000000 ``` For example: -- Address `0x10000264` → file offset `0x264` (612 in decimal) -- Address `0x10000286` → file offset `0x286` (646 in decimal) +- Address `0x10000264` -> file offset `0x264` (612 in decimal) +- Address `0x10000286` -> file offset `0x286` (646 in decimal) -### Step 19: Hack #1 — Change regular_fav_num from 42 to 43 +### Step 19: Hack #1 - Change regular_fav_num from 42 to 43 From our GDB analysis, we know the instruction at `0x10000264` is: ``` -movs r1, #0x2a → bytes: 2a 21 +movs r1, #0x2a -> bytes: 2a 21 ``` To change the value from 42 (`0x2a`) to 43 (`0x2b`): -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` +1. In HxD, open `C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0014_static-variables\build\0x0014_static-variables.bin` 2. Press **Ctrl+G** (Go to offset) 3. Enter offset: `264` 4. You should see the byte `2A` at this position 5. Change `2A` to `2B` 6. The instruction is now `movs r1, #0x2b` (43 in decimal) -> 🔍 **How Thumb encoding works:** In `movs r1, #imm8`, the immediate value is the first byte, and the opcode `21` is the second byte. So the bytes `2a 21` encode `movs r1, #0x2a`. +> ?? **How Thumb encoding works:** In `movs r1, #imm8`, the immediate value is the first byte, and the opcode `21` is the second byte. So the bytes `2a 21` encode `movs r1, #0x2a`. -### Step 20: Hack #2 — Invert the Button Logic +### Step 20: Hack #2 - Invert the Button Logic #### Understand the Encoding @@ -778,15 +778,15 @@ From GDB, we found the `eor.w r3, r3, #1` instruction at `0x10000286` that inver This is the 32-bit Thumb-2 encoding of `eor.w r3, r3, #1`. The bytes break down as: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ eor.w r3, r3, #1 → bytes: 83 F0 01 03 │ -│ │ -│ Byte 0: 0x83 ─┐ │ -│ Byte 1: 0xF0 ─┘ First halfword (opcode + source register) │ -│ Byte 2: 0x01 ──── Immediate value (#1) ← CHANGE THIS │ -│ Byte 3: 0x03 ──── Destination register (r3) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| eor.w r3, r3, #1 -> bytes: 83 F0 01 03 | +| | +| Byte 0: 0x83 -?? | +| Byte 1: 0xF0 -+ First halfword (opcode + source register) | +| Byte 2: 0x01 ---- Immediate value (#1) ?? CHANGE THIS | +| Byte 3: 0x03 ---- Destination register (r3) | +| | ++-----------------------------------------------------------------+ ``` To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0` (making XOR do nothing): @@ -800,30 +800,30 @@ To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0`: 3. You should see the byte `01` at this position 4. Change `01` to `00` -> 🔍 **Why offset `0x288` and not `0x286`?** The immediate value `#1` is in the **third byte** of the 4-byte instruction. The instruction starts at file offset `0x286`, so the immediate byte is at `0x286 + 2 = 0x288`. +> ?? **Why offset `0x288` and not `0x286`?** The immediate value `#1` is in the **third byte** of the 4-byte instruction. The instruction starts at file offset `0x286`, so the immediate byte is at `0x286 + 2 = 0x288`. Now the logic is permanently changed: -- Button released (input = 1): `1 XOR 0 = 1` → LED **ON** -- Button pressed (input = 0): `0 XOR 0 = 0` → LED **OFF** +- Button released (input = 1): `1 XOR 0 = 1` -> LED **ON** +- Button pressed (input = 0): `0 XOR 0 = 0` -> LED **OFF** This is the **opposite** of the original behavior! ### Step 21: Save the Patched Binary -1. Click **File** → **Save As** +1. Click **File** -> **Save As** 2. Save as `0x0014_static-variables-h.bin` in the build directory 3. Close the hex editor --- -## 🔬 Part 9: Converting and Flashing the Hacked Binary +## ? Part 9: Converting and Flashing the Hacked Binary ### Step 22: Convert to UF2 Format Open a terminal and navigate to your project directory: ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0014_static-variables ``` Run the conversion command: @@ -833,7 +833,7 @@ python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --fam ``` **What this command means:** -- `uf2conv.py` = the conversion script (in the parent `Embedded-Hacking-main` directory) +- `uf2conv.py` = the conversion script (in the parent `Embedded-Hacking` directory) - `--base 0x10000000` = the XIP base address where code runs from - `--family 0xe48bff59` = the RP2350 family ID - `--output build\hacked.uf2` = the output filename @@ -848,7 +848,7 @@ python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --fam **Check the serial output:** ``` -regular_fav_num: 43 ← Changed from 42! +regular_fav_num: 43 ?? Changed from 42! static_fav_num: 42 regular_fav_num: 43 static_fav_num: 43 @@ -859,13 +859,13 @@ static_fav_num: 43 - LED should now be **ON by default** (when button is NOT pressed) - LED should turn **OFF** when you press the button -🎉 **BOOM! We successfully:** +? **BOOM! We successfully:** 1. Changed the printed value from 42 to 43 2. Inverted the LED/button logic --- -## 📊 Part 10: Summary and Review +## ? Part 10: Summary and Review ### What We Accomplished @@ -890,46 +890,46 @@ static_fav_num: 43 ### GPIO Input Configuration ``` -┌─────────────────────────────────────────────────────────────────┐ -│ GPIO Input Setup Steps │ -│ │ -│ 1. gpio_init(pin) - Initialize the pin │ -│ 2. gpio_set_dir(pin, GPIO_IN) - Set as input │ -│ 3. gpio_pull_up(pin) - Enable pull-up │ -│ OR gpio_pull_down(pin) - OR enable pull-down │ -│ 4. gpio_get(pin) - Read the state │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| GPIO Input Setup Steps | +| | +| 1. gpio_init(pin) - Initialize the pin | +| 2. gpio_set_dir(pin, GPIO_IN) - Set as input | +| 3. gpio_pull_up(pin) - Enable pull-up | +| OR gpio_pull_down(pin) - OR enable pull-down | +| 4. gpio_get(pin) - Read the state | +| | ++-----------------------------------------------------------------+ ``` ### The Binary Hacking Workflow ``` -┌─────────────────────────────────────────────────────────────────┐ -│ 1. Analyze the binary with GDB │ -│ - Disassemble functions with x/Ni │ -│ - Identify key instructions and addresses │ -├─────────────────────────────────────────────────────────────────┤ -│ 2. Understand compiler optimizations │ -│ - Some functions get inlined (gpio_pull_up → gpio_set_pulls)│ -│ - Some variables are optimized away │ -├─────────────────────────────────────────────────────────────────┤ -│ 3. Calculate file offsets │ -│ - file_offset = address - 0x10000000 │ -├─────────────────────────────────────────────────────────────────┤ -│ 4. Patch the .bin file with a hex editor │ -│ - Open the .bin file in HxD / ImHex │ -│ - Go to the calculated offset │ -│ - Change the target byte(s) │ -├─────────────────────────────────────────────────────────────────┤ -│ 5. Convert to UF2 │ -│ python uf2conv.py file.bin --base 0x10000000 │ -│ --family 0xe48bff59 --output hacked.uf2 │ -├─────────────────────────────────────────────────────────────────┤ -│ 6. Flash and verify │ -│ - Hold BOOTSEL, plug in, drag UF2 │ -│ - Check serial output and button/LED behavior │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| 1. Analyze the binary with GDB | +| - Disassemble functions with x/Ni | +| - Identify key instructions and addresses | ++-----------------------------------------------------------------+ +| 2. Understand compiler optimizations | +| - Some functions get inlined (gpio_pull_up -> gpio_set_pulls)| +| - Some variables are optimized away | ++-----------------------------------------------------------------+ +| 3. Calculate file offsets | +| - file_offset = address - 0x10000000 | ++-----------------------------------------------------------------+ +| 4. Patch the .bin file with a hex editor | +| - Open the .bin file in HxD / ImHex | +| - Go to the calculated offset | +| - Change the target byte(s) | ++-----------------------------------------------------------------+ +| 5. Convert to UF2 | +| python uf2conv.py file.bin --base 0x10000000 | +| --family 0xe48bff59 --output hacked.uf2 | ++-----------------------------------------------------------------+ +| 6. Flash and verify | +| - Hold BOOTSEL, plug in, drag UF2 | +| - Check serial output and button/LED behavior | ++-----------------------------------------------------------------+ ``` ### Key Memory Addresses @@ -943,40 +943,13 @@ static_fav_num: 43 --- -## ✅ Practice Exercises - -### Exercise 1: Change Static Variable Initial Value -The static variable starts at 42. Hack the binary to make it start at 100 instead. - -**Hint:** Find where `DAT_200005a8` is initialized in the .data section. - -### Exercise 2: Make the LED Blink -Instead of responding to button presses, hack the binary to make the LED blink continuously. - -**Hint:** You'll need to change the GPIO output logic to toggle instead of following button state. - -### Exercise 3: Reverse Engineer gpio_set_pulls -Using GDB, disassemble the `gpio_set_pulls` function and figure out what registers it writes to. - -**Hint:** Look for writes to addresses around `0x40038000` (PADS_BANK0). - -### Exercise 4: Add a Second Static Variable -If you had two static variables, where would they be stored in memory? Would they be next to each other? - -**Hint:** Static variables in the same compilation unit are typically placed consecutively in the .data section. - -### Exercise 5: Overflow Faster -The static variable overflows after 255 iterations. Can you hack it to overflow sooner? - -**Hint:** Change the increment from `+1` to `+10` by modifying the `adds r3,#0x1` instruction. - --- -## 🎓 Key Takeaways +## ? Key Takeaways 1. **Static variables persist** - They keep their value between function calls and loop iterations. -2. **Static storage ≠ heap** - Static variables are in a fixed location, not dynamically allocated. +2. **Static storage ? heap** - Static variables are in a fixed location, not dynamically allocated. 3. **Compilers optimize aggressively** - Regular variables may be optimized away if the compiler sees no effect. @@ -996,7 +969,7 @@ The static variable overflows after 255 iterations. Can you hack it to overflow --- -## 📖 Glossary +## ? Glossary | Term | Definition | | --------------------- | ---------------------------------------------------------------- | @@ -1019,7 +992,7 @@ The static variable overflows after 255 iterations. Can you hack it to overflow --- -## 🔗 Additional Resources +## ? Additional Resources ### GPIO Input Reference @@ -1061,4 +1034,6 @@ The static variable overflows after 255 iterations. Can you hack it to overflow **Remember:** Static variables are your friends when you need to remember values across function calls. But they also make your program's behavior more complex to analyze - which is exactly why we practice reverse engineering! -Happy hacking! 🔧 +Happy hacking! ? + + diff --git a/WEEK07/WEEK07-01-S.md b/WEEK07/WEEK07-01-S.md deleted file mode 100644 index 6c8de05..0000000 --- a/WEEK07/WEEK07-01-S.md +++ /dev/null @@ -1,91 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 7 -Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics - -### Non-Credit Practice Exercise 1 Solution: Change Both LCD Lines - -#### Answers - -##### String Locations in Flash - -| String | Address | File Offset | Length (bytes) | Hex Encoding | -|---------------|--------------|-------------|----------------|---------------------------------------------------| -| "Reverse" | 0x10003ee8 | 0x3EE8 | 8 (7 + null) | 52 65 76 65 72 73 65 00 | -| "Engineering" | 0x10003ef0 | 0x3EF0 | 12 (11 + null) | 45 6E 67 69 6E 65 65 72 69 6E 67 00 | - -##### Line 1 Patch: "Reverse" → "Exploit" - -| Character | Hex | -|-----------|--------| -| E | 0x45 | -| x | 0x78 | -| p | 0x70 | -| l | 0x6c | -| o | 0x6f | -| i | 0x69 | -| t | 0x74 | -| \0 | 0x00 | - -``` -Offset 0x3EE8: -Before: 52 65 76 65 72 73 65 00 ("Reverse") -After: 45 78 70 6C 6F 69 74 00 ("Exploit") -``` - -##### Line 2 Patch: "Engineering" → "Hacking!!!!" - -| Character | Hex | -|-----------|--------| -| H | 0x48 | -| a | 0x61 | -| c | 0x63 | -| k | 0x6b | -| i | 0x69 | -| n | 0x6e | -| g | 0x67 | -| ! | 0x21 | -| ! | 0x21 | -| ! | 0x21 | -| ! | 0x21 | -| \0 | 0x00 | - -``` -Offset 0x3EF0: -Before: 45 6E 67 69 6E 65 65 72 69 6E 67 00 ("Engineering") -After: 48 61 63 6B 69 6E 67 21 21 21 21 00 ("Hacking!!!!") -``` - -##### Conversion and Flash - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants -python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### LCD Verification - -``` -Line 1: Exploit -Line 2: Hacking!!!! -``` - -#### Reflection Answers - -1. **Why must the replacement string be the same length (or shorter) as the original? What specific data would you corrupt if you used a longer string?** - Strings are stored consecutively in the `.rodata` section. "Reverse" occupies 8 bytes starting at `0x10003ee8` and "Engineering" starts immediately at `0x10003ef0`. If the replacement string is longer than 8 bytes, the extra bytes would overwrite the beginning of "Engineering" (or whatever data follows). The `.rodata` section has no gaps—it's a packed sequence of constants, format strings, and other read-only data. Corrupting adjacent data could break LCD line 2, crash `printf` format strings, or cause undefined behavior. - -2. **The two strings are stored only 8 bytes apart (0x3EE8 to 0x3EF0). "Reverse" is 7 characters + null = 8 bytes. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)?** - "Reversal" needs 9 bytes (8 chars + null terminator). The 9th byte (the `0x00` null terminator) would be written to address `0x10003ef0`, which is the first byte of "Engineering" — the letter 'E' (`0x45`). This would overwrite 'E' with `0x00`, turning "Engineering" into an empty string. The LCD would display "Reversal" on line 1 and nothing on line 2, because `lcd_puts` would see a null terminator immediately at the start of the second string. - -3. **If you wanted the LCD to display "Hello" on line 1 (5 characters instead of 7), what would you put in the remaining 2 bytes plus null? Write out the full 8-byte hex sequence.** - "Hello" = 5 characters, followed by the null terminator and 2 padding null bytes: - ``` - 48 65 6C 6C 6F 00 00 00 - H e l l o \0 \0 \0 - ``` - The first `0x00` at position 5 terminates the string. The remaining two `0x00` bytes are padding that fills the original 8-byte allocation. These padding bytes are never read by `lcd_puts` because it stops at the first null terminator. - -4. **Could you change the LCD to display nothing on line 1 by patching just one byte? Which byte and what value?** - Yes. Change the first byte at offset `0x3EE8` from `0x52` ('R') to `0x00` (null). This makes the string start with a null terminator, so `lcd_puts` sees an empty string and displays nothing. Only one byte needs to change: the byte at file offset `0x3EE8`, from `0x52` to `0x00`. diff --git a/WEEK07/WEEK07-01.md b/WEEK07/WEEK07-01.md deleted file mode 100644 index ade4e69..0000000 --- a/WEEK07/WEEK07-01.md +++ /dev/null @@ -1,205 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 7 -Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics - -### Non-Credit Practice Exercise 1: Change Both LCD Lines - -#### Objective -Patch the LCD display strings in the `.bin` file to change "Reverse" to "Exploit" on line 1 and "Engineering" to "Hacking!!!!" on line 2, using GDB to locate the string addresses, a hex editor to perform the patches, and the Pico 2 hardware to verify the changes on the physical LCD. - -#### Prerequisites -- Completed Week 7 tutorial (GDB and hex editor sections) -- `0x0017_constants.bin` binary available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with 1602 LCD connected via IC - -#### Task Description -The LCD currently displays "Reverse" on line 1 and "Engineering" on line 2. You will find both string literals in flash memory using GDB, calculate their file offsets, and patch them to display custom text. The critical constraint is that replacement strings must be the **same length** as the originals (or shorter, padded with null bytes) otherwise you'll corrupt adjacent data. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0017_constants.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Locate the String Literals in Memory - -From the tutorial, we know the strings are in the `.rodata` section: - -```gdb -(gdb) x/s 0x10003ee8 -``` - -Output: -``` -0x10003ee8: "Reverse" -``` - -```gdb -(gdb) x/s 0x10003ef0 -``` - -Output: -``` -0x10003ef0: "Engineering" -``` - -##### Step 3: Examine the Raw Bytes - -Look at the hex encoding of both strings: - -```gdb -(gdb) x/8xb 0x10003ee8 -``` - -Output: -``` -0x10003ee8: 0x52 0x65 0x76 0x65 0x72 0x73 0x65 0x00 - R e v e r s e \0 -``` - -```gdb -(gdb) x/12xb 0x10003ef0 -``` - -Output: -``` -0x10003ef0: 0x45 0x6e 0x67 0x69 0x6e 0x65 0x65 0x72 - E n g i n e e r -0x10003ef8: 0x69 0x6e 0x67 0x00 - i n g \0 -``` - -##### Step 4: Plan Your Replacement Strings - -**Original strings and their lengths:** -- "Reverse" = 7 characters + null terminator = 8 bytes -- "Engineering" = 11 characters + null terminator = 12 bytes - -**Replacement strings (MUST be same length or shorter):** -- "Exploit" = 7 characters ? (same as "Reverse") -- "Hacking!!!!" = 11 characters ? (same as "Engineering") - -Build the ASCII hex for "Exploit": - -| Character | Hex | -| --------- | ------ | -| E | `0x45` | -| x | `0x78` | -| p | `0x70` | -| l | `0x6c` | -| o | `0x6f` | -| i | `0x69` | -| t | `0x74` | -| \0 | `0x00` | - -Build the ASCII hex for "Hacking!!!!": - -| Character | Hex | -| --------- | ------ | -| H | `0x48` | -| a | `0x61` | -| c | `0x63` | -| k | `0x6b` | -| i | `0x69` | -| n | `0x6e` | -| g | `0x67` | -| ! | `0x21` | -| ! | `0x21` | -| ! | `0x21` | -| ! | `0x21` | -| \0 | `0x00` | - -##### Step 5: Calculate File Offsets - -``` -file_offset = address - 0x10000000 -``` - -- "Reverse" at `0x10003ee8` ? file offset `0x3EE8` -- "Engineering" at `0x10003ef0` ? file offset `0x3EF0` - -##### Step 6: Patch String 1 "Reverse" ? "Exploit" - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants\build\0x0017_constants.bin` -2. Press **Ctrl+G** and enter offset: `3EE8` -3. You should see: `52 65 76 65 72 73 65 00` ("Reverse\0") -4. Replace with: `45 78 70 6C 6F 69 74 00` ("Exploit\0") - -##### Step 7: Patch String 2 "Engineering" ? "Hacking!!!!" - -1. Press **Ctrl+G** and enter offset: `3EF0` -2. You should see: `45 6E 67 69 6E 65 65 72 69 6E 67 00` ("Engineering\0") -3. Replace with: `48 61 63 6B 69 6E 67 21 21 21 21 00` ("Hacking!!!!\0") - -##### Step 8: Save and Convert - -1. Click **File** ? **Save As** ? `0x0017_constants-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants -python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 9: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the LCD:** -- Line 1 should now show: `Exploit` -- Line 2 should now show: `Hacking!!!!` - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate string literals in flash memory using GDB -- Convert ASCII characters to hex bytes for patching -- Patch multiple strings in a single binary -- Understand the same-length constraint for string patching - -#### Questions for Reflection - -###### Question 1: Why must the replacement string be the same length (or shorter) as the original? What specific data would you corrupt if you used a longer string? - -###### Question 2: The two strings "Reverse" and "Engineering" are stored only 8 bytes apart (`0x3EE8` to `0x3EF0`). "Reverse" is 7 characters + null = 8 bytes it perfectly fills the gap. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)? - -###### Question 3: If you wanted the LCD to display "Hello" on line 1 (5 characters instead of 7), what would you put in the remaining 2 bytes plus null? Write out the full 8-byte hex sequence. - -###### Question 4: Could you change the LCD to display nothing on line 1 by patching just one byte? Which byte and what value? - -#### Tips and Hints -- Use an ASCII table to convert characters: uppercase A-Z = `0x41`-`0x5A`, lowercase a-z = `0x61`-`0x7A` -- The null terminator `0x00` marks the end of the string anything after it is ignored by `lcd_puts` -- If your replacement is shorter, pad with `0x00` bytes to fill the original length -- The 1602 LCD has 16 characters per line you cannot display more than 16 characters per line regardless of string length - -#### Next Steps -- Proceed to Exercise 2 to explore finding all string literals in the binary -- Try displaying reversed text: can you make it show "gninnignE" and "esreveR"? -- Use GDB to verify your patches before flashing: `set {char[8]} 0x10003ee8 = "Exploit"` to test in RAM first diff --git a/WEEK07/WEEK07-02-S.md b/WEEK07/WEEK07-02-S.md deleted file mode 100644 index f288ca8..0000000 --- a/WEEK07/WEEK07-02-S.md +++ /dev/null @@ -1,68 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 7 -Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics - -### Non-Credit Practice Exercise 2 Solution: Find All String Literals in the Binary - -#### Answers - -##### String Catalog - -| Address | File Offset | String Content | Length | Purpose | -|---------------|-------------|--------------------------|--------|-----------------------------| -| `0x10003ee8` | `0x3EE8` | "Reverse" | 8 | LCD line 1 text | -| `0x10003ef0` | `0x3EF0` | "Engineering" | 12 | LCD line 2 text | -| `0x10003efc` | `0x3EFC` | "FAV_NUM: %d\r\n" | 16 | printf format string | -| `0x10003f0c` | `0x3F0C` | "OTHER_FAV_NUM: %d\r\n" | 22 | printf format string | -| Various | Various | SDK panic/assert strings | Varies | Pico SDK internal messages | -| Various | Various | Source file paths | Varies | SDK debug/assert references | - -##### GDB String Search Commands - -```gdb -(gdb) x/s 0x10003ee8 -0x10003ee8: "Reverse" - -(gdb) x/s 0x10003ef0 -0x10003ef0: "Engineering" - -(gdb) x/s 0x10003efc -0x10003efc: "FAV_NUM: %d\r\n" - -(gdb) x/s 0x10003f0c -0x10003f0c: "OTHER_FAV_NUM: %d\r\n" -``` - -##### Scanning for Strings - -```gdb -(gdb) x/20s 0x10003e00 -(gdb) x/50s 0x10003d00 -``` - -##### Literal Pool Reference - -From the literal pool at `0x100002a4`: - -| Pool Address | Value | String It Points To | -|----------------|---------------|---------------------------| -| `0x100002ac` | `0x10003EE8` | "Reverse" | -| `0x100002b0` | `0x10003EF0` | "Engineering" | -| `0x100002b4` | `0x10003EFC` | "FAV_NUM: %d\r\n" | -| `0x100002b8` | `0x10003F0C` | "OTHER_FAV_NUM: %d\r\n" | - -#### Reflection Answers - -1. **How many distinct strings did you find? Were any of them surprising or unexpected?** - At minimum 4 application-level strings: "Reverse", "Engineering", "FAV_NUM: %d\r\n", and "OTHER_FAV_NUM: %d\r\n". Beyond these, the Pico SDK embeds additional strings — panic handler messages, assert failure messages, and source file path strings used for debug output. The SDK strings are surprising because they reveal internal implementation details: file paths expose the build environment directory structure, and error messages reveal which SDK functions have built-in error checking. A reverse engineer can learn the SDK version and build configuration just from these strings. - -2. **Why are strings so valuable to a reverse engineer? What can an attacker learn about a program just from its strings?** - Strings are high-entropy human-readable data that reveals program behavior without reading assembly. An attacker can learn: what the program displays or communicates (LCD messages, serial output), what libraries it uses (SDK error messages), how it handles errors (panic/assert strings), what data formats it processes (`printf` format strings with `%d`, `%s`, `%f`), network endpoints or credentials (URLs, passwords, API keys), the build environment (file paths), and the overall purpose of the firmware. Strings are often the first thing a reverse engineer examines in an unknown binary. - -3. **What technique could a developer use to make strings harder to find in a binary? (Think about what the strings look like in memory.)** - String encryption/obfuscation: encrypt all string literals at compile time using XOR, AES, or a custom cipher, and decrypt them into a RAM buffer only when needed at runtime. This way, scanning the binary with `strings` or a hex editor reveals only ciphertext — random-looking bytes instead of readable text. Other techniques include: splitting strings across multiple locations and assembling them at runtime, using character arrays initialized by code rather than string literals, replacing strings with numeric lookup indices into an encrypted table, or using compile-time obfuscation tools that automatically transform string constants. - -4. **The printf format strings contain \r\n. In the binary, these appear as two bytes: 0x0D 0x0A. Why two bytes instead of the four characters \, r, \, n?** - The C compiler processes escape sequences during compilation. In source code, `\r` is written as two characters (backslash + r), but the compiler converts it to a single byte: `0x0D` (carriage return, ASCII 13). Similarly, `\n` becomes `0x0A` (line feed, ASCII 10). These are **control characters** — non-printable ASCII codes that control terminal behavior. The backslash notation is just a human-readable way to represent these bytes in source code. By the time the string reaches the binary, all escape sequences have been resolved to their single-byte equivalents. diff --git a/WEEK07/WEEK07-02.md b/WEEK07/WEEK07-02.md deleted file mode 100644 index 9a3224c..0000000 --- a/WEEK07/WEEK07-02.md +++ /dev/null @@ -1,157 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 7 -Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics - -### Non-Credit Practice Exercise 2: Find All String Literals in the Binary - -#### Objective -Systematically search through the `0x0017_constants` binary using GDB and a hex editor to locate every human-readable string literal, catalog their addresses, contents, and purposes, and gain experience identifying data structures in compiled binaries. - -#### Prerequisites -- Completed Week 7 tutorial (GDB section) -- `0x0017_constants.elf` and `0x0017_constants.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) - -#### Task Description -Compiled binaries contain string literals in the `.rodata` section format strings for `printf`, LCD messages, library strings, and more. You will use two techniques to find them: (1) searching with GDB's `x/s` command to examine suspected string regions, and (2) visually scanning the binary in a hex editor for ASCII sequences. You will document every string you find, its address, and its likely purpose. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0017_constants.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Locate the Known Strings - -We already know about these strings from the tutorial: - -```gdb -(gdb) x/s 0x10003ee8 -0x10003ee8: "Reverse" - -(gdb) x/s 0x10003ef0 -0x10003ef0: "Engineering" -``` - -These are the LCD display strings. Now let's find more. - -##### Step 3: Find printf Format Strings - -The program uses `printf("FAV_NUM: %d\r\n", ...)` and `printf("OTHER_FAV_NUM: %d\r\n", ...)`. These format strings must be somewhere in flash. Look in the `.rodata` section near the LCD strings: - -```gdb -(gdb) x/10s 0x10003ec0 -``` - -This displays 10 consecutive strings starting from that address. Examine the output you should find the `printf` format strings. Try different starting addresses if needed: - -```gdb -(gdb) x/20s 0x10003e00 -``` - -##### Step 4: Search the Binary Systematically - -Use GDB to scan through flash memory in large chunks, looking for readable strings: - -```gdb -(gdb) x/50s 0x10003d00 -``` - -Many results will be garbage (non-ASCII data interpreted as text), but real strings will stand out. Look for: -- Format strings containing `%d`, `%s`, `%f`, `\r\n` -- Error messages from the Pico SDK -- Function names or debug info - -##### Step 5: Open the Binary in a Hex Editor - -1. Open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants\build\0x0017_constants.bin` in HxD -2. Switch to the "Text" pane (right side) to see ASCII representation -3. Scroll through the binary and look for readable text sequences - -In HxD, printable ASCII characters (0x200x7E) are displayed as text; non-printable bytes appear as dots. - -##### Step 6: Use Hex Editor Search - -Most hex editors can search for ASCII text: - -1. Press **Ctrl+F** (Find) -2. Switch to "Text" or "String" search mode -3. Search for known strings like `Reverse`, `FAV_NUM`, `Engineering` -4. For each hit, note the file offset - -##### Step 7: Catalog Your Findings - -Create a table of all strings you find: - -| Address | File Offset | String Content | Purpose | -| -------------- | ----------- | ----------------------- | --------------------------- | -| `0x10003ee8` | `0x3EE8` | "Reverse" | LCD line 1 text | -| `0x10003ef0` | `0x3EF0` | "Engineering" | LCD line 2 text | -| `0x10003eXX` | `0x3EXX` | "FAV_NUM: %d\r\n" | printf format string | -| `0x10003eXX` | `0x3EXX` | "OTHER_FAV_NUM: %d\r\n" | printf format string | -| ... | ... | ... | ... | - -Fill in the actual addresses you discover. - -##### Step 8: Identify SDK and Library Strings - -The Pico SDK may include additional strings (error messages, assert messages, etc.). Look for text patterns like: -- "panic" or "assert" -- File paths (e.g., paths from the SDK source) -- Function names - -These strings reveal internal SDK behavior that isn't visible in the source code. - -#### Expected Output - -After completing this exercise, you should be able to: -- Systematically enumerate string literals in a compiled binary -- Use both GDB (`x/s`) and hex editor text view to find strings -- Distinguish between application strings, format strings, and SDK/library strings -- Understand that string literals reveal program functionality to a reverse engineer - -#### Questions for Reflection - -###### Question 1: How many distinct strings did you find? Were any of them surprising or unexpected? - -###### Question 2: Why are strings so valuable to a reverse engineer? What can an attacker learn about a program just from its strings? - -###### Question 3: What technique could a developer use to make strings harder to find in a binary? (Think about what the strings look like in memory.) - -###### Question 4: The `printf` format strings contain `\r\n`. In the binary, these appear as two bytes: `0x0D 0x0A`. Why two bytes instead of the four characters `\`, `r`, `\`, `n`? - -#### Tips and Hints -- In GDB, `x/s` treats any address as the start of a null-terminated string it will print garbage if the address isn't really a string -- Use `x/Ns address` where N is a number to print N consecutive strings (useful for scanning regions) -- In HxD, use **Edit** ? **Select Block** to highlight a region and examine the text pane -- Real strings are typically 4+ printable ASCII characters followed by a null byte (`0x00`) -- The `.rodata` section is usually located after the `.text` (code) section in the binary - -#### Next Steps -- Proceed to Exercise 3 to trace the IC struct pointer chain -- Try the `strings` command if available: `strings 0x0017_constants.bin` will extract all printable character sequences -- Consider: if you found a password string in an embedded device binary, what security implications would that have? diff --git a/WEEK07/WEEK07-03-S.md b/WEEK07/WEEK07-03-S.md deleted file mode 100644 index b3af2d1..0000000 --- a/WEEK07/WEEK07-03-S.md +++ /dev/null @@ -1,91 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 7 -Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics - -### Non-Credit Practice Exercise 3 Solution: Trace the I²C Struct Pointer Chain - -#### Answers - -##### Complete Pointer Chain - -``` -I2C_PORT (source macro: #define I2C_PORT i2c1) - ↓ -i2c1 (SDK macro: #define i2c1 (&i2c1_inst)) - ↓ -&i2c1_inst = 0x2000062c (SRAM address of i2c_inst_t struct) - ↓ -i2c1_inst.hw = 0x40098000 (pointer to I²C1 hardware register base) - ↓ -I²C1 Hardware Registers (memory-mapped I/O silicon) - +-- IC_CON at 0x40098000 - +-- IC_TAR at 0x40098004 - +-- IC_SAR at 0x40098008 - +-- IC_DATA_CMD at 0x40098010 -``` - -##### Literal Pool Load - -```gdb -(gdb) x/6wx 0x100002a4 -0x100002a4: 0x000186a0 0x2000062c 0x10003ee8 0x10003ef0 -0x100002b4: 0x10003efc 0x10003f0c -``` - -The value `0x2000062c` at pool address `0x100002a8` is loaded into `r0` by a `ldr r0, [pc, #offset]` instruction before the `bl i2c_init` call. - -##### i2c1_inst Struct in SRAM - -```gdb -(gdb) x/2wx 0x2000062c -0x2000062c : 0x40098000 0x00000000 -``` - -| Offset | Field | Value | Size | Meaning | -|--------|-------------------|-------------|---------|-------------------------------| -| +0x00 | hw | 0x40098000 | 4 bytes | Pointer to I²C1 hardware regs | -| +0x04 | restart_on_next | 0x00000000 | 4 bytes | false (no pending restart) | - -Total struct size: 8 bytes (4-byte pointer + 4-byte bool padded to word alignment). - -##### Hardware Registers at 0x40098000 - -```gdb -(gdb) x/8wx 0x40098000 -``` - -| Offset | Register | Address | Description | -|--------|-------------|-------------|---------------------------| -| +0x00 | IC_CON | 0x40098000 | I²C control register | -| +0x04 | IC_TAR | 0x40098004 | Target address register | -| +0x08 | IC_SAR | 0x40098008 | Slave address register | -| +0x10 | IC_DATA_CMD | 0x40098010 | Data command register | - -##### I²C0 Comparison - -```gdb -(gdb) x/2wx 0x20000628 -``` - -| Controller | Struct Address | hw Pointer | Separation | -|------------|---------------|-------------|-------------| -| I²C0 | 0x20000628 | 0x40090000 | Base | -| I²C1 | 0x2000062c | 0x40098000 | +0x8000 | - -Same struct layout, different hardware pointer — demonstrating the SDK's abstraction. - -#### Reflection Answers - -1. **Why does the SDK use a struct with a pointer to hardware registers instead of accessing 0x40098000 directly? What advantage does this abstraction provide?** - The struct abstraction allows the same code to work for both I²C controllers — I²C0 at `0x40090000` and I²C1 at `0x40098000` — by simply passing a different struct pointer. Functions like `i2c_init(i2c_inst_t *i2c, uint baudrate)` accept a pointer parameter, so one implementation serves both controllers. Without the struct, every I²C function would need either hardcoded addresses (duplicating code for each controller) or `if/else` branches. The abstraction also enables portability: if a future chip moves the hardware registers, only the struct initialization changes — not every function that accesses I²C. - -2. **The hw pointer stores 0x40098000. In the binary, this appears as bytes 00 80 09 40. Why is the byte order reversed from how we write the address?** - ARM Cortex-M33 uses **little-endian** byte ordering: the least significant byte (LSB) is stored at the lowest memory address. For the 32-bit value `0x40098000`: byte 0 (lowest address) = `0x00` (LSB), byte 1 = `0x80`, byte 2 = `0x09`, byte 3 = `0x40` (MSB). We write numbers with the MSB first (big-endian notation), but the processor stores them LSB-first. This is a fundamental property of the ARM architecture that affects how you read multi-byte values in hex editors and GDB `x/bx` output. - -3. **If you changed the hw pointer at 0x2000062c from 0x40098000 to 0x40090000 using GDB, what I²C controller would the program use? What would happen to the LCD?** - The program would use **I²C0** instead of I²C1, because all subsequent hardware register accesses (via `i2c1_inst.hw->...`) would read/write the I²C0 registers at `0x40090000`. However, the LCD is physically wired to the I²C1 pins (GPIO 14 for SDA, GPIO 15 for SCL), and those GPIOs are configured for the I²C1 peripheral. The I²C0 controller drives different default pins (GPIO 0/1). So the program would send I²C commands through the wrong controller on the wrong pins — the LCD would receive no signals and would stop updating, displaying whatever was last written before the pointer change. - -4. **The macro chain has 4 levels of indirection (I2C_PORT → i2c1 → &i2c1_inst → hw → registers). Is this typical for embedded SDKs? What are the trade-offs of this approach?** - Yes, this is typical. STM32 HAL, Nordic nRF5 SDK, ESP-IDF, and most professional embedded SDKs use similar multi-level abstractions. **Benefits:** code reuse across multiple peripheral instances, clean type-safe APIs, portability across chip revisions, and testability (you can mock the struct for unit tests). **Costs:** complexity for reverse engineers (harder to trace from API call to hardware), potential code bloat if not optimized, and a steeper learning curve for SDK users. In practice, modern compilers (with `-O2` or higher) optimize away most indirection — the final binary often inlines the pointer dereferences into direct register accesses, so the runtime overhead is negligible. diff --git a/WEEK07/WEEK07-03.md b/WEEK07/WEEK07-03.md deleted file mode 100644 index 6f0cc58..0000000 --- a/WEEK07/WEEK07-03.md +++ /dev/null @@ -1,206 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 7 -Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics - -### Non-Credit Practice Exercise 3: Trace the IC Struct Pointer Chain - -#### Objective -Use GDB to follow the `i2c1_inst` struct pointer chain from the code instruction that loads it, through the struct in SRAM, to the hardware registers at `0x40098000`. Document every step of the chain: `I2C_PORT` ? `i2c1` ? `&i2c1_inst` ? `hw` ? `0x40098000`, and verify each pointer and value in memory. - -#### Prerequisites -- Completed Week 7 tutorial (Parts 3-4 on structs and the macro chain) -- `0x0017_constants.elf` binary available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- Understanding of C pointers and structs - -#### Task Description -The Pico SDK uses a chain of macros and structs to abstract hardware access. When you write `I2C_PORT` in C, it expands through multiple macro definitions to ultimately become a pointer to an `i2c_inst_t` struct in SRAM, which in turn contains a pointer to the IC hardware registers. You will trace this entire chain in GDB, examining each link to understand how the SDK connects your code to silicon. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0017_constants.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find the i2c_init Call - -Run the program to allow initialization to complete, then halt: - -```gdb -(gdb) b *0x10000234 -(gdb) c -``` - -Now step through to just before the `i2c_init` call: - -```gdb -(gdb) x/10i 0x1000023c -``` - -Look for the instruction that loads the `i2c1_inst` pointer into `r0`: - -``` -0x1000023c: ldr r0, [pc, #offset] ; Load &i2c1_inst into r0 -0x1000023e: ldr r1, =0x186A0 ; 100000 (baud rate) -0x10000240: bl i2c_init ; Call i2c_init(i2c1, 100000) -``` - -##### Step 3: Follow the PC-Relative Load - -The `ldr r0, [pc, #offset]` instruction loads a value from a **literal pool** a data area near the code. Examine what's at the literal pool: - -```gdb -(gdb) x/4wx 0x100002a8 -``` - -Look for a value in the `0x2000xxxx` range this is the SRAM address of `i2c1_inst`. It should be `0x2000062c`. - -##### Step 4: Examine the i2c1_inst Struct in SRAM - -Now examine the struct at that SRAM address: - -```gdb -(gdb) x/2wx 0x2000062c -``` - -Expected output: -``` -0x2000062c: 0x40098000 0x00000000 -``` - -This maps to the `i2c_inst_t` struct: - -| Offset | Field | Value | Meaning | -| ------ | ----------------- | ------------ | -------------------------------- | -| `+0x00` | `hw` | `0x40098000` | Pointer to IC1 hardware regs | -| `+0x04` | `restart_on_next` | `0x00000000` | `false` (no pending restart) | - -##### Step 5: Follow the hw Pointer to Hardware Registers - -The first member of the struct (`hw`) points to `0x40098000` the IC1 hardware register block. Examine it: - -```gdb -(gdb) x/8wx 0x40098000 -``` - -You should see the IC1 control and status registers: - -| Offset | Register | Description | -| ------ | -------------- | ------------------------------ | -| `+0x00` | IC_CON | IC control register | -| `+0x04` | IC_TAR | Target address register | -| `+0x08` | IC_SAR | Slave address register | -| `+0x0C` | (reserved) | | -| `+0x10` | IC_DATA_CMD | Data command register | - -##### Step 6: Verify the IC Target Address - -After `i2c_init` and `lcd_i2c_init` have run, check the target address register: - -Let the program run past initialization: - -```gdb -(gdb) delete -(gdb) b * -(gdb) c -``` - -Then examine IC_TAR: - -```gdb -(gdb) x/1wx 0x40098004 -``` - -You should see `0x27` (or a value containing 0x27) this is the LCD's IC address! - -##### Step 7: Document the Complete Chain - -Create a diagram of the complete pointer chain: - -``` -Your Code: I2C_PORT - - ? (preprocessor macro) -i2c1 - - ? (macro: #define i2c1 (&i2c1_inst)) -&i2c1_inst = 0x2000062c (SRAM address) - - ? (struct member access) -i2c1_inst.hw = 0x40098000 (hardware register base) - - ? (memory-mapped I/O) -IC1 Hardware Registers (silicon) - - +-- IC_CON at 0x40098000 - +-- IC_TAR at 0x40098004 - +-- IC_DATA_CMD at 0x40098010 - +-- ... -``` - -##### Step 8: Compare with IC0 - -The Pico 2 has two IC controllers. Find the `i2c0_inst` struct and compare: - -```gdb -(gdb) x/2wx 0x20000628 -``` - -If IC0's struct is at a nearby address, you should see: -- `hw` pointing to `0x40090000` (IC0 base, different from IC1's `0x40098000`) -- `restart_on_next` = 0 - -This demonstrates how the SDK uses the same struct layout for both IC controllers, with only the hardware pointer changing. - -#### Expected Output - -After completing this exercise, you should be able to: -- Trace pointer chains from high-level code to hardware registers -- Understand how the Pico SDK uses structs to abstract hardware -- Read struct members from raw memory using GDB -- Navigate from SRAM data structures to memory-mapped I/O registers - -#### Questions for Reflection - -###### Question 1: Why does the SDK use a struct with a pointer to hardware registers instead of accessing `0x40098000` directly? What advantage does this abstraction provide? - -###### Question 2: The `hw` pointer stores `0x40098000`. In the binary, this appears as bytes `00 80 09 40`. Why is the byte order reversed from how we write the address? - -###### Question 3: If you changed the `hw` pointer at `0x2000062c` from `0x40098000` to `0x40090000` using GDB (`set {int}0x2000062c = 0x40090000`), what IC controller would the program use? What would happen to the LCD? - -###### Question 4: The macro chain has 4 levels of indirection (I2C_PORT ? i2c1 ? &i2c1_inst ? hw ? registers). Is this typical for embedded SDKs? What are the trade-offs of this approach? - -#### Tips and Hints -- Use `x/wx` to examine 32-bit words (pointers are 32 bits on ARM Cortex-M33) -- SRAM addresses start with `0x20xxxxxx`; hardware register addresses start with `0x40xxxxxx` -- The literal pool (where PC-relative loads get their data) is usually right after the function's code -- `i2c_inst_t` is only 8 bytes: 4-byte pointer + 4-byte bool (padded to 4 bytes for alignment) -- IC0 base = `0x40090000`, IC1 base = `0x40098000` they are `0x8000` bytes apart - -#### Next Steps -- Proceed to Exercise 4 to patch the LCD to display your own custom message -- Try modifying the `restart_on_next` field in GDB and observe if it changes IC behavior -- Explore the IC hardware registers at `0x40098000` can you read the IC_STATUS register to see if the bus is active? diff --git a/WEEK07/WEEK07-04-S.md b/WEEK07/WEEK07-04-S.md deleted file mode 100644 index fb7e7ad..0000000 --- a/WEEK07/WEEK07-04-S.md +++ /dev/null @@ -1,96 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 7 -Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics - -### Non-Credit Practice Exercise 4 Solution: Display Your Own Custom Message on the LCD - -#### Answers - -##### String Constraints - -| Line | Original | Address | File Offset | Max Chars | Allocated Bytes | -|------|--------------|-------------|-------------|-----------|-----------------| -| 1 | "Reverse" | 0x10003ee8 | 0x3EE8 | 7 | 8 | -| 2 | "Engineering" | 0x10003ef0 | 0x3EF0 | 11 | 12 | - -##### Example Patch: "Hello!!" and "World!!" - -**Line 1: "Hello!!" (7 characters — exact fit)** - -| Character | Hex | -|-----------|--------| -| H | 0x48 | -| e | 0x65 | -| l | 0x6C | -| l | 0x6C | -| o | 0x6F | -| ! | 0x21 | -| ! | 0x21 | -| \0 | 0x00 | - -``` -Offset 0x3EE8: -Before: 52 65 76 65 72 73 65 00 ("Reverse") -After: 48 65 6C 6C 6F 21 21 00 ("Hello!!") -``` - -**Line 2: "World!!" (7 characters — needs 5 bytes of null padding)** - -| Character | Hex | -|-----------|--------| -| W | 0x57 | -| o | 0x6F | -| r | 0x72 | -| l | 0x6C | -| d | 0x64 | -| ! | 0x21 | -| ! | 0x21 | -| \0 | 0x00 | -| \0 (pad) | 0x00 | -| \0 (pad) | 0x00 | -| \0 (pad) | 0x00 | -| \0 (pad) | 0x00 | - -``` -Offset 0x3EF0: -Before: 45 6E 67 69 6E 65 65 72 69 6E 67 00 ("Engineering") -After: 57 6F 72 6C 64 21 21 00 00 00 00 00 ("World!!") -``` - -##### Example Patch: Short String "Hi" - -**Line 1: "Hi" (2 characters — needs 5 bytes of null padding)** - -``` -Offset 0x3EE8: -Before: 52 65 76 65 72 73 65 00 ("Reverse") -After: 48 69 00 00 00 00 00 00 ("Hi") -``` - -##### Conversion and Flash - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants -python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -#### Reflection Answers - -1. **You padded short strings with 0x00 null bytes. Would it also work to pad with 0x20 (space characters)? What would be the difference on the LCD display?** - Both approaches produce valid strings, but the display differs. With `0x00` padding, the string terminates at the first null byte — `lcd_puts` stops reading there, and the remaining bytes are ignored. The LCD shows only your text. With `0x20` (space) padding, the spaces become part of the string — `lcd_puts` sends them to the LCD as visible blank characters. The LCD would show your text followed by trailing spaces. Functionally both work, but `0x00` padding is cleaner because the string length matches your intended text, and the LCD positions after your text remain in whatever state the LCD controller initialized them to (typically blank anyway). - -2. **The LCD is a 1602 (16 columns × 2 rows). What would happen if you could somehow put a 20-character string in memory? Would the LCD display all 20, or only the first 16?** - The LCD would display only the first 16 characters in the visible area. The HD44780 controller (used in 1602 LCD modules) has 40 bytes of DDRAM per line, so characters 17-20 would be written to DDRAM but are beyond the visible 16-column window. They would only become visible if you issued a display shift command to scroll the view. The `lcd_puts` function writes all characters to the controller regardless of line length — it has no built-in truncation. The 16-character limit is a physical display constraint, not a software one. - -3. **If you wanted to combine the string hacks from Exercise 1 (changing both LCD lines) AND a hypothetical numeric hack (e.g., changing the movs r1, #42 encoding at offset 0x28E), could you do all patches in a single .bin file? What offsets would you need to modify?** - Yes, all patches can be applied to the same `.bin` file since they are at non-overlapping offsets. The three patch locations are: - - **Offset 0x28E**: FAV_NUM — change `movs r1, #42` immediate byte from `0x2A` to desired value (1 byte) - - **Offset 0x3EE8**: LCD line 1 — replace the 8-byte "Reverse" string - - **Offset 0x3EF0**: LCD line 2 — replace the 12-byte "Engineering" string - - Each patch modifies a different region of the binary, so they are completely independent. You could also patch the `movw r1, #1337` instruction at offset `0x298` (the imm8 byte of the OTHER_FAV_NUM encoding) for a fourth independent patch. - -4. **Besides LCD text, what other strings could you patch in a real-world embedded device to change its behavior? Think about Wi-Fi SSIDs, Bluetooth device names, HTTP headers, etc.** - Real-world embedded devices contain many patchable strings: **Wi-Fi SSIDs** and **passwords** (change what network the device connects to), **Bluetooth device names** (change how it appears during pairing), **HTTP/HTTPS URLs** (redirect API calls to a different server), **MQTT broker addresses** (redirect IoT telemetry), **DNS hostnames**, **firmware version strings** (spoof version for update bypass), **serial number formats**, **command-line interface prompts**, **error and debug messages** (hide forensic evidence), **TLS/SSL certificate fields**, **NTP server addresses** (manipulate time synchronization), and **authentication tokens or API keys**. String patching is one of the most practical firmware modification techniques because it's simple to execute and can dramatically change device behavior. diff --git a/WEEK07/WEEK07-04.md b/WEEK07/WEEK07-04.md deleted file mode 100644 index 5339d50..0000000 --- a/WEEK07/WEEK07-04.md +++ /dev/null @@ -1,148 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 7 -Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics - -### Non-Credit Practice Exercise 4: Display Your Own Custom Message on the LCD - -#### Objective -Patch both LCD string literals in the binary to display your name (or any custom message) on the 1602 LCD, respecting the character length constraints, converting your text to hex bytes, and verifying the result on hardware. - -#### Prerequisites -- Completed Week 7 tutorial (hex editor section) and Exercise 1 -- `0x0017_constants.bin` binary available in your build directory -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with 1602 LCD connected via I²C - -#### Task Description -You will choose two custom messages to display on the LCD — one for each line. Line 1 replaces "Reverse" (7 characters max) and line 2 replaces "Engineering" (11 characters max). You must convert your chosen text to ASCII hex, handle the case where your text is shorter than the original (pad with null bytes), patch the binary, and flash it to see your custom message on the physical LCD. - -#### Step-by-Step Instructions - -##### Step 1: Choose Your Messages - -Plan two messages that fit the constraints: - -| Line | Original | Max Length | Your Message | Length | Valid? | -| ---- | ------------- | ---------- | ------------ | ------ | ------ | -| 1 | "Reverse" | 7 chars | | | | -| 2 | "Engineering" | 11 chars | | | | - -**Examples that work:** -- Line 1: "Hello!!" (7 chars) ✅ -- Line 2: "World!!" (7 chars, pad with 4 null bytes) ✅ -- Line 1: "Hi" (2 chars, pad with 5 null bytes) ✅ -- Line 2: "My Name Here" — ❌ (12 chars, too long!) - -> ⚠️ **Remember:** The 1602 LCD can display up to 16 characters per line, but the binary only allocates 8 bytes for "Reverse" and 12 bytes for "Engineering". You cannot exceed these byte allocations. - -##### Step 2: Convert Your Messages to Hex - -Use an ASCII table to convert each character: - -**Common ASCII values:** - -| Character | Hex | Character | Hex | Character | Hex | -| --------- | ------ | --------- | ------ | --------- | ------ | -| Space | `0x20` | 0-9 | `0x30`-`0x39` | A-Z | `0x41`-`0x5A` | -| ! | `0x21` | : | `0x3A` | a-z | `0x61`-`0x7A` | -| " | `0x22` | ? | `0x3F` | \0 (null) | `0x00` | - -Write out the hex bytes for each message, including the null terminator and any padding: - -**Line 1 (8 bytes total):** -``` -[char1] [char2] [char3] [char4] [char5] [char6] [char7] [0x00] -``` - -If your message is shorter than 7 characters, fill the remaining bytes with `0x00`. - -**Line 2 (12 bytes total):** -``` -[char1] [char2] [char3] [char4] [char5] [char6] [char7] [char8] [char9] [char10] [char11] [0x00] -``` - -If your message is shorter than 11 characters, fill the remaining bytes with `0x00`. - -##### Step 3: Open the Binary and Navigate - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants\build\0x0017_constants.bin` -2. Press **Ctrl+G** and enter offset: `3EE8` (Line 1: "Reverse") -3. Verify you see: `52 65 76 65 72 73 65 00` ("Reverse\0") - -##### Step 4: Patch Line 1 - -Replace the 8 bytes starting at offset `0x3EE8` with your prepared hex sequence. - -For example, to write "Hello!!" (7 chars + null): -``` -Before: 52 65 76 65 72 73 65 00 (Reverse) -After: 48 65 6C 6C 6F 21 21 00 (Hello!!) -``` - -For a shorter message like "Hi" (2 chars + null + padding): -``` -Before: 52 65 76 65 72 73 65 00 (Reverse) -After: 48 69 00 00 00 00 00 00 (Hi\0\0\0\0\0\0) -``` - -##### Step 5: Patch Line 2 - -1. Press **Ctrl+G** and enter offset: `3EF0` (Line 2: "Engineering") -2. Verify you see: `45 6E 67 69 6E 65 65 72 69 6E 67 00` -3. Replace the 12 bytes with your prepared hex sequence - -##### Step 6: Save the Patched Binary - -1. Click **File** → **Save As** → `0x0017_constants-h.bin` - -##### Step 7: Convert to UF2 and Flash - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants -python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -##### Step 8: Verify on the LCD - -Check the physical LCD display. Your custom messages should appear on lines 1 and 2. - -If the LCD shows garbled text or nothing at all: -- Verify your hex conversion was correct -- Ensure you included the null terminator (`0x00`) -- Confirm you didn't accidentally modify bytes outside the string regions -- Re-open the binary and double-check offsets `0x3EE8` and `0x3EF0` - -#### Expected Output - -After completing this exercise, you should be able to: -- Convert any ASCII text to hex bytes for binary patching -- Handle strings shorter than the allocated space using null padding -- Patch string literals in any compiled binary -- Verify patches work on real hardware - -#### Questions for Reflection - -###### Question 1: You padded short strings with `0x00` null bytes. Would it also work to pad with `0x20` (space characters)? What would be the difference on the LCD display? - -###### Question 2: The LCD is a 1602 (16 columns × 2 rows). What would happen if you could somehow put a 20-character string in memory? Would the LCD display all 20, or only the first 16? - -###### Question 3: If you wanted to combine the string hacks from Exercise 1 (changing both LCD lines) AND a hypothetical numeric hack (e.g., changing the `movs r1, #42` encoding at offset `0x28E`), could you do all patches in a single `.bin` file? What offsets would you need to modify? - -###### Question 4: Besides LCD text, what other strings could you patch in a real-world embedded device to change its behavior? Think about Wi-Fi SSIDs, Bluetooth device names, HTTP headers, etc. - -#### Tips and Hints -- HxD shows the ASCII representation of bytes in the right panel — use this to verify your patches look correct -- A quick way to compute ASCII: lowercase letter hex = uppercase letter hex + `0x20` -- If you make a mistake, close the file WITHOUT saving and start over with the original `.bin` -- Take a photo of your custom LCD display for your portfolio! - -#### Next Steps -- Review all four WEEK07 exercises and verify you understand string patching, data analysis, struct tracing, and custom message creation -- Try patching the `printf` format strings to display different labels in the serial output -- Challenge: can you make the LCD display emoji-like characters using the LCD's custom character feature (if supported by the backpack)? diff --git a/WEEK07/WEEK07.md b/WEEK07/WEEK07.md index f2b35ff..43dbbe8 100644 --- a/WEEK07/WEEK07.md +++ b/WEEK07/WEEK07.md @@ -1,12 +1,12 @@ -# Week 7: Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics +?# Week 7: Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics -## 🎯 What You'll Learn This Week +## ? What You'll Learn This Week By the end of this tutorial, you will be able to: - Understand the difference between `#define` macros and `const` variables - Know how constants are stored differently in memory (compile-time vs runtime) -- Understand the I²C (Inter-Integrated Circuit) communication protocol -- Configure I²C peripherals and communicate with LCD displays +- Understand the I2C (Inter-Integrated Circuit) communication protocol +- Configure I2C peripherals and communicate with LCD displays - Understand C structs and how the Pico SDK uses them for hardware abstraction - Use GDB to examine constants, structs, and string literals in memory - Hack constant values and string literals using a hex editor @@ -14,7 +14,7 @@ By the end of this tutorial, you will be able to: --- -## 📚 Part 1: Understanding Constants in C +## Part 1: Understanding Constants in C ### Two Types of Constants @@ -39,20 +39,20 @@ printf("Value: %d", FAV_NUM); Think of it like a "find and replace" in a text editor. The compiler never sees `FAV_NUM` - it only sees `42`! ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Preprocessor Macro Flow │ -│ │ -│ Source Code Preprocessor Compiler │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ #define │ │ Replace │ │ Compile │ │ -│ │ FAV_NUM │ ─────► │ FAV_NUM │ ─────► │ binary │ │ -│ │ 42 │ │ with 42 │ │ code │ │ -│ └──────────┘ └──────────┘ └──────────┘ │ -│ │ -│ FAV_NUM doesn't exist in the final binary! │ -│ The value 42 is embedded directly in instructions. │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Preprocessor Macro Flow | +| | +| Source Code Preprocessor Compiler | +| +----------+ +----------+ +----------+ | +| | #define | | Replace | | Compile | | +| | FAV_NUM | -----? | FAV_NUM | -----? | binary | | +| | 42 | | with 42 | | code | | +| +----------+ +----------+ +----------+ | +| | +| FAV_NUM doesn't exist in the final binary! | +| The value 42 is embedded directly in instructions. | +| | ++-----------------------------------------------------------------+ ``` ### Const Variables @@ -66,19 +66,19 @@ const int OTHER_FAV_NUM = 1337; Unlike `#define`, this creates a real memory location in the `.rodata` (read-only data) section of flash: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Const Variable in Memory │ -│ │ -│ Flash Memory (.rodata section) │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ Address: 0x10001234 │ │ -│ │ Value: 0x00000539 (1337 in hex) │ │ -│ │ Name: OTHER_FAV_NUM (in debug symbols only) │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ The variable EXISTS in memory and can be read at runtime. │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Const Variable in Memory | +| | +| Flash Memory (.rodata section) | +| +------------------------------------------------------------+ | +| | Address: 0x10001234 | | +| | Value: 0x00000539 (1337 in hex) | | +| | Name: OTHER_FAV_NUM (in debug symbols only) | | +| +------------------------------------------------------------+ | +| | +| The variable EXISTS in memory and can be read at runtime. | +| | ++-----------------------------------------------------------------+ ``` ### Comparison: #define vs const @@ -93,35 +93,35 @@ Unlike `#define`, this creates a real memory location in the `.rodata` (read-onl --- -## 📚 Part 2: Understanding I²C Communication +## Part 2: Understanding I2C Communication -### What is I²C? +### What is I2C? -**I²C** (pronounced "I-squared-C" or "I-two-C") stands for **Inter-Integrated Circuit**. It's a way for chips to talk to each other using just TWO wires! +**I2C** (pronounced "I-squared-C" or "I-two-C") stands for **Inter-Integrated Circuit**. It's a way for chips to talk to each other using just TWO wires! ``` -┌─────────────────────────────────────────────────────────────────┐ -│ I²C Bus - Two Wires, Many Devices │ -│ │ -│ 3.3V │ -│ │ │ -│ ┴ Pull-up ┴ Pull-up │ -│ │ │ │ -│ SDA ─┼────────────┼─────────────────────────────────────── │ -│ │ │ │ -│ SCL ─┼────────────┼─────────────────────────────────────── │ -│ │ │ │ │ │ -│ ┌───┴────┐ ┌──┴───┐ ┌────┴──┐ ┌─────┴───┐ │ -│ │ Pico │ │ LCD │ │Sensor │ │ EEPROM │ │ -│ │(Master)│ │ 0x27 │ │ 0x48 │ │ 0x50 │ │ -│ └────────┘ └──────┘ └───────┘ └─────────┘ │ -│ │ -│ Each device has a unique address (0x27, 0x48, 0x50...) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| I2C Bus - Two Wires, Many Devices | +| | +| 3.3V | +| | | +| + Pull-up + Pull-up | +| | | | +| SDA -+------------+--------------------------------------- | +| | | | +| SCL -+------------+--------------------------------------- | +| | | | | | +| +---+----+ +--+---+ +----+--+ +-----+---+ | +| | Pico | | LCD | |Sensor | | EEPROM | | +| |(Master)| | 0x27 | | 0x48 | | 0x50 | | +| +--------+ +------+ +-------+ +---------+ | +| | +| Each device has a unique address (0x27, 0x48, 0x50...) | +| | ++-----------------------------------------------------------------+ ``` -### The Two I²C Wires +### The Two I2C Wires | Wire | Name | Purpose | | ------- | ------------ | ------------------------------------ | @@ -130,13 +130,13 @@ Unlike `#define`, this creates a real memory location in the `.rodata` (read-onl ### Why Pull-Up Resistors? -I²C uses **open-drain** signals, meaning devices can only pull the line LOW. They can't drive it HIGH! Pull-up resistors are needed to bring the lines back to HIGH when no device is pulling them down. +I2C uses **open-drain** signals, meaning devices can only pull the line LOW. They can't drive it HIGH! Pull-up resistors are needed to bring the lines back to HIGH when no device is pulling them down. The Pico 2 has internal pull-ups that we can enable with `gpio_pull_up()`. -### I²C Addresses +### I2C Addresses -Every I²C device has a unique **7-bit address**. Common addresses: +Every I2C device has a unique **7-bit address**. Common addresses: | Device Type | Typical Address | | --------------------- | ---------------- | @@ -145,27 +145,27 @@ Every I²C device has a unique **7-bit address**. Common addresses: | EEPROM | `0x50` | | Real-time clock | `0x68` | -### I²C Communication Flow +### I2C Communication Flow ``` -┌─────────────────────────────────────────────────────────────────┐ -│ I²C Transaction │ -│ │ -│ 1. Master sends START condition │ -│ 2. Master sends device address (7 bits) + R/W bit │ -│ 3. Addressed device sends ACK (acknowledge) │ -│ 4. Data is transferred (8 bits at a time) │ -│ 5. Receiver sends ACK after each byte │ -│ 6. Master sends STOP condition │ -│ │ -│ START ──► Address ──► ACK ──► Data ──► ACK ──► STOP │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| I2C Transaction | +| | +| 1. Master sends START condition | +| 2. Master sends device address (7 bits) + R/W bit | +| 3. Addressed device sends ACK (acknowledge) | +| 4. Data is transferred (8 bits at a time) | +| 5. Receiver sends ACK after each byte | +| 6. Master sends STOP condition | +| | +| START --? Address --? ACK --? Data --? ACK --? STOP | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 3: Understanding C Structs +## Part 3: Understanding C Structs ### What is a Struct? @@ -222,11 +222,11 @@ struct i2c_inst { --- -## 📚 Part 4: Understanding the Pico SDK's I²C Structs +## Part 4: Understanding the Pico SDK's I2C Structs ### The i2c_inst_t Struct -The Pico SDK uses a struct to represent each I²C controller: +The Pico SDK uses a struct to represent each I2C controller: ```c struct i2c_inst { @@ -247,24 +247,24 @@ struct i2c_inst { When you write `I2C_PORT` in your code, here's what happens: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Macro Expansion Chain │ -│ │ -│ In your code: #define I2C_PORT i2c1 │ -│ │ │ -│ ▼ │ -│ In i2c.h: #define i2c1 (&i2c1_inst) │ -│ │ │ -│ ▼ │ -│ In i2c.c: i2c_inst_t i2c1_inst = {i2c1_hw, false}; │ -│ │ │ -│ ▼ │ -│ In i2c.h: #define i2c1_hw ((i2c_hw_t *)I2C1_BASE) │ -│ │ │ -│ ▼ │ -│ In addressmap.h: #define I2C1_BASE 0x40098000 │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Macro Expansion Chain | +| | +| In your code: #define I2C_PORT i2c1 | +| | | +| ? | +| In i2c.h: #define i2c1 (&i2c1_inst) | +| | | +| ? | +| In i2c.c: i2c_inst_t i2c1_inst = {i2c1_hw, false}; | +| | | +| ? | +| In i2c.h: #define i2c1_hw ((i2c_hw_t *)I2C1_BASE) | +| | | +| ? | +| In addressmap.h: #define I2C1_BASE 0x40098000 | +| | ++-----------------------------------------------------------------+ ``` So `I2C_PORT` eventually becomes a pointer to a struct that contains a pointer to hardware registers at address `0x40098000`! @@ -274,25 +274,25 @@ So `I2C_PORT` eventually becomes a pointer to a struct that contains a pointer t The `i2c_hw_t *hw` member points to the actual silicon: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Memory Map │ -│ │ -│ Address 0x40098000: I²C1 Hardware Registers │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ Offset 0x00: IC_CON (Control register) │ │ -│ │ Offset 0x04: IC_TAR (Target address register) │ │ -│ │ Offset 0x10: IC_DATA_CMD (Data command register) │ │ -│ │ ... │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ The i2c_hw_t struct maps directly to these registers! │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Memory Map | +| | +| Address 0x40098000: I2C1 Hardware Registers | +| +------------------------------------------------------------+ | +| | Offset 0x00: IC_CON (Control register) | | +| | Offset 0x04: IC_TAR (Target address register) | | +| | Offset 0x10: IC_DATA_CMD (Data command register) | | +| | ... | | +| +------------------------------------------------------------+ | +| | +| The i2c_hw_t struct maps directly to these registers! | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 5: The ARM Calling Convention (AAPCS) +## Part 5: The ARM Calling Convention (AAPCS) ### How Arguments Are Passed @@ -322,7 +322,7 @@ bl i2c_init ; Call the function --- -## 📚 Part 6: Setting Up Your Environment +## Part 6: Setting Up Your Environment ### Prerequisites @@ -333,7 +333,7 @@ Before we start, make sure you have: 4. GDB (`arm-none-eabi-gdb`) installed 5. Python installed (for UF2 conversion) 6. A serial monitor (PuTTY, minicom, or screen) -7. A 1602 LCD display with I²C backpack (PCF8574) +7. A 1602 LCD display with I2C backpack (PCF8574) 8. A hex editor (HxD, ImHex, or similar) 9. The sample project: `0x0017_constants` @@ -349,43 +349,43 @@ Connect your LCD like this: | SCL | GPIO 3 | ``` -┌─────────────────────────────────────────────────────────────────┐ -│ I²C LCD Wiring │ -│ │ -│ Pico 2 1602 LCD + I²C Backpack │ -│ ┌──────────┐ ┌──────────────────────┐ │ -│ │ │ │ │ │ -│ │ GPIO 2 │─────── SDA ─────►│ SDA │ │ -│ │ (SDA) │ │ │ │ -│ │ │ │ ┌────────────┐ │ │ -│ │ GPIO 3 │─────── SCL ─────►│ SCL│ Reverse │ │ │ -│ │ (SCL) │ │ │Engineering │ │ │ -│ │ │ │ └────────────┘ │ │ -│ │ 3.3V │─────── VCC ─────►│ VCC │ │ -│ │ │ │ │ │ -│ │ GND │─────── GND ─────►│ GND │ │ -│ │ │ │ │ │ -│ └──────────┘ └──────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| I2C LCD Wiring | +| | +| Pico 2 1602 LCD + I2C Backpack | +| +----------+ +----------------------+ | +| | | | | | +| | GPIO 2 |------- SDA -----?| SDA | | +| | (SDA) | | | | +| | | | +------------+ | | +| | GPIO 3 |------- SCL -----?| SCL| Reverse | | | +| | (SCL) | | |Engineering | | | +| | | | +------------+ | | +| | 3.3V |------- VCC -----?| VCC | | +| | | | | | +| | GND |------- GND -----?| GND | | +| | | | | | +| +----------+ +----------------------+ | +| | ++-----------------------------------------------------------------+ ``` ### Project Structure ``` Embedded-Hacking/ -├── 0x0017_constants/ -│ ├── build/ -│ │ ├── 0x0017_constants.uf2 -│ │ └── 0x0017_constants.bin -│ ├── 0x0017_constants.c -│ └── lcd_1602.h -└── uf2conv.py ++-- 0x0017_constants/ +| +-- build/ +| | +-- 0x0017_constants.uf2 +| | +-- 0x0017_constants.bin +| +-- 0x0017_constants.c +| +-- lcd_1602.h ++-- uf2conv.py ``` --- -## 🔬 Part 7: Hands-On Tutorial - Constants and I²C LCD +## ? Part 7: Hands-On Tutorial - Constants and I2C LCD ### Step 1: Review the Source Code @@ -431,10 +431,10 @@ int main(void) { **What this code does:** -1. **Lines 7-10:** Define preprocessor macros for constants and I²C configuration +1. **Lines 7-10:** Define preprocessor macros for constants and I2C configuration 2. **Line 12:** Define a `const` variable stored in flash 3. **Line 15:** Initialize UART for serial output -4. **Lines 17-21:** Initialize I²C1 at 100kHz, configure GPIO pins, enable pull-ups +4. **Lines 17-21:** Initialize I2C1 at 100kHz, configure GPIO pins, enable pull-ups 5. **Lines 23-27:** Initialize LCD and display "Reverse" on line 0, "Engineering" on line 1 6. **Lines 29-32:** Infinite loop printing both constant values to serial terminal @@ -463,9 +463,9 @@ OTHER_FAV_NUM: 1337 --- -## 🔬 Part 8: Debugging with GDB (Dynamic Analysis) +## ? Part 8: Debugging with GDB (Dynamic Analysis) -> 🔄 **REVIEW:** This setup is identical to previous weeks. If you need a refresher on OpenOCD and GDB connection, refer back to Week 3 Part 6. +> ? **REVIEW:** This setup is identical to previous weeks. If you need a refresher on OpenOCD and GDB connection, refer back to Week 3 Part 6. ### Starting the Debug Session @@ -473,7 +473,7 @@ OTHER_FAV_NUM: 1337 ```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" @@ -573,17 +573,17 @@ c GDB responds: ``` -Breakpoint 1 at 0x10000234: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0017_constants/0x0017_constants.c, line 16. +Breakpoint 1 at 0x10000234: file C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0017_constants/0x0017_constants.c, line 16. Note: automatically using hardware breakpoints for read-only addresses. (gdb) c Continuing. Thread 1 "rp2350.cm0" hit Breakpoint 1, main () - at C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0017_constants/0x0017_constants.c:16 + at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0017_constants/0x0017_constants.c:16 16 stdio_init_all(); ``` -> ⚠️ **Note:** If GDB says `The program is not being run.` when you type `c`, the target hasn't been started yet. Use `monitor reset halt` first, then `c` to continue to your breakpoint. +> **Note:** If GDB says `The program is not being run.` when you type `c`, the target hasn't been started yet. Use `monitor reset halt` first, then `c` to continue to your breakpoint. ### Step 6: Find the #define Constant (FAV_NUM) @@ -618,13 +618,13 @@ Look for this instruction: ... ``` -**Surprise!** The `const` variable is ALSO embedded as an immediate value — not loaded from memory! The compiler saw that `OTHER_FAV_NUM` is never address-taken (`&OTHER_FAV_NUM` is never used), so it optimized the `const` the same way as `#define` — as a constant embedded directly in the instruction. +**Surprise!** The `const` variable is ALSO embedded as an immediate value - not loaded from memory! The compiler saw that `OTHER_FAV_NUM` is never address-taken (`&OTHER_FAV_NUM` is never used), so it optimized the `const` the same way as `#define` - as a constant embedded directly in the instruction. The difference is the instruction encoding: -- `FAV_NUM` (42): `movs r1, #0x2a` — 16-bit Thumb instruction (values 0-255) -- `OTHER_FAV_NUM` (1337): `movw r1, #0x539` — 32-bit Thumb-2 instruction (values 0-65535) +- `FAV_NUM` (42): `movs r1, #0x2a` - 16-bit Thumb instruction (values 0-255) +- `OTHER_FAV_NUM` (1337): `movw r1, #0x539` - 32-bit Thumb-2 instruction (values 0-65535) -> 💡 **Why `movw` instead of `movs`?** The value 1337 doesn't fit in 8 bits (max 255), so the compiler uses `movw` (Move Wide) which can encode any 16-bit immediate (0-65535) in a 32-bit instruction. +> Tip: **Why `movw` instead of `movs`?** The value 1337 doesn't fit in 8 bits (max 255), so the compiler uses `movw` (Move Wide) which can encode any 16-bit immediate (0-65535) in a 32-bit instruction. ### Step 8: Examine the Literal Pool @@ -647,9 +647,9 @@ These are the values that `ldr rN, [pc, #offset]` instructions load: | `0x100002b4` | `0x10003EFC` | "FAV_NUM: %d\r\n" format str | | `0x100002b8` | `0x10003F0C` | "OTHER_FAV_NUM: %d\r\n" fmt | -> 💡 **Why does the disassembly at `0x100002a4` show `strh r0, [r4, #52]` instead of data?** Same reason as Week 6 — GDB's `x/i` tries to decode raw data as instructions. Use `x/wx` to see the actual word values. +> Tip: **Why does the disassembly at `0x100002a4` show `strh r0, [r4, #52]` instead of data?** Same reason as Week 6 - GDB's `x/i` tries to decode raw data as instructions. Use `x/wx` to see the actual word values. -### Step 9: Examine the I²C Struct +### Step 9: Examine the I2C Struct Find the i2c1_inst struct address loaded into r0 before i2c_init: @@ -684,9 +684,9 @@ Output: 0x10003ef0: "Engineering" ``` -### Step 11: Step Through I²C Initialization +### Step 11: Step Through I2C Initialization -Use `si` to step through instructions and watch the I²C setup: +Use `si` to step through instructions and watch the I2C setup: ``` si @@ -695,36 +695,36 @@ i r r0 r1 --- -## 🔬 Part 9: Understanding the Assembly +## ? Part 9: Understanding the Assembly Now that we've explored the binary in GDB, let's make sense of the key patterns we found. ### Step 12: Analyze #define vs const in Assembly -From GDB, we discovered something interesting — **both constants ended up as instruction immediates!** +From GDB, we discovered something interesting - **both constants ended up as instruction immediates!** -**For FAV_NUM (42) — a `#define` macro:** +**For FAV_NUM (42) - a `#define` macro:** ``` 0x1000028e: movs r1, #42 @ 0x2a ``` -The value 42 is embedded directly in a 16-bit Thumb instruction. This is expected — `#define` is text replacement, so the compiler never sees `FAV_NUM`, only `42`. +The value 42 is embedded directly in a 16-bit Thumb instruction. This is expected - `#define` is text replacement, so the compiler never sees `FAV_NUM`, only `42`. -**For OTHER_FAV_NUM (1337) — a `const` variable:** +**For OTHER_FAV_NUM (1337) - a `const` variable:** ``` 0x10000296: movw r1, #1337 @ 0x539 ``` -The value 1337 is ALSO embedded directly in an instruction — but this time a 32-bit Thumb-2 `movw` because the value doesn't fit in 8 bits. +The value 1337 is ALSO embedded directly in an instruction - but this time a 32-bit Thumb-2 `movw` because the value doesn't fit in 8 bits. **Why wasn't `const` stored in memory?** In theory, `const int OTHER_FAV_NUM = 1337` creates a variable in the `.rodata` section. But the compiler optimized it away because: 1. We never take the address of `OTHER_FAV_NUM` (no `&OTHER_FAV_NUM`) 2. The value fits in a 16-bit `movw` immediate 3. Loading from an immediate is faster than loading from memory -> 💡 **Key takeaway for reverse engineering:** Don't assume `const` variables will appear as memory loads. Modern compilers aggressively inline constant values. The C keyword `const` is a **source-level** concept — the compiler may or may not honor it in the final binary. +> Tip: **Key takeaway for reverse engineering:** Don't assume `const` variables will appear as memory loads. Modern compilers aggressively inline constant values. The C keyword `const` is a **source-level** concept - the compiler may or may not honor it in the final binary. -### Step 13: Analyze the I²C Struct Layout +### Step 13: Analyze the I2C Struct Layout In GDB, we examined the `i2c1_inst` struct at `0x2000062c`: @@ -736,19 +736,19 @@ In GDB, we examined the `i2c1_inst` struct at `0x2000062c`: This maps to the `i2c_inst_t` struct: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ i2c_inst_t at 0x2000062c │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ Offset Type Name Value │ │ -│ │ 0x00 i2c_hw_t * hw 0x40098000 │ │ -│ │ 0x04 bool restart_on_next 0x00 (false) │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| i2c_inst_t at 0x2000062c | +| | +| +------------------------------------------------------------+ | +| | Offset Type Name Value | | +| | 0x00 i2c_hw_t * hw 0x40098000 | | +| | 0x04 bool restart_on_next 0x00 (false) | | +| +------------------------------------------------------------+ | +| | ++-----------------------------------------------------------------+ ``` -The first member (`hw`) points to `0x40098000` — the I²C1 hardware register base. This is the end of the macro chain: `I2C_PORT` → `i2c1` → `&i2c1_inst` → `hw` → `0x40098000`. +The first member (`hw`) points to `0x40098000` - the I2C1 hardware register base. This is the end of the macro chain: `I2C_PORT` -> `i2c1` -> `&i2c1_inst` -> `hw` -> `0x40098000`. ### Step 14: Locate the String Literals @@ -762,21 +762,21 @@ We found the LCD strings in flash memory: 0x10003ef0: "Engineering" ``` -These are stored consecutively in the `.rodata` section. Note the addresses — we'll need them for patching. +These are stored consecutively in the `.rodata` section. Note the addresses - we'll need them for patching. --- -## 🔬 Part 10: Hacking the Binary with a Hex Editor +## ? Part 10: Hacking the Binary with a Hex Editor -Now for the fun part — we'll patch the `.bin` file directly using a hex editor! +Now for the fun part - we'll patch the `.bin` file directly using a hex editor! -> 💡 **Why a hex editor?** GDB **cannot write to flash memory** — the `0x10000000+` address range where program instructions and read-only data live. Trying `set *(char *)0x1000028e = 0x2b` in GDB gives `Writing to flash memory forbidden in this context`. To make **permanent** patches that survive a power cycle, we edit the `.bin` file directly with a hex editor and re-flash it. +> Tip: **Why a hex editor?** GDB **cannot write to flash memory** - the `0x10000000+` address range where program instructions and read-only data live. Trying `set *(char *)0x1000028e = 0x2b` in GDB gives `Writing to flash memory forbidden in this context`. To make **permanent** patches that survive a power cycle, we edit the `.bin` file directly with a hex editor and re-flash it. ### Step 15: Open the Binary in a Hex Editor 1. Open **HxD** (or your preferred hex editor: ImHex, 010 Editor, etc.) -2. Click **File** → **Open** -3. Navigate to `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants\build\` +2. Click **File** -> **Open** +3. Navigate to `C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0017_constants\build\` 4. Open `0x0017_constants.bin` ### Step 16: Calculate the File Offset @@ -788,22 +788,22 @@ file_offset = address - 0x10000000 ``` For example: -- Address `0x1000028e` → file offset `0x28E` (654 in decimal) -- Address `0x10003ee8` → file offset `0x3EE8` (16104 in decimal) +- Address `0x1000028e` -> file offset `0x28E` (654 in decimal) +- Address `0x10003ee8` -> file offset `0x3EE8` (16104 in decimal) -### Step 17: Understand FAV_NUM Encoding (movs — 16-bit Thumb) +### Step 17: Understand FAV_NUM Encoding (movs - 16-bit Thumb) From our GDB analysis, we know the instruction at `0x1000028e` is: ``` -movs r1, #0x2a → bytes: 2a 21 +movs r1, #0x2a -> bytes: 2a 21 ``` In HxD, navigate to file offset `0x28E` and verify you see the byte `2A` followed by `21`. -> 🔍 **How Thumb encoding works:** In `movs r1, #imm8`, the immediate value is the first byte, and the opcode `21` is the second byte. So the bytes `2a 21` encode `movs r1, #0x2a` (42). If you wanted to change this to 43, you'd change `2A` to `2B`. +> ?? **How Thumb encoding works:** In `movs r1, #imm8`, the immediate value is the first byte, and the opcode `21` is the second byte. So the bytes `2a 21` encode `movs r1, #0x2a` (42). If you wanted to change this to 43, you'd change `2A` to `2B`. -### Step 18: Understand OTHER_FAV_NUM Encoding (movw — 32-bit Thumb-2) +### Step 18: Understand OTHER_FAV_NUM Encoding (movw - 32-bit Thumb-2) From GDB, we found the `movw r1, #1337` instruction at `0x10000296`. Examine the exact bytes: @@ -815,18 +815,18 @@ From GDB, we found the `movw r1, #1337` instruction at `0x10000296`. Examine the This is the 32-bit Thumb-2 encoding of `movw r1, #0x539` (1337). The bytes break down as: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ movw r1, #0x539 → bytes: 40 F2 39 51 │ -│ │ -│ Byte 0: 0x40 ─┐ │ -│ Byte 1: 0xF2 ─┘ First halfword (opcode + upper imm bits) │ -│ Byte 2: 0x39 ──── Lower 8 bits of immediate (imm8) ← CHANGE │ -│ Byte 3: 0x51 ──── Destination register (r1) + upper imm bits │ -│ │ -│ imm16 = 0x0539 = 1337 decimal │ -│ imm8 field = 0x39 (lower 8 bits of the value) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| movw r1, #0x539 -> bytes: 40 F2 39 51 | +| | +| Byte 0: 0x40 -?? | +| Byte 1: 0xF2 -+ First halfword (opcode + upper imm bits) | +| Byte 2: 0x39 ---- Lower 8 bits of immediate (imm8) ?? CHANGE | +| Byte 3: 0x51 ---- Destination register (r1) + upper imm bits | +| | +| imm16 = 0x0539 = 1337 decimal | +| imm8 field = 0x39 (lower 8 bits of the value) | +| | ++-----------------------------------------------------------------+ ``` The file offset is `0x10000296 - 0x10000000 = 0x296`. The imm8 byte is the 3rd byte of the instruction: `0x296 + 2 = 0x298`. @@ -838,11 +838,11 @@ To change `movw r1, #1337` to `movw r1, #1344`: 3. You should see the byte `39` at this position 4. Change `39` to `40` -> 🔍 **Why offset `0x298` and not `0x296`?** The lower 8 bits of the immediate (`imm8`) are in the **third byte** of the 4-byte `movw` instruction. The instruction starts at file offset `0x296`, so imm8 is at `0x296 + 2 = 0x298`. Changing `0x39` to `0x40` changes the value from `0x539` (1337) to `0x540` (1344). +> ?? **Why offset `0x298` and not `0x296`?** The lower 8 bits of the immediate (`imm8`) are in the **third byte** of the 4-byte `movw` instruction. The instruction starts at file offset `0x296`, so imm8 is at `0x296 + 2 = 0x298`. Changing `0x39` to `0x40` changes the value from `0x539` (1337) to `0x540` (1344). -### Step 19: Hack — Change LCD Text from "Reverse" to "Exploit" +### Step 19: Hack - Change LCD Text from "Reverse" to "Exploit" -**IMPORTANT:** The new string must be the **same length** as the original! "Reverse" and "Exploit" are both 7 characters — perfect! +**IMPORTANT:** The new string must be the **same length** as the original! "Reverse" and "Exploit" are both 7 characters - perfect! From our GDB analysis in Step 10, we found the string at `0x10003ee8`. File offset = `0x10003ee8 - 0x10000000 = 0x3EE8`. @@ -864,20 +864,20 @@ From our GDB analysis in Step 10, we found the string at `0x10003ee8`. File offs ### Step 20: Save the Patched Binary -1. Click **File** → **Save As** +1. Click **File** -> **Save As** 2. Save as `0x0017_constants-h.bin` in the build directory 3. Close the hex editor --- -## 🔬 Part 11: Converting and Flashing the Hacked Binary +## ? Part 11: Converting and Flashing the Hacked Binary ### Step 21: Convert to UF2 Format Open a terminal and navigate to your project directory: ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0017_constants ``` Run the conversion command: @@ -907,18 +907,18 @@ OTHER_FAV_NUM: 1337 ... ``` -The numbers are unchanged — we only patched the LCD string! +The numbers are unchanged - we only patched the LCD string! -🎉 **BOOM! We successfully changed the LCD text from "Reverse" to "Exploit" without access to the source code!** +? **BOOM! We successfully changed the LCD text from "Reverse" to "Exploit" without access to the source code!** --- -## 📊 Part 12: Summary and Review +## ? Part 12: Summary and Review ### What We Accomplished 1. **Learned about constants** - `#define` macros vs `const` variables -2. **Understood I²C communication** - Two-wire protocol for peripheral communication +2. **Understood I2C communication** - Two-wire protocol for peripheral communication 3. **Explored C structs** - How the Pico SDK abstracts hardware 4. **Mastered the macro chain** - From `I2C_PORT` to `0x40098000` 5. **Examined structs in GDB** - Inspected memory layout of `i2c_inst_t` @@ -928,52 +928,52 @@ The numbers are unchanged — we only patched the LCD string! ### #define vs const Summary ``` -┌─────────────────────────────────────────────────────────────────┐ -│ #define FAV_NUM 42 │ -│ ─────────────────── │ -│ • Text replacement at compile time │ -│ • No memory allocated │ -│ • Cannot take address (&FAV_NUM is invalid) │ -│ • In binary: value appears as immediate (movs r1, #0x2a) │ -│ • To hack: patch the instruction operand │ -├─────────────────────────────────────────────────────────────────┤ -│ const int OTHER_FAV_NUM = 1337 │ -│ ────────────────────────────── │ -│ • Theoretically in .rodata, but compiler optimized it away │ -│ • Value embedded as immediate: movw r1, #0x539 (32-bit instr) │ -│ • Optimization: compiler saw &OTHER_FAV_NUM is never used │ -│ • In binary: immediate in instruction, same as #define! │ -│ • To hack: patch instruction operand (imm8 byte at offset +2) │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| #define FAV_NUM 42 | +| ------------------- | +| - Text replacement at compile time | +| - No memory allocated | +| - Cannot take address (&FAV_NUM is invalid) | +| - In binary: value appears as immediate (movs r1, #0x2a) | +| - To hack: patch the instruction operand | ++-----------------------------------------------------------------+ +| const int OTHER_FAV_NUM = 1337 | +| ------------------------------ | +| - Theoretically in .rodata, but compiler optimized it away | +| - Value embedded as immediate: movw r1, #0x539 (32-bit instr) | +| - Optimization: compiler saw &OTHER_FAV_NUM is never used | +| - In binary: immediate in instruction, same as #define! | +| - To hack: patch instruction operand (imm8 byte at offset +2) | ++-----------------------------------------------------------------+ ``` -### I²C Configuration Summary +### I2C Configuration Summary ``` -┌─────────────────────────────────────────────────────────────────┐ -│ I²C Setup Steps │ -│ │ -│ 1. i2c_init(i2c1, 100000) - Initialize at 100kHz │ -│ 2. gpio_set_function(pin, I2C) - Assign pins to I²C │ -│ 3. gpio_pull_up(sda_pin) - Enable SDA pull-up │ -│ 4. gpio_pull_up(scl_pin) - Enable SCL pull-up │ -│ 5. lcd_i2c_init(...) - Initialize the device │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| I2C Setup Steps | +| | +| 1. i2c_init(i2c1, 100000) - Initialize at 100kHz | +| 2. gpio_set_function(pin, I2C) - Assign pins to I2C | +| 3. gpio_pull_up(sda_pin) - Enable SDA pull-up | +| 4. gpio_pull_up(scl_pin) - Enable SCL pull-up | +| 5. lcd_i2c_init(...) - Initialize the device | +| | ++-----------------------------------------------------------------+ ``` ### The Struct Chain ``` -┌─────────────────────────────────────────────────────────────────┐ -│ I2C_PORT → i2c1 → &i2c1_inst → i2c_inst_t │ -│ │ │ -│ ├── hw → i2c_hw_t * │ -│ │ └── 0x40098000 │ -│ │ │ -│ └── restart_on_next (bool) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| I2C_PORT -> i2c1 -> &i2c1_inst -> i2c_inst_t | +| | | +| +-- hw -> i2c_hw_t * | +| | +-- 0x40098000 | +| | | +| +-- restart_on_next (bool) | +| | ++-----------------------------------------------------------------+ ``` ### Key Memory Addresses @@ -984,51 +984,24 @@ The numbers are unchanged — we only patched the LCD string! | `0x1000028e` | FAV_NUM value in instruction | | `0x10000296` | OTHER_FAV_NUM value in instruction | | `0x10003ee8` | "Reverse" string literal (example) | -| `0x40098000` | I²C1 hardware registers base | +| `0x40098000` | I2C1 hardware registers base | | `0x2000062C` | i2c1_inst struct in SRAM | --- -## ✅ Practice Exercises - -### Exercise 1: Change Both LCD Lines -Change "Engineering" to "Hacking!!!" (same number of characters). - -**Hint:** Find the second string after "Reverse" in memory. - -### Exercise 2: Change the I²C Address -The LCD is at address `0x27`. Find where this is passed to `lcd_i2c_init` and change it. - -**Warning:** If you change to an invalid address, the LCD won't work! - -### Exercise 3: Find All String Literals -Search the binary for all readable strings. How many can you find? What do they reveal about the program? - -**Hint:** In GDB, use `x/s` to search for strings in the binary, or scan through the `.bin` file in your hex editor. - -### Exercise 4: Trace the Struct Pointer -Follow the `i2c1_inst` pointer from the code to SRAM. What values are stored in the struct? - -**Hint:** The first member should point to `0x40098000`. - -### Exercise 5: Add Your Own Message -Can you make the LCD display your name? Remember the character limit! - -**Hint:** Line 1 and Line 2 each have 16 characters maximum on a 1602 LCD. - --- -## 🎓 Key Takeaways +## ? Key Takeaways 1. **#define is text replacement** - It happens before compilation, no memory used. 2. **const creates real variables** - Stored in .rodata, takes memory, has an address. -3. **I²C uses two wires** - SDA for data, SCL for clock, pull-ups required. +3. **I2C uses two wires** - SDA for data, SCL for clock, pull-ups required. 4. **Structs group related data** - The SDK uses them to abstract hardware. -5. **Macros can chain** - `I2C_PORT` → `i2c1` → `&i2c1_inst` → hardware pointer. +5. **Macros can chain** - `I2C_PORT` -> `i2c1` -> `&i2c1_inst` -> hardware pointer. 6. **ARM passes args in registers** - r0-r3 for first four arguments. @@ -1042,7 +1015,7 @@ Can you make the LCD display your name? Remember the character limit! --- -## 📖 Glossary +## ? Glossary | Term | Definition | | ----------------------- | --------------------------------------------------- | @@ -1050,22 +1023,22 @@ Can you make the LCD display your name? Remember the character limit! | **AAPCS** | ARM Architecture Procedure Call Standard | | **const** | Keyword marking a variable as read-only | | **Forward Declaration** | Telling compiler a type exists before defining it | -| **I²C** | Inter-Integrated Circuit - two-wire serial protocol | +| **I2C** | Inter-Integrated Circuit - two-wire serial protocol | | **Immediate Value** | A constant embedded directly in an instruction | | **Open-Drain** | Output that can only pull low, not drive high | -| **PCF8574** | Common I²C I/O expander chip used in LCD backpacks | +| **PCF8574** | Common I2C I/O expander chip used in LCD backpacks | | **Preprocessor** | Tool that processes code before compilation | | **Pull-Up Resistor** | Resistor that holds a line HIGH by default | -| **SCL** | Serial Clock - I²C timing signal | -| **SDA** | Serial Data - I²C data line | +| **SCL** | Serial Clock - I2C timing signal | +| **SDA** | Serial Data - I2C data line | | **Struct** | User-defined type grouping related variables | | **typedef** | Creates an alias for a type | --- -## 🔗 Additional Resources +## ? Additional Resources -### I²C Timing Reference +### I2C Timing Reference | Speed Mode | Maximum Frequency | | ---------- | ----------------- | @@ -1073,7 +1046,7 @@ Can you make the LCD display your name? Remember the character limit! | Fast | 400 kHz | | Fast Plus | 1 MHz | -### Common I²C Addresses +### Common I2C Addresses | Device | Address | | --------------------- | ------------- | @@ -1091,15 +1064,17 @@ Can you make the LCD display your name? Remember the character limit! | `ldr rN, [pc, #off]` | Load larger value from literal pool | | `ldr rN, =value` | Pseudo-instruction for loading any constant | -### RP2350 I²C Memory Map +### RP2350 I2C Memory Map | Address | Description | | ------------ | ----------------------- | -| `0x40090000` | I²C0 hardware registers | -| `0x40098000` | I²C1 hardware registers | +| `0x40090000` | I2C0 hardware registers | +| `0x40098000` | I2C1 hardware registers | --- -**Remember:** When you see complex nested structures in a binary, take your time to understand the hierarchy. Use GDB to examine struct layouts in memory and trace pointer chains. And always remember — even "constants" can be hacked! +**Remember:** When you see complex nested structures in a binary, take your time to understand the hierarchy. Use GDB to examine struct layouts in memory and trace pointer chains. And always remember - even "constants" can be hacked! + +Happy hacking! ? + -Happy hacking! 🔧 diff --git a/WEEK09/WEEK09-01-S.md b/WEEK09/WEEK09-01-S.md deleted file mode 100644 index a10bc14..0000000 --- a/WEEK09/WEEK09-01-S.md +++ /dev/null @@ -1,66 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 9 -Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics - -### Non-Credit Practice Exercise 1 Solution: Change the Sleep Duration - -#### Answers - -##### Sleep Duration Values - -| Parameter | Original | Patched | -|---------------|-------------|-------------| -| Duration (ms) | 2000 | 5000 | -| Hex | 0x000007D0 | 0x00001388 | -| Little-endian | D0 07 00 00 | 88 13 00 00 | - -##### Why a Literal Pool Is Needed - -The value 2000 exceeds the 8-bit immediate range of `movs` (0–255) and the 16-bit range is also impractical for a single-instruction load. The compiler stores `0x000007D0` in a **literal pool** near the function code and loads it with a `ldr r0, [pc, #offset]` instruction that reads the 32-bit word from the pool into `r0` before the `bl sleep_ms` call. - -##### Patch Procedure - -1. Find the literal pool entry containing `D0 07 00 00` in HxD -2. Replace with `88 13 00 00` - -``` -Before: D0 07 00 00 (2000) -After: 88 13 00 00 (5000) -``` - -##### Conversion and Flash - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators -python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Serial Output After Patch - -``` -arithmetic_operator: 50 -increment_operator: 5 -relational_operator: 0 -logical_operator: 0 -bitwise_operator: 12 -assignment_operator: 11 -Humidity: 51.0%, Temperature: 24.0°C -``` - -Output repeats every **5 seconds** instead of 2 seconds. - -#### Reflection Answers - -1. **Why can't 2000 be encoded as a movs immediate? What is the maximum value movs can hold?** - The `movs Rd, #imm8` instruction is a 16-bit Thumb encoding that has only 8 bits for the immediate value, giving a range of 0–255. The value 2000 (`0x7D0`) is far beyond this range. Even the 32-bit Thumb-2 `movw` instruction can only encode 0–65535, which could handle 2000, but the compiler chose a literal pool approach. The literal pool is a general-purpose solution that works for any 32-bit value, including addresses and large constants. - -2. **If you wanted to change the sleep to exactly 1 second (1000ms), what 4 bytes would you write in little-endian? Show your work.** - 1000 decimal = `0x000003E8` hex. In little-endian byte order (LSB first): `E8 03 00 00`. Breakdown: byte 0 = `0xE8` (LSB), byte 1 = `0x03`, byte 2 = `0x00`, byte 3 = `0x00` (MSB). - -3. **Could other code in the program reference the same literal pool entry containing 0x7D0? What would happen if it did?** - Yes, the compiler may share literal pool entries to save space. If another instruction also loads `0x7D0` from the same pool address (using its own `ldr rN, [pc, #offset]`), then patching that pool entry would change the value for ALL instructions that reference it. This is a risk with literal pool patching — you might unintentionally modify other parts of the program. To check, search for all `ldr` instructions whose PC-relative offset resolves to the same pool address. - -4. **What would happen if you set sleep_ms to 0? Would the program crash or just run extremely fast?** - The program would not crash — `sleep_ms(0)` is a valid call that returns immediately. The loop would run as fast as the processor can execute it, printing operator values and reading the DHT11 sensor with no delay between iterations. The serial output would flood extremely quickly. However, the DHT11 sensor has a minimum sampling interval of about 1 second; reading it more frequently may return stale data or read errors ("DHT11 read failed"), but the program itself would continue running. diff --git a/WEEK09/WEEK09-01.md b/WEEK09/WEEK09-01.md deleted file mode 100644 index bb40eec..0000000 --- a/WEEK09/WEEK09-01.md +++ /dev/null @@ -1,141 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 9 -Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics - -### Non-Credit Practice Exercise 1: Change the Sleep Duration - -#### Objective -Find the `sleep_ms(2000)` call in the `0x001a_operators` binary using GDB, identify the immediate value `0x7d0` (2000) being loaded into `r0`, calculate the file offset, patch it to `0x1388` (5000) using a hex editor, and verify on hardware that the serial output now prints every 5 seconds instead of every 2 seconds. - -#### Prerequisites -- Completed Week 9 tutorial (GDB and hex editor sections) -- `0x001a_operators.elf` and `0x001a_operators.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with DHT11 sensor connected - -#### Task Description -The program calls `sleep_ms(2000)` at the end of its main loop, causing a 2-second delay between each set of serial output. The value `2000` (`0x7D0`) is loaded into register `r0` before the `bl sleep_ms` call. You will locate this value in the disassembly, find the corresponding bytes in the `.bin` file, and patch them to `5000` (`0x1388`) so the loop runs every 5 seconds instead. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x001a_operators.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find the sleep_ms Call - -Disassemble main and look for the `sleep_ms` call: - -```gdb -(gdb) x/60i 0x10000234 -``` - -Look for an instruction pattern like: - -``` -ldr r0, =0x7d0 ; 2000 milliseconds -bl sleep_ms -``` - -The value `0x7d0` will be loaded from the literal pool. - -##### Step 3: Examine the Literal Pool Value - -Once you find the `ldr r0, [pc, #offset]` instruction, examine the literal pool entry it references: - -```gdb -(gdb) x/wx -``` - -You should see `0x000007d0` (2000 in hex). - -##### Step 4: Calculate the File Offset - -``` -file_offset = literal_pool_address - 0x10000000 -``` - -Note the file offset of the 4-byte word containing `0x7d0`. - -##### Step 5: Encode the New Value - -The new value `5000` in hex is `0x1388`. In little-endian byte order: - -| Value | Hex | Little-Endian Bytes | -| ----- | ---------- | ------------------- | -| 2000 | `0x000007D0` | `D0 07 00 00` | -| 5000 | `0x00001388` | `88 13 00 00` | - -##### Step 6: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators\build\0x001a_operators.bin` -2. Press **Ctrl+G** and enter the file offset you calculated -3. You should see: `D0 07 00 00` -4. Replace with: `88 13 00 00` - -##### Step 7: Save and Convert - -1. Click **File** → **Save As** → `0x001a_operators-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators -python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 8: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the serial monitor:** -- Output should now appear every **5 seconds** instead of every 2 seconds -- All operator values should remain unchanged (50, 5, 0, 0, 12, 11) - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate literal pool values referenced by `ldr` instructions -- Understand little-endian byte ordering for 32-bit values -- Patch timing constants in embedded firmware -- Calculate file offsets from memory addresses - -#### Questions for Reflection - -###### Question 1: The value `2000` is stored in the literal pool as a 32-bit word, not as an immediate in the instruction. Why can't `2000` be encoded as an immediate in a single `movs` instruction? - -###### Question 2: If you wanted to change the delay to exactly 1 second (1000ms = `0x3E8`), what bytes would you write in little-endian order? - -###### Question 3: The literal pool value is shared — could other code in the binary also reference this same `0x7D0` value? How would you check? - -###### Question 4: What would happen if you changed the sleep value to `0` (`00 00 00 00`)? Would the program crash or just run extremely fast? - -#### Tips and Hints -- `movs` can only encode immediates 0-255; values larger than 255 must be loaded from the literal pool via `ldr` -- Always verify the bytes BEFORE patching — make sure you see `D0 07 00 00` at your calculated offset -- The literal pool is typically right after the function's `b.n` (loop branch) instruction -- Use a stopwatch or count "one-one-thousand" to verify the timing change diff --git a/WEEK09/WEEK09-02-S.md b/WEEK09/WEEK09-02-S.md deleted file mode 100644 index 8c06b2c..0000000 --- a/WEEK09/WEEK09-02-S.md +++ /dev/null @@ -1,66 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 9 -Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics - -### Non-Credit Practice Exercise 2 Solution: Invert the Temperature Reading - -#### Answers - -##### IEEE-754 Sign Bit Flip - -| Value | IEEE-754 Hex | Little-Endian Bytes | Sign Bit | -|---------|--------------|--------------------|---------| -| +0.1f | 0x3DCCCCCD | CD CC CC 3D | 0 | -| -0.1f | 0xBDCCCCCD | CD CC CC BD | 1 | - -Only the **last byte** changes in little-endian: `3D` → `BD`. This flips bit 31 (the IEEE-754 sign bit) from 0 to 1, negating the value. - -##### Patch at File Offset 0x42C - -``` -Offset 0x42C: -Before: CD CC CC 3D (+0.1f) -After: CD CC CC BD (-0.1f) -``` - -Only 1 byte changes: offset `0x42F` from `0x3D` to `0xBD`. - -##### How the Temperature Changes - -The DHT11 returns raw integer and decimal parts (e.g., integer=24, decimal=0). The firmware computes: - -``` -temperature = integer_part + (decimal_part × 0.1f) -``` - -With `vfma.f32 s15, s13, s11`: result = s15 + (s13 × s11) = integer + (decimal × 0.1f) - -After patching to -0.1f: result = integer + (decimal × -0.1f) - -For a reading of 24.5°C: original = 24 + (5 × 0.1) = 24.5°C, patched = 24 + (5 × -0.1) = 23.5°C - -For a reading of 24.0°C: integer=24, decimal=0, so 24 + (0 × -0.1) = 24.0°C (unchanged when decimal is 0). - -##### Serial Output After Patch - -``` -Humidity: 51.0%, Temperature: 23.5°C -``` - -(Temperature decimal contribution is inverted; effect depends on the decimal component.) - -#### Reflection Answers - -1. **Why does changing the byte from 0x3D to 0xBD negate the float? What specific bit is being flipped?** - In IEEE-754 single-precision format, bit 31 is the **sign bit**: 0 = positive, 1 = negative. The byte `0x3D` in binary is `0011 1101` and `0xBD` is `1011 1101` — only bit 7 of that byte differs, which corresponds to bit 31 of the 32-bit float (since it's the MSB of the last byte in little-endian storage). The exponent and mantissa bits remain identical, so the magnitude stays exactly `0.1` — only the sign changes. - -2. **Why does patching one constant affect both humidity AND temperature? They use different vfma instructions (at 0x410 and 0x414) — so why are both affected?** - Both `vfma` instructions at `0x410` (humidity) and `0x414` (temperature) load the scaling constant from the **same literal pool entry** at offset `0x42C`. The compiler recognized that both computations use the same `0.1f` value and stored it only once to save space. Both `vldr s11, [pc, #offset]` instructions resolve to address `0x1000042C`. So patching that single 4-byte value changes the scaling factor for both humidity and temperature simultaneously. - -3. **What is the IEEE-754 encoding of 0.5f? If the raw sensor decimal reading was 8, what would the computed value be with 0.5f instead of 0.1f?** - 0.5f in IEEE-754: sign=0, exponent=126 (`0x7E`), mantissa=0. Hex = `0x3F000000`. Little-endian bytes: `00 00 00 3F`. With a raw decimal reading of 8: `8 × 0.5 = 4.0`. So if the integer part was 24, the result would be `24 + 4.0 = 28.0°C` instead of `24 + 0.8 = 24.8°C`. - -4. **Could you achieve the same inversion by patching the vfma instruction instead of the constant? What instruction change would work?** - Yes. You could change `vfma.f32` (fused multiply-add: d = d + a×b) to `vfms.f32` (fused multiply-subtract: d = d - a×b). This would compute `temperature = integer - (decimal × 0.1f)` instead of `integer + (decimal × 0.1f)`, achieving the same sign inversion on the decimal contribution. The instruction encoding difference between `vfma` and `vfms` is typically a single bit in the opcode. However, this approach is more complex than simply flipping the sign bit of the constant. diff --git a/WEEK09/WEEK09-02.md b/WEEK09/WEEK09-02.md deleted file mode 100644 index ac0e5cf..0000000 --- a/WEEK09/WEEK09-02.md +++ /dev/null @@ -1,136 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 9 -Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics - -### Non-Credit Practice Exercise 2: Invert the Temperature Reading - -#### Objective -Using GDB to locate the IEEE-754 scaling constant `0.1f` at file offset `0x42C`, patch it to `-0.1f` using a hex editor, and verify on hardware that the serial output now displays negative temperature values. - -#### Prerequisites -- Completed Week 9 tutorial (GDB, Ghidra, and hex editor sections) -- `0x001a_operators.elf` and `0x001a_operators.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with DHT11 sensor connected - -#### Task Description -The DHT11 driver uses a scaling constant of `0.1f` stored at address `0x1000042C` (file offset `0x42C`) to convert raw sensor data into human-readable values. By changing this constant to `-0.1f`, you will invert the decimal component of the temperature calculation, causing the reported temperature to drop. This exercise teaches IEEE-754 float encoding and how a single 4-byte patch can dramatically change sensor behavior. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x001a_operators.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Verify the Current Scaling Constant - -Examine the float constant at the known address: - -```gdb -(gdb) x/wx 0x1000042c -``` - -Output: -``` -0x1000042c: 0x3dcccccd -``` - -This is `0.1f` in IEEE-754 encoding (approximately — the repeating binary fraction makes it `0x3dcccccd`). - -##### Step 3: Understand the IEEE-754 Encoding - -**Current value (0.1f):** - -| Field | Bits | Value | -| -------- | ---------- | ----- | -| Sign | `0` | Positive | -| Exponent | `01111011` | 123 (biased) | -| Mantissa | `10011001100110011001101` | ~1.6 | - -**New value (-0.1f):** -- Flip only the sign bit (bit 31) from `0` to `1` -- `0x3dcccccd` → `0xbdcccccd` - -| Value | Hex | Little-Endian Bytes | -| ------ | ------------ | ------------------- | -| 0.1f | `0x3dcccccd` | `cd cc cc 3d` | -| -0.1f | `0xbdcccccd` | `cd cc cc bd` | - -> 💡 **Key insight:** To negate an IEEE-754 float, you only need to flip the most significant bit. In little-endian, this is the **last** byte — change `3d` to `bd`. - -##### Step 4: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators\build\0x001a_operators.bin` -2. Press **Ctrl+G** and enter offset: `42C` -3. You should see: `cd cc cc 3d` (or `cc cc cc 3d`) -4. Replace with: `cd cc cc bd` (or `cc cc cc bd`) - -> ⚠️ **Note:** The exact bytes may be `cc cc cc 3d` or `cd cc cc 3d` depending on compiler rounding. Just change the last byte from `3d` to `bd`. - -##### Step 5: Save and Convert - -1. Click **File** → **Save As** → `0x001a_operators-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators -python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 6: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the serial monitor:** -- All operator values remain unchanged (50, 5, 0, 0, 12, 11) -- Temperature should now display with an inverted decimal component -- Humidity will also be affected (same constant is shared) - -#### Expected Output - -After completing this exercise, you should be able to: -- Decode and encode IEEE-754 floating-point values -- Understand that flipping one bit (sign bit) negates a float -- Patch floating-point constants in compiled binaries -- Predict how a constant change propagates through calculations - -#### Questions for Reflection - -###### Question 1: Why does changing one byte (`3d` → `bd`) negate the entire float value? What does the sign bit (bit 31) control in IEEE-754? - -###### Question 2: The scaling constant `0.1f` is used by BOTH the humidity and temperature `vfma.f32` instructions. Why does patching this single constant affect both readings? - -###### Question 3: If you wanted to change the constant to `0.5f` (`0x3f000000`, little-endian `00 00 00 3f`) instead of `-0.1f`, how would the temperature reading change? If the raw decimal part is 8, what would the new output be? - -###### Question 4: Could you achieve negative temperature by patching the `vfma.f32` instruction itself instead of the constant? What instruction might you replace it with? - -#### Tips and Hints -- IEEE-754 sign bit is the MSB (bit 31) — `0` = positive, `1` = negative -- In little-endian, the sign bit is in the **last** (highest address) byte -- Use an online IEEE-754 converter to verify your understanding -- If the output looks completely wrong (NaN, inf), you may have changed the wrong byte — start over with a fresh copy of the `.bin` file diff --git a/WEEK09/WEEK09-03-S.md b/WEEK09/WEEK09-03-S.md deleted file mode 100644 index f0fa0d1..0000000 --- a/WEEK09/WEEK09-03-S.md +++ /dev/null @@ -1,74 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 9 -Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics - -### Non-Credit Practice Exercise 3 Solution: Add a Fixed Temperature Offset - -#### Answers - -##### Two Patches Required - -This exercise requires **two** simultaneous patches to add a fixed +10°C offset to every temperature reading. - -##### Patch 1: Instruction at Offset 0x414 - -Change `vfma.f32` to `vadd.f32`: - -| Item | Original | Patched | -|------------|------------------------------|------------------------------| -| Instruction | vfma.f32 s15, s13, s11 | vadd.f32 s15, s15, s11 | -| Encoding | e6 ee a5 7a | b4 ee a5 7a | -| Operation | s15 = s15 + (s13 × s11) | s15 = s15 + s11 | - -``` -Offset 0x414: -Before: E6 EE A5 7A (vfma.f32) -After: B4 EE A5 7A (vadd.f32) -``` - -Only the first two bytes change: `E6 EE` → `B4 EE`. - -##### Patch 2: Constant at Offset 0x42C - -Change the constant from 0.1f to 10.0f: - -| Value | IEEE-754 Hex | Little-Endian Bytes | -|--------|--------------|--------------------| -| 0.1f | 0x3DCCCCCD | CD CC CC 3D | -| 10.0f | 0x41200000 | 00 00 20 41 | - -``` -Offset 0x42C: -Before: CD CC CC 3D (0.1f) -After: 00 00 20 41 (10.0f) -``` - -##### Why Both Patches Are Needed - -- **Original:** `vfma.f32` computes `temp = integer + (decimal × 0.1f)` — fractional scaling -- **After both patches:** `vadd.f32` computes `temp = integer + 10.0f` — fixed offset addition -- **If only constant changed:** `vfma.f32` would compute `temp = integer + (decimal × 10.0f)` — amplified decimal, not a fixed offset - -##### Serial Output After Patch - -``` -Humidity: 51.0%, Temperature: 34.0°C -``` - -(Original 24.0°C + 10.0°C offset = 34.0°C) - -#### Reflection Answers - -1. **Why are both patches needed? What would happen if you only changed the constant to 10.0f but left vfma unchanged?** - If you only changed `0.1f` to `10.0f` but left `vfma.f32`, the computation would be `temp = integer + (decimal × 10.0f)`. For a reading of 24.5°C (integer=24, decimal=5): result = 24 + (5 × 10.0) = 74.0°C — wildly incorrect. The `vfma` instruction multiplies two operands and adds, so the constant serves as a multiplier for the decimal part. By changing to `vadd.f32`, we eliminate the multiplication entirely and just add the constant directly to the integer, giving `24 + 10.0 = 34.0°C`. - -2. **The humidity vfma instruction at 0x410 was NOT changed. Both vfma instructions (0x410 and 0x414) load the same 0.1f constant from 0x42C. With the constant now 10.0f, what happens to the humidity computation?** - The humidity `vfma` at `0x410` now computes `hum = integer + (decimal × 10.0f)`. If the humidity decimal part is 0 (e.g., raw humidity = 51.0%), then `51 + (0 × 10.0) = 51.0%` — unchanged. But if the decimal part is non-zero (e.g., raw = 51.3%, decimal=3), the result would be `51 + (3 × 10.0) = 81.0%` — grossly incorrect. The DHT11 sensor's humidity decimal is often 0, so you might not notice the bug immediately, but it's a latent defect. - -3. **If you wanted to add a 10°C offset to temperature WITHOUT affecting humidity, what additional patch(es) would you need?** - You would need to ensure humidity still uses the original `0.1f` scaling. Options: (1) Also change the humidity `vfma` at `0x410` to `vadd.f32` and create a separate literal pool entry with `0.1f` for it — but this requires finding free space. (2) More practically, place a second copy of `0.1f` (`CD CC CC 3D`) in unused space in the binary, and redirect the humidity `vldr` instruction's PC-relative offset to point to that new location instead of `0x42C`. (3) Alternatively, NOP out the humidity `vfma` entirely if the decimal contribution is negligible. - -4. **Why do only the first 2 bytes differ between vfma and vadd? What do the last 2 bytes encode?** - In the ARM VFPv4 encoding, the first two bytes (`E6 EE` vs `B4 EE`) contain the **opcode** that distinguishes the operation type (fused multiply-add vs addition). The last two bytes (`A5 7A`) encode the **operand registers**: the source and destination VFP registers (s15, s13, s11). Since both instructions operate on the same registers, the operand encoding is identical. Only the operation code changes — this is a characteristic of the ARM instruction set where opcode and operand fields are cleanly separated. diff --git a/WEEK09/WEEK09-03.md b/WEEK09/WEEK09-03.md deleted file mode 100644 index 7060ce6..0000000 --- a/WEEK09/WEEK09-03.md +++ /dev/null @@ -1,152 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 9 -Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics - -### Non-Credit Practice Exercise 3: Add a Fixed Temperature Offset - -#### Objective -Patch both the `vfma.f32` instruction at file offset `0x414` and the scaling constant at `0x42C` to replace the multiply-add with a simple add of `10.0f`, causing every temperature reading to be increased by exactly 10°C, and verify on hardware. - -#### Prerequisites -- Completed Week 9 tutorial and Exercise 2 -- `0x001a_operators.elf` and `0x001a_operators.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with DHT11 sensor connected - -#### Task Description -The temperature calculation uses `vfma.f32 s15, s13, s11` which computes `s15 = s15 + (s13 × s11)` where `s11 = 0.1f`. You will make TWO patches: (1) change the `vfma.f32` instruction to `vadd.f32` so it performs addition instead of multiply-add, and (2) change the constant from `0.1f` to `10.0f`. The result: every temperature reading will have 10°C added to it. This exercise teaches ARM floating-point instruction encoding and multi-site patching. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x001a_operators.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Verify the Current Instructions - -Examine the floating-point instructions near the temperature calculation: - -```gdb -(gdb) x/4bx 0x10000414 -``` - -Output: -``` -0x10000414: 0xe6 0xee 0xa5 0x7a -``` - -This is `vfma.f32 s15, s13, s11` — the temperature fused multiply-add. - -##### Step 3: Verify the Current Constant - -```gdb -(gdb) x/wx 0x1000042c -``` - -Output: -``` -0x1000042c: 0x3dcccccd -``` - -This is `0.1f`. - -##### Step 4: Understand the Two Patches - -**Patch 1 — Instruction at offset `0x414`:** - -| Original | Bytes (LE) | New | Bytes (LE) | -| --------------------------- | --------------- | --------------------------- | --------------- | -| `vfma.f32 s15, s13, s11` | `e6 ee a5 7a` | `vadd.f32 s15, s15, s11` | `b4 ee a5 7a` | - -> 🔍 Only the first two bytes change: `e6 ee` → `b4 ee`. The operand bytes `a5 7a` stay the same. - -**Patch 2 — Constant at offset `0x42C`:** - -| Original | Hex | LE Bytes | New | Hex | LE Bytes | -| -------- | ------------ | --------------- | ----- | ------------ | --------------- | -| 0.1f | `0x3dcccccd` | `cd cc cc 3d` | 10.0f | `0x41200000` | `00 00 20 41` | - -##### Step 5: Patch the Instruction with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators\build\0x001a_operators.bin` -2. Press **Ctrl+G** and enter offset: `414` -3. You should see: `e6 ee a5 7a` -4. Change the first two bytes: `e6 ee` → `b4 ee` -5. Result should be: `b4 ee a5 7a` - -##### Step 6: Patch the Constant with HxD - -1. Press **Ctrl+G** and enter offset: `42C` -2. You should see: `cd cc cc 3d` (or `cc cc cc 3d`) -3. Replace all 4 bytes with: `00 00 20 41` - -##### Step 7: Save and Convert - -1. Click **File** → **Save As** → `0x001a_operators-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators -python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 8: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the serial monitor:** -- All operator values remain unchanged (50, 5, 0, 0, 12, 11) -- Temperature should now read approximately **10°C higher** than the real temperature -- Humidity will also be affected (the constant is shared) - -> ⚠️ **Note:** Since the constant at `0x42C` is shared between the humidity `vfma.f32` at `0x410` and the temperature one at `0x414`, changing it to `10.0f` will also affect humidity. The humidity instruction still uses `vfma` (multiply-add), so it will compute `humidity_int + (decimal × 10.0)` — producing very large humidity values. - -#### Expected Output - -After completing this exercise, you should be able to: -- Distinguish between `vfma.f32` (multiply-add) and `vadd.f32` (add) encodings -- Perform multi-site patches in a single binary -- Understand how shared constants affect multiple code paths -- Predict the side effects of patching shared data - -#### Questions for Reflection - -###### Question 1: Why did we need to change BOTH the instruction AND the constant? What would happen if you only changed the constant to `10.0f` but left the `vfma.f32` instruction unchanged? - -###### Question 2: The humidity `vfma.f32` at offset `0x410` was NOT changed to `vadd`. With the constant now set to `10.0f`, what formula does the humidity instruction compute? If the raw humidity integer is 51 and decimal is 0, what value would it display? - -###### Question 3: If you wanted to add 10°C to temperature WITHOUT affecting humidity, you would need to change both instructions. What bytes would you write at offset `0x410` to change the humidity `vfma` to `vadd` as well, and what constant would preserve the original humidity calculation? - -###### Question 4: The `vadd.f32 s15, s15, s11` encoding is `b4 ee a5 7a`. In the original `vfma.f32 s15, s13, s11` (`e6 ee a5 7a`), why do only the first two bytes differ? What do those bytes encode? - -#### Tips and Hints -- Make BOTH patches before saving — if you only patch one, the results will be wrong -- The `vfma` → `vadd` change only affects the first two bytes of the 4-byte instruction -- `10.0f` in IEEE-754 is `0x41200000` — memorize this common value -- Always start with a fresh copy of the `.bin` file if you need to redo the exercise -- Compare the original and hacked serial output side by side to verify only temperature changed as expected diff --git a/WEEK09/WEEK09-04-S.md b/WEEK09/WEEK09-04-S.md deleted file mode 100644 index b58ca2e..0000000 --- a/WEEK09/WEEK09-04-S.md +++ /dev/null @@ -1,72 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 9 -Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics - -### Non-Credit Practice Exercise 4 Solution: Find All printf Format Strings - -#### Answers - -##### Complete String Catalog - -| # | String | Type | Used By | -|---|-----------------------------------------------------|---------------|-------------| -| 1 | `"arithmetic_operator: %d\r\n"` | Format string | printf #1 | -| 2 | `"increment_operator: %d\r\n"` | Format string | printf #2 | -| 3 | `"relational_operator: %d\r\n"` | Format string | printf #3 | -| 4 | `"logical_operator: %d\r\n"` | Format string | printf #4 | -| 5 | `"bitwise_operator: %d\r\n"` | Format string | printf #5 | -| 6 | `"assignment_operator: %d\r\n"` | Format string | printf #6 | -| 7 | `"Humidity: %.1f%%, Temperature: %.1f°C\r\n"` | Format string | printf #7 | -| 8 | `"DHT11 read failed\r\n"` | Plain string | puts | - -##### Format Specifier Analysis - -| Specifier | Meaning | Used In | -|-----------|------------------------------------------|---------------| -| `%d` | Signed decimal integer | Strings 1–6 | -| `%.1f` | Float with 1 decimal place | String 7 | -| `%%` | Literal percent sign (escaped) | String 7 | -| `\r\n` | Carriage return + line feed (0x0D 0x0A) | All strings | -| `°C` | UTF-8 degree symbol + C (0xC2 0xB0 0x43)| String 7 | - -##### Expected Operator Output Values - -| Operator | Expression | Value | Explanation | -|-----------------------|------------------------|-------|------------------------------------| -| arithmetic_operator | 5 × 10 | 50 | Multiplication | -| increment_operator | x++ (x=5) | 5 | Post-increment returns old value | -| relational_operator | 6 > 10 | 0 | False | -| logical_operator | (6>10) && (10>6) | 0 | Short-circuit: first operand false | -| bitwise_operator | 6 << 1 | 12 | Left shift = multiply by 2 | -| assignment_operator | 6 + 5 | 11 | Addition assignment | - -##### GDB Search Commands - -```gdb -(gdb) x/20s 0x10003e00 -(gdb) x/50s 0x10003d00 -``` - -##### Special Byte Sequences in Strings - -| Sequence | Bytes | Meaning | -|----------|------------|---------------------| -| `\r\n` | 0x0D 0x0A | CRLF line ending | -| `%%` | 0x25 0x25 | Literal % character | -| `°` | 0xC2 0xB0 | UTF-8 degree symbol | - -#### Reflection Answers - -1. **The humidity/temperature format string contains %%. What would happen if you patched one of the % characters to a different character (e.g., changed %% to %,)?** - The `%%` escape produces a literal `%` in the output. If you change it to `%,` (bytes `25 2C`), `printf` would interpret `%,` as the start of a format specifier where `,` is the conversion character. Since `,` is not a valid `printf` conversion specifier, the behavior is **undefined** — most implementations would either print garbage, skip it, or consume the next argument from the stack incorrectly. This could corrupt the remaining output or even crash if `printf` tries to read a non-existent argument. - -2. **If you changed "arithmetic_operator" to "hacked_operator__" (same length) in the binary, what would the serial output look like? Would the computed value change?** - The serial output would show `hacked_operator__: 50` instead of `arithmetic_operator: 50`. The **computed value (50) would not change** — it's determined by the actual multiplication instruction in the code, not by the format string. The format string is just a label for display purposes. The `%d` specifier still reads the same `r1` register value (50) passed as the second argument to `printf`. - -3. **What happens if you make a format string 1 byte longer (e.g., add a character)? Where would the extra byte be stored?** - The extra byte would overwrite the **null terminator** (`0x00`) of the current string, and the byte after that is the first byte of the next consecutive string in `.rodata`. This effectively merges the two strings: `printf` would continue reading past the intended end into the next string's data until it finds another `0x00`. The output would include garbage characters from the adjacent string. If the adjacent data happens to contain `%` followed by a valid specifier, `printf` might try to read additional arguments from the stack, potentially causing a crash or information leak. - -4. **The "DHT11 read failed" message uses puts instead of printf. Why would the compiler choose puts over printf for this particular string?** - The compiler (with optimizations enabled) recognizes that `printf("DHT11 read failed\r\n")` has **no format specifiers** — it's a plain string with no `%d`, `%s`, `%f`, etc. Since no formatting is needed, the compiler optimizes it to `puts("DHT11 read failed")` (which automatically appends a newline). This is a common GCC optimization (`-fprintf-return-value`) because `puts` is simpler and faster than `printf` — it doesn't need to parse the format string looking for specifiers. The `\r\n` may be handled slightly differently depending on the implementation, but the key insight is that the compiler automatically selects the more efficient function. diff --git a/WEEK09/WEEK09-04.md b/WEEK09/WEEK09-04.md deleted file mode 100644 index 661833d..0000000 --- a/WEEK09/WEEK09-04.md +++ /dev/null @@ -1,140 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 9 -Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics - -### Non-Credit Practice Exercise 4: Find All printf Format Strings - -#### Objective -Systematically search through the `0x001a_operators` binary using GDB and a hex editor to locate every `printf` format string, catalog their addresses, file offsets, contents, and purposes, and gain experience identifying data structures in compiled binaries. - -#### Prerequisites -- Completed Week 9 tutorial (GDB section) -- `0x001a_operators.elf` and `0x001a_operators.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) - -#### Task Description -The `0x001a_operators` program contains multiple `printf` calls, each with a format string like `"arithmetic_operator: %d\r\n"`. These strings are stored in the `.rodata` section of flash memory. You will use GDB to trace each `printf` call to its format string argument, then verify the strings in HxD. You will also search for strings that are NOT directly referenced by `printf` (like the `"DHT11 read failed"` error message). This exercise builds your ability to map out all human-readable data in a binary. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x001a_operators.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Disassemble Main to Find printf Calls - -```gdb -(gdb) x/60i 0x10000234 -``` - -Look for repeated patterns of: -``` -ldr r0, [pc, #offset] ; Load format string address -movs r1, #value ; Load the value to print -bl printf ; Call printf -``` - -Each `ldr r0` before a `bl printf` loads the format string address from the literal pool. - -##### Step 3: Examine the Literal Pool - -Find the literal pool after the loop branch (typically after the `b.n` instruction). Examine the word values: - -```gdb -(gdb) x/10wx -``` - -Each word that falls in the `0x10003xxx` range is likely a pointer to a string in `.rodata`. - -##### Step 4: Read Each Format String - -For each address found in the literal pool, use `x/s` to read the string: - -```gdb -(gdb) x/s -``` - -Document each one. You should find at least these format strings: -- `"arithmetic_operator: %d\r\n"` -- `"increment_operator: %d\r\n"` -- `"relational_operator: %d\r\n"` -- `"logical_operator: %d\r\n"` -- `"bitwise_operator: %d\r\n"` -- `"assignment_operator: %d\r\n"` -- `"Humidity: %.1f%%, Temperature: %.1f°C\r\n"` -- `"DHT11 read failed"` - -##### Step 5: Build a String Catalog - -Fill in a table like this (addresses will vary — use the ones you find): - -| Address | File Offset | String Content | Used By | -| -------------- | ----------- | --------------------------------------- | ----------- | -| `0x10003xxx` | `0x3xxx` | `"arithmetic_operator: %d\r\n"` | printf #1 | -| `0x10003xxx` | `0x3xxx` | `"increment_operator: %d\r\n"` | printf #2 | -| `0x10003xxx` | `0x3xxx` | `"relational_operator: %d\r\n"` | printf #3 | -| `0x10003xxx` | `0x3xxx` | `"logical_operator: %d\r\n"` | printf #4 | -| `0x10003xxx` | `0x3xxx` | `"bitwise_operator: %d\r\n"` | printf #5 | -| `0x10003xxx` | `0x3xxx` | `"assignment_operator: %d\r\n"` | printf #6 | -| `0x10003xxx` | `0x3xxx` | `"Humidity: %.1f%%, Temperature: %.1f°C\r\n"` | printf #7 | -| `0x10003xxx` | `0x3xxx` | `"DHT11 read failed"` | puts | - -##### Step 6: Verify in HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators\build\0x001a_operators.bin` -2. For each string, press **Ctrl+G** and enter the file offset -3. Verify you can read the ASCII text in the right panel -4. Note how the strings are stored consecutively in memory, each terminated by `0x00` - -##### Step 7: Search for Hidden Strings - -Scroll through the `.rodata` region in HxD (typically starting around offset `0x3000`+) and look for any ASCII text in the right panel that you didn't find via `printf` calls. Library functions may have their own strings. - -#### Expected Output - -After completing this exercise, you should be able to: -- Trace `printf` calls to their format string arguments using GDB -- Read string addresses from literal pools -- Build a complete catalog of strings in a binary -- Navigate to specific offsets in a hex editor to verify string data - -#### Questions for Reflection - -###### Question 1: The format string `"Humidity: %.1f%%, Temperature: %.1f°C\r\n"` uses `%%` to print a literal percent sign. What would happen if you patched one of the `%` characters to a space (`0x20`)? - -###### Question 2: If you patched the string `"arithmetic_operator: %d\r\n"` to `"HACKED_OPERATOR!: %d\r\n"` (same length, 27 characters + null), what would the serial output show? Would the actual computed value change? - -###### Question 3: The strings are stored consecutively in `.rodata`. If you made `"arithmetic_operator: %d\r\n"` one byte longer, which string would be corrupted and how? - -###### Question 4: Why does the compiler use `puts` instead of `printf` for `"DHT11 read failed"`? What's the difference between the two functions for strings with no format specifiers? - -#### Tips and Hints -- Format strings always start with a printable ASCII character — look for bytes in the `0x20`-`0x7E` range -- The `\r\n` at the end of format strings shows up as bytes `0x0D 0x0A` -- Use HxD's **Search** → **Find** (Ctrl+F) with "Text string" mode to search for known text like "arithmetic" -- Strings in `.rodata` are typically 4-byte aligned, so there may be padding bytes (`0x00`) between them -- The `°` character in "°C" is a multi-byte UTF-8 sequence (`0xC2 0xB0`), not a single byte diff --git a/WEEK09/WEEK09.md b/WEEK09/WEEK09.md index 37b2550..9b32a2a 100644 --- a/WEEK09/WEEK09.md +++ b/WEEK09/WEEK09.md @@ -1,6 +1,6 @@ -# Week 9: Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics. +?# Week 9: Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics. -## 🎯 What You'll Learn This Week +## ? What You'll Learn This Week By the end of this tutorial, you will be able to: - Understand all six types of C operators (arithmetic, increment, relational, logical, bitwise, assignment) @@ -13,7 +13,7 @@ By the end of this tutorial, you will be able to: --- -## 📚 Part 1: Understanding C Operators +## Part 1: Understanding C Operators ### What Are Operators? @@ -32,7 +32,7 @@ By the end of this tutorial, you will be able to: --- -## 📚 Part 2: Arithmetic Operators +## Part 2: Arithmetic Operators ### Basic Math in C @@ -45,23 +45,23 @@ int result = x * y; // result = 50 ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Arithmetic Operators │ -│ │ -│ Operator Example Result Description │ -│ ───────────────────────────────────────────────────────────── │ -│ + 5 + 10 15 Addition │ -│ - 10 - 5 5 Subtraction │ -│ * 5 * 10 50 Multiplication │ -│ / 10 / 5 2 Division │ -│ % 10 % 3 1 Modulus (remainder) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Arithmetic Operators | +| | +| Operator Example Result Description | +| ------------------------------------------------------------- | +| + 5 + 10 15 Addition | +| - 10 - 5 5 Subtraction | +| * 5 * 10 50 Multiplication | +| / 10 / 5 2 Division | +| % 10 % 3 1 Modulus (remainder) | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 3: Increment and Decrement Operators +## Part 3: Increment and Decrement Operators ### Pre-Increment vs Post-Increment @@ -77,29 +77,29 @@ int b = ++x; // Pre-increment: x becomes 7, then b = 7 | Type | Syntax | When Value Changes | Example Result | | ------------------ | ------ | --------------------- | -------------------- | -| **Post-increment** | `x++` | AFTER the expression | `a = x++` → a=5, x=6 | -| **Pre-increment** | `++x` | BEFORE the expression | `b = ++x` → x=7, b=7 | +| **Post-increment** | `x++` | AFTER the expression | `a = x++` -> a=5, x=6 | +| **Pre-increment** | `++x` | BEFORE the expression | `b = ++x` -> x=7, b=7 | ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Post-Increment (x++) │ -│ │ -│ int x = 5; │ -│ int result = x++; │ -│ │ -│ Step 1: result = x (result gets 5) │ -│ Step 2: x = x + 1 (x becomes 6) │ -│ │ -│ Final: result = 5, x = 6 │ -│ │ -│ Think of it as: "Use first, THEN increment" │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Post-Increment (x++) | +| | +| int x = 5; | +| int result = x++; | +| | +| Step 1: result = x (result gets 5) | +| Step 2: x = x + 1 (x becomes 6) | +| | +| Final: result = 5, x = 6 | +| | +| Think of it as: "Use first, THEN increment" | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 4: Relational Operators +## Part 4: Relational Operators ### Comparing Values @@ -112,24 +112,24 @@ bool result = (x > y); // false, because 6 is NOT greater than 10 ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Relational Operators │ -│ │ -│ Operator Example Result Description │ -│ ───────────────────────────────────────────────────────────── │ -│ > 6 > 10 false Greater than │ -│ < 6 < 10 true Less than │ -│ >= 6 >= 6 true Greater than or equal │ -│ <= 6 <= 10 true Less than or equal │ -│ == 6 == 10 false Equal to │ -│ != 6 != 10 true Not equal to │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Relational Operators | +| | +| Operator Example Result Description | +| ------------------------------------------------------------- | +| > 6 > 10 false Greater than | +| < 6 < 10 true Less than | +| >= 6 >= 6 true Greater than or equal | +| <= 6 <= 10 true Less than or equal | +| == 6 == 10 false Equal to | +| != 6 != 10 true Not equal to | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 5: Logical Operators +## Part 5: Logical Operators ### Combining Conditions @@ -142,33 +142,33 @@ bool result = (x > y) && (y > x); // false AND true = false ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Logical Operators │ -│ │ -│ Operator Name Example Result │ -│ ───────────────────────────────────────────────────────────── │ -│ && AND true && true true │ -│ && AND true && false false │ -│ || OR true || false true │ -│ || OR false || false false │ -│ ! NOT !true false │ -│ │ -│ Truth Table for AND (&&): │ -│ ┌───────┬───────┬────────┐ │ -│ │ A │ B │ A && B │ │ -│ ├───────┼───────┼────────┤ │ -│ │ false │ false │ false │ │ -│ │ false │ true │ false │ │ -│ │ true │ false │ false │ │ -│ │ true │ true │ true │ │ -│ └───────┴───────┴────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Logical Operators | +| | +| Operator Name Example Result | +| ------------------------------------------------------------- | +| && AND true && true true | +| && AND true && false false | +| || OR true || false true | +| || OR false || false false | +| ! NOT !true false | +| | +| Truth Table for AND (&&): | +| +-------+-------+--------+ | +| | A | B | A && B | | +| +-------+-------+--------+ | +| | false | false | false | | +| | false | true | false | | +| | true | false | false | | +| | true | true | true | | +| +-------+-------+--------+ | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 6: Bitwise Operators +## Part 6: Bitwise Operators ### Manipulating Individual Bits @@ -180,21 +180,21 @@ int result = x << 1; // Shift left by 1: 0b00001100 = 12 ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Bitwise Left Shift (<<) │ -│ │ -│ x = 6 = 0b00000110 │ -│ │ -│ x << 1 means "shift all bits LEFT by 1 position" │ -│ │ -│ Before: 0 0 0 0 0 1 1 0 (6) │ -│ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ │ -│ After: 0 0 0 0 1 1 0 0 (12) │ -│ │ -│ Each left shift DOUBLES the value! │ -│ 6 << 1 = 12 (same as 6 * 2) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Bitwise Left Shift (<<) | +| | +| x = 6 = 0b00000110 | +| | +| x << 1 means "shift all bits LEFT by 1 position" | +| | +| Before: 0 0 0 0 0 1 1 0 (6) | +| ? ? ? ? ? ? ? ? | +| After: 0 0 0 0 1 1 0 0 (12) | +| | +| Each left shift DOUBLES the value! | +| 6 << 1 = 12 (same as 6 * 2) | +| | ++-----------------------------------------------------------------+ ``` **Common Bitwise Operators:** @@ -210,7 +210,7 @@ int result = x << 1; // Shift left by 1: 0b00001100 = 12 --- -## 📚 Part 7: Assignment Operators +## Part 7: Assignment Operators ### Shorthand for Math + Assign @@ -222,69 +222,69 @@ x += 5; // Same as: x = x + 5; Result: x = 11 ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Compound Assignment Operators │ -│ │ -│ Operator Example Equivalent To Result (if x=6) │ -│ ───────────────────────────────────────────────────────────── │ -│ += x += 5 x = x + 5 x = 11 │ -│ -= x -= 2 x = x - 2 x = 4 │ -│ *= x *= 3 x = x * 3 x = 18 │ -│ /= x /= 2 x = x / 2 x = 3 │ -│ %= x %= 4 x = x % 4 x = 2 │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Compound Assignment Operators | +| | +| Operator Example Equivalent To Result (if x=6) | +| ------------------------------------------------------------- | +| += x += 5 x = x + 5 x = 11 | +| -= x -= 2 x = x - 2 x = 4 | +| *= x *= 3 x = x * 3 x = 18 | +| /= x /= 2 x = x / 2 x = 3 | +| %= x %= 4 x = x % 4 x = 2 | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 8: Understanding the DHT11 Sensor +## Part 8: Understanding the DHT11 Sensor ### What is the DHT11? The **DHT11** is a low-cost digital temperature and humidity sensor. It uses a single wire for communication (plus power and ground). ``` -┌─────────────────────────────────────────────────────────────────┐ -│ DHT11 Pinout │ -│ │ -│ ┌─────────────┐ │ -│ │ DHT11 │ │ -│ │ ┌─────┐ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ └─────┘ │ │ -│ │ 1 2 3 4 │ │ -│ └──┬──┬──┬──┬─┘ │ -│ │ │ │ │ │ -│ VCC DATA NC GND │ -│ │ -│ Pin 1: VCC (3.3V or 5V) │ -│ Pin 2: DATA (connect to GPIO) │ -│ Pin 3: Not Connected │ -│ Pin 4: GND (Ground) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| DHT11 Pinout | +| | +| +-------------+ | +| | DHT11 | | +| | +-----+ | | +| | | | | | +| | | | | | +| | +-----+ | | +| | 1 2 3 4 | | +| +--+--+--+--+-+ | +| | | | | | +| VCC DATA NC GND | +| | +| Pin 1: VCC (3.3V or 5V) | +| Pin 2: DATA (connect to GPIO) | +| Pin 3: Not Connected | +| Pin 4: GND (Ground) | +| | ++-----------------------------------------------------------------+ ``` ### DHT11 Specifications | Parameter | Range | Accuracy | | --------------- | ------------ | -------- | -| **Humidity** | 20% - 90% RH | ±5% RH | -| **Temperature** | 0°C - 50°C | ±2°C | +| **Humidity** | 20% - 90% RH | ?5% RH | +| **Temperature** | 0 deg C - 50 deg C | ?2 deg C | ### How DHT11 Communication Works The DHT11 uses a custom one-wire protocol: 1. **Host sends start signal** - Pull data line LOW for 18ms -2. **DHT11 responds** - Pulls line LOW for 80µs, then HIGH for 80µs +2. **DHT11 responds** - Pulls line LOW for 80 us, then HIGH for 80 us 3. **Data transmission** - 40 bits sent (8 humidity int, 8 humidity decimal, 8 temp int, 8 temp decimal, 8 checksum) --- -## 📚 Part 9: Understanding Pointers (Quick Review) +## Part 9: Understanding Pointers (Quick Review) ### The & Operator (Address-Of) @@ -295,32 +295,32 @@ float hum, temp; // Pass ADDRESSES to the function so it can modify our variables if (dht11_read(&hum, &temp)) { - printf("Humidity: %.1f%%, Temperature: %.1f°C\n", hum, temp); + printf("Humidity: %.1f%%, Temperature: %.1f?C\n", hum, temp); } ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Passing by Reference │ -│ │ -│ Stack Memory │ -│ ┌────────────────────────────┐ │ -│ │ Address 0x20000008: hum │◄─── &hum (passed to function) │ -│ │ Value: 51.0 │ │ -│ ├────────────────────────────┤ │ -│ │ Address 0x2000000C: temp │◄─── &temp (passed to function) │ -│ │ Value: 23.8 │ │ -│ └────────────────────────────┘ │ -│ │ -│ dht11_read() receives the ADDRESSES, so it can write │ -│ new values directly into hum and temp! │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Passing by Reference | +| | +| Stack Memory | +| +----------------------------+ | +| | Address 0x20000008: hum |?--- &hum (passed to function) | +| | Value: 51.0 | | +| +----------------------------+ | +| | Address 0x2000000C: temp |?--- &temp (passed to function) | +| | Value: 23.8 | | +| +----------------------------+ | +| | +| dht11_read() receives the ADDRESSES, so it can write | +| new values directly into hum and temp! | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 10: Setting Up Your Environment +## Part 10: Setting Up Your Environment ### Prerequisites @@ -344,43 +344,43 @@ Connect your DHT11 like this: | GND | GND | ``` -┌─────────────────────────────────────────────────────────────────┐ -│ DHT11 Wiring │ -│ │ -│ Pico 2 DHT11 Sensor │ -│ ┌──────────┐ ┌──────────┐ │ -│ │ │ │ │ │ -│ │ GPIO 4 │────────── DATA ──────►│ DATA │ │ -│ │ │ │ │ │ -│ │ 3.3V │────────── VCC ───────►│ VCC │ │ -│ │ │ │ │ │ -│ │ GND │────────── GND ───────►│ GND │ │ -│ │ │ │ │ │ -│ └──────────┘ └──────────┘ │ -│ │ -│ Note: Some DHT11 modules have a built-in pull-up resistor. │ -│ If yours doesn't, add a 10K resistor between DATA and VCC. │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| DHT11 Wiring | +| | +| Pico 2 DHT11 Sensor | +| +----------+ +----------+ | +| | | | | | +| | GPIO 4 |---------- DATA ------?| DATA | | +| | | | | | +| | 3.3V |---------- VCC -------?| VCC | | +| | | | | | +| | GND |---------- GND -------?| GND | | +| | | | | | +| +----------+ +----------+ | +| | +| Note: Some DHT11 modules have a built-in pull-up resistor. | +| If yours doesn't, add a 10K resistor between DATA and VCC. | +| | ++-----------------------------------------------------------------+ ``` ### Project Structure ``` Embedded-Hacking/ -├── 0x001a_operators/ -│ ├── build/ -│ │ ├── 0x001a_operators.uf2 -│ │ └── 0x001a_operators.bin -│ ├── main/ -│ │ └── 0x001a_operators.c -│ └── dht11.h -└── uf2conv.py ++-- 0x001a_operators/ +| +-- build/ +| | +-- 0x001a_operators.uf2 +| | +-- 0x001a_operators.bin +| +-- main/ +| | +-- 0x001a_operators.c +| +-- dht11.h ++-- uf2conv.py ``` --- -## 🔬 Part 11: Hands-On Tutorial - The Operators Code +## ? Part 11: Hands-On Tutorial - The Operators Code ### Step 1: Review the Source Code @@ -417,7 +417,7 @@ int main(void) { float hum, temp; if (dht11_read(&hum, &temp)) { - printf("Humidity: %.1f%%, Temperature: %.1f°C\r\n", hum, temp); + printf("Humidity: %.1f%%, Temperature: %.1f?C\r\n", hum, temp); } else { printf("DHT11 read failed\r\n"); } @@ -432,20 +432,20 @@ int main(void) { Let's trace through what happens to `x`: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Variable x Through the Program │ -│ │ -│ Line │ x value │ Result │ -│ ──────────────────┼─────────┼───────────────────────────────── │ -│ int x = 5; │ 5 │ x initialized to 5 │ -│ x * y │ 5 │ arithmetic = 5 * 10 = 50 │ -│ x++ │ 5→6 │ increment = 5 (then x becomes 6) │ -│ x > y │ 6 │ relational = (6 > 10) = false │ -│ (x>y) && (y>x) │ 6 │ logical = false && true = false │ -│ x << 1 │ 6 │ bitwise = 6 << 1 = 12 │ -│ x += 5 │ 6→11 │ assignment = 6 + 5 = 11 │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Variable x Through the Program | +| | +| Line | x value | Result | +| ------------------+---------+--------------------------------- | +| int x = 5; | 5 | x initialized to 5 | +| x * y | 5 | arithmetic = 5 * 10 = 50 | +| x++ | 5->6 | increment = 5 (then x becomes 6) | +| x > y | 6 | relational = (6 > 10) = false | +| (x>y) && (y>x) | 6 | logical = false && true = false | +| x << 1 | 6 | bitwise = 6 << 1 = 12 | +| x += 5 | 6->11 | assignment = 6 + 5 = 11 | +| | ++-----------------------------------------------------------------+ ``` ### Step 3: Flash the Binary to Your Pico 2 @@ -467,25 +467,25 @@ relational_operator: 0 logical_operator: 0 bitwise_operator: 12 assignment_operator: 11 -Humidity: 51.0%, Temperature: 23.8°C +Humidity: 51.0%, Temperature: 23.8 deg C ``` **Understanding the Output:** | Variable | Value | Explanation | | ------------------- | ------ | --------------------------------------------- | -| arithmetic_operator | 50 | 5 × 10 = 50 | +| arithmetic_operator | 50 | 5 * 10 = 50 | | increment_operator | 5 | Post-increment returns value BEFORE increment | | relational_operator | 0 | 6 > 10 is false (0) | | logical_operator | 0 | false AND true = false (0) | | bitwise_operator | 12 | 6 (0b0110) << 1 = 12 (0b1100) | | assignment_operator | 11 | 6 + 5 = 11 | | Humidity | 51.0% | Real reading from DHT11 | -| Temperature | 23.8°C | Real reading from DHT11 | +| Temperature | 23.8 deg C | Real reading from DHT11 | --- -## 🔬 Part 12: Debugging with GDB +## ? Part 12: Debugging with GDB ### Step 5: Start OpenOCD (Terminal 1) @@ -493,7 +493,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" @@ -640,7 +640,7 @@ The program will loop, printing values to serial. --- -## 🔬 Part 13: Setting Up Ghidra for Analysis +## ? Part 13: Setting Up Ghidra for Analysis ### Step 17: Start Ghidra @@ -652,7 +652,7 @@ ghidraRun ### Step 18: Create a New Project -1. Click **File** → **New Project** +1. Click **File** -> **New Project** 2. Select **Non-Shared Project** 3. Click **Next** 4. Enter Project Name: `0x001a_operators` @@ -666,12 +666,12 @@ ghidraRun ### Step 20: Configure the Binary Format -**Click the three dots (…) next to "Language" and:** +**Click the three dots (...) next to "Language" and:** 1. Search for "Cortex" 2. Select **ARM Cortex 32 little endian default** 3. Click **OK** -**Click the "Options…" button and:** +**Click the "Options..." button and:** 1. Change **Block Name** to `.text` 2. Change **Base Address** to `10000000` 3. Click **OK** @@ -686,25 +686,25 @@ Wait for analysis to complete. --- -## 🔬 Part 14: Finding the Reset_Handler +## ? Part 14: Finding the Reset_Handler ### Step 22: Understand the Vector Table In ARM Cortex-M, the **vector table** is at the base of flash (0x10000000). The second entry (offset 4) contains the Reset_Handler address. ``` -┌─────────────────────────────────────────────────────────────────┐ -│ ARM Vector Table at 0x10000000 │ -│ │ -│ Offset Contents Description │ -│ ───────────────────────────────────────────────────────────── │ -│ 0x00 Initial SP value Stack pointer at reset │ -│ 0x04 Reset_Handler addr First code to execute │ -│ 0x08 NMI_Handler addr Non-maskable interrupt │ -│ 0x0C HardFault_Handler Hard fault handler │ -│ ... │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| ARM Vector Table at 0x10000000 | +| | +| Offset Contents Description | +| ------------------------------------------------------------- | +| 0x00 Initial SP value Stack pointer at reset | +| 0x04 Reset_Handler addr First code to execute | +| 0x08 NMI_Handler addr Non-maskable interrupt | +| 0x0C HardFault_Handler Hard fault handler | +| ... | +| | ++-----------------------------------------------------------------+ ``` ### Step 23: Read the Reset_Handler Address @@ -715,18 +715,18 @@ In ARM Cortex-M, the **vector table** is at the base of flash (0x10000000). The **Important:** This is **little-endian**, so we need to reverse the byte order! ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Little-Endian Byte Order │ -│ │ -│ In memory: 5d 01 00 10 │ -│ Reversed: 10 00 01 5d │ -│ As hex: 0x1000015d │ -│ │ -│ But wait! ARM uses the THUMB bit! │ -│ The lowest bit indicates Thumb mode (always set for Cortex-M) │ -│ Real address: 0x1000015d - 1 = 0x1000015c │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Little-Endian Byte Order | +| | +| In memory: 5d 01 00 10 | +| Reversed: 10 00 01 5d | +| As hex: 0x1000015d | +| | +| But wait! ARM uses the THUMB bit! | +| The lowest bit indicates Thumb mode (always set for Cortex-M) | +| Real address: 0x1000015d - 1 = 0x1000015c | +| | ++-----------------------------------------------------------------+ ``` ### Step 24: Navigate to Reset_Handler @@ -740,7 +740,7 @@ If Ghidra didn't automatically recognize this as a function: 1. Click on the address `0x1000015c` 2. Right-click and press `F` to create a function -3. Right-click → **Edit Function Signature** +3. Right-click -> **Edit Function Signature** 4. Change the name to `Reset_Handler` 5. Click **OK** @@ -749,17 +749,17 @@ If Ghidra didn't automatically recognize this as a function: The Reset_Handler typically calls three functions: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Reset_Handler Flow (crt0.S) │ -│ │ -│ Reset_Handler: │ -│ 1. Call some_init() ◄── Initialize hardware │ -│ 2. Call main() ◄── THIS IS WHAT WE WANT! │ -│ 3. Call exit() ◄── Never returns │ -│ │ -│ The MIDDLE function is main! │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Reset_Handler Flow (crt0.S) | +| | +| Reset_Handler: | +| 1. Call some_init() ?-- Initialize hardware | +| 2. Call main() ?-- THIS IS WHAT WE WANT! | +| 3. Call exit() ?-- Never returns | +| | +| The MIDDLE function is main! | +| | ++-----------------------------------------------------------------+ ``` Look at the end of Reset_Handler for three function calls. The middle one is `main`! @@ -767,13 +767,13 @@ Look at the end of Reset_Handler for three function calls. The middle one is `ma ### Step 27: Navigate to Main 1. Double-click on the middle function call (should be around `0x10000234`) -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `int main(void)` 4. Click **OK** --- -## 🔬 Part 15: Resolving Functions in Ghidra +## ? Part 15: Resolving Functions in Ghidra ### Step 28: Resolve stdio_init_all @@ -781,7 +781,7 @@ The first function call in main is `stdio_init_all`: 1. Find the call at approximately `0x10000238` 2. Double-click to navigate to the function -3. Right-click → **Edit Function Signature** +3. Right-click -> **Edit Function Signature** 4. Change to: `bool stdio_init_all(void)` 5. Click **OK** @@ -798,7 +798,7 @@ bl FUN_xxxxx ; dht11_init - The argument `4` is the GPIO pin number - We physically connected the DHT11 to GPIO 4! -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `void dht11_init(uint pin)` 3. Click **OK** @@ -807,7 +807,7 @@ bl FUN_xxxxx ; dht11_init Look for repeated function calls with string addresses: 1. Find a call like the one at `0x10000262` -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `int printf(char *format, ...)` 4. Check the **Varargs** checkbox 5. Click **OK** @@ -821,7 +821,7 @@ ldr r0, =0x7d0 ; 2000 milliseconds bl FUN_xxxxx ; sleep_ms ``` -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `void sleep_ms(uint ms)` 3. Click **OK** @@ -840,7 +840,7 @@ bl FUN_xxxxx ; dht11_read - `sp + 0xc` = address of `temp` variable - These are `float` pointers passed to the function -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `bool dht11_read(float *humidity, float *temperature)` 3. Click **OK** @@ -853,32 +853,32 @@ ldr r0, ="DHT11 read failed" bl FUN_xxxxx ; puts ``` -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `int puts(char *s)` 3. Click **OK** --- -## 🔬 Part 16: Understanding IEEE-754 Floating-Point +## ? Part 16: Understanding IEEE-754 Floating-Point ### What is IEEE-754? IEEE-754 is the standard for representing decimal numbers in binary. A 32-bit float is divided into three parts: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ IEEE-754 Single Precision (32-bit) Float │ -│ │ -│ ┌─────┬─────────────┬───────────────────────────────────────┐ │ -│ │ S │ Exponent │ Mantissa (Fraction) │ │ -│ │ 1 │ 8 bits │ 23 bits │ │ -│ └─────┴─────────────┴───────────────────────────────────────┘ │ -│ bit bits bits │ -│ 31 30-23 22-0 │ -│ │ -│ Value = (-1)^S × (1 + Mantissa) × 2^(Exponent - 127) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| IEEE-754 Single Precision (32-bit) Float | +| | +| +-----+-------------+---------------------------------------+ | +| | S | Exponent | Mantissa (Fraction) | | +| | 1 | 8 bits | 23 bits | | +| +-----+-------------+---------------------------------------+ | +| bit bits bits | +| 31 30-23 22-0 | +| | +| Value = (-1)^S * (1 + Mantissa) * 2^(Exponent - 127) | +| | ++-----------------------------------------------------------------+ ``` ### Example: Decoding 0x3dcccccc (0.1f) @@ -895,7 +895,7 @@ Let's decode the bytes `cc cc cc 3d`: 4. **Calculate value:** - Actual exponent: 123 - 127 = -4 - Mantissa value: 1.6 (approximately) - - Final value: 1.6 × 2^(-4) ≈ 0.1 + - Final value: 1.6 * 2^(-4) ? 0.1 ### Example: Encoding -1.0f as 0xbf800000 @@ -927,7 +927,7 @@ print(f"Bytes: {encoded.hex()}") # 0000 80bf --- -## 🔬 Part 17: Finding the Temperature Hack Point +## ? Part 17: Finding the Temperature Hack Point ### Step 34: Locate the dht11_read Function @@ -945,19 +945,19 @@ vfma.f32 s15, s13, s11 ; Fused multiply-add for temperature The constant `0.1` (at address `0x1000042c`) is loaded into register `s11` and used to scale the raw sensor readings. ``` -┌─────────────────────────────────────────────────────────────────┐ -│ DHT11 Scaling Calculation │ -│ │ -│ Raw sensor data: integer + decimal parts │ -│ Example: 238 (integer=23, decimal=8) │ -│ │ -│ Formula: result = integer + (decimal × 0.1) │ -│ 23.8 = 23 + (8 × 0.1) │ -│ │ -│ The vfma.f32 instruction does: s15 = s13 + (s11 × something) │ -│ Where s11 = 0.1f (our target!) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| DHT11 Scaling Calculation | +| | +| Raw sensor data: integer + decimal parts | +| Example: 238 (integer=23, decimal=8) | +| | +| Formula: result = integer + (decimal * 0.1) | +| 23.8 = 23 + (8 * 0.1) | +| | +| The vfma.f32 instruction does: s15 = s13 + (s11 * something) | +| Where s11 = 0.1f (our target!) | +| | ++-----------------------------------------------------------------+ ``` ### Step 36: Identify Key Offsets @@ -972,11 +972,11 @@ Make note of these offsets in the binary file: --- -## 🔬 Part 18: Manual Hacking in Ghidra +## ? Part 18: Manual Hacking in Ghidra ### Step 37: Open the Bytes Editor -1. Click **Window** → **Bytes** +1. Click **Window** -> **Bytes** 2. A new panel appears showing raw hex bytes ### Step 38: Enable Editing @@ -1011,11 +1011,11 @@ print(f"New: {new}") # 5.0 --- -## 🔬 Part 19: Exporting and Testing +## ? Part 19: Exporting and Testing ### Step 41: Export the Patched Binary -1. Click **File** → **Export Program** +1. Click **File** -> **Export Program** 2. Set **Format** to **Binary** 3. Navigate to your build directory 4. Name the file `0x001a_operators-h.bin` @@ -1026,7 +1026,7 @@ print(f"New: {new}") # 5.0 Open a terminal and run: ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x001a_operators python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` @@ -1040,7 +1040,7 @@ You should see dramatically increased temperature readings! --- -## 📊 Part 20: Summary and Review +## ? Part 20: Summary and Review ### What We Accomplished @@ -1055,18 +1055,18 @@ You should see dramatically increased temperature readings! ### The Hacking Workflow ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Binary Hacking Workflow │ -│ │ -│ 1. Analyze the binary in Ghidra │ -│ 2. Identify target values/instructions │ -│ 3. Calculate file offsets from memory addresses │ -│ 4. Determine replacement bytes │ -│ 5. Patch the binary (manual in hex editor) │ -│ 6. Export and convert to UF2 │ -│ 7. Flash and test │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Binary Hacking Workflow | +| | +| 1. Analyze the binary in Ghidra | +| 2. Identify target values/instructions | +| 3. Calculate file offsets from memory addresses | +| 4. Determine replacement bytes | +| 5. Patch the binary (manual in hex editor) | +| 6. Export and convert to UF2 | +| 7. Flash and test | +| | ++-----------------------------------------------------------------+ ``` ### Key Memory Addresses @@ -1082,34 +1082,9 @@ You should see dramatically increased temperature readings! --- -## ✅ Practice Exercises - -### Exercise 1: Change the Sleep Duration -Find the `sleep_ms(2000)` call and change it to 5000ms (5 seconds). - -**Hint:** Look for `0x7d0` (2000) being loaded into r0. - -### Exercise 2: Invert Temperature Reading -Using HxD, change the scaling constant at offset `0x42C` to make temperature readings negative. - -**Hint:** Encode -0.1f as IEEE-754 and write those bytes. - -### Exercise 3: Add a Fixed Offset -Using HxD, patch the instruction at offset `0x414` and the constant at `0x42C` to add exactly 10°C to every reading. - -**Hint:** Change vfma to vadd and set the pool constant to 10.0f (`00 00 20 41`). - -### Exercise 4: Find All printf Strings -Search the binary for all format strings like "%d" and "%.1f". - -**Hint:** Use GDB's `x/s` command to search flash memory, or scan in HxD's ASCII panel. - -### Exercise 5: Trace the Variable Flow -In Ghidra, trace how x changes through each operator. Match your findings to the output values. - --- -## 🎓 Key Takeaways +## ? Key Takeaways 1. **Post-increment returns the OLD value** - `x++` gives you x, THEN adds 1 @@ -1121,7 +1096,7 @@ In Ghidra, trace how x changes through each operator. Match your findings to the 5. **IEEE-754 is how floats are stored** - Sign, exponent, mantissa -6. **File offset = Memory address - Base** - 0x10000410 → offset 0x410 +6. **File offset = Memory address - Base** - 0x10000410 -> offset 0x410 7. **Little-endian reverses byte order** - 0x3dcccccc stored as cc cc cc 3d @@ -1131,7 +1106,7 @@ In Ghidra, trace how x changes through each operator. Match your findings to the --- -## 📖 Glossary +## ? Glossary | Term | Definition | | ------------------ | --------------------------------------------------- | @@ -1156,7 +1131,7 @@ In Ghidra, trace how x changes through each operator. Match your findings to the --- -## 🔗 Additional Resources +## ? Additional Resources ### IEEE-754 Float Quick Reference @@ -1174,16 +1149,16 @@ In Ghidra, trace how x changes through each operator. Match your findings to the | Instruction | Description | | --------------------- | ---------------------------------------- | -| `vfma.f32 Sd, Sn, Sm` | Sd = Sd + (Sn × Sm) (fused multiply-add) | +| `vfma.f32 Sd, Sn, Sm` | Sd = Sd + (Sn * Sm) (fused multiply-add) | | `vadd.f32 Sd, Sn, Sm` | Sd = Sn + Sm | | `vsub.f32 Sd, Sn, Sm` | Sd = Sn - Sm | -| `vmul.f32 Sd, Sn, Sm` | Sd = Sn × Sm | +| `vmul.f32 Sd, Sn, Sm` | Sd = Sn * Sm | | `vldr.f32 Sd, [addr]` | Load float from memory | | `vstr.f32 Sd, [addr]` | Store float to memory | --- -## 🚨 Real-World Implications +## ? Real-World Implications ### Why This Matters @@ -1210,4 +1185,6 @@ By manipulating sensor readings, an attacker could: **Remember:** The techniques you learned today can be used for good (security research, debugging) or bad (sabotage, fraud). Always use your skills ethically and legally. Understanding how attacks work helps us build more secure systems! -Happy hacking! 🔧 +Happy hacking! ? + + diff --git a/WEEK10/WEEK10-01-S.md b/WEEK10/WEEK10-01-S.md deleted file mode 100644 index 25d7eba..0000000 --- a/WEEK10/WEEK10-01-S.md +++ /dev/null @@ -1,57 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 10 -Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics - -### Non-Credit Practice Exercise 1 Solution: Change Servo Angle Range - -#### Answers - -##### IEEE-754 Angle Encodings - -| Angle | IEEE-754 Hex | Little-Endian Bytes | Sign | Exponent | Mantissa | -|--------|--------------|--------------------|----- |----------|-----------| -| 0.0f | 0x00000000 | 00 00 00 00 | 0 | 0 | 0 | -| 45.0f | 0x42340000 | 00 00 34 42 | 0 | 132 | 0x340000 | -| 135.0f | 0x43070000 | 00 00 07 43 | 0 | 134 | 0x070000 | -| 180.0f | 0x43340000 | 00 00 34 43 | 0 | 134 | 0x340000 | - -##### Patch 1: Maximum Angle 180.0f → 135.0f - -``` -Before: 00 00 34 43 (180.0f) -After: 00 00 07 43 (135.0f) -``` - -##### Patch 2: Minimum Angle 0.0f → 45.0f - -``` -Before: 00 00 00 00 (0.0f) -After: 00 00 34 42 (45.0f) -``` - -##### Result - -Servo sweeps from **45° to 135°** instead of 0° to 180°, a 90° range centered at the midpoint. - -##### Conversion and Flash - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals -python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -#### Reflection Answers - -1. **Break down the IEEE-754 encoding of 180.0f (0x43340000). What are the sign bit, exponent, and mantissa fields?** - `0x43340000` in binary: `0 10000110 01101000000000000000000`. **Sign** = 0 (positive). **Exponent** = `10000110` = 134, biased exponent = 134 - 127 = 7. **Mantissa** = `0x340000` = `01101000...0`, representing 1.01101₂ = 1 + 0.25 + 0.125 + 0.03125 = 1.40625. Value = 1.40625 × 2⁷ = 1.40625 × 128 = **180.0**. - -2. **Why is 0.0f represented as 0x00000000 (all zeros) in IEEE-754? Most floats have a non-zero exponent — what makes zero special?** - Zero is a **special case** in IEEE-754. When both the exponent and mantissa are all zeros, the value is defined as ±0.0 (the sign bit distinguishes +0.0 from -0.0). This is by design — the IEEE-754 standard reserves the all-zeros exponent for zero and denormalized numbers. Unlike normal floats that have an implicit leading 1 in the mantissa (1.xxx), zero has no such implicit bit. This special encoding means you can check for zero by testing if all 32 bits are 0, which is efficient for hardware. - -3. **What is the IEEE-754 encoding of 90.0f? Show the sign, exponent, and mantissa calculation.** - 90.0 = 1.40625 × 2⁶. **Sign** = 0. **Exponent** = 6 + 127 = 133 = `0x85` = `10000101`. **Mantissa**: 90.0 / 64 = 1.40625, fractional part = 0.40625 = 0.25 + 0.125 + 0.03125 = `0110100...0` = `0x340000`. Result: `0 10000101 01101000000000000000000` = `0x42B40000`. Little-endian: `00 00 B4 42`. - -4. **The compiler might use movs r0, #0 instead of loading 0.0f from a literal pool. Why would it choose one approach over the other?** - For integer zero, the compiler prefers `movs r0, #0` (a 2-byte Thumb instruction) because it's smaller and faster than a literal pool load. However, for **floating-point** zero used with VFP instructions, the compiler must load it into an FPU register (e.g., `s0`). If the FPU has a `vmov.f32 s0, #0.0` immediate form available, it can encode zero directly. Otherwise, it loads from a literal pool or uses `movs` to set an integer register to 0 and transfers it with `vmov s0, r0`. The choice depends on instruction context — integer vs. FPU register — and optimization level. diff --git a/WEEK10/WEEK10-01.md b/WEEK10/WEEK10-01.md deleted file mode 100644 index 9ce41ca..0000000 --- a/WEEK10/WEEK10-01.md +++ /dev/null @@ -1,155 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 10 -Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics - -### Non-Credit Practice Exercise 1: Change Servo Angle Range - -#### Objective -Find the IEEE-754 floating-point value `0x43340000` (180.0f) in the `0x0020_dynamic-conditionals` binary using GDB, calculate the file offset, patch it to `0x43070000` (135.0f) using a hex editor, then find and patch the `0x00000000` (0.0f) value to `0x42340000` (45.0f), and verify on hardware that the servo now sweeps from 45° to 135° instead of 0° to 180°. - -#### Prerequisites -- Completed Week 10 tutorial (GDB and hex editor sections) -- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with SG90 servo motor connected on GPIO 6 - -#### Task Description -The program uses `servo_set_angle(0.0f)` and `servo_set_angle(180.0f)` to sweep the servo across its full range. The float values `0.0` (`0x00000000`) and `180.0` (`0x43340000`) are loaded from the literal pool into registers before the `bl servo_set_angle` calls. You will locate these values in the disassembly, find the corresponding bytes in the `.bin` file, and patch them to `45.0` (`0x42340000`) and `135.0` (`0x43070000`) so the servo sweeps a narrower 90° range centered at 90°. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find the servo_set_angle Calls - -Disassemble main and look for the `servo_set_angle` calls: - -```gdb -(gdb) disassemble 0x10000234,+250 -``` - -Look for instruction patterns like: - -``` -ldr r0, [pc, #offset] ; load float from literal pool -bl servo_set_angle -``` - -There will be multiple pairs — one loading `0x00000000` (0.0f) and another loading `0x43340000` (180.0f). - -##### Step 3: Examine the Literal Pool Values - -Once you find the `ldr r0, [pc, #offset]` instructions, examine the literal pool entries they reference: - -```gdb -(gdb) x/wx -``` - -You should see `0x43340000` (180.0f in IEEE-754). - -```gdb -(gdb) x/wx -``` - -You should see `0x00000000` (0.0f in IEEE-754). - -##### Step 4: Calculate the File Offsets - -``` -file_offset = literal_pool_address - 0x10000000 -``` - -Note the file offsets for both 4-byte words. - -##### Step 5: Encode the New Values - -**IEEE-754 Encoding:** - -| Angle | IEEE-754 Hex | Little-Endian Bytes | -| ----- | -------------- | ------------------- | -| 0.0f | `0x00000000` | `00 00 00 00` | -| 45.0f | `0x42340000` | `00 00 34 42` | -| 135.0f| `0x43070000` | `00 00 07 43` | -| 180.0f| `0x43340000` | `00 00 34 43` | - -##### Step 6: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin` -2. Press **Ctrl+G** and enter the file offset for the 180.0f value -3. You should see: `00 00 34 43` -4. Replace with: `00 00 07 43` (135.0f) -5. Press **Ctrl+G** and enter the file offset for the 0.0f value -6. You should see: `00 00 00 00` -7. Replace with: `00 00 34 42` (45.0f) - -###### Question 1: How many literal pool entries reference `0x43340000`? Do you need to patch all of them? - -##### Step 7: Save and Convert - -1. Click **File** → **Save As** → `0x0020_dynamic-conditionals-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals -python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 8: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the servo behavior:** -- Press '1' → servo sweeps from **45°** to **135°** (was 0° to 180°) -- Press '2' → servo sweeps from **135°** to **45°** (was 180° to 0°) -- The total sweep range should be noticeably smaller - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate IEEE-754 floating-point values in the literal pool -- Understand the IEEE-754 single-precision encoding format -- Patch floating-point angle constants in embedded firmware -- Calculate file offsets from memory addresses - -#### Questions for Reflection - -###### Question 1: The value `180.0f` is encoded as `0x43340000` in IEEE-754. Break this down: what are the sign bit, exponent, and mantissa fields? - -###### Question 2: Why is `0.0f` stored as `0x00000000` and not some other pattern? What makes zero special in IEEE-754? - -###### Question 3: If you wanted to set the servo to exactly 90° (center), what IEEE-754 hex value would you use? Show the calculation. - -###### Question 4: Could the compiler optimize `0.0f` differently — for example, using `movs r0, #0` instead of a literal pool load? How would that affect your patching strategy? - -#### Tips and Hints -- IEEE-754 for 90.0f: sign=0, exponent=127+6=133=0x85, mantissa=0x340000 → `0x42b40000` -- There may be multiple references to `0x43340000` in the literal pool — the case '1' and case '2' branches each load both angles -- Be careful with `0x00000000` — make sure you are patching a float literal pool entry and not zeroed data -- Use `x/4wx
` in GDB to view several literal pool words at once diff --git a/WEEK10/WEEK10-02-S.md b/WEEK10/WEEK10-02-S.md deleted file mode 100644 index 8158c68..0000000 --- a/WEEK10/WEEK10-02-S.md +++ /dev/null @@ -1,64 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 10 -Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics - -### Non-Credit Practice Exercise 2 Solution: Add a Third Command - -#### Answers - -##### Patch 1: Compare Byte '2' → '3' - -Change `cmp r4, #0x32` to `cmp r4, #0x33`: - -``` -Before: 32 2C (cmp r4, #0x32 = '2') -After: 33 2C (cmp r4, #0x33 = '3') -``` - -Only the immediate byte changes: `0x32` → `0x33`. - -##### Patch 2: Case Angle 1 — 180.0f → 90.0f - -``` -Before: 00 00 34 43 (180.0f) -After: 00 00 B4 42 (90.0f) -``` - -##### Patch 3: Case Angle 2 — 0.0f → 90.0f - -``` -Before: 00 00 00 00 (0.0f) -After: 00 00 B4 42 (90.0f) -``` - -##### IEEE-754 Reference - -| Angle | Hex | Little-Endian | -|--------|-------------|----------------| -| 0.0f | 0x00000000 | 00 00 00 00 | -| 90.0f | 0x42B40000 | 00 00 B4 42 | -| 180.0f | 0x43340000 | 00 00 34 43 | - -##### Behavior After Patch - -| Key | Action | -|-----|--------------------------------------| -| '1' | Sweep 0° → 180° (unchanged) | -| '3' | Move to 90° center (new command) | -| '2' | Falls to default — prints "??" | - -#### Reflection Answers - -1. **Why does this exercise repurpose the existing case '2' path instead of adding a completely new branch? What would adding a new branch require?** - Adding a new branch would require inserting new instructions into the binary — additional `cmp`, `beq`, angle-loading code, and a `servo_set_angle` call. This would shift all subsequent code addresses, breaking every PC-relative branch, literal pool reference, and function call in the program. In a compiled binary without relocation information, inserting bytes is extremely difficult. Repurposing the existing case '2' path reuses the existing branch structure, angle-loading instructions, and function calls — only the data values change, not the code layout. - -2. **The cmp instruction uses an 8-bit immediate field. What is the range of characters you could compare against? Could you use a non-ASCII value?** - The `cmp Rn, #imm8` Thumb instruction has an 8-bit unsigned immediate, giving a range of 0–255 (`0x00`–`0xFF`). This covers all ASCII characters (0–127) plus extended values (128–255). You could compare against any byte value, including non-printable characters (`0x01`–`0x1F`), DEL (`0x7F`), or extended characters (`0x80`–`0xFF`). However, the user needs to be able to type the character via `getchar()` — non-printable characters would require special terminal input (e.g., Ctrl combinations). - -3. **How would you keep BOTH the original '2' command AND add '3' as a new command, using only data patches (no instruction insertion)?** - You could repurpose the **default/else** branch path. After the `cmp r4, #0x32` (case '2'), there's typically a branch to a default handler that prints "??". If you change the compare in the default path (or an unused branch) to `cmp r4, #0x33`, and redirect its logic to reuse one of the existing `servo_set_angle` code paths, you could handle both. Alternatively, if the binary has any unreachable code or NOP sleds, you could repurpose that space. The constraint is that you cannot increase the binary size — only modify existing bytes. - -4. **What would happen if you changed the compare value to 0x00 (null)? Could a user ever trigger this case?** - A compare against `0x00` would trigger on a null byte. In terminal input via `getchar()`, a null character is not easily typed — most terminals don't send `0x00` on any key press. On some systems, Ctrl+@ or Ctrl+Shift+2 generates a null byte, but this is platform-dependent. In practice, comparing against `0x00` would create an unreachable case — the command would exist in the binary but could never be triggered via normal serial terminal input, effectively making it a dead code path. diff --git a/WEEK10/WEEK10-02.md b/WEEK10/WEEK10-02.md deleted file mode 100644 index ac088ea..0000000 --- a/WEEK10/WEEK10-02.md +++ /dev/null @@ -1,159 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 10 -Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics - -### Non-Credit Practice Exercise 2: Add a Third Command - -#### Objective -Find the comparison instruction `cmp r4, #0x32` ('2') in the `0x0020_dynamic-conditionals` binary using GDB, locate the branch target for case '2', and modify existing code so that pressing '3' (0x33) moves the servo to the center position (90°) by patching one of the existing comparisons and its corresponding angle value to `0x42b40000` (90.0f). - -#### Prerequisites -- Completed Week 10 tutorial (GDB and hex editor sections) -- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with SG90 servo motor connected on GPIO 6 - -#### Task Description -The program has two active commands: '1' (0x31) moves the servo 0°→180° and '2' (0x32) moves it 180°→0°. Adding a completely new code path would require rewriting branch offsets throughout the binary. Instead, you will repurpose the '2' command by changing its comparison value from `0x32` ('2') to `0x33` ('3') and patching both of its angle values to `0x42b40000` (90.0f), so pressing '3' moves the servo to center and holds it there. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find the Comparison Instructions - -Disassemble main and locate both `cmp` instructions: - -```gdb -(gdb) disassemble 0x10000234,+250 -``` - -Look for: - -``` -cmp r4, #0x31 ; compare with '1' -beq -cmp r4, #0x32 ; compare with '2' -beq -``` - -Note the address of the `cmp r4, #0x32` instruction. - -##### Step 3: Examine the Comparison Byte - -The `cmp r4, #0x32` instruction encodes `0x32` as an immediate. Examine the instruction bytes: - -```gdb -(gdb) x/2bx -``` - -You should see a byte containing `0x32`. - -##### Step 4: Find the Angle Values for Case '2' - -Follow the `beq` target for case '2' and look for the literal pool loads: - -```gdb -(gdb) x/wx -(gdb) x/wx -``` - -Case '2' loads `0x43340000` (180.0f) first, then `0x00000000` (0.0f). - -##### Step 5: Calculate the File Offsets - -``` -file_offset = address - 0x10000000 -``` - -Note offsets for: -1. The `0x32` byte in the `cmp` instruction -2. Both angle literal pool entries for case '2' - -##### Step 6: Encode the New Values - -| Patch Target | Original | New | Purpose | -| ---------------- | ---------------- | ---------------- | ------------------------- | -| Compare byte | `32` | `33` | Match '3' instead of '2' | -| Angle 1 (180.0f) | `00 00 34 43` | `00 00 b4 42` | 90.0f center position | -| Angle 2 (0.0f) | `00 00 00 00` | `00 00 b4 42` | 90.0f center position | - -##### Step 7: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin` -2. Press **Ctrl+G** and go to the compare byte offset -3. Change `32` to `33` -4. Go to the first angle offset for case '2' and replace `00 00 34 43` with `00 00 b4 42` -5. Go to the second angle offset for case '2' and replace `00 00 00 00` with `00 00 b4 42` - -###### Question 1: Why do we set both angle values to 90.0f instead of just one? - -##### Step 8: Save and Convert - -1. Click **File** → **Save As** → `0x0020_dynamic-conditionals-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals -python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 9: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the behavior:** -- Press '1' → servo sweeps 0° to 180° (unchanged) -- Press '3' → servo moves to **90° center** and stays -- Press '2' → now falls through to default "??" (no longer mapped) - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate and patch comparison immediate values in ARM Thumb instructions -- Repurpose existing code paths instead of adding new ones -- Understand how `cmp` immediates are encoded in the instruction stream -- Combine multiple patches (comparison + data) for a single behavior change - -#### Questions for Reflection - -###### Question 1: Why is it easier to repurpose an existing command than to add a truly new third code path in the binary? - -###### Question 2: The `cmp` instruction uses an 8-bit immediate field. What range of ASCII characters could you use as command keys? - -###### Question 3: After your patch, pressing '2' now triggers the default "??" branch. Could you keep both '2' AND '3' working? What would that require? - -###### Question 4: What would happen if you changed the comparison to `0x00`? Could a user ever trigger that command via `getchar()`? - -#### Tips and Hints -- The `cmp rN, #imm8` instruction in Thumb has the immediate in the lower byte of the instruction word -- IEEE-754 for 90.0f: `0x42b40000` → little-endian `00 00 b4 42` -- Be careful not to confuse literal pool entries between case '1' and case '2' — trace the branch targets carefully -- The `getchar()` function reads one byte from UART, so any byte value 0x00-0xFF is theoretically possible diff --git a/WEEK10/WEEK10-03-S.md b/WEEK10/WEEK10-03-S.md deleted file mode 100644 index 45d9b41..0000000 --- a/WEEK10/WEEK10-03-S.md +++ /dev/null @@ -1,49 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 10 -Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics - -### Non-Credit Practice Exercise 3 Solution: Reverse the Servo Direction - -#### Answers - -##### Four Literal Pool Swaps - -| Patch | Location | Original | Patched | -|-------|---------------|-------------------|-------------------| -| Case 1 Angle 1 | Literal pool | 00 00 00 00 (0.0f) | 00 00 34 43 (180.0f) | -| Case 1 Angle 2 | Literal pool | 00 00 34 43 (180.0f) | 00 00 00 00 (0.0f) | -| Case 2 Angle 1 | Literal pool | 00 00 34 43 (180.0f) | 00 00 00 00 (0.0f) | -| Case 2 Angle 2 | Literal pool | 00 00 00 00 (0.0f) | 00 00 34 43 (180.0f) | - -##### Behavior After Patch - -| Key | Original | Patched | -|-----|---------------------|---------------------| -| '1' | 0° → 180° (sweep up) | 180° → 0° (sweep down) | -| '2' | 180° → 0° (sweep down) | 0° → 180° (sweep up) | - -The terminal output text ("Moving to 180..." / "Moving to 0...") remains unchanged — it still says the original directions. Only the physical servo behavior is reversed. - -##### GDB Verification - -```gdb -(gdb) x/4wx -``` - -Examine all angle entries in the literal pool to identify which 4-byte words to swap. - -#### Reflection Answers - -1. **After this patch, the serial output still says "Moving to 180" when the servo actually moves to 0. Why is this a security concern? What real-world attack does this mimic?** - This is a classic **display spoofing** attack. The user interface (serial output) shows one thing while the hardware does another. In real-world systems, this mimics attacks on SCADA/ICS systems where operator displays show "normal" readings while the physical process is manipulated (similar to Stuxnet, which showed normal centrifuge speeds while actually damaging them). In medical devices, this could display a safe dosage while delivering a different amount. The lesson is that **you cannot trust the display if the firmware has been tampered with** — the display text and the actual behavior are patched independently. - -2. **Instead of swapping the data values, could you achieve the same result by swapping the branch targets (making case '1' jump to case '2' code and vice versa)? What are the trade-offs?** - Yes, you could swap the `beq` target addresses so that when the user presses '1', execution jumps to the case '2' code path and vice versa. **Trade-offs:** Swapping branch targets changes the instructions (modifying the offset bytes in `beq`), which is more complex — you need to correctly calculate the new PC-relative offsets. Swapping data values is simpler (just exchange 4-byte float values) and less error-prone. However, swapping branches would also swap the printf messages, so "Moving to 180" would display for the path that actually moves to 180 — keeping the display consistent. The data-swap approach intentionally creates a mismatch between display and behavior. - -3. **If the compiler shares a single literal pool entry for 0x43340000 (180.0f) across both cases, how does swapping that one entry affect the behavior?** - If the compiler optimized by sharing a single `0x43340000` literal pool entry for all references to 180.0f, then both case '1' and case '2' load from the same address. Changing that one entry to `0x00000000` (0.0f) would affect **both** cases simultaneously — they would both use 0.0f where they originally used 180.0f. Similarly, if there's only one `0x00000000` entry shared, changing it affects both cases. You would need to verify whether each case uses its own pool entry or shares entries by examining the `ldr` offsets. If shared, you may need to find unused space to create a second copy of the value. - -4. **How would you verify the patch is correct without physical hardware? What GDB commands would you use?** - Set breakpoints before each `bl servo_set_angle` call, then examine `r0` (or `s0`) which holds the angle argument. Run through both cases and verify: (1) `b *` → `c` → press '1' → `info float` or `p $s0` — should show 180.0f (was 0.0f). (2) Continue to second call — should show 0.0f (was 180.0f). Repeat for case '2'. You can also examine the literal pool directly: `x/wx ` to verify the bytes were swapped. Additionally, `x/f ` displays the value as a float for quick verification. diff --git a/WEEK10/WEEK10-03.md b/WEEK10/WEEK10-03.md deleted file mode 100644 index a6479a0..0000000 --- a/WEEK10/WEEK10-03.md +++ /dev/null @@ -1,141 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 10 -Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics - -### Non-Credit Practice Exercise 3: Reverse the Servo Direction - -#### Objective -Find the branch targets for case '1' and case '2' in the `0x0020_dynamic-conditionals` binary using GDB, identify where each case loads its angle values from the literal pool, and swap the angle pairs so that pressing '1' now does what '2' originally did (180°→0°) and pressing '2' does what '1' originally did (0°→180°). - -#### Prerequisites -- Completed Week 10 tutorial (GDB and hex editor sections) -- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with SG90 servo motor connected on GPIO 6 - -#### Task Description -Currently, case '1' calls `servo_set_angle(0.0f)` then `servo_set_angle(180.0f)`, while case '2' calls `servo_set_angle(180.0f)` then `servo_set_angle(0.0f)`. To reverse the servo direction, you will swap the literal pool angle values between the two cases. This means patching case '1' to load 180.0f first and 0.0f second, and case '2' to load 0.0f first and 180.0f second. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Map Both Case Branch Targets - -Disassemble main and trace both paths: - -```gdb -(gdb) disassemble 0x10000234,+250 -``` - -Identify: -- Case '1' branch target → first `ldr r0` (loads 0.0f), `bl servo_set_angle`, second `ldr r0` (loads 180.0f), `bl servo_set_angle` -- Case '2' branch target → first `ldr r0` (loads 180.0f), `bl servo_set_angle`, second `ldr r0` (loads 0.0f), `bl servo_set_angle` - -##### Step 3: Find All Four Literal Pool Entries - -Examine the literal pool entries for each angle load: - -```gdb -(gdb) x/wx -(gdb) x/wx -(gdb) x/wx -(gdb) x/wx -``` - -Record which addresses contain `0x00000000` (0.0f) and which contain `0x43340000` (180.0f). - -##### Step 4: Calculate All File Offsets - -``` -file_offset = literal_pool_address - 0x10000000 -``` - -You need four offsets — two for case '1' angles and two for case '2' angles. - -##### Step 5: Plan the Swap - -| Location | Original | New | -| -------------- | ---------------- | ---------------- | -| Case 1 Angle 1 | `00 00 00 00` (0.0f) | `00 00 34 43` (180.0f) | -| Case 1 Angle 2 | `00 00 34 43` (180.0f) | `00 00 00 00` (0.0f) | -| Case 2 Angle 1 | `00 00 34 43` (180.0f) | `00 00 00 00` (0.0f) | -| Case 2 Angle 2 | `00 00 00 00` (0.0f) | `00 00 34 43` (180.0f) | - -###### Question 1: Could the compiler share a single literal pool entry for all references to `0x43340000`? How would that affect your patching plan? - -##### Step 6: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin` -2. For each of the four literal pool entries, navigate to its file offset and swap the values as planned -3. Be methodical — patch one at a time and verify each before moving to the next - -##### Step 7: Save and Convert - -1. Click **File** → **Save As** → `0x0020_dynamic-conditionals-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals -python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 8: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the behavior:** -- Press '1' → servo sweeps from **180° to 0°** (was 0° to 180°) -- Press '2' → servo sweeps from **0° to 180°** (was 180° to 0°) -- The text output still says "one" and "two" — only the physical behavior changed - -#### Expected Output - -After completing this exercise, you should be able to: -- Trace multiple branch paths to their literal pool references -- Understand how data (angles) and code (branches) are separate concerns -- Swap data values to reverse physical behavior while keeping code structure intact -- Recognize shared vs. separate literal pool entries - -#### Questions for Reflection - -###### Question 1: The terminal still prints "one" and "two" with the original meanings, but the servo does the opposite. Why is this a security concern in real embedded systems? - -###### Question 2: Instead of swapping literal pool values, could you swap the branch targets themselves? What are the pros and cons of each approach? - -###### Question 3: If the literal pool entries are shared between cases (one `0x43340000` word referenced by both), how would your patch strategy change? - -###### Question 4: What tool could you use to confirm the swapped behavior without physical hardware — just by reading the patched disassembly? - -#### Tips and Hints -- Use `x/4wx ` to dump the entire literal pool at once and see all angle values together -- If the compiler shares a single literal pool entry for 180.0f across both cases, swapping it would affect both — you may need to create a duplicate entry -- The simplest approach: if each case has its own literal pool entries, just swap the 4-byte values at each offset -- Verify by disassembling the patched binary in GDB to confirm the `ldr` instructions now reference the swapped values diff --git a/WEEK10/WEEK10-04-S.md b/WEEK10/WEEK10-04-S.md deleted file mode 100644 index fac3964..0000000 --- a/WEEK10/WEEK10-04-S.md +++ /dev/null @@ -1,56 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 10 -Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics - -### Non-Credit Practice Exercise 4 Solution: Speed Profile - -#### Answers - -##### Sleep Duration Values - -| Parameter | Original | Case 1 (Fast Snap) | Case 2 (Slow Sweep) | -|-------------|---------|--------------------|--------------------| -| Duration | 500 ms | 100 ms | 1000 ms | -| Hex | 0x1F4 | 0x64 | 0x3E8 | -| LE Bytes | F4 01 00 00 | 64 00 00 00 | E8 03 00 00 | - -##### Patch Case 1: 500ms → 100ms (Fast Snap) - -``` -Before: F4 01 00 00 (500ms) -After: 64 00 00 00 (100ms) -``` - -##### Patch Case 2: 500ms → 1000ms (Slow Sweep) - -``` -Before: F4 01 00 00 (500ms) -After: E8 03 00 00 (1000ms) -``` - -##### Literal Pool Considerations - -If the compiler shares a single literal pool entry for `0x000001F4` across both cases, you **cannot** patch them independently without additional work. Verify by checking whether case 1 and case 2 `ldr` instructions reference the same pool address. If shared, you need to find unused space in the binary for a second value or repurpose another unused literal pool entry. - -##### Behavior After Patch - -| Key | Original | Patched | -|-----|-------------------|----------------------------------| -| '1' | 500ms between moves | 100ms — near-instantaneous snap | -| '2' | 500ms between moves | 1000ms — slow, deliberate sweep | - -#### Reflection Answers - -1. **Why does 100ms feel like an instant "snap" while 1000ms feels like a smooth sweep? The servo moves the same distance either way.** - Human perception of motion depends on the **pause between position updates**, not the motor speed. At 100ms delay, the servo reaches each angle before the next one is set — the positions update so quickly that the motion appears continuous and instant. At 1000ms delay, there's a full second between movements, so you can see the servo pause at each intermediate angle. The SG90 servo physically takes about 200–300ms to traverse its full range at no load, so 100ms is faster than the travel time (the servo is still moving when the next command arrives), creating a snappy feel. At 1000ms, the servo has already completed its move and waits idle before the next command. - -2. **If both cases share the same literal pool entry for 500ms, what strategy would you use to give them different sleep values?** - Several approaches: (1) **Find unused literal pool space** — look for entries that are no longer referenced and overwrite one with `0x64` (100ms) while keeping the other for `0x3E8` (1000ms). (2) **Repurpose an existing value** — if another constant in the pool happens to equal your desired value, redirect the `ldr` offset to point there. (3) **Change the `ldr` to a `movs`** — for values ≤ 255 (like 100), replace the 4-byte `ldr r0, [pc, #offset]` with `movs r0, #0x64` (2 bytes) + `nop` (2 bytes) for padding. This works for case 1 (100 fits in 8 bits) but not case 2 (1000 exceeds 255). - -3. **What is the minimum sleep_ms value where the SG90 servo can actually complete a full 0°–180° sweep before the next command?** - The SG90 servo has a rated speed of approximately 0.12 seconds per 60° at 4.8V. For a full 180° sweep: 0.12 × (180/60) = 0.12 × 3 = **0.36 seconds (360ms)**. In practice, with load and signal processing overhead, **400–500ms** is a safe minimum for reliable full-range travel. Below this, the servo may not reach the target angle before the next position command arrives, resulting in incomplete movements or jittery behavior. The original 500ms value was chosen to reliably allow full travel. - -4. **What would happen if you set sleep_ms to 0 for both cases? How would the servo physically behave?** - With `sleep_ms(0)`, the loop runs at full CPU speed, sending angle commands as fast as the processor can execute. The servo would receive thousands of position updates per second, alternating between two angles. Physically, the servo would **vibrate or oscillate** — it never has time to reach either target angle before being told to go to the other one. The PWM signal would switch so rapidly that the servo's control circuit would see constantly changing targets, producing a buzzing sound and erratic oscillation near the midpoint. This could also overheat the servo motor due to constant direction changes. diff --git a/WEEK10/WEEK10-04.md b/WEEK10/WEEK10-04.md deleted file mode 100644 index 1642eae..0000000 --- a/WEEK10/WEEK10-04.md +++ /dev/null @@ -1,144 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 10 -Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics - -### Non-Credit Practice Exercise 4: Speed Profile - -#### Objective -Find both `sleep_ms(500)` calls in the `0x0020_dynamic-conditionals` binary using GDB, identify the literal pool values `0x1f4` (500) loaded into `r0` before each `bl sleep_ms`, calculate the file offsets, and patch case '1' to use `0x64` (100ms) for fast movement and case '2' to use `0x3e8` (1000ms) for slow movement, then verify on hardware that the two keys produce visibly different servo speeds. - -#### Prerequisites -- Completed Week 10 tutorial (GDB and hex editor sections) -- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with SG90 servo motor connected on GPIO 6 - -#### Task Description -Both case '1' and case '2' call `sleep_ms(500)` between their two `servo_set_angle` calls. The value `500` (`0x1F4`) is loaded from the literal pool into `r0` before each `bl sleep_ms`. You will find both `sleep_ms` literal pool entries — one in case '1' and one in case '2' — and patch them to different values: `100` (`0x64`) for fast snapping and `1000` (`0x3E8`) for slow sweeping, creating distinct speed profiles per key. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find Both sleep_ms Calls - -Disassemble main and locate the `sleep_ms` calls: - -```gdb -(gdb) disassemble 0x10000234,+250 -``` - -Look for the pattern repeated in both cases: - -``` -ldr r0, [pc, #offset] ; load delay value -bl sleep_ms -``` - -Each case has at least one `sleep_ms` call. Identify which belongs to case '1' and which to case '2' by tracing the branch targets. - -##### Step 3: Examine the Literal Pool Entries - -For each `sleep_ms` call, examine the referenced literal pool entry: - -```gdb -(gdb) x/wx -(gdb) x/wx -``` - -Both should show `0x000001f4` (500). - -##### Step 4: Calculate the File Offsets - -``` -file_offset = literal_pool_address - 0x10000000 -``` - -Note the file offsets for both 4-byte sleep values. - -##### Step 5: Encode the New Values - -| Case | Original | New | Speed | -| ------ | ----------------- | ----------------- | -------- | -| Case 1 | `F4 01 00 00` (500ms) | `64 00 00 00` (100ms) | Fast snap | -| Case 2 | `F4 01 00 00` (500ms) | `E8 03 00 00` (1000ms) | Slow sweep | - -###### Question 1: Each case calls `sleep_ms` twice (once between the first and second `servo_set_angle`). Do both share the same literal pool entry, or does each have its own? - -##### Step 6: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin` -2. Press **Ctrl+G** and go to the case '1' sleep value offset -3. Replace `F4 01 00 00` with `64 00 00 00` (100ms) -4. Press **Ctrl+G** and go to the case '2' sleep value offset -5. Replace `F4 01 00 00` with `E8 03 00 00` (1000ms) - -##### Step 7: Save and Convert - -1. Click **File** → **Save As** → `0x0020_dynamic-conditionals-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals -python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 8: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the behavior:** -- Press '1' → servo snaps **fast** (100ms between angles) — almost instant jump -- Press '2' → servo moves **slow** (1000ms between angles) — takes a full second at each position -- The speed difference should be very obvious visually and audibly - -#### Expected Output - -After completing this exercise, you should be able to: -- Distinguish between multiple literal pool entries for the same value -- Trace which code path references which literal pool entry -- Patch timing constants independently per branch -- Understand how sleep duration affects perceived motor behavior - -#### Questions for Reflection - -###### Question 1: Why does 100ms make the servo appear to "snap" while 1000ms makes it appear to "sweep"? Is the servo actually moving faster, or is it about the pause between commands? - -###### Question 2: If the compiler uses a single shared literal pool entry for all `sleep_ms(500)` calls, what alternative patching strategy would you need to create different speeds per case? - -###### Question 3: What is the minimum `sleep_ms` value that would still allow the servo to physically reach its target angle before the next command? How would you determine this experimentally? - -###### Question 4: Could you set the sleep to `0` (`00 00 00 00`)? What would happen to the servo behavior? - -#### Tips and Hints -- `100` decimal = `0x64`, fits in one byte: `64 00 00 00` in little-endian -- `1000` decimal = `0x3E8`: `E8 03 00 00` in little-endian -- If both `sleep_ms` calls share one literal pool word, you cannot give them different values by patching data alone — you would need to patch one `ldr` instruction to point to a different pool entry or use a `movs` immediate -- The SG90 servo takes about 200-300ms to traverse its full range, so 100ms will cause it to not quite reach the endpoint before the next command fires diff --git a/WEEK10/WEEK10.md b/WEEK10/WEEK10.md index 3839bfc..b99456a 100644 --- a/WEEK10/WEEK10.md +++ b/WEEK10/WEEK10.md @@ -1,6 +1,6 @@ -# Week 10: Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics +?# Week 10: Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics -## 🎯 What You'll Learn This Week +## ? What You'll Learn This Week By the end of this tutorial, you will be able to: - Understand the difference between static and dynamic conditionals in C @@ -15,7 +15,7 @@ By the end of this tutorial, you will be able to: --- -## 📚 Part 1: Understanding Conditionals in C +## Part 1: Understanding Conditionals in C ### What Are Conditionals? @@ -30,7 +30,7 @@ By the end of this tutorial, you will be able to: --- -## 📚 Part 2: Static Conditionals +## Part 2: Static Conditionals ### What Makes a Conditional "Static"? @@ -51,27 +51,27 @@ while (true) { ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Static Conditional Flow │ -│ │ -│ choice = 1 (set once, never changes) │ -│ │ │ -│ ▼ │ -│ ┌─────────────┐ │ -│ │ choice == 1 │────YES────► printf("1") │ -│ └─────────────┘ │ -│ │NO (never taken) │ -│ ▼ │ -│ ┌─────────────┐ │ -│ │ choice == 2 │────YES────► printf("2") (never reached) │ -│ └─────────────┘ │ -│ │NO │ -│ ▼ │ -│ printf("?") (never reached) │ -│ │ -│ The branching logic EXISTS but only ONE path ever executes! │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Static Conditional Flow | +| | +| choice = 1 (set once, never changes) | +| | | +| ? | +| +-------------+ | +| | choice == 1 |----YES----? printf("1") | +| +-------------+ | +| |NO (never taken) | +| ? | +| +-------------+ | +| | choice == 2 |----YES----? printf("2") (never reached) | +| +-------------+ | +| |NO | +| ? | +| printf("?") (never reached) | +| | +| The branching logic EXISTS but only ONE path ever executes! | +| | ++-----------------------------------------------------------------+ ``` ### The if/else Statement @@ -116,7 +116,7 @@ switch (choice) { --- -## 📚 Part 3: Dynamic Conditionals +## Part 3: Dynamic Conditionals ### What Makes a Conditional "Dynamic"? @@ -139,31 +139,31 @@ while (true) { ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Dynamic Conditional Flow │ -│ │ -│ ┌────────────────┐ │ -│ │ choice=getchar │◄──── User types 'x' on keyboard │ -│ └────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────┐ │ -│ │ choice=='1' │────YES────► printf("1"), move servo │ -│ └─────────────┘ │ -│ │NO │ -│ ▼ │ -│ ┌─────────────┐ │ -│ │ choice=='2' │────YES────► printf("2"), move servo │ -│ └─────────────┘ │ -│ │NO │ -│ ▼ │ -│ printf("??") │ -│ │ │ -│ └──────────────────► Loop back to getchar() │ -│ │ -│ EACH iteration can take a DIFFERENT path! │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Dynamic Conditional Flow | +| | +| +----------------+ | +| | choice=getchar |?---- User types 'x' on keyboard | +| +----------------+ | +| | | +| ? | +| +-------------+ | +| | choice=='1' |----YES----? printf("1"), move servo | +| +-------------+ | +| |NO | +| ? | +| +-------------+ | +| | choice=='2' |----YES----? printf("2"), move servo | +| +-------------+ | +| |NO | +| ? | +| printf("??") | +| | | +| +------------------? Loop back to getchar() | +| | +| EACH iteration can take a DIFFERENT path! | +| | ++-----------------------------------------------------------------+ ``` ### The getchar() Function @@ -180,25 +180,25 @@ uint8_t choice = getchar(); // Waits for user to type something --- -## 📚 Part 4: Understanding PWM (Pulse Width Modulation) +## Part 4: Understanding PWM (Pulse Width Modulation) ### What is PWM? **PWM** (Pulse Width Modulation) is a technique for controlling power by rapidly switching a signal on and off. The ratio of "on time" to "off time" determines the average power delivered. ``` -┌─────────────────────────────────────────────────────────────────┐ -│ PWM Signal - 50% Duty Cycle │ -│ │ -│ HIGH ─┐ ┌─────┐ ┌─────┐ ┌─────┐ │ -│ │ │ │ │ │ │ │ │ -│ LOW └─────┘ └─────┘ └─────┘ └───── │ -│ ◄──T──► │ -│ ON OFF │ -│ │ -│ Duty Cycle = ON time / Total period = 50% │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| PWM Signal - 50% Duty Cycle | +| | +| HIGH -+ +-----+ +-----+ +-----+ | +| | | | | | | | | +| LOW +-----+ +-----+ +-----+ +----- | +| ?--T--? | +| ON OFF | +| | +| Duty Cycle = ON time / Total period = 50% | +| | ++-----------------------------------------------------------------+ ``` ### PWM for Servo Control @@ -206,96 +206,96 @@ uint8_t choice = getchar(); // Waits for user to type something Servo motors use PWM differently - they care about the **pulse width**, not the duty cycle percentage: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Servo PWM Signal (50 Hz = 20ms period) │ -│ │ -│ 0° Position (1ms pulse): │ -│ HIGH ─┐ │ -│ │ 1ms │ -│ LOW └────────────────────────────── (19ms) ────────── │ -│ ◄────────────── 20ms ─────────────────────────► │ -│ │ -│ 90° Position (1.5ms pulse): │ -│ HIGH ─────┐ │ -│ │ 1.5ms │ -│ LOW └─────────────────────────── (18.5ms) ─────── │ -│ │ -│ 180° Position (2ms pulse): │ -│ HIGH ─────────┐ │ -│ │ 2ms │ -│ LOW └───────────────────────── (18ms) ─────── │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Servo PWM Signal (50 Hz = 20ms period) | +| | +| 0? Position (1ms pulse): | +| HIGH -?? | +| | 1ms | +| LOW +------------------------------ (19ms) ---------- | +| ?-------------- 20ms -------------------------? | +| | +| 90? Position (1.5ms pulse): | +| HIGH -----+ | +| | 1.5ms | +| LOW +--------------------------- (18.5ms) ------- | +| | +| 180? Position (2ms pulse): | +| HIGH ---------+ | +| | 2ms | +| LOW +------------------------- (18ms) ------- | +| | ++-----------------------------------------------------------------+ ``` ### The Magic Numbers | Angle | Pulse Width | PWM Ticks (at 1MHz) | | ----- | ----------- | ------------------- | -| 0° | 1000 µs | 1000 | -| 90° | 1500 µs | 1500 | -| 180° | 2000 µs | 2000 | +| 0? | 1000 ?s | 1000 | +| 90? | 1500 ?s | 1500 | +| 180? | 2000 ?s | 2000 | --- -## 📚 Part 5: PWM Timing Calculations +## Part 5: PWM Timing Calculations ### From 150 MHz to 50 Hz The RP2350's system clock runs at **150 MHz** (150 million cycles per second). A servo needs a **50 Hz** signal (one pulse every 20 ms). How do we bridge this gap? ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Clock Division Chain │ -│ │ -│ System Clock: 150 MHz │ -│ │ │ -│ │ ÷ 150 (clock divider) │ -│ ▼ │ -│ PWM Tick Rate: 1 MHz (1 tick = 1 microsecond) │ -│ │ │ -│ │ Count to 20,000 (wrap value = 19,999) │ -│ ▼ │ -│ PWM Frequency: 50 Hz (20 ms period) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Clock Division Chain | +| | +| System Clock: 150 MHz | +| | | +| | ? 150 (clock divider) | +| ? | +| PWM Tick Rate: 1 MHz (1 tick = 1 microsecond) | +| | | +| | Count to 20,000 (wrap value = 19,999) | +| ? | +| PWM Frequency: 50 Hz (20 ms period) | +| | ++-----------------------------------------------------------------+ ``` ### The Math **Step 1: Clock Division** ``` -PWM Tick Rate = System Clock ÷ Divider -1,000,000 Hz = 150,000,000 Hz ÷ 150 +PWM Tick Rate = System Clock ? Divider +1,000,000 Hz = 150,000,000 Hz ? 150 ``` **Step 2: Frame Period** ``` -Period = (Wrap Value + 1) × Tick Duration -20 ms = 20,000 ticks × 1 µs/tick +Period = (Wrap Value + 1) * Tick Duration +20 ms = 20,000 ticks * 1 ?s/tick ``` **Step 3: Pulse Width to Ticks** ``` -Ticks = Pulse Width (µs) × 1 tick/µs -1500 ticks = 1500 µs × 1 +Ticks = Pulse Width (?s) * 1 tick/?s +1500 ticks = 1500 ?s * 1 ``` -### Worked Example: 90° Angle +### Worked Example: 90? Angle -Let's calculate what happens when we command 90°: +Let's calculate what happens when we command 90?: 1. **Angle to Pulse Width:** ``` - Pulse = MIN + (angle/180) × (MAX - MIN) - Pulse = 1000 + (90/180) × (2000 - 1000) - Pulse = 1000 + 0.5 × 1000 - Pulse = 1500 µs + Pulse = MIN + (angle/180) * (MAX - MIN) + Pulse = 1000 + (90/180) * (2000 - 1000) + Pulse = 1000 + 0.5 * 1000 + Pulse = 1500 ?s ``` 2. **Pulse to PWM Ticks:** ``` - Level = 1500 µs × 1 tick/µs = 1500 ticks + Level = 1500 ?s * 1 tick/?s = 1500 ticks ``` 3. **Hardware Timing:** @@ -305,35 +305,35 @@ Let's calculate what happens when we command 90°: --- -## 📚 Part 6: Understanding the SG90 Servo Motor +## Part 6: Understanding the SG90 Servo Motor ### What is the SG90? The **SG90** is a small, inexpensive hobby servo motor commonly used in robotics projects: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ SG90 Servo Motor │ -│ │ -│ ┌───────────────┐ │ -│ │ ┌─────┐ │ │ -│ │ │ ARM │ │ ◄── Rotates 0° to 180° │ -│ │ └──┬──┘ │ │ -│ │ │ │ │ -│ │ ┌────┴────┐ │ │ -│ │ │ MOTOR │ │ │ -│ │ │ GEAR │ │ │ -│ │ │ BOX │ │ │ -│ │ └─────────┘ │ │ -│ └───────┬───────┘ │ -│ │ │ -│ ┌───────┼───────┐ │ -│ │ │ │ │ -│ ORANGE RED BROWN │ -│ Signal VCC GND │ -│ (PWM) (5V) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| SG90 Servo Motor | +| | +| +---------------+ | +| | +-----+ | | +| | | ARM | | ?-- Rotates 0? to 180? | +| | +--+--+ | | +| | | | | +| | +----+----+ | | +| | | MOTOR | | | +| | | GEAR | | | +| | | BOX | | | +| | +---------+ | | +| +-------+-------+ | +| | | +| +-------+-------+ | +| | | | | +| ORANGE RED BROWN | +| Signal VCC GND | +| (PWM) (5V) | +| | ++-----------------------------------------------------------------+ ``` ### SG90 Specifications @@ -341,8 +341,8 @@ The **SG90** is a small, inexpensive hobby servo motor commonly used in robotics | Parameter | Value | | ----------------- | ------------------------- | | **Voltage** | 4.8V - 6V (typically 5V) | -| **Rotation** | 0° to 180° | -| **Pulse Width** | 1000µs - 2000µs | +| **Rotation** | 0? to 180? | +| **Pulse Width** | 1000 us - 2000 us | | **Frequency** | 50 Hz (20ms period) | | **Stall Current** | ~650mA (can spike to 1A+) | @@ -356,9 +356,9 @@ The **SG90** is a small, inexpensive hobby servo motor commonly used in robotics --- -## 📚 Part 7: Power Supply Safety +## Part 7: Power Supply Safety -### ⚠️ CRITICAL WARNING ⚠️ +### CRITICAL WARNING **NEVER power the servo directly from the Pico's 3.3V pin!** @@ -370,35 +370,35 @@ Servos can draw over 1000mA during movement spikes. The Pico's 3.3V regulator ca ### Correct Power Setup ``` -┌─────────────────────────────────────────────────────────────────┐ -│ CORRECT Power Wiring │ -│ │ -│ USB ────► VBUS (5V) ───┬──► Servo VCC (Red) │ -│ │ │ -│ └──► Capacitor (+) │ -│ │ │ -│ Pico GND ──────────────┬────┴──► Capacitor (-) │ -│ │ │ -│ └──────► Servo GND (Brown) │ -│ │ -│ Pico GPIO 6 ──────────────────► Servo Signal (Orange) │ -│ │ -│ IMPORTANT: Use a 1000µF 25V capacitor across the servo │ -│ power to absorb current spikes! │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| CORRECT Power Wiring | +| | +| USB ----? VBUS (5V) ---+--? Servo VCC (Red) | +| | | +| +--? Capacitor (+) | +| | | +| Pico GND --------------+----+--? Capacitor (-) | +| | | +| +------? Servo GND (Brown) | +| | +| Pico GPIO 6 ------------------? Servo Signal (Orange) | +| | +| IMPORTANT: Use a 1000 uF 25V capacitor across the servo | +| power to absorb current spikes! | +| | ++-----------------------------------------------------------------+ ``` ### Why the Capacitor? -The **1000µF capacitor** acts as a tiny battery: +The **1000 uF capacitor** acts as a tiny battery: - Absorbs sudden current demands when servo moves - Prevents voltage drops that could reset the Pico - Smooths out electrical noise --- -## 📚 Part 8: Setting Up Your Environment +## Part 8: Setting Up Your Environment ### Prerequisites @@ -409,7 +409,7 @@ Before we start, make sure you have: 4. Python installed (for UF2 conversion) 5. A serial monitor (PuTTY, minicom, or screen) 6. An SG90 servo motor -7. A 1000µF 25V capacitor +7. A 1000 uF 25V capacitor 8. The sample projects: `0x001d_static-conditionals` and `0x0020_dynamic-conditionals` ### Hardware Setup @@ -423,52 +423,52 @@ Connect your servo like this: | Orange (Signal) | GPIO 6 | ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Servo Wiring with Capacitor │ -│ │ -│ Pico 2 SG90 Servo │ -│ ┌──────────┐ ┌──────────┐ │ -│ │ │ │ │ │ -│ │ GPIO 6 │─────── Orange ───────►│ Signal │ │ -│ │ │ │ │ │ -│ │ VBUS(5V) │───┬─── Red ──────────►│ VCC │ │ -│ │ │ │ │ │ │ -│ │ GND │───┼─── Brown ────────►│ GND │ │ -│ │ │ │ └──────────┘ │ -│ └──────────┘ │ │ -│ │ ┌─────────┐ │ -│ └────┤ + CAP - ├──── GND │ -│ │ 1000µF │ │ -│ │ 25V │ │ -│ └─────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Servo Wiring with Capacitor | +| | +| Pico 2 SG90 Servo | +| +----------+ +----------+ | +| | | | | | +| | GPIO 6 |------- Orange -------?| Signal | | +| | | | | | +| | VBUS(5V) |---+--- Red ----------?| VCC | | +| | | | | | | +| | GND |---+--- Brown --------?| GND | | +| | | | +----------+ | +| +----------+ | | +| | +---------+ | +| +----+ + CAP - +---- GND | +| | 1000 uF | | +| | 25V | | +| +---------+ | +| | ++-----------------------------------------------------------------+ ``` ### Project Structure ``` Embedded-Hacking/ -├── 0x001d_static-conditionals/ -│ ├── build/ -│ │ ├── 0x001d_static-conditionals.uf2 -│ │ └── 0x001d_static-conditionals.bin -│ ├── main/ -│ │ └── 0x001d_static-conditionals.c -│ └── servo.h -├── 0x0020_dynamic-conditionals/ -│ ├── build/ -│ │ ├── 0x0020_dynamic-conditionals.uf2 -│ │ └── 0x0020_dynamic-conditionals.bin -│ ├── main/ -│ │ └── 0x0020_dynamic-conditionals.c -│ └── servo.h -└── uf2conv.py ++-- 0x001d_static-conditionals/ +| +-- build/ +| | +-- 0x001d_static-conditionals.uf2 +| | +-- 0x001d_static-conditionals.bin +| +-- main/ +| | +-- 0x001d_static-conditionals.c +| +-- servo.h ++-- 0x0020_dynamic-conditionals/ +| +-- build/ +| | +-- 0x0020_dynamic-conditionals.uf2 +| | +-- 0x0020_dynamic-conditionals.bin +| +-- main/ +| | +-- 0x0020_dynamic-conditionals.c +| +-- servo.h ++-- uf2conv.py ``` --- -## 🔬 Part 9: Hands-On Tutorial - Static Conditionals Code +## ? Part 9: Hands-On Tutorial - Static Conditionals Code ### Step 1: Review the Source Code @@ -526,23 +526,23 @@ int main(void) { Since `choice = 1` and NEVER changes: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Static Conditional Execution │ -│ │ -│ Every loop iteration: │ -│ │ -│ 1. Check if (choice == 1) → TRUE → print "1" │ -│ 2. Check switch case 1 → MATCH → print "one" │ -│ 3. Move servo to 0° │ -│ 4. Wait 500ms │ -│ 5. Move servo to 180° │ -│ 6. Wait 500ms │ -│ 7. Repeat forever... │ -│ │ -│ Output always: "1" then "one" (forever) │ -│ Servo: sweeps 0° → 180° → 0° → 180° (forever) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Static Conditional Execution | +| | +| Every loop iteration: | +| | +| 1. Check if (choice == 1) -> TRUE -> print "1" | +| 2. Check switch case 1 -> MATCH -> print "one" | +| 3. Move servo to 0? | +| 4. Wait 500ms | +| 5. Move servo to 180? | +| 6. Wait 500ms | +| 7. Repeat forever... | +| | +| Output always: "1" then "one" (forever) | +| Servo: sweeps 0? -> 180? -> 0? -> 180? (forever) | +| | ++-----------------------------------------------------------------+ ``` ### Step 3: Flash the Binary to Your Pico 2 @@ -567,12 +567,12 @@ one ``` **Watch the servo:** -- It should sweep from 0° to 180° every second +- It should sweep from 0? to 180? every second - The movement is continuous and repetitive --- -## 🔬 Part 10: Debugging with GDB (Static Conditionals) +## ? Part 10: Debugging with GDB (Static Conditionals) ### Step 5: Start OpenOCD (Terminal 1) @@ -580,7 +580,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" @@ -704,7 +704,7 @@ quit --- -## 🔬 Part 11: Setting Up Ghidra for Static Conditionals +## ? Part 11: Setting Up Ghidra for Static Conditionals ### Step 17: Start Ghidra @@ -716,7 +716,7 @@ ghidraRun ### Step 18: Create a New Project -1. Click **File** → **New Project** +1. Click **File** -> **New Project** 2. Select **Non-Shared Project** 3. Click **Next** 4. Enter Project Name: `0x001d_static-conditionals` @@ -730,12 +730,12 @@ ghidraRun ### Step 20: Configure the Binary Format -**Click the three dots (…) next to "Language" and:** +**Click the three dots (...) next to "Language" and:** 1. Search for "Cortex" 2. Select **ARM Cortex 32 little endian default** 3. Click **OK** -**Click the "Options…" button and:** +**Click the "Options..." button and:** 1. Change **Block Name** to `.text` 2. Change **Base Address** to `10000000` 3. Click **OK** @@ -750,12 +750,12 @@ Wait for analysis to complete. --- -## 🔬 Part 12: Resolving Functions in Ghidra (Static) +## ? Part 12: Resolving Functions in Ghidra (Static) ### Step 22: Navigate to Main 1. Press `G` (Go to address) and type `10000234` -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `int main(void)` 4. Click **OK** @@ -764,7 +764,7 @@ Wait for analysis to complete. At address `0x10000236`: 1. Double-click on the called function -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `bool stdio_init_all(void)` 4. Click **OK** @@ -777,7 +777,7 @@ movs r0, #0x6 ; GPIO pin 6 bl FUN_xxxxx ; servo_init ``` -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `void servo_init(uint pin)` 3. Click **OK** @@ -796,7 +796,7 @@ bl FUN_xxxxx ; puts - The hex `0x0d` is carriage return "\r" - We see "1" echoed in PuTTY -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `int puts(char *s)` 3. Click **OK** @@ -808,7 +808,7 @@ Look for a function that loads float constants. Inside the function, you'll find These are the servo pulse limits! -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `void servo_set_angle(float degrees)` 3. Click **OK** @@ -821,17 +821,17 @@ ldr r0, =0x1f4 ; 500 milliseconds bl FUN_xxxxx ; sleep_ms ``` -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `void sleep_ms(uint ms)` 3. Click **OK** --- -## 🔬 Part 13: Hacking Static Conditionals +## ? Part 13: Hacking Static Conditionals ### Step 28: Open the Bytes Editor -1. Click **Window** → **Bytes** +1. Click **Window** -> **Bytes** 2. A new panel appears showing raw hex bytes 3. Click the pencil icon to enable editing @@ -874,7 +874,7 @@ Find the sleep_ms delay value: ### Step 32: Export and Flash -1. Click **File** → **Export Program** +1. Click **File** -> **Export Program** 2. Set **Format** to **Binary** 3. Name: `0x001d_static-conditionals-h.bin` 4. Click **OK** @@ -882,7 +882,7 @@ Find the sleep_ms delay value: Convert and flash: ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001d_static-conditionals +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x001d_static-conditionals python ..\uf2conv.py build\0x001d_static-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` @@ -901,7 +901,7 @@ fun --- -## 🔬 Part 14: Dynamic Conditionals - The Source Code +## ? Part 14: Dynamic Conditionals - The Source Code ### Step 34: Review the Dynamic Code @@ -958,8 +958,8 @@ int main(void) { | User Types | Output | Servo Action | | ------------- | ----------- | ------------ | -| '1' (0x31) | "1" + "one" | 0° → 180° | -| '2' (0x32) | "2" + "two" | 180° → 0° | +| '1' (0x31) | "1" + "one" | 0? -> 180? | +| '2' (0x32) | "2" + "two" | 180? -> 0? | | Anything else | "??" + "??" | No movement | ### Step 36: Flash and Test @@ -972,7 +972,7 @@ int main(void) { --- -## 🔬 Part 15: Debugging with GDB (Dynamic Conditionals) +## ? Part 15: Debugging with GDB (Dynamic Conditionals) ### Step 37: Start OpenOCD (Terminal 1) @@ -980,7 +980,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" @@ -1103,7 +1103,7 @@ quit --- -## 🔬 Part 16: Setting Up Ghidra for Dynamic Conditionals +## ? Part 16: Setting Up Ghidra for Dynamic Conditionals ### Step 49: Create New Project @@ -1120,12 +1120,12 @@ Press `G` and go to `10000234`. Follow the same process: -1. **main** at `0x10000234` → `int main(void)` -2. **stdio_init_all** → `bool stdio_init_all(void)` -3. **servo_init** → `void servo_init(uint pin)` -4. **puts** → `int puts(char *s)` -5. **servo_set_angle** → `void servo_set_angle(float degrees)` -6. **sleep_ms** → `void sleep_ms(uint ms)` +1. **main** at `0x10000234` -> `int main(void)` +2. **stdio_init_all** -> `bool stdio_init_all(void)` +3. **servo_init** -> `void servo_init(uint pin)` +4. **puts** -> `int puts(char *s)` +5. **servo_set_angle** -> `void servo_set_angle(float degrees)` +6. **sleep_ms** -> `void sleep_ms(uint ms)` ### Step 52: Identify getchar @@ -1140,7 +1140,7 @@ cmp r4, #0x31 ; Compare to '1' beq LAB_xxxxx ; Branch if equal ``` -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `int getchar(void)` 3. Click **OK** @@ -1159,7 +1159,7 @@ This confirms it's a UART initialization function! --- -## 🔬 Part 17: Understanding Branch Instructions +## ? Part 17: Understanding Branch Instructions ### ARM Branch Instructions @@ -1189,25 +1189,25 @@ skip_printf: ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Conditional Branch Flow │ -│ │ -│ cmp r4, #0x31 │ -│ │ │ -│ │ (Sets flags based on r4 - 0x31) │ -│ ▼ │ -│ beq target_address │ -│ │ │ -│ ├── If r4 == 0x31: Jump to target_address │ -│ │ │ -│ └── If r4 != 0x31: Continue to next instruction │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Conditional Branch Flow | +| | +| cmp r4, #0x31 | +| | | +| | (Sets flags based on r4 - 0x31) | +| ? | +| beq target_address | +| | | +| +-- If r4 == 0x31: Jump to target_address | +| | | +| +-- If r4 != 0x31: Continue to next instruction | +| | ++-----------------------------------------------------------------+ ``` --- -## 🔬 Part 18: Advanced Hacking - Creating Stealth Commands +## ? Part 18: Advanced Hacking - Creating Stealth Commands ### The Goal @@ -1219,14 +1219,14 @@ We want to create **secret commands** that: ### Step 54: Plan the Patches **Original behavior:** -- '1' (0x31) → prints "1" and "one", moves servo -- '2' (0x32) → prints "2" and "two", moves servo +- '1' (0x31) -> prints "1" and "one", moves servo +- '2' (0x32) -> prints "2" and "two", moves servo **Hacked behavior:** -- 'x' (0x78) → moves servo SILENTLY -- 'y' (0x79) → moves servo SILENTLY -- '1' (0x31) → prints "1" and "one" (normal) -- '2' (0x32) → prints "2" and "two" (normal) +- 'x' (0x78) -> moves servo SILENTLY +- 'y' (0x79) -> moves servo SILENTLY +- '1' (0x31) -> prints "1" and "one" (normal) +- '2' (0x32) -> prints "2" and "two" (normal) ### Step 55: Change Comparison Values @@ -1244,12 +1244,12 @@ For the stealth keys, we need to jump PAST the printf calls directly to the serv **Original flow:** ``` -compare → branch → printf("1") → printf("one") → servo code +compare -> branch -> printf("1") -> printf("one") -> servo code ``` **Hacked flow:** ``` -compare 'x' → branch → [skip prints] → servo code +compare 'x' -> branch -> [skip prints] -> servo code ``` Change the `beq` target addresses: @@ -1279,14 +1279,14 @@ Here's the complete patch list: ### Step 59: Hack the Angle Value -Let's also change 180° to 30° for fun! +Let's also change 180? to 30? for fun! **Original:** `0x43340000` (180.0f in IEEE-754) **New:** `0x41f00000` (30.0f in IEEE-754) **Calculation for 30.0f:** ``` -30.0 = 1.875 × 2^4 +30.0 = 1.875 * 2^4 Sign = 0 Exponent = 127 + 4 = 131 = 0x83 Mantissa = 0.875 = 0x700000 @@ -1302,19 +1302,19 @@ Little-endian: 00 00 f0 41 2. Convert to UF2: ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0020_dynamic-conditionals python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` 3. Flash and test: - - Press '1' → prints "1" and "one", servo moves - - Press '2' → prints "2" and "two", servo moves - - Press 'x' → NO OUTPUT, but servo moves silently! - - Press 'y' → NO OUTPUT, but servo moves silently! + - Press '1' -> prints "1" and "one", servo moves + - Press '2' -> prints "2" and "two", servo moves + - Press 'x' -> NO OUTPUT, but servo moves silently! + - Press 'y' -> NO OUTPUT, but servo moves silently! --- -## 📊 Part 19: Summary and Review +## ? Part 19: Summary and Review ### What We Accomplished @@ -1331,38 +1331,38 @@ python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 - ### Static vs Dynamic Summary ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Static Conditionals │ -│ ─────────────────── │ -│ • Variable set once, never changes │ -│ • Same path taken every iteration │ -│ • Compiler may optimize out dead branches │ -│ • Example: int choice = 1; if (choice == 1) │ -├─────────────────────────────────────────────────────────────────┤ -│ Dynamic Conditionals │ -│ ──────────────────── │ -│ • Variable changes based on input/sensors │ -│ • Different paths taken based on runtime state │ -│ • All branches must remain in binary │ -│ • Example: choice = getchar(); if (choice == '1') │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Static Conditionals | +| ------------------- | +| - Variable set once, never changes | +| - Same path taken every iteration | +| - Compiler may optimize out dead branches | +| - Example: int choice = 1; if (choice == 1) | ++-----------------------------------------------------------------+ +| Dynamic Conditionals | +| -------------------- | +| - Variable changes based on input/sensors | +| - Different paths taken based on runtime state | +| - All branches must remain in binary | +| - Example: choice = getchar(); if (choice == '1') | ++-----------------------------------------------------------------+ ``` ### PWM Calculation Summary ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Servo PWM Calculation Chain │ -│ │ -│ Angle (degrees) → Pulse Width (µs) → PWM Ticks → Servo Motion │ -│ │ -│ 0° → 1000 µs → 1000 ticks → Fully counter-clockwise │ -│ 90° → 1500 µs → 1500 ticks → Center position │ -│ 180° → 2000 µs → 2000 ticks → Fully clockwise │ -│ │ -│ Formula: pulse = 1000 + (angle/180) × 1000 │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Servo PWM Calculation Chain | +| | +| Angle (degrees) -> Pulse Width (?s) -> PWM Ticks -> Servo Motion | +| | +| 0? -> 1000 ?s -> 1000 ticks -> Fully counter-clockwise | +| 90? -> 1500 ?s -> 1500 ticks -> Center position | +| 180? -> 2000 ?s -> 2000 ticks -> Fully clockwise | +| | +| Formula: pulse = 1000 + (angle/180) * 1000 | +| | ++-----------------------------------------------------------------+ ``` ### Key Memory Addresses @@ -1378,36 +1378,9 @@ python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 - --- -## ✅ Practice Exercises - -### Exercise 1: Change Servo Angle Range -Modify the servo to sweep from 45° to 135° instead of 0° to 180°. - -**Hint:** Calculate IEEE-754 values for 45.0f and 135.0f. - -### Exercise 2: Add a Third Command -Add support for key '3' that moves the servo to 90° (center position). - -**Hint:** You'll need to find space in the binary or modify existing code. - -### Exercise 3: Reverse the Servo Direction -Make '1' do what '2' does and vice versa. - -**Hint:** Swap the branch targets. - -### Exercise 4: Speed Profile -Create fast movement for '1' (100ms) and slow movement for '2' (1000ms). - -**Hint:** Find both sleep_ms calls and patch them differently. - -### Exercise 5: Complete Stealth Mode -Make ALL servo movements silent - remove ALL printf and puts calls. - -**Hint:** NOP out every output function call. - --- -## 🎓 Key Takeaways +## ? Key Takeaways 1. **Static conditionals have fixed outcomes** - The same path always executes @@ -1415,7 +1388,7 @@ Make ALL servo movements silent - remove ALL printf and puts calls. 3. **PWM frequency = 50Hz for servos** - One pulse every 20ms -4. **Pulse width encodes position** - 1ms=0°, 1.5ms=90°, 2ms=180° +4. **Pulse width encodes position** - 1ms=0?, 1.5ms=90?, 2ms=180? 5. **beq = branch if equal** - Jumps when comparison matches @@ -1431,7 +1404,7 @@ Make ALL servo movements silent - remove ALL printf and puts calls. --- -## 📖 Glossary +## ? Glossary | Term | Definition | | ----------------------- | --------------------------------------------------- | @@ -1449,7 +1422,7 @@ Make ALL servo movements silent - remove ALL printf and puts calls. --- -## 🔗 Additional Resources +## ? Additional Resources ### ASCII Reference Table @@ -1491,7 +1464,7 @@ Make ALL servo movements silent - remove ALL printf and puts calls. --- -## 🚨 Real-World Implications +## ? Real-World Implications ### Why Stealth Commands Matter @@ -1526,4 +1499,6 @@ A fast-moving servo is like a nuclear fuel rod: **Remember:** The techniques you learned today demonstrate how conditional logic can be manipulated at the binary level. Understanding these attacks helps us build more secure embedded systems. Always use your skills ethically and responsibly! -Happy hacking! 🔧 +Happy hacking! ? + + diff --git a/WEEK11/WEEK11-01-S.md b/WEEK11/WEEK11-01-S.md deleted file mode 100644 index a4e934b..0000000 --- a/WEEK11/WEEK11-01-S.md +++ /dev/null @@ -1,66 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 11 -Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control - -### Non-Credit Practice Exercise 1 Solution: Add a Fourth LED - -#### Answers - -##### Struct Layout (simple_led_ctrl_t) - -| Offset | Field | Size | Original Value | Hex | -|--------|------------|--------|----------------|------| -| 0 | led1_pin | 1 byte | GPIO 16 | 0x10 | -| 1 | led2_pin | 1 byte | GPIO 17 | 0x11 | -| 2 | led3_pin | 1 byte | GPIO 18 | 0x12 | -| 3 | led1_state | 1 byte | false (0) | 0x00 | -| 4 | led2_state | 1 byte | false (0) | 0x00 | -| 5 | led3_state | 1 byte | false (0) | 0x00 | - -Total struct size: **6 bytes** (3 pin bytes + 3 state bytes). - -##### Assembly Initialization Pattern - -```asm -movs r0, #0x10 ; led1_pin = 16 -strb r0, [r4, #0] ; struct offset 0 -movs r0, #0x11 ; led2_pin = 17 -strb r0, [r4, #1] ; struct offset 1 -movs r0, #0x12 ; led3_pin = 18 -strb r0, [r4, #2] ; struct offset 2 -``` - -##### Patch: Add GPIO 19 at Struct Offset 3 - -Writing `0x13` to offset 3 **overwrites led1_state**: - -| Offset | Before | After | Impact | -|--------|-------------|-------------|----------------------| -| 3 | 0x00 (led1_state = false) | 0x13 (led4_pin = GPIO 19) | led1_state corrupted | - -##### GDB Verification - -```gdb -(gdb) b *0x10000280 -(gdb) c -(gdb) x/8bx -``` - -Before patch: `10 11 12 00 00 00` -After patch: `10 11 12 13 00 00` - -#### Reflection Answers - -1. **The original struct has 6 members (3 pins + 3 states) in 6 bytes. If you add a fourth pin at offset 3, you overwrite led1_state. What is the practical impact on LED 1 behavior?** - The byte `0x13` (decimal 19) is written to offset 3, which the program reads as `led1_state`. Since `bool` in C treats any non-zero value as `true`, `led1_state` would be interpreted as `true` (on) immediately after the struct is initialized. LED 1 would appear to be in the "on" state from the start, regardless of whether the user pressed button 1. The `leds_all_off` function may reset it to 0, but every time the struct is re-initialized on the stack (each loop iteration), the corrupted state returns. The fourth LED at GPIO 19 would need additional `gpio_init` and `gpio_set_dir` calls to actually function — just writing the pin number into the struct doesn't configure the GPIO hardware. - -2. **How would you verify the exact struct layout and offsets using GDB's memory examination commands?** - Set a breakpoint after struct initialization (`b *0x10000280`), then `x/6bx ` to see all 6 bytes. Verify: offsets 0–2 should show `10 11 12` (pin values), offsets 3–5 should show `00 00 00` (state values). Use `x/1bx ` for individual fields. To find the struct base, examine `r4` at the breakpoint since the `strb r0, [r4, #N]` instructions use r4 as the base. You can also use `p/x $r4` to get the base address, then `x/6bx $r4` for the complete layout. - -3. **If the get_led_pin function uses a bounds check (e.g., if led_num > 3 return 0), what additional patch would you need?** - You would need to find the comparison instruction in `get_led_pin` (at approximately `0x100002a0`) — likely a `cmp rN, #3` followed by a conditional branch. Patch the immediate from `#3` to `#4` so the bounds check allows led_num = 4. For example, if the check is `cmp r1, #3; bhi default`, change `03` to `04` in the `cmp` instruction's immediate byte. Without this patch, passing led_num=4 would fail the bounds check and return 0 (no pin), so the fourth LED would never be addressed. - -4. **Could you extend the struct without overwriting existing fields by finding free space elsewhere in the binary? What challenges would that introduce?** - You could find unused space (padding, NOP sleds, or unused data) and place the extended struct there. However, this introduces major challenges: (1) Every instruction that references the original struct address via `r4` would need to be redirected to the new location. (2) All `strb`/`ldrb` offsets would need updating. (3) Stack-allocated structs are recreated each loop iteration — you'd need to change the stack frame size (`sub sp, sp, #N`). (4) Functions that receive the struct pointer as an argument would need their call sites updated. In practice, relocating a struct in a compiled binary is extremely complex and error-prone — overwriting adjacent fields is the pragmatic (if destructive) approach. diff --git a/WEEK11/WEEK11-01.md b/WEEK11/WEEK11-01.md deleted file mode 100644 index 6dcdb83..0000000 --- a/WEEK11/WEEK11-01.md +++ /dev/null @@ -1,156 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 11 -Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics - -### Non-Credit Practice Exercise 1: Add a Fourth LED - -#### Objective -Find the struct initialization pattern in the `0x0026_functions` binary using GDB where `led1_pin` (0x10), `led2_pin` (0x11), and `led3_pin` (0x12) are stored, locate an unused byte in the struct memory region, and patch it to include a fourth LED on GPIO 19 (0x13) by extending the struct data and modifying the `ir_to_led_number` function to handle a fourth button mapping. - -#### Prerequisites -- Completed Week 11 tutorial (GDB and hex editor sections) -- `0x0026_functions.elf` and `0x0026_functions.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with IR remote and LEDs on GPIO 16, 17, 18 (and GPIO 19 wired for the new LED) - -#### Task Description -The `simple_led_ctrl_t` struct stores three LED pin numbers: `led1_pin` (16/0x10), `led2_pin` (17/0x11), `led3_pin` (18/0x12). These are stored as consecutive bytes in the struct initialization. You will find where the struct is initialized in the binary, locate the `movs` instructions that set the pin values, and add `led4_pin` = 19 (0x13) by patching a nearby unused or default byte. You will also need to find where `ir_to_led_number` returns values 1, 2, or 3 and adjust the NEC command comparison to map a fourth button to LED 4. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0026_functions.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find the Struct Initialization - -Disassemble main and look for the struct pin assignments: - -```gdb -(gdb) disassemble 0x10000234,+300 -``` - -Look for consecutive `movs` instructions: - -``` -movs r0, #0x10 ; led1_pin = 16 -strb r0, [r4, #0] ; store to struct offset 0 -movs r0, #0x11 ; led2_pin = 17 -strb r0, [r4, #1] ; store to struct offset 1 -movs r0, #0x12 ; led3_pin = 18 -strb r0, [r4, #2] ; store to struct offset 2 -``` - -##### Step 3: Examine the Struct in Memory - -Set a breakpoint after initialization and examine the struct: - -```gdb -(gdb) break *0x10000280 -(gdb) monitor reset halt -(gdb) continue -(gdb) x/8bx -``` - -You should see: `10 11 12 00 00 00` — the three pin values followed by the state booleans (all false/0x00). - -##### Step 4: Find the `get_led_pin` Function - -Look for the function that reads from the struct based on LED number: - -```gdb -(gdb) disassemble 0x100002a0,+50 -``` - -This function takes a struct pointer and LED number and returns the GPIO pin by reading from a struct offset. - -##### Step 5: Calculate File Offsets - -``` -file_offset = address - 0x10000000 -``` - -Note offsets for: -1. The `movs r0, #0x12` instruction (last pin assignment) -2. The byte after `led3_pin` in the struct (where `led4_pin` would go) - -##### Step 6: Plan the Patches - -| Patch Target | Original | New | Purpose | -| --------------------- | -------- | ------ | ------------------------- | -| Struct byte after 0x12 | `00` | `13` | Add led4_pin = GPIO 19 | - -###### Question 1: The struct layout has `led3_pin` at offset 2 and `led1_state` at offset 3. If you write `0x13` to offset 3, what happens to `led1_state`? - -##### Step 7: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin` -2. Navigate to the struct initialization area -3. Apply the patches identified in Step 6 - -##### Step 8: Save and Convert - -1. Click **File** → **Save As** → `0x0026_functions-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions -python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 9: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the behavior:** -- Existing buttons 1, 2, 3 should still control their LEDs -- Verify with GDB that the struct now contains `10 11 12 13` at the pin offsets - -#### Expected Output - -After completing this exercise, you should be able to: -- Understand struct memory layout and member offsets -- Identify struct initialization patterns in ARM assembly -- Patch struct data members in binary firmware -- Reason about the consequences of overwriting adjacent struct fields - -#### Questions for Reflection - -###### Question 1: The original struct has 6 members (3 pins + 3 states) in 6 bytes. If you add a fourth pin at offset 3, you overwrite `led1_state`. What is the practical impact on LED 1 behavior? - -###### Question 2: How would you verify the exact struct layout and offsets using GDB's memory examination commands? - -###### Question 3: If the `get_led_pin` function uses a bounds check (e.g., `if led_num > 3 return 0`), what additional patch would you need? - -###### Question 4: Could you extend the struct without overwriting existing fields by finding free space elsewhere in the binary? What challenges would that introduce? - -#### Tips and Hints -- GPIO 19 = `0x13` in hex -- The struct is likely stack-allocated, so the initialization `movs`/`strb` sequence happens every loop iteration -- Overwriting `led1_state` (offset 3) with `0x13` means LED 1 will appear as "on" (non-zero boolean) — this may cause LED 1 to be on at startup -- The `get_led_pin` function likely uses the LED number as an index into the struct — trace how it calculates the offset diff --git a/WEEK11/WEEK11-02-S.md b/WEEK11/WEEK11-02-S.md deleted file mode 100644 index 7b81a3b..0000000 --- a/WEEK11/WEEK11-02-S.md +++ /dev/null @@ -1,62 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 11 -Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control - -### Non-Credit Practice Exercise 2 Solution: Change Blink Count - -#### Answers - -##### Blink Count Parameter - -| Parameter | Original | Patched | -|-------------|---------|---------| -| Blink count | 3 | 5 | -| Hex | 0x03 | 0x05 | -| Register | r1 | r1 | -| Instruction | movs r1, #3 | movs r1, #5 | - -##### Assembly Context (blink_led Call) - -```asm -movs r0, ; r0 = GPIO pin number -movs r1, #3 ; r1 = blink count ← PATCH THIS -movs r2, #0x32 ; r2 = delay (50ms) -bl blink_led ; blink_led(pin, 3, 50) -``` - -##### Patch - -The immediate byte in `movs r1, #3` is the first byte of the 2-byte Thumb instruction: - -``` -Before: 03 21 (movs r1, #3) -After: 05 21 (movs r1, #5) -``` - -File offset = instruction address - 0x10000000. - -##### Behavior After Patch - -| Button | LED | Original | Patched | -|--------|--------|-------------------|---------------------| -| 1 | Red | Blinks 3×, stays on | Blinks 5×, stays on | -| 2 | Green | Blinks 3×, stays on | Blinks 5×, stays on | -| 3 | Yellow | Blinks 3×, stays on | Blinks 5×, stays on | - -Total blink time at 50ms delay: 5 × (50 + 50) = **500ms** (was 300ms). - -#### Reflection Answers - -1. **movs rN, #imm8 can encode values 0–255. What is the maximum blink count with a single byte patch?** - The maximum is **255** (`0xFF`). The `movs Rd, #imm8` Thumb instruction uses a full 8-bit immediate field, giving an unsigned range of 0–255. Setting the blink count to 255 would make each LED blink 255 times per button press — at 50ms on + 50ms off per blink, that's 255 × 100ms = **25.5 seconds** of blinking before the LED stays on. A count of 0 would skip the blink loop entirely (LED turns on immediately with no blinking). - -2. **Why is blink count in r1 and not r0? What does r0 hold at this point?** - The ARM calling convention (AAPCS) passes the first four function arguments in registers `r0`, `r1`, `r2`, `r3` in order. The `blink_led` function signature is `blink_led(uint8_t pin, uint8_t count, uint32_t delay_ms)`. So `r0` = pin (the GPIO number of the LED to blink), `r1` = count (how many times to blink), and `r2` = delay_ms (the delay in milliseconds between on/off transitions). The blink count is the second parameter, hence `r1`. - -3. **If you wanted a blink count larger than 255 (e.g., 1000), what instruction sequence would the compiler generate instead of movs?** - For values exceeding 255, the compiler would use a 32-bit Thumb-2 `movw r1, #imm16` instruction, which can encode 0–65535. For example, `movw r1, #1000` would be 4 bytes: `40 F2 E8 31` (encoding `movw r1, #0x3E8`). For values exceeding 65535, the compiler would add `movt r1, #imm16` to set the upper 16 bits, or use a literal pool load (`ldr r1, [pc, #offset]`). The function parameter type (`uint8_t`) would still truncate to 0–255, so a count of 1000 would wrap to 232 (1000 mod 256) unless the function uses a wider type internally. - -4. **Is there one shared movs r1, #3 instruction for all three LEDs, or does each blink_led call have its own? How can you tell?** - Each `blink_led` call likely has its **own** `movs r1, #3` instruction. The compiler generates separate parameter setup sequences for each `bl blink_led` call site — the `movs r0, ` instruction before each call loads a different GPIO pin. You can verify by disassembling the full range (`disassemble 0x10000234,+300`) and counting how many `movs r1, #3` instructions appear before `bl blink_led` calls. If there are three separate call sites with three separate `movs r1, #3` instructions, you need to **patch all three** to change the blink count for every LED. If only one is patched, only that LED's blink count changes. diff --git a/WEEK11/WEEK11-02.md b/WEEK11/WEEK11-02.md deleted file mode 100644 index 74c60f1..0000000 --- a/WEEK11/WEEK11-02.md +++ /dev/null @@ -1,144 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 11 -Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics - -### Non-Credit Practice Exercise 2: Change Blink Count - -#### Objective -Find the `blink_led(pin, 3, 50)` call in the `0x0026_functions` binary using GDB, identify the immediate value `#3` being loaded into `r1` (the blink count parameter), calculate the file offset, and patch it to `#5` so that each LED blinks 5 times instead of 3 when activated by the IR remote. - -#### Prerequisites -- Completed Week 11 tutorial (GDB and hex editor sections) -- `0x0026_functions.elf` and `0x0026_functions.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with IR remote and LEDs on GPIO 16, 17, 18 - -#### Task Description -The `blink_led` function is called with three parameters: the GPIO pin number, a blink count of `3`, and a delay of `50`ms. The blink count is loaded as a small immediate value (`movs r1, #3`) directly in the instruction before the `bl blink_led` call. You will locate this instruction, find the byte encoding the `#3` immediate, and patch it to `#5` so the LEDs blink 5 times per button press. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0026_functions.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find the blink_led Call - -Disassemble main and look for the function call sequence: - -```gdb -(gdb) disassemble 0x10000234,+300 -``` - -Look for the parameter setup before `bl blink_led`: - -``` -movs r0, ; GPIO pin number (from get_led_pin) -movs r1, #3 ; blink count = 3 -movs r2, #0x32 ; delay = 50ms -bl blink_led -``` - -Note the address of the `movs r1, #3` instruction. - -##### Step 3: Examine the Instruction Encoding - -Look at the raw bytes of the `movs r1, #3` instruction: - -```gdb -(gdb) x/2bx -``` - -In Thumb encoding, `movs r1, #imm8` has the immediate in the lower byte. You should see a byte containing `03`. - -##### Step 4: Calculate the File Offset - -``` -file_offset = address - 0x10000000 -``` - -Note the file offset of the byte containing `03`. - -##### Step 5: Encode the New Value - -| Parameter | Original | New | Encoding | -| ---------- | -------- | ---- | -------------- | -| Blink count | `03` | `05` | `movs r1, #5` | - -##### Step 6: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin` -2. Press **Ctrl+G** and enter the file offset -3. You should see: `03` -4. Replace with: `05` - -###### Question 1: The `movs r1, #3` is a 2-byte Thumb instruction. Which byte contains the immediate — the first or the second? - -##### Step 7: Save and Convert - -1. Click **File** → **Save As** → `0x0026_functions-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions -python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 8: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the behavior:** -- Press button 1 → Red LED blinks **5 times** (was 3), then stays on -- Press button 2 → Green LED blinks **5 times** (was 3), then stays on -- Press button 3 → Yellow LED blinks **5 times** (was 3), then stays on -- Count carefully — you should see exactly 5 on/off cycles - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate small immediate values in Thumb `movs` instructions -- Understand Thumb instruction encoding for immediate operands -- Patch function parameters by modifying instruction immediates -- Verify behavioral changes by counting observable events - -#### Questions for Reflection - -###### Question 1: The `movs rN, #imm8` instruction can encode values 0-255. What is the maximum blink count you could set with a single byte patch? - -###### Question 2: Why is the blink count passed in `r1` and not `r0`? What does `r0` hold at this point in the calling convention? - -###### Question 3: If you wanted to set the blink count to 256 or higher, the `movs` immediate would not be enough. What instruction sequence would the compiler need to generate instead? - -###### Question 4: The same `blink_led` function is called for all three buttons. Does that mean there is only one `movs r1, #3` to patch, or could there be multiple call sites? - -#### Tips and Hints -- Small immediates (0-255) are encoded directly in the `movs` instruction — no literal pool needed -- The ARM calling convention uses `r0`, `r1`, `r2`, `r3` for the first four function parameters in order -- Look for `movs r1, #3` right before `bl blink_led` — there may be one shared call site or multiple per button -- If there are multiple `movs r1, #3` instructions (one per case), you need to patch all of them for consistent behavior diff --git a/WEEK11/WEEK11-03-S.md b/WEEK11/WEEK11-03-S.md deleted file mode 100644 index 891d47c..0000000 --- a/WEEK11/WEEK11-03-S.md +++ /dev/null @@ -1,70 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 11 -Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control - -### Non-Credit Practice Exercise 3 Solution: Swap All Three LEDs - -#### Answers - -##### GPIO Rotation Patch - -| Struct Member | Original | Patched | Effect | -|--------------|-----------------|-----------------|---------------------| -| led1_pin | 0x10 (GPIO 16 Red) | 0x11 (GPIO 17 Green) | Button 1 → Green | -| led2_pin | 0x11 (GPIO 17 Green) | 0x12 (GPIO 18 Yellow) | Button 2 → Yellow | -| led3_pin | 0x12 (GPIO 18 Yellow) | 0x10 (GPIO 16 Red) | Button 3 → Red | - -##### Assembly Patches - -Three single-byte patches in `movs` immediate fields: - -``` -Patch 1 (led1_pin): 10 → 11 -Before: 10 20 (movs r0, #0x10) -After: 11 20 (movs r0, #0x11) - -Patch 2 (led2_pin): 11 → 12 -Before: 11 20 (movs r0, #0x11) -After: 12 20 (movs r0, #0x12) - -Patch 3 (led3_pin): 12 → 10 -Before: 12 20 (movs r0, #0x12) -After: 10 20 (movs r0, #0x10) -``` - -##### GDB Verification - -```gdb -(gdb) b *0x10000280 -(gdb) c -(gdb) x/6bx -``` - -Before patch: `10 11 12 00 00 00` -After patch: `11 12 10 00 00 00` - -##### Behavior After Patch - -| Button (IR) | NEC Code | Terminal Log | Actual LED | -|------------|----------|---------------------------|-----------| -| Button 1 | 0x0C | "LED 1 activated on GPIO 16" | Green (GPIO 17) | -| Button 2 | 0x18 | "LED 2 activated on GPIO 17" | Yellow (GPIO 18) | -| Button 3 | 0x5E | "LED 3 activated on GPIO 18" | Red (GPIO 16) | - -The terminal logs are **desynchronized** from actual behavior. - -#### Reflection Answers - -1. **Terminal log still says "LED 1 activated on GPIO 16" even though GPIO 17 (Green) is actually blinking. Why don't the logs update automatically?** - The `printf` format strings and their arguments are separate from the struct pin assignments. The log message "LED 1 activated on GPIO 16" is generated from hardcoded format strings or from reading the **original** pin value before our patch takes effect. The GPIO number in the log comes from a different code path — likely a format string like `"LED %d activated on GPIO %d\r\n"` where the GPIO value was loaded from the struct at a different point or is computed independently. Since we only patched the `movs` instructions that store pin values into the struct, the logging code still uses whatever values it computes independently. - -2. **If the struct initialization used ldr from a literal pool instead of movs immediates, how would the patching differ?** - With literal pool loads, the pin values would be stored as 32-bit words in a data area near the function code. You would need to: (1) find the `ldr r0, [pc, #offset]` instruction, (2) calculate the PC-relative offset to locate the literal pool entry, (3) navigate to the pool address in the hex editor, and (4) modify the 4-byte value there. For example, GPIO 16 would be `10 00 00 00` (little-endian) in the pool. This is more work than patching a 1-byte `movs` immediate, and you'd need to verify no other code shares the same pool entry. The `movs` approach is simpler because the value is encoded directly in the instruction. - -3. **Could you achieve the same LED rotation by patching gpio_init/gpio_put calls instead of the struct initialization? Which approach is cleaner?** - Patching `gpio_init` and `gpio_put` calls would require finding every call site that references each GPIO pin and modifying the pin argument. This is scattered throughout multiple functions (`process_ir_led_command`, `blink_led`, `leds_all_off`). The struct initialization approach is **far cleaner** — three adjacent `movs` instructions in one location control the entire mapping. By patching the struct data at its source, every function that reads from the struct automatically gets the new values. This demonstrates the power of data-driven design: changing the data at one point affects all code that uses it. - -4. **In a real attack, why is log desynchronization (display says one thing, hardware does another) dangerous for forensic analysis?** - Log desynchronization is dangerous because forensic investigators rely on logs to reconstruct what happened. If logs show "LED 1 on GPIO 16" but the hardware actually activated GPIO 17, investigators would draw incorrect conclusions about which physical device was controlled. In industrial systems, this could mask sabotage — operators see "normal" readings while equipment is being misused. In security systems, tampered firmware could log "door locked" while actually unlocking it. The logs become actively misleading, not just incomplete. This is a form of **anti-forensics** that makes post-incident analysis unreliable and can delay or prevent discovery of the actual attack. diff --git a/WEEK11/WEEK11-03.md b/WEEK11/WEEK11-03.md deleted file mode 100644 index 3d96833..0000000 --- a/WEEK11/WEEK11-03.md +++ /dev/null @@ -1,150 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 11 -Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics - -### Non-Credit Practice Exercise 3: Swap All Three LEDs - -#### Objective -Find the struct initialization instructions where `led1_pin` = 0x10 (GPIO 16, Red), `led2_pin` = 0x11 (GPIO 17, Green), and `led3_pin` = 0x12 (GPIO 18, Yellow) are written in the `0x0026_functions` binary using GDB, calculate the file offsets, and rotate the GPIO values so that button 1→Green (0x11), button 2→Yellow (0x12), and button 3→Red (0x10), then verify on hardware that the LED mapping has shifted. - -#### Prerequisites -- Completed Week 11 tutorial (GDB and hex editor sections) -- `0x0026_functions.elf` and `0x0026_functions.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with IR remote and LEDs on GPIO 16, 17, 18 - -#### Task Description -The struct initialization sets `led1_pin` = 16 (0x10), `led2_pin` = 17 (0x11), `led3_pin` = 18 (0x12) using `movs` instructions that store each value into the struct. By patching the immediate values in these three `movs` instructions, you can rotate the LED assignment: `led1_pin` = 17 (Green), `led2_pin` = 18 (Yellow), `led3_pin` = 16 (Red). This means button 1 will light the Green LED, button 2 the Yellow LED, and button 3 the Red LED. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0026_functions.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find the Three movs Instructions - -Disassemble main and find the struct pin initialization: - -```gdb -(gdb) disassemble 0x10000234,+300 -``` - -Look for three consecutive `movs`/`strb` pairs: - -``` -movs r0, #0x10 ; led1_pin = 16 (Red) -strb r0, [r4, #0] -movs r0, #0x11 ; led2_pin = 17 (Green) -strb r0, [r4, #1] -movs r0, #0x12 ; led3_pin = 18 (Yellow) -strb r0, [r4, #2] -``` - -Note the address of each `movs` instruction. - -##### Step 3: Examine the Instruction Bytes - -Check the raw encoding of each `movs`: - -```gdb -(gdb) x/2bx -(gdb) x/2bx -(gdb) x/2bx -``` - -Each will have the GPIO pin number as the immediate byte. - -##### Step 4: Calculate the File Offsets - -``` -file_offset = address - 0x10000000 -``` - -Note the offset of the immediate byte in each of the three `movs` instructions. - -##### Step 5: Plan the Rotation - -| Struct Member | Original | New | Effect | -| ------------- | ----------------- | ----------------- | ---------------- | -| `led1_pin` | `10` (GPIO 16 Red) | `11` (GPIO 17 Green) | Button 1 → Green | -| `led2_pin` | `11` (GPIO 17 Green) | `12` (GPIO 18 Yellow) | Button 2 → Yellow | -| `led3_pin` | `12` (GPIO 18 Yellow) | `10` (GPIO 16 Red) | Button 3 → Red | - -##### Step 6: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin` -2. Go to the first `movs` immediate offset and change `10` to `11` -3. Go to the second `movs` immediate offset and change `11` to `12` -4. Go to the third `movs` immediate offset and change `12` to `10` - -###### Question 1: All three patches are single-byte changes in `movs` immediates. Why is this simpler than patching literal pool entries? - -##### Step 7: Save and Convert - -1. Click **File** → **Save As** → `0x0026_functions-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions -python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 8: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the behavior:** -- Press button 1 → **Green** LED blinks (was Red) -- Press button 2 → **Yellow** LED blinks (was Green) -- Press button 3 → **Red** LED blinks (was Yellow) -- Terminal still says "LED 1 activated on GPIO 16" — but the actual LED is Green (GPIO 17) - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate struct initialization patterns in ARM Thumb assembly -- Patch multiple `movs` immediates to rotate data values -- Understand the disconnect between logged values and actual hardware behavior -- Recognize log desynchronization as a security concern - -#### Questions for Reflection - -###### Question 1: The terminal log still says "LED 1 activated on GPIO 16" even though GPIO 17 (Green) is actually blinking. Why don't the logs update automatically? - -###### Question 2: If the struct initialization used `ldr` from a literal pool instead of `movs` immediates, how would the patching approach differ? - -###### Question 3: Could you achieve the same LED rotation by patching the `gpio_init` and `gpio_put` calls instead of the struct? Which approach is cleaner and why? - -###### Question 4: In a real attack scenario, why is log desynchronization (logs say one thing, hardware does another) particularly dangerous for forensic analysis? - -#### Tips and Hints -- The three `movs` instructions are likely within 10-20 bytes of each other — use `x/20i` to see them all at once -- The `movs rN, #imm8` immediate is in the lower byte of the 2-byte Thumb instruction -- Make sure you patch the `movs` for the struct initialization, not any other `movs #0x10/0x11/0x12` that may exist elsewhere -- Verify by examining the struct in memory after initialization: `x/6bx ` should show `11 12 10` for the pin bytes diff --git a/WEEK11/WEEK11-04-S.md b/WEEK11/WEEK11-04-S.md deleted file mode 100644 index 17629b2..0000000 --- a/WEEK11/WEEK11-04-S.md +++ /dev/null @@ -1,60 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 11 -Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control - -### Non-Credit Practice Exercise 4 Solution: Change Blink Speed - -#### Answers - -##### Delay Parameter - -| Parameter | Original | Patched | -|----------|-------------|--------------| -| Delay | 50ms | 25ms | -| Hex | 0x32 | 0x19 | -| Register | r2 | r2 | -| Instruction | movs r2, #0x32 | movs r2, #0x19 | - -##### Assembly Context - -```asm -movs r0, ; r0 = GPIO pin -movs r1, #3 ; r1 = blink count -movs r2, #0x32 ; r2 = delay 50ms ← PATCH THIS -bl blink_led ; blink_led(pin, 3, 50) -``` - -##### Patch - -``` -Before: 32 22 (movs r2, #0x32 = 50ms) -After: 19 22 (movs r2, #0x19 = 25ms) -``` - -**Warning:** The byte `0x32` is also ASCII '2'. Verify you're patching the correct `movs r2` instruction by checking surrounding bytes — `movs r1, #3` (`03 21`) should appear immediately before, and `bl blink_led` immediately after. - -##### Timing Comparison - -| Metric | Original (50ms) | Patched (25ms) | -|-------------------|-----------------|----------------| -| On-time per blink | 50ms | 25ms | -| Off-time per blink| 50ms | 25ms | -| One blink cycle | 100ms | 50ms | -| 3 blinks total | 300ms | 150ms | -| Perceived speed | Normal | 2× faster | - -#### Reflection Answers - -1. **If delay = 1ms (0x01), would you still see the LED blink, or would it appear constantly on?** - At 1ms on/off (2ms per cycle, 500Hz flicker), the LED would appear **constantly on** to the human eye. Human flicker fusion threshold is approximately 60Hz — anything above that appears as a steady light. At 500Hz, the LED is switching far too fast for the eye to perceive individual blinks. The LED would look like it's at roughly 50% brightness (since it's on half the time) compared to being fully on. The 3 blinks would complete in just 6ms total, appearing as a brief flash rather than distinct blinks. - -2. **0x32 appears as both the delay value (50ms) and potentially ASCII '2'. How would you systematically find ALL occurrences of 0x32 and determine which to patch?** - Search the binary for all `0x32` bytes, then examine the **context** of each occurrence: (1) Check the byte following `0x32` — if it's `0x22`, this is `movs r2, #0x32` (the delay parameter). If it's `0x2C`, it's `cmp r4, #0x32` (comparing against ASCII '2'). (2) Examine surrounding instructions: the delay `0x32` will be preceded by `movs r1, #3` (blink count) and followed by `bl blink_led`. A comparison `0x32` will be near `beq`/`bne` branches. (3) Use GDB to disassemble the region (`x/10i `) and read the instruction mnemonic. (4) Cross-reference with the function structure — delay patches are in `blink_led` call setup, comparisons are in `ir_to_led_number` or similar dispatcher functions. - -3. **For a delay of 500ms (0x1F4), the value won't fit in a movs immediate (max 255). How would the compiler handle it?** - For 500 (`0x1F4`), the compiler would use either: (1) A 32-bit `movw r2, #0x1F4` Thumb-2 instruction (4 bytes), which can encode any 16-bit immediate (0–65535). (2) A literal pool load: `ldr r2, [pc, #offset]` that reads `0x000001F4` from a nearby data word. The `movw` approach is preferred for values 256–65535 because it's a single instruction with no data dependency. For values exceeding 65535, a literal pool or `movw`+`movt` pair would be necessary. - -4. **The blink function uses the delay for both on-time and off-time (symmetrical blink). Could you make the LED stay on longer than off by patching only one instruction?** - Not with a single patch to the `movs r2` instruction, because `blink_led` uses the same delay parameter for both the on-phase and off-phase `sleep_ms` calls internally. To create asymmetric blink timing, you would need to patch **inside** the `blink_led` function itself — find the two `sleep_ms` calls within the blink loop and modify their delay arguments independently. For example, find the `ldr`/`movs` that sets up `r0` before each `bl sleep_ms` inside `blink_led`, and patch one to a different value. This would require disassembling `blink_led` to locate both `sleep_ms` call sites. diff --git a/WEEK11/WEEK11-04.md b/WEEK11/WEEK11-04.md deleted file mode 100644 index 1ce0b49..0000000 --- a/WEEK11/WEEK11-04.md +++ /dev/null @@ -1,146 +0,0 @@ -# Embedded Systems Reverse Engineering -[Repository](https://github.com/mytechnotalent/Embedded-Hacking) - -## Week 11 -Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics - -### Non-Credit Practice Exercise 4: Change Blink Speed - -#### Objective -Find the `blink_led(pin, 3, 50)` call in the `0x0026_functions` binary using GDB, identify the immediate value `0x32` (50) being loaded into `r2` (the delay parameter), calculate the file offset, and patch it to `0x19` (25) so that each LED blinks at double speed when activated by the IR remote. - -#### Prerequisites -- Completed Week 11 tutorial (GDB and hex editor sections) -- `0x0026_functions.elf` and `0x0026_functions.bin` available in your build directory -- GDB (`arm-none-eabi-gdb`) and OpenOCD installed -- A hex editor (HxD, ImHex, or similar) -- Python installed (for UF2 conversion) -- Raspberry Pi Pico 2 with IR remote and LEDs on GPIO 16, 17, 18 - -#### Task Description -The `blink_led` function takes a delay parameter of `50`ms (`0x32`) in register `r2`. This value controls how long each LED stays on and off during the blink cycle. By patching this to `25`ms (`0x19`), the LEDs will blink twice as fast, creating a noticeably quicker flashing pattern when any IR remote button is pressed. - -#### Step-by-Step Instructions - -##### Step 1: Start the Debug Session - -**Terminal 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" -``` - -**Terminal 2 - Start GDB:** - -```powershell -arm-none-eabi-gdb build\0x0026_functions.elf -``` - -**Connect to target:** - -```gdb -(gdb) target remote :3333 -(gdb) monitor reset halt -``` - -##### Step 2: Find the blink_led Call - -Disassemble main and look for the parameter setup before `bl blink_led`: - -```gdb -(gdb) disassemble 0x10000234,+300 -``` - -Look for: - -``` -movs r0, ; GPIO pin number -movs r1, #3 ; blink count -movs r2, #0x32 ; delay = 50ms -bl blink_led -``` - -Note the address of the `movs r2, #0x32` instruction. - -##### Step 3: Examine the Instruction Encoding - -Look at the raw bytes: - -```gdb -(gdb) x/2bx -``` - -The `movs r2, #0x32` instruction has `0x32` (50) as the immediate byte. - -##### Step 4: Calculate the File Offset - -``` -file_offset = address - 0x10000000 -``` - -Note the file offset of the byte containing `32`. - -##### Step 5: Encode the New Value - -| Parameter | Original | New | Effect | -| --------- | --------------- | --------------- | --------------- | -| Delay | `32` (50ms) | `19` (25ms) | 2x faster blink | - -**Be careful:** `0x32` is also the ASCII code for '2'. Make sure you are patching the `movs r2` instruction and not a comparison value like `cmp r4, #0x32`. - -##### Step 6: Patch with HxD - -1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin` -2. Press **Ctrl+G** and enter the file offset -3. You should see: `32` -4. Replace with: `19` - -###### Question 1: How can you confirm you are patching the delay parameter and not some other `0x32` byte in the binary? - -##### Step 7: Save and Convert - -1. Click **File** → **Save As** → `0x0026_functions-h.bin` - -```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions -python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 -``` - -##### Step 8: Flash and Verify - -1. Hold BOOTSEL and plug in your Pico 2 -2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive - -**Check the behavior:** -- Press button 1 → Red LED blinks 3 times but **noticeably faster** (25ms on/off vs 50ms) -- Press button 2 → Green LED blinks 3 times at **double speed** -- Press button 3 → Yellow LED blinks 3 times at **double speed** -- The total blink sequence should complete in roughly half the original time - -#### Expected Output - -After completing this exercise, you should be able to: -- Locate function parameters in ARM Thumb `movs` instructions -- Distinguish between identical byte values used in different contexts -- Patch timing parameters to change observable hardware behavior -- Understand the relationship between delay values and perceived blink speed - -#### Questions for Reflection - -###### Question 1: The `blink_led` function calls `sleep_ms` internally with the delay value. If you set the delay to `1`ms (0x01), would you still see the LED blink, or would it appear constantly on? - -###### Question 2: The value `0x32` appears in this binary as both a delay parameter (50ms) and potentially as an ASCII comparison ('2'). How would you systematically find ALL occurrences of `0x32` and determine which one to patch? - -###### Question 3: If you wanted a delay of 500ms (0x1F4), the value would not fit in a `movs` immediate. How would the compiler handle this larger delay value? - -###### Question 4: The blink function uses the delay for both the on-time and the off-time. Could you make the LED stay on longer than it stays off? What kind of patch would that require? - -#### Tips and Hints -- `25` decimal = `0x19` hex — fits in one byte, so the `movs` encoding works directly -- Verify location by checking the surrounding instructions: `movs r1, #3` should be right before and `bl blink_led` right after -- The total blink time for 3 blinks at 50ms = 3 × (50 + 50) = 300ms; at 25ms = 3 × (25 + 25) = 150ms -- If there are multiple call sites for `blink_led`, each may have its own `movs r2, #0x32` that needs patching diff --git a/WEEK11/WEEK11.md b/WEEK11/WEEK11.md index 8203abf..dd5d933 100644 --- a/WEEK11/WEEK11.md +++ b/WEEK11/WEEK11.md @@ -1,6 +1,6 @@ -# Week 11: Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics +?# Week 11: Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics -## 🎯 What You'll Learn This Week +## ? What You'll Learn This Week By the end of this tutorial, you will be able to: - Understand C structures (structs) and how they organize related data @@ -15,7 +15,7 @@ By the end of this tutorial, you will be able to: --- -## 📚 Part 1: Understanding C Structures (Structs) +## Part 1: Understanding C Structures (Structs) ### What is a Struct? @@ -34,25 +34,25 @@ typedef struct { ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Structure as a Container │ -│ │ -│ simple_led_ctrl_t leds │ -│ ┌─────────────────────────────────────────────────────────────┐│ -│ │ led1_pin: 16 led2_pin: 17 led3_pin: 18 ││ -│ │ ┌────────┐ ┌────────┐ ┌────────┐ ││ -│ │ │ 16 │ │ 17 │ │ 18 │ ││ -│ │ └────────┘ └────────┘ └────────┘ ││ -│ │ ││ -│ │ led1_state: false led2_state: false led3_state: false ││ -│ │ ┌────────┐ ┌────────┐ ┌────────┐ ││ -│ │ │ false │ │ false │ │ false │ ││ -│ │ └────────┘ └────────┘ └────────┘ ││ -│ └─────────────────────────────────────────────────────────────┘│ -│ │ -│ All 6 members live together as ONE variable called "leds" │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Structure as a Container | +| | +| simple_led_ctrl_t leds | +| +-------------------------------------------------------------+| +| | led1_pin: 16 led2_pin: 17 led3_pin: 18 || +| | +--------+ +--------+ +--------+ || +| | | 16 | | 17 | | 18 | || +| | +--------+ +--------+ +--------+ || +| | || +| | led1_state: false led2_state: false led3_state: false || +| | +--------+ +--------+ +--------+ || +| | | false | | false | | false | || +| | +--------+ +--------+ +--------+ || +| +-------------------------------------------------------------+| +| | +| All 6 members live together as ONE variable called "leds" | +| | ++-----------------------------------------------------------------+ ``` ### Why Use Structs? @@ -75,28 +75,28 @@ typedef struct { --- -## 📚 Part 2: Struct Memory Layout +## Part 2: Struct Memory Layout ### How Structs are Stored in Memory When you create a struct, the compiler places each member in consecutive memory locations: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Memory Layout of simple_led_ctrl_t │ -│ │ -│ Address Member Size Value │ -│ ───────────────────────────────────────────────────────────── │ -│ 0x2000000 led1_pin 1 byte 16 (0x10) │ -│ 0x2000001 led2_pin 1 byte 17 (0x11) │ -│ 0x2000002 led3_pin 1 byte 18 (0x12) │ -│ 0x2000003 led1_state 1 byte 0 (false) │ -│ 0x2000004 led2_state 1 byte 0 (false) │ -│ 0x2000005 led3_state 1 byte 0 (false) │ -│ │ -│ Total struct size: 6 bytes │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Memory Layout of simple_led_ctrl_t | +| | +| Address Member Size Value | +| ------------------------------------------------------------- | +| 0x2000000 led1_pin 1 byte 16 (0x10) | +| 0x2000001 led2_pin 1 byte 17 (0x11) | +| 0x2000002 led3_pin 1 byte 18 (0x12) | +| 0x2000003 led1_state 1 byte 0 (false) | +| 0x2000004 led2_state 1 byte 0 (false) | +| 0x2000005 led3_state 1 byte 0 (false) | +| | +| Total struct size: 6 bytes | +| | ++-----------------------------------------------------------------+ ``` ### Accessing Struct Members @@ -129,21 +129,21 @@ ptr->led1_pin = 16; // Using arrow with pointer ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Dot vs Arrow Operator │ -│ │ -│ struct_variable.member ◄── Use with actual struct │ -│ │ -│ pointer_to_struct->member ◄── Use with pointer to struct │ -│ │ -│ The arrow (->) is shorthand for (*pointer).member │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Dot vs Arrow Operator | +| | +| struct_variable.member ?-- Use with actual struct | +| | +| pointer_to_struct->member ?-- Use with pointer to struct | +| | +| The arrow (->) is shorthand for (*pointer).member | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 3: Designated Initializers +## Part 3: Designated Initializers ### Clean Struct Initialization @@ -168,29 +168,29 @@ simple_led_ctrl_t leds = { --- -## 📚 Part 4: Understanding the NEC IR Protocol +## Part 4: Understanding the NEC IR Protocol ### What is Infrared (IR) Communication? **Infrared** communication uses invisible light pulses to send data. Your TV remote uses IR to send commands to your TV. The LED in the remote flashes on and off very quickly in specific patterns that represent different buttons. ``` -┌─────────────────────────────────────────────────────────────────┐ -│ IR Communication │ -│ │ -│ Remote Control IR Receiver │ -│ ┌──────────┐ ┌──────────┐ │ -│ │ Button │ │ │ │ -│ │ 1 │ ─── IR Light Pulses ──► │ ████ │ │ -│ │ ┌───┐ │ ~~~~~~~~~~~~► │ Sensor │ │ -│ │ │ ● │ │ │ │ │ -│ │ └───┘ │ └────┬─────┘ │ -│ │ IR LED │ │ │ -│ └──────────┘ ▼ │ -│ GPIO Pin │ -│ (Digital signal) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| IR Communication | +| | +| Remote Control IR Receiver | +| +----------+ +----------+ | +| | Button | | | | +| | 1 | --- IR Light Pulses --- | ++ | | +| | +---+ | ~~~~~~~~~~~~? | Sensor | | +| | | ?? | | | | | +| | +---+ | +----+-----+ | +| | IR LED | | | +| +----------+ ? | +| GPIO Pin | +| (Digital signal) | +| | ++-----------------------------------------------------------------+ ``` ### The NEC Protocol @@ -204,18 +204,18 @@ simple_led_ctrl_t leds = { 5. **Command Inverse** - 8 bits (for error checking) ``` -┌─────────────────────────────────────────────────────────────────┐ -│ NEC Protocol Frame │ -│ │ -│ ┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ │ -│ │ Leader │ Address │ Address │ Command │ Command │ Stop │ │ -│ │ Pulse │ 8-bit │ Inverse │ 8-bit │ Inverse │ Bit │ │ -│ │ 9+4.5ms │ │ 8-bit │ │ 8-bit │ │ │ -│ └─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ │ -│ │ -│ Total: 32 bits of data (+ leader + stop) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| NEC Protocol Frame | +| | +| +---------+---------+---------+---------+---------+---------+ | +| | Leader | Address | Address | Command | Command | Stop | | +| | Pulse | 8-bit | Inverse | 8-bit | Inverse | Bit | | +| | 9+4.5ms | | 8-bit | | 8-bit | | | +| +---------+---------+---------+---------+---------+---------+ | +| | +| Total: 32 bits of data (+ leader + stop) | +| | ++-----------------------------------------------------------------+ ``` ### NEC Command Codes for Our Remote @@ -230,7 +230,7 @@ simple_led_ctrl_t leds = { --- -## 📚 Part 5: Understanding Functions in C +## Part 5: Understanding Functions in C ### What is a Function? @@ -249,30 +249,30 @@ int result = add_numbers(5, 3); // result = 8 ### Function Components ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Anatomy of a Function │ -│ │ -│ return_type function_name ( parameters ) { │ -│ // function body │ -│ return value; │ -│ } │ -│ │ -│ Example: │ -│ ┌─────────────────────────────────────────────────────────────┐│ -│ │ int ir_to_led_number ( int ir_command ) { ││ -│ │ ─── ─────────────── ─────────────── ││ -│ │ │ │ │ ││ -│ │ │ │ └── Parameter (input) ││ -│ │ │ └── Function name ││ -│ │ └── Return type (what it gives back) ││ -│ │ ││ -│ │ if (ir_command == 0x0C) return 1; ◄── Body ││ -│ │ if (ir_command == 0x18) return 2; ││ -│ │ return 0; ◄── Return value ││ -│ │ } ││ -│ └─────────────────────────────────────────────────────────────┘│ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Anatomy of a Function | +| | +| return_type function_name ( parameters ) { | +| // function body | +| return value; | +| } | +| | +| Example: | +| +-------------------------------------------------------------+| +| | int ir_to_led_number ( int ir_command ) { || +| | --- --------------- --------------- || +| | | | | || +| | | | +-- Parameter (input) || +| | | +-- Function name || +| | +-- Return type (what it gives back) || +| | || +| | if (ir_command == 0x0C) return 1; ?-- Body || +| | if (ir_command == 0x18) return 2; || +| | return 0; ?-- Return value || +| | } || +| +-------------------------------------------------------------+| +| | ++-----------------------------------------------------------------+ ``` ### Types of Functions @@ -286,7 +286,7 @@ int result = add_numbers(5, 3); // result = 8 --- -## 📚 Part 6: Functions with Struct Pointers +## Part 6: Functions with Struct Pointers ### Passing Structs to Functions @@ -306,32 +306,32 @@ leds_all_off(&my_leds); // Pass the ADDRESS of my_leds ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Passing Struct by Pointer │ -│ │ -│ main() { │ -│ simple_led_ctrl_t leds; ◄── Struct lives here │ -│ leds_all_off(&leds); ◄── Pass ADDRESS (pointer) │ -│ } │ │ -│ │ │ -│ ▼ │ -│ leds_all_off(simple_led_ctrl_t *leds) { │ -│ gpio_put(leds->led1_pin, false); │ -│ ──── │ -│ │ │ -│ └── Arrow because leds is a POINTER │ -│ } │ -│ │ -│ WHY use pointers? │ -│ • Efficient: Only 4 bytes (address) instead of entire struct │ -│ • Allows modification: Function can change the original │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Passing Struct by Pointer | +| | +| main() { | +| simple_led_ctrl_t leds; ?-- Struct lives here | +| leds_all_off(&leds); ?-- Pass ADDRESS (pointer) | +| } | | +| | | +| ? | +| leds_all_off(simple_led_ctrl_t *leds) { | +| gpio_put(leds->led1_pin, false); | +| ---- | +| | | +| +-- Arrow because leds is a POINTER | +| } | +| | +| WHY use pointers? | +| - Efficient: Only 4 bytes (address) instead of entire struct | +| - Allows modification: Function can change the original | +| | ++-----------------------------------------------------------------+ ``` --- -## 📚 Part 7: How Compilers Handle Structs +## Part 7: How Compilers Handle Structs ### Struct "Flattening" in Assembly @@ -357,32 +357,32 @@ bl gpio_init ; call gpio_init(18) ``` ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Struct Flattening │ -│ │ -│ C Level (High-level abstraction): │ -│ ┌─────────────────────────────────────────────────────────────┐│ -│ │ gpio_init(leds.led1_pin); ││ -│ │ gpio_init(leds.led2_pin); ││ -│ │ gpio_init(leds.led3_pin); ││ -│ └─────────────────────────────────────────────────────────────┘│ -│ │ │ -│ │ Compiler transforms │ -│ ▼ │ -│ Assembly Level (Flattened): │ -│ ┌─────────────────────────────────────────────────────────────┐│ -│ │ movs r0, #16 ; Just the VALUE, no struct reference ││ -│ │ bl gpio_init ││ -│ │ movs r0, #17 ; Next value directly ││ -│ │ bl gpio_init ││ -│ │ movs r0, #18 ; Next value directly ││ -│ │ bl gpio_init ││ -│ └─────────────────────────────────────────────────────────────┘│ -│ │ -│ The struct abstraction DISAPPEARS at the assembly level! │ -│ We just see individual values being loaded and used. │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Struct Flattening | +| | +| C Level (High-level abstraction): | +| +-------------------------------------------------------------+| +| | gpio_init(leds.led1_pin); || +| | gpio_init(leds.led2_pin); || +| | gpio_init(leds.led3_pin); || +| +-------------------------------------------------------------+| +| | | +| | Compiler transforms | +| ? | +| Assembly Level (Flattened): | +| +-------------------------------------------------------------+| +| | movs r0, #16 ; Just the VALUE, no struct reference || +| | bl gpio_init || +| | movs r0, #17 ; Next value directly || +| | bl gpio_init || +| | movs r0, #18 ; Next value directly || +| | bl gpio_init || +| +-------------------------------------------------------------+| +| | +| The struct abstraction DISAPPEARS at the assembly level! | +| We just see individual values being loaded and used. | +| | ++-----------------------------------------------------------------+ ``` ### Why This Matters for Reverse Engineering @@ -393,7 +393,7 @@ bl gpio_init ; call gpio_init(18) --- -## 📚 Part 8: Setting Up Your Environment +## Part 8: Setting Up Your Environment ### Prerequisites @@ -422,61 +422,61 @@ Before we start, make sure you have: | LED | GPIO Pin | Resistor | | ------ | -------- | --------- | -| Red | GPIO 16 | 220Ω-330Ω | -| Green | GPIO 17 | 220Ω-330Ω | -| Yellow | GPIO 18 | 220Ω-330Ω | +| Red | GPIO 16 | 220-330 ohm | +| Green | GPIO 17 | 220-330 ohm | +| Yellow | GPIO 18 | 220-330 ohm | ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Complete Wiring Diagram │ -│ │ -│ Pico 2 Components │ -│ ┌──────────┐ │ -│ │ │ ┌─────────────┐ │ -│ │ GPIO 5 │──────────────┤ IR Receiver │ │ -│ │ │ │ (VS1838B) │ │ -│ │ │ └──────┬──────┘ │ -│ │ │ │ │ -│ │ GPIO 16 │───[220Ω]───(RED LED)────┐ │ -│ │ │ │ │ -│ │ GPIO 17 │───[220Ω]───(GRN LED)────┤ │ -│ │ │ │ │ -│ │ GPIO 18 │───[220Ω]───(YEL LED)────┤ │ -│ │ │ │ │ -│ │ 3.3V │─────────────────────────┼── IR VCC │ -│ │ │ │ │ -│ │ GND │─────────────────────────┴── All GNDs │ -│ │ │ │ -│ └──────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Complete Wiring Diagram | +| | +| Pico 2 Components | +| +----------+ | +| | | +-------------+ | +| | GPIO 5 |--------------+ IR Receiver | | +| | | | (VS1838B) | | +| | | +------+------+ | +| | | | | +| | GPIO 16 |---[220 ohm]---(RED LED)----+ | +| | | | | +| | GPIO 17 |---[220 ohm]---(GRN LED)----+ | +| | | | | +| | GPIO 18 |---[220 ohm]---(YEL LED)----+ | +| | | | | +| | 3.3V |-------------------------+-- IR VCC | +| | | | | +| | GND |-------------------------+-- All GNDs | +| | | | +| +----------+ | +| | ++-----------------------------------------------------------------+ ``` ### Project Structure ``` Embedded-Hacking/ -├── 0x0023_structures/ -│ ├── build/ -│ │ ├── 0x0023_structures.uf2 -│ │ └── 0x0023_structures.bin -│ ├── main/ -│ │ └── 0x0023_structures.c -│ └── ir.h -├── 0x0026_functions/ -│ ├── build/ -│ │ ├── 0x0026_functions.uf2 -│ │ ├── 0x0026_functions.bin -│ │ └── 0x0026_functions.elf -│ ├── main/ -│ │ └── 0x0026_functions.c -│ └── ir.h -└── uf2conv.py ++-- 0x0023_structures/ +| +-- build/ +| | +-- 0x0023_structures.uf2 +| | +-- 0x0023_structures.bin +| +-- main/ +| | +-- 0x0023_structures.c +| +-- ir.h ++-- 0x0026_functions/ +| +-- build/ +| | +-- 0x0026_functions.uf2 +| | +-- 0x0026_functions.bin +| | +-- 0x0026_functions.elf +| +-- main/ +| | +-- 0x0026_functions.c +| +-- ir.h ++-- uf2conv.py ``` --- -## 🔬 Part 9: Hands-On Tutorial - Structures Code +## ? Part 9: Hands-On Tutorial - Structures Code ### Step 1: Review the Source Code @@ -551,24 +551,24 @@ int main(void) { ### Step 2: Understand the Program Flow ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Program Flow │ -│ │ -│ 1. Initialize UART (stdio_init_all) │ -│ 2. Create LED struct with pins 16, 17, 18 │ -│ 3. Initialize GPIO pins as outputs │ -│ 4. Initialize IR receiver on GPIO 5 │ -│ 5. Enter infinite loop: │ -│ a. Check for IR key press │ -│ b. If key received: │ -│ - Print the NEC command code │ -│ - Turn all LEDs off │ -│ - Check which button: 0x0C, 0x18, or 0x5E │ -│ - Turn on the matching LED │ -│ - Apply states to GPIO pins │ -│ c. Sleep briefly and repeat │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Program Flow | +| | +| 1. Initialize UART (stdio_init_all) | +| 2. Create LED struct with pins 16, 17, 18 | +| 3. Initialize GPIO pins as outputs | +| 4. Initialize IR receiver on GPIO 5 | +| 5. Enter infinite loop: | +| a. Check for IR key press | +| b. If key received: | +| - Print the NEC command code | +| - Turn all LEDs off | +| - Check which button: 0x0C, 0x18, or 0x5E | +| - Turn on the matching LED | +| - Apply states to GPIO pins | +| c. Sleep briefly and repeat | +| | ++-----------------------------------------------------------------+ ``` ### Step 3: Flash the Binary to Your Pico 2 @@ -582,13 +582,13 @@ int main(void) { ### Step 4: Verify It's Working **Open PuTTY (115200 baud) and test:** -- Press "1" on remote → Red LED lights, terminal shows `NEC command: 0x0C` -- Press "2" on remote → Green LED lights, terminal shows `NEC command: 0x18` -- Press "3" on remote → Yellow LED lights, terminal shows `NEC command: 0x5E` +- Press "1" on remote -> Red LED lights, terminal shows `NEC command: 0x0C` +- Press "2" on remote -> Green LED lights, terminal shows `NEC command: 0x18` +- Press "3" on remote -> Yellow LED lights, terminal shows `NEC command: 0x5E` --- -## 🔬 Part 10: Debugging with GDB (Structures) +## ? Part 10: Debugging with GDB (Structures) ### Step 5: Start OpenOCD (Terminal 1) @@ -596,7 +596,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" @@ -724,7 +724,7 @@ quit --- -## 🔬 Part 11: Setting Up Ghidra for Structures +## ? Part 11: Setting Up Ghidra for Structures ### Step 17: Start Ghidra @@ -736,7 +736,7 @@ ghidraRun ### Step 18: Create a New Project -1. Click **File** → **New Project** +1. Click **File** -> **New Project** 2. Select **Non-Shared Project** 3. Click **Next** 4. Enter Project Name: `0x0023_structures` @@ -749,12 +749,12 @@ ghidraRun ### Step 20: Configure the Binary Format -**Click the three dots (…) next to "Language" and:** +**Click the three dots (...) next to "Language" and:** 1. Search for "Cortex" 2. Select **ARM Cortex 32 little endian default** 3. Click **OK** -**Click the "Options…" button and:** +**Click the "Options..." button and:** 1. Change **Block Name** to `.text` 2. Change **Base Address** to `10000000` 3. Click **OK** @@ -769,12 +769,12 @@ Wait for analysis to complete. --- -## 🔬 Part 12: Resolving Functions - Structures Project +## ? Part 12: Resolving Functions - Structures Project ### Step 22: Navigate to Main 1. Press `G` (Go to address) and type `10000234` -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `int main(void)` 4. Click **OK** @@ -783,7 +783,7 @@ Wait for analysis to complete. At address `0x10000236`: 1. Double-click on the called function -2. Right-click → **Edit Function Signature** +2. Right-click -> **Edit Function Signature** 3. Change to: `bool stdio_init_all(void)` 4. Click **OK** @@ -803,7 +803,7 @@ bl FUN_xxxxx ; gpio_init ``` This pattern reveals the struct members! Update the function signature: -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `void gpio_init(uint gpio)` 3. Click **OK** @@ -816,7 +816,7 @@ movs r0, #0x5 ; GPIO 5 for IR receiver bl FUN_xxxxx ; ir_init ``` -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `void ir_init(uint pin)` 3. Click **OK** @@ -824,7 +824,7 @@ bl FUN_xxxxx ; ir_init Right after ir_init, look for the "IR receiver on GPIO" string being loaded: -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `int printf(char *format, ...)` 3. Check the **Varargs** checkbox 4. Click **OK** @@ -839,7 +839,7 @@ cmp r0, #0 ; Check if >= 0 blt no_key ; If negative, no key pressed ``` -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `int ir_getkey(void)` 3. Click **OK** @@ -852,13 +852,13 @@ movs r0, #0x0A ; 10 milliseconds bl FUN_xxxxx ; sleep_ms ``` -1. Right-click → **Edit Function Signature** +1. Right-click -> **Edit Function Signature** 2. Change to: `void sleep_ms(uint ms)` 3. Click **OK** --- -## 🔬 Part 13: Recognizing Struct Patterns in Assembly +## ? Part 13: Recognizing Struct Patterns in Assembly ### Step 29: Identify GPIO Set Direction @@ -876,31 +876,31 @@ This is the compiler's version of `gpio_set_dir(pin, GPIO_OUT)`. Create a mental (or written) map: ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Struct Member Mapping │ -│ │ -│ Assembly Value → Struct Member → Physical LED │ -│ ───────────────────────────────────────────────────────────── │ -│ 0x10 (16) → led1_pin → Red LED │ -│ 0x11 (17) → led2_pin → Green LED │ -│ 0x12 (18) → led3_pin → Yellow LED │ -│ │ -│ NEC Code → State Member → Action │ -│ ───────────────────────────────────────────────────────────── │ -│ 0x0C → led1_state=true → Red LED ON │ -│ 0x18 → led2_state=true → Green LED ON │ -│ 0x5E → led3_state=true → Yellow LED ON │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Struct Member Mapping | +| | +| Assembly Value -> Struct Member -> Physical LED | +| ------------------------------------------------------------- | +| 0x10 (16) -> led1_pin -> Red LED | +| 0x11 (17) -> led2_pin -> Green LED | +| 0x12 (18) -> led3_pin -> Yellow LED | +| | +| NEC Code -> State Member -> Action | +| ------------------------------------------------------------- | +| 0x0C -> led1_state=true -> Red LED ON | +| 0x18 -> led2_state=true -> Green LED ON | +| 0x5E -> led3_state=true -> Yellow LED ON | +| | ++-----------------------------------------------------------------+ ``` --- -## 🔬 Part 14: Hacking Structures +## ? Part 14: Hacking Structures ### Step 31: Open the Bytes Editor -1. Click **Window** → **Bytes** +1. Click **Window** -> **Bytes** 2. Click the pencil icon to enable editing ### Step 32: Swap LED Pin Assignments @@ -916,19 +916,19 @@ We'll swap the red and green LED pins to reverse their behavior! **Before:** ``` -LED 1 (0x0C) → GPIO 16 → Red LED -LED 2 (0x18) → GPIO 17 → Green LED +LED 1 (0x0C) -> GPIO 16 -> Red LED +LED 2 (0x18) -> GPIO 17 -> Green LED ``` **After:** ``` -LED 1 (0x0C) → GPIO 17 → Green LED (SWAPPED!) -LED 2 (0x18) → GPIO 16 → Red LED (SWAPPED!) +LED 1 (0x0C) -> GPIO 17 -> Green LED (SWAPPED!) +LED 2 (0x18) -> GPIO 16 -> Red LED (SWAPPED!) ``` ### Step 33: Export and Flash -1. Click **File** → **Export Program** +1. Click **File** -> **Export Program** 2. Set **Format** to **Binary** 3. Name: `0x0023_structures-h.bin` 4. Click **OK** @@ -936,43 +936,43 @@ LED 2 (0x18) → GPIO 16 → Red LED (SWAPPED!) Convert and flash: ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0023_structures +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0023_structures python ..\uf2conv.py build\0x0023_structures-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` ### Step 34: Verify the Hack **Open PuTTY and test:** -- Press "1" on remote → **GREEN** LED lights (was red!) +- Press "1" on remote -> **GREEN** LED lights (was red!) - Terminal still shows `NEC command: 0x0C` -- Press "2" on remote → **RED** LED lights (was green!) +- Press "2" on remote -> **RED** LED lights (was green!) - Terminal still shows `NEC command: 0x18` **The log says one thing, but the hardware does another!** --- -## 🔬 Part 15: Security Implications - Log Desynchronization +## ? Part 15: Security Implications - Log Desynchronization ### The Danger of Mismatched Logs ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Log vs Reality Desynchronization │ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Terminal Log │ │ Physical LEDs │ │ -│ ├─────────────────┤ ├─────────────────┤ │ -│ │ NEC: 0x0C │ ◄─────── │ GREEN LED on │ ◄── Mismatch! │ -│ │ (expects RED) │ │ (not red!) │ │ -│ ├─────────────────┤ ├─────────────────┤ │ -│ │ NEC: 0x18 │ ◄─────── │ RED LED on │ ◄── Mismatch! │ -│ │ (expects GREEN) │ │ (not green!) │ │ -│ └─────────────────┘ └─────────────────┘ │ -│ │ -│ The OPERATOR sees correct logs but WRONG physical behavior! │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Log vs Reality Desynchronization | +| | +| +-----------------+ +-----------------+ | +| | Terminal Log | | Physical LEDs | | +| +-----------------+ +-----------------+ | +| | NEC: 0x0C | ?------- | GREEN LED on | ?-- Mismatch! | +| | (expects RED) | | (not red!) | | +| +-----------------+ +-----------------+ | +| | NEC: 0x18 | ?------- | RED LED on | ?-- Mismatch! | +| | (expects GREEN) | | (not green!) | | +| +-----------------+ +-----------------+ | +| | +| The OPERATOR sees correct logs but WRONG physical behavior! | +| | ++-----------------------------------------------------------------+ ``` ### Real-World Example: Stuxnet @@ -990,7 +990,7 @@ Our LED example demonstrates the same principle: --- -## 🔬 Part 16: Functions Project - Advanced Code +## ? Part 16: Functions Project - Advanced Code ### Step 35: Review the Functions Code @@ -1049,28 +1049,28 @@ int process_ir_led_command(int ir_command, simple_led_ctrl_t *leds, uint8_t blin ### Step 36: Understand the Function Call Chain ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Function Call Chain │ -│ │ -│ main() │ -│ │ │ -│ └──► process_ir_led_command(key, &leds, 3) │ -│ │ │ -│ ├──► leds_all_off(&leds) │ -│ │ └──► gpio_put() × 3 │ -│ │ │ -│ ├──► ir_to_led_number(ir_command) │ -│ │ └──► returns 1, 2, or 3 │ -│ │ │ -│ ├──► get_led_pin(&leds, led_num) │ -│ │ └──► returns GPIO pin number │ -│ │ │ -│ ├──► blink_led(pin, 3, 50) │ -│ │ └──► gpio_put() + sleep_ms() in loop │ -│ │ │ -│ └──► gpio_put(pin, true) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Function Call Chain | +| | +| main() | +| | | +| +--? process_ir_led_command(key, &leds, 3) | +| | | +| +--? leds_all_off(&leds) | +| | +--? gpio_put() * 3 | +| | | +| +--? ir_to_led_number(ir_command) | +| | +--? returns 1, 2, or 3 | +| | | +| +--? get_led_pin(&leds, led_num) | +| | +--? returns GPIO pin number | +| | | +| +--? blink_led(pin, 3, 50) | +| | +--? gpio_put() + sleep_ms() in loop | +| | | +| +--? gpio_put(pin, true) | +| | ++-----------------------------------------------------------------+ ``` ### Step 37: Flash and Test @@ -1078,13 +1078,13 @@ int process_ir_led_command(int ir_command, simple_led_ctrl_t *leds, uint8_t blin 1. Flash `0x0026_functions.uf2` to your Pico 2 2. Open PuTTY 3. Press remote buttons: - - "1" → Red LED blinks 3 times, then stays on - - "2" → Green LED blinks 3 times, then stays on - - "3" → Yellow LED blinks 3 times, then stays on + - "1" -> Red LED blinks 3 times, then stays on + - "2" -> Green LED blinks 3 times, then stays on + - "3" -> Yellow LED blinks 3 times, then stays on --- -## 🔬 Part 17: Debugging with GDB (Functions) +## ? Part 17: Debugging with GDB (Functions) ### Step 38: Start OpenOCD (Terminal 1) @@ -1092,7 +1092,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" @@ -1160,7 +1160,7 @@ stepi 50 info registers ``` -Watch the call chain: `process_ir_led_command` → `leds_all_off` → `ir_to_led_number` → `get_led_pin` → `blink_led`. +Watch the call chain: `process_ir_led_command` -> `leds_all_off` -> `ir_to_led_number` -> `get_led_pin` -> `blink_led`. ### Step 45: Examine ir_to_led_number @@ -1213,7 +1213,7 @@ quit --- -## 🔬 Part 18: Analyzing .ELF Files in Ghidra +## ? Part 18: Analyzing .ELF Files in Ghidra ### Step 50: Create New Ghidra Project @@ -1246,7 +1246,7 @@ With .ELF files, you get more information: --- -## 🔬 Part 19: Hacking the Functions Project +## ? Part 19: Hacking the Functions Project ### Step 53: Find LED Pin Values @@ -1268,14 +1268,14 @@ We'll swap the red (GPIO 16) and yellow (GPIO 18) LEDs: **Before:** ``` -Button 1 → LED 1 → GPIO 16 → Red -Button 3 → LED 3 → GPIO 18 → Yellow +Button 1 -> LED 1 -> GPIO 16 -> Red +Button 3 -> LED 3 -> GPIO 18 -> Yellow ``` **After:** ``` -Button 1 → LED 1 → GPIO 18 → Yellow (SWAPPED!) -Button 3 → LED 3 → GPIO 16 → Red (SWAPPED!) +Button 1 -> LED 1 -> GPIO 18 -> Yellow (SWAPPED!) +Button 3 -> LED 3 -> GPIO 16 -> Red (SWAPPED!) ``` ### Step 55: Export the Patched .BIN @@ -1289,23 +1289,23 @@ Button 3 → LED 3 → GPIO 16 → Red (SWAPPED!) ### Step 56: Convert and Flash ```powershell -cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions +cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0026_functions python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` ### Step 57: Verify the Hack **Open PuTTY and test:** -- Press "1" → **YELLOW** LED blinks (was red!) +- Press "1" -> **YELLOW** LED blinks (was red!) - Terminal shows: `LED 1 activated on GPIO 16` (WRONG - it's actually GPIO 18!) -- Press "3" → **RED** LED blinks (was yellow!) +- Press "3" -> **RED** LED blinks (was yellow!) - Terminal shows: `LED 3 activated on GPIO 18` (WRONG - it's actually GPIO 16!) **Again, logs don't match reality!** --- -## 📊 Part 20: Summary and Review +## ? Part 20: Summary and Review ### What We Accomplished @@ -1322,49 +1322,49 @@ python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe ### Struct Operations Summary ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Struct Operations │ -│ │ -│ Definition: │ -│ typedef struct { │ -│ uint8_t pin; │ -│ bool state; │ -│ } led_t; │ -│ │ -│ Creation: │ -│ led_t led = { .pin = 16, .state = false }; │ -│ │ -│ Access (variable): led.pin │ -│ Access (pointer): ptr->pin or (*ptr).pin │ -│ │ -│ Passing to function: void func(led_t *led) │ -│ Calling: func(&led) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Struct Operations | +| | +| Definition: | +| typedef struct { | +| uint8_t pin; | +| bool state; | +| } led_t; | +| | +| Creation: | +| led_t led = { .pin = 16, .state = false }; | +| | +| Access (variable): led.pin | +| Access (pointer): ptr->pin or (*ptr).pin | +| | +| Passing to function: void func(led_t *led) | +| Calling: func(&led) | +| | ++-----------------------------------------------------------------+ ``` ### Function Types Summary ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Function Patterns │ -│ │ -│ No params, no return: │ -│ void leds_all_off(void) │ -│ │ -│ With params, no return: │ -│ void blink_led(uint8_t pin, uint8_t count, uint32_t delay) │ -│ │ -│ No params, with return: │ -│ int ir_getkey(void) │ -│ │ -│ With params, with return: │ -│ int ir_to_led_number(int ir_command) │ -│ │ -│ With struct pointer: │ -│ uint8_t get_led_pin(simple_led_ctrl_t *leds, int led_num) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ ++-----------------------------------------------------------------+ +| Function Patterns | +| | +| No params, no return: | +| void leds_all_off(void) | +| | +| With params, no return: | +| void blink_led(uint8_t pin, uint8_t count, uint32_t delay) | +| | +| No params, with return: | +| int ir_getkey(void) | +| | +| With params, with return: | +| int ir_to_led_number(int ir_command) | +| | +| With struct pointer: | +| uint8_t get_led_pin(simple_led_ctrl_t *leds, int led_num) | +| | ++-----------------------------------------------------------------+ ``` ### Key Memory Addresses @@ -1382,36 +1382,9 @@ python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe --- -## ✅ Practice Exercises - -### Exercise 1: Add a Fourth LED -Modify the struct to include a fourth LED on GPIO 19. - -**Hint:** Add `led4_pin` and `led4_state` members. - -### Exercise 2: Change Blink Count -Find and modify the blink count from 3 to 5 blinks. - -**Hint:** Look for the value passed to `process_ir_led_command`. - -### Exercise 3: Swap All Three LEDs -Create a rotation where 1→Green, 2→Yellow, 3→Red. - -**Hint:** Patch all three GPIO values. - -### Exercise 4: Change Blink Speed -Make the LEDs blink faster by changing the delay from 50ms to 25ms. - -**Hint:** Find `0x32` (50) in the function parameters. - -### Exercise 5: Disable One LED -Make button 2 do nothing (LED stays off). - -**Hint:** NOP out the gpio_put call or change the NEC code comparison. - --- -## 🎓 Key Takeaways +## ? Key Takeaways 1. **Structs group related data** - Better organization than separate variables @@ -1435,7 +1408,7 @@ Make button 2 do nothing (LED stays off). --- -## 📖 Glossary +## ? Glossary | Term | Definition | | -------------------------- | -------------------------------------------------- | @@ -1453,7 +1426,7 @@ Make button 2 do nothing (LED stays off). --- -## 🔗 Additional Resources +## ? Additional Resources ### NEC IR Command Reference @@ -1486,7 +1459,7 @@ Make button 2 do nothing (LED stays off). --- -## 🚨 Real-World Implications +## ? Real-World Implications ### What You've Learned in This Course @@ -1526,4 +1499,6 @@ This is just the beginning: **Congratulations on completing this course! You now have the curiosity, persistence, and skills that embedded systems engineers and security researchers thrive on. Keep experimenting, documenting, and sharing your work. The world needs more builders and defenders like you!** -Happy hacking! 🔧 +Happy hacking! ? + +