mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-20 06:44:41 +02:00
Added WEEK04
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 1: Analyze Variable Storage in Ghidra
|
||||
|
||||
#### 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
|
||||
|
||||
```bash
|
||||
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.)
|
||||
Reference in New Issue
Block a user