mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-10-04 06:57:02 +02:00
Week 4-BN: step-by-step function resolution in BN, int32_t note, serial tip
- Step 16: G/N/Y key table and worked examples (main, stdio_init_all, uart_init, __wrap_printf); note BN shows int32_t where Ghidra shows int - Step 26: worked check + pointer to Step 16 - troubleshooting: macOS serial capture needs raw termios at 115200
This commit is contained in:
1 parent
4f56b897cf
commit
55888dadb0
2 files changed
+49
-7
No files matched your search
+49
-7
@@ -485,7 +485,7 @@ With the target halted at the breakpoint, **Step Into** (`F7`) and **Step Over**
|
|||||||
3. Click **Resume** in Binary Ninja. The target is already running the loop, so the breakpoint fires on the next iteration. Binary Ninja stops with the program counter at `0x1000023e` and `r1 = 0x2b`.
|
3. Click **Resume** in Binary Ninja. The target is already running the loop, so the breakpoint fires on the next iteration. Binary Ninja stops with the program counter at `0x1000023e` and `r1 = 0x2b`.
|
||||||
4. Open the **Registers** widget (bug icon -> **Registers**).
|
4. Open the **Registers** widget (bug icon -> **Registers**).
|
||||||
5. Find `r1`. Its value is `0x2b`.
|
5. Find `r1`. Its value is `0x2b`.
|
||||||
6. **Double-click the value, type `46`, and press Enter.** Binary Ninja parses the new value as hex, so `46` means `0x46` (70). The edited value turns **orange**.
|
6. **Double-click the value, type `46`, and press Enter.** Binary Ninja parses the new value as hex, so `46` means `0x46` (70). **The widget does not visibly change** — Binary Ninja writes the value to the target but does not repaint the register in the widget. The edit is real; you confirm it by the printed output in the next steps, not by the widget.
|
||||||
7. **Move the breakpoint past the call.** You want `printf` to run once and then stop, so move the breakpoint from `0x1000023e` to the instruction *after* the call, `0x10000242` (the `b.n` that closes the loop): remove the breakpoint at `0x1000023e` and set a hardware breakpoint at `0x10000242`. Two reasons not to just click **Step Over** here: a breakpoint left on the current PC re-traps the step, and Binary Ninja's **Step Over** steps *into* `__wrap_printf` on this raw `.bin` because the image carries no symbol for the call. Moving the breakpoint to the return site is deterministic.
|
7. **Move the breakpoint past the call.** You want `printf` to run once and then stop, so move the breakpoint from `0x1000023e` to the instruction *after* the call, `0x10000242` (the `b.n` that closes the loop): remove the breakpoint at `0x1000023e` and set a hardware breakpoint at `0x10000242`. Two reasons not to just click **Step Over** here: a breakpoint left on the current PC re-traps the step, and Binary Ninja's **Step Over** steps *into* `__wrap_printf` on this raw `.bin` because the image carries no symbol for the call. Moving the breakpoint to the return site is deterministic.
|
||||||
8. Click **Resume** in Binary Ninja. The core executes `bl __wrap_printf` with `r1 = 0x46`, so this iteration prints `age: 70`, then stops at `0x10000242`.
|
8. Click **Resume** in Binary Ninja. The core executes `bl __wrap_printf` with `r1 = 0x46`, so this iteration prints `age: 70`, then stops at `0x10000242`.
|
||||||
9. Look at your serial monitor — the `screen` session on the Pico's USB serial port — and at the **Target** tab in Binary Ninja:
|
9. Look at your serial monitor — the `screen` session on the Pico's USB serial port — and at the **Target** tab in Binary Ninja:
|
||||||
@@ -529,13 +529,49 @@ Press **Pause** to stop the output flood.
|
|||||||
|
|
||||||
### Step 16: Resolve the functions in the Binary Ninja GUI
|
### Step 16: Resolve the functions in the Binary Ninja GUI
|
||||||
|
|
||||||
Now we use the ELF symbol map from Step 4 to name the functions in Binary Ninja. For each row below:
|
We now name the functions in Binary Ninja using the ELF symbol map from Step 4. Binary Ninja loaded the raw `.bin` with **no symbols**, so every function shows as `sub_<addr>` — resolving means giving each one its real name and signature.
|
||||||
|
|
||||||
1. Press `G` and type the address.
|
Three keys do all the work:
|
||||||
2. Press `N` and type the ELF symbol name.
|
|
||||||
3. For functions with arguments, press `Y` and set the signature shown.
|
|
||||||
|
|
||||||
This is **our code plus the library functions it actually calls** — not the whole SDK. `main` only calls `stdio_init_all` and `printf`, so we follow that chain down: `stdio_init_all` pulls in the stdio/UART setup, and `printf` lands in the SDK's `__wrap_printf`.
|
| Key | Binary Ninja action | Use it for |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `G` | Go to address | Jump to a function's address |
|
||||||
|
| `N` | Rename symbol | Give the function its real name |
|
||||||
|
| `Y` | Set type | Give the function its signature |
|
||||||
|
|
||||||
|
For each function below, the loop is the same: `G` to its address, `N` to rename it, `Y` to set its signature.
|
||||||
|
|
||||||
|
#### Worked example: `main`
|
||||||
|
|
||||||
|
1. Press `G`, type `0x10000234`, press Enter. The view jumps there; the cursor lands on `sub_10000234`.
|
||||||
|
2. Press `N`, type `main`, press Enter.
|
||||||
|
3. Press `Y`, type `int main(void)`, press Enter.
|
||||||
|
|
||||||
|
> **Binary Ninja shows `int32_t` where Ghidra shows `int`.** After you set `int main(void)`, the decompiler header may read `int32_t main(void)`. That is the same type — on this platform `int` is 32 bits and Binary Ninja's parser normalises it to `int32_t`. Do not fight it; it is not an error.
|
||||||
|
|
||||||
|
#### Worked example: `stdio_init_all`
|
||||||
|
|
||||||
|
1. `G` -> `0x10002f54`.
|
||||||
|
2. `N` -> `stdio_init_all`.
|
||||||
|
3. `Y` -> `bool stdio_init_all(void)`.
|
||||||
|
|
||||||
|
It returns **`bool`**, not `void` — the ELF says `_Bool stdio_init_all(void)`. Our `main` ignores the return value, so the decompiler still reads cleanly.
|
||||||
|
|
||||||
|
#### Worked example: `uart_init`
|
||||||
|
|
||||||
|
1. `G` -> `0x10000e10`.
|
||||||
|
2. `N` -> `uart_init`.
|
||||||
|
3. `Y` -> `uint uart_init(uart_inst_t *uart, uint baudrate)`.
|
||||||
|
|
||||||
|
#### Worked example: `__wrap_printf`
|
||||||
|
|
||||||
|
1. `G` -> `0x100030e4`.
|
||||||
|
2. `N` -> `__wrap_printf`.
|
||||||
|
3. `Y` -> `int __wrap_printf(const char *fmt, ...)`. Keep the `...` — `printf` is variadic.
|
||||||
|
|
||||||
|
> **`printf` in our source is `__wrap_printf` in the binary.** The SDK links our `printf` calls to its `__wrap_printf` wrapper. Rename it `printf` if you prefer the lesson's shorthand, but `__wrap_printf` is the real symbol.
|
||||||
|
|
||||||
|
The rest of the chain is the same three keystrokes per function. This is **our code plus the library functions it actually calls** — not the whole SDK. `main` only calls `stdio_init_all` and `printf`, so we follow that chain down: `stdio_init_all` pulls in the stdio/UART setup, and `printf` lands in the SDK's `__wrap_printf`.
|
||||||
|
|
||||||
The call chain for this project:
|
The call chain for this project:
|
||||||
|
|
||||||
@@ -823,7 +859,9 @@ Same idea as Project 1, different addresses. Here the format string is at `0x100
|
|||||||
|
|
||||||
### Step 26: Resolve the functions in the Binary Ninja GUI
|
### Step 26: Resolve the functions in the Binary Ninja GUI
|
||||||
|
|
||||||
Use the Project 2 ELF symbol map from Step 4. For each row, press `G` (address), `N` (name), and `Y` (signature):
|
Same three keys as Step 16 — `G` to the address, `N` to rename, `Y` to set the signature — using the Project 2 ELF symbol map from Step 4.
|
||||||
|
|
||||||
|
Quick worked check: `G` -> `0x10000234`, `N` -> `main`, `Y` -> `int main(void)` (Binary Ninja shows `int32_t main(void)`; that is the same 32-bit `int` — see the note in Step 16). Then work down the table the same way.
|
||||||
|
|
||||||
Same idea as Project 1: **our code plus what it calls**, not the whole SDK. The call chain here is one function longer because `main` also drives the GPIO and sleeps:
|
Same idea as Project 1: **our code plus what it calls**, not the whole SDK. The call chain here is one function longer because `main` also drives the GPIO and sleeps:
|
||||||
|
|
||||||
@@ -1113,6 +1151,10 @@ If it keeps reverting, the core is running, which almost always means the breakp
|
|||||||
|
|
||||||
> **The Registers widget is a snapshot, not a live view.** Binary Ninja reads the registers at each stop and shows that snapshot; it does not poll the target, and there is no "refresh registers" command (only "Force Update Memory Cache", which is for memory). So a value changed outside Binary Ninja will not appear until the next stop.
|
> **The Registers widget is a snapshot, not a live view.** Binary Ninja reads the registers at each stop and shows that snapshot; it does not poll the target, and there is no "refresh registers" command (only "Force Update Memory Cache", which is for memory). So a value changed outside Binary Ninja will not appear until the next stop.
|
||||||
|
|
||||||
|
### The serial capture is garbage on macOS
|
||||||
|
|
||||||
|
Reading `/dev/cu.usbmodem*` with a bare `read()` returns garbage. Set **raw termios at 115200** first: clear canonical/echo flags, set `CLOCAL|CREAD`, and `B115200` on input and output. `screen /dev/cu.usbmodem* 115200` does all of this for you; a script must call `tcsetattr` itself. Once set, the capture reads clean `age: 43` lines.
|
||||||
|
|
||||||
### It worked for a second, then stopped (Binary Ninja's view desyncs)
|
### It worked for a second, then stopped (Binary Ninja's view desyncs)
|
||||||
|
|
||||||
This is the most common failure, and it has one main cause: **driving the core from the OpenOCD command port while Binary Ninja is connected.**
|
This is the most common failure, and it has one main cause: **driving the core from the OpenOCD command port while Binary Ninja is connected.**
|
||||||
|
|||||||
Binary file not shown.
Reference in new issue
Block a user