7.3 KiB
Embedded Systems Reverse Engineering
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.binbinary 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
ghidraRun
- Click File ? New Project
- Select Non-Shared Project
- Click Next
- Enter Project Name:
week04-ex01-intro-to-variables - Choose a project directory
- Click Finish
Step 2: Import the Binary
- Navigate to your file explorer
- Find
Embedded-Hacking\0x0005_intro-to-variables\build\0x0005_intro-to-variables.bin - Drag and drop the
.binfile into Ghidra's project window
Step 3: Configure Import Settings
When the import dialog appears:
- Click the three dots (…) next to Language
- Search for:
Cortex - Select: ARM Cortex 32 little endian default
- Click OK
Now click Options… button:
- Change Block Name to:
.text - Change Base Address to:
10000000(XIP flash base) - Click OK
Then click OK on the main import dialog.
Step 4: Analyze the Binary
- Double-click the imported file in the project window
- When prompted "Analyze now?" click Yes
- Leave all default analysis options selected
- Click Analyze
- 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_1000019aFUN_10000210FUN_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:
- Reset handler copies data
frame_dummyrunsmain()is called
Click on FUN_10000234 - this should be our main() function.
Look at the Decompile window:
void FUN_10000234(void)
{
FUN_100030cc();
do {
FUN_10003100("age: %d\r\n", 0x2b);
} while (true);
}
Observations:
FUN_100030cc()is likelystdio_init_all()FUN_10003100()is likelyprintf()- The magic value
0x2bappears (what is this?)
Step 7: Convert 0x2b to Decimal
Let's figure out what 0x2b means:
Manual calculation:
0x2bin hexadecimal2 × 16 + 11 = 32 + 11 = 43in decimal
In GDB (alternative method):
(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:
- Right-click on
FUN_10000234in the Symbol Tree - Select Rename Function
- Enter:
main - Press Enter
Update main's signature:
- In the Decompile window, right-click on
main - Select Edit Function Signature
- Change to:
int main(void) - Click OK
Rename FUN_100030cc to stdio_init_all:
- Click on
FUN_100030ccin the decompile window - Right-click ? Edit Function Signature
- Change name to:
stdio_init_all - Change signature to:
bool stdio_init_all(void) - Click OK
Rename FUN_10003100 to printf:
- Click on
FUN_10003100 - Right-click ? Edit Function Signature
- Change name to:
printf - Check the Varargs checkbox (printf accepts variable arguments)
- Click OK
Step 9: Examine the Optimized Code
After renaming, the decompiled main should now look like:
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:
uint8_t age = 42;
age = 43;
The compiler optimized it completely away!
Why?
age = 42is immediately overwritten- The value
42is never used - The compiler replaces
agewith the constant43(0x2b) - 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:
10000xxx movs r1, #0x2b
10000xxx ...
10000xxx bl printf
What this does:
movs r1, #0x2b- Moves the immediate value 0x2b (43) into register r1bl 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.binto see how uninitialized variables behave - Explore the
.rodatasection 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.)