> For the complete documentation index, see [llms.txt](https://breakpoint-journal.gitbook.io/breakpoint/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://breakpoint-journal.gitbook.io/breakpoint/embedded-systems/cracking-open-a-blink-mini-camera.md).

# Cracking Open a Blink Mini Camera

I've always had a fascination with embedded reverse engineering but never took the time to truly get into it. I personally loved watching [Flashback's YouTube channel](https://www.youtube.com/@FlashbackTeam), and I actually almost interned for a reverse engineering lab at JHU's APL while in college. Looking back that would've been a fantastic introduction into the mystical realm of RE, but I ultimately turned them down for a SWE internship at SpaceX, an internship I didn't even decide to return to for fulltime... (if you're a student you should definitely check out JHU's Applied Physics Lab for entry-level roles!)

<div align="center"><figure><img src="/files/MXTxCFCQ1UXkmMNjf6kI" alt="" width="240"><figcaption></figcaption></figure></div>

Anywho... I finally plucked up the courage to order one of the cheaper cameras I could find on Amazon, even purchasing the free trial of Prime to satisfy my impatient and newfound drive to pursue this area. My dad had bought one of these before, so I thought, why not try to crack it open?

## TLDR&#x20;

In this post, I'll walk through the steps I took to setup, crack open, and extract the firmware off the Blink Mini BCM00300UB, as well as some minor firmware analysis. Unfortunately, I wasn't able to get very far on the actual decompilation as the processor and instruction set appear to be proprietary.

{% hint style="info" %}
I also want to give a big shoutout to [Astrid Yu's blog series on this content](https://astrid.tech/projects/blink-mini-re/). After the camera arrived, I stumbled upon their blog and realized they already did a large portion of the grunt work in identifying what clips and tools were necessary to analyze this device. Check'em out at [astrid.tech](https://astrid.tech).
{% endhint %}

## :shopping\_cart: Let's Go Shopping

I fortunately didn't have to search very far for the equipment I needed. Astrid's blog posts were a tremendous help in expediting the time with which it took me get started on this project. In the next section I'll cover how exactly they discovered this.

<table data-view="cards"><thead><tr><th align="center"></th><th align="center"></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td align="center">Blink Mini Camera</td><td align="center">$24.99</td><td data-object-fit="contain"><a href="/files/QH1IKHOJZs3ZWV9kRNAI">/files/QH1IKHOJZs3ZWV9kRNAI</a></td><td><a href="https://a.co/d/0j3k7AIr">https://a.co/d/0j3k7AIr</a></td></tr><tr><td align="center">SOIC8 USB Programmer + 1.8V Adapter</td><td align="center">$14.99</td><td data-object-fit="contain"><a href="/files/5kl8GRAjMvPdNlVJx8ag">/files/5kl8GRAjMvPdNlVJx8ag</a></td><td><a href="https://a.co/d/01ILoUyz">https://a.co/d/01ILoUyz</a></td></tr><tr><td align="center">WSON8 Probe</td><td align="center">$25.98</td><td data-object-fit="contain"><a href="/files/doKZwcuwJ9PgLTuku6MD">/files/doKZwcuwJ9PgLTuku6MD</a></td><td><a href="https://a.co/d/01iJcxHw">https://a.co/d/01iJcxHw</a></td></tr></tbody></table>

The disassembly wasn't too tricky, I used a small philips screwdriver with some other tools from a [$3.89 iPhone repair kit](https://www.walmart.com/ip/Cell-Phone-Repair-Tool-Kit-iPhone-Android-8-Piece-Set-Plastic-Metal-Tools-Includes-Screwdrivers-Suction-Cup-Disassembling-Tools-Multicolor/20391602111?wmlspartner=wlpa\&selectedSellerId=101657011\&adid=2222222222720391602111_101657011_14069003552_202077872\&wl0=\&wl1=g\&wl2=c\&wl3=42423897272\&wl4=pla-2449037643288\&wl5=9197632\&wl6=\&wl7=\&wl8=\&wl9=pla\&wl10=5357014655\&wl11=online\&wl12=20391602111_101657011\&veh=sem\&gad_source=1\&gad_campaignid=202077872\&gbraid=0AAAAADmfBIoifz4HppCwU3jr-3sDVhuzH\&gclid=CjwKCAjw0dPRBhAPEiwAE5vTTizyk2j4K1kR01Oq-WsHSujfyYjwyXDmleDmLYmXTXFKZVAyBoeyLhoCMskQAvD_BwE).&#x20;

{% hint style="warning" %}
You should be careful with the speaker however as it will block the separation of the casing.&#x20;
{% endhint %}

Anyways, the camera separates into two separate boards. The first appears to be the WiFi module and the second appears to be the actual camera and processor.

<div><figure><img src="/files/G52xpjpQBi5dcPFZHpWz" alt=""><figcaption></figcaption></figure> <figure><img src="/files/Jh3mTENhYpUfnWNZTpka" alt=""><figcaption></figcaption></figure> <figure><img src="/files/h9vrHBjha087zLbykVIk" alt=""><figcaption></figcaption></figure> <figure><img src="/files/ZqEJ6VEVaz3NZqj9N3Oj" alt=""><figcaption></figcaption></figure></div>

<div><figure><img src="/files/M6KAc5ZyWPn6scGWAC2E" alt=""><figcaption></figcaption></figure> <figure><img src="/files/ZWjY9dBVVvYqZkfm0sxT" alt=""><figcaption></figcaption></figure></div>

### Couple of things...

* Board manufactured by Immedia Semiconductors. This model appears to be the 1660 Main Rev C2
* AC1002B CPU
* Windbond SPI NOR Flashrom

After snooping around for a bit, it appears the **AC1002B** is a proprietary processor developed by **Immedia Semiconductor** and used in more models than just the Blink Mini -  Amazon’s Blink XT2 security camera utilizes the same CPU as well ([Factsheet](https://www.askalexa.com/pluginfile.php/2534/local_category_languages/factsheets/0/Blink%20Factsheet)). It is not a publicly available or open-source chip, so detailed technical specifications such as full instruction set, architecture, or manufacturing process are not publicly disclosed.

<figure><img src="/files/XcsJAXb7sWmugemqkzP3" alt="" width="187"><figcaption></figcaption></figure>

But perhaps we can glean some information from the strings of the firmware!

## TODO

* [ ] Find filesystem, mount it, navigate
* [ ] What is Trusted Boot
* [ ] Learn about general encryption / cryptography issues
* [ ] Capture traffic, decrypt HTTPS traffic or disable HTTPS by supplying my own private key
* [ ] Enable debug mode
* [x] Setup MCP
* [ ] Find filesystem, mount it, navigate
* [ ] What is Trusted Boot
* [ ] Learn about general encryption / cryptography issues
* [ ] Capture traffic, decrypt HTTPS traffic or disable HTTPS by supplying my own private key
* [ ] Enable debug mode
* [ ] Address Tables
* [ ] gp SetRegister where applicable
* [ ] trace strings back to original
* [ ] use the 2018 arc600 gnu tools instead (<https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/tags?after=arc-2018.09-rc2>)
* [ ] intercept traffic with apktools

## Extracting the Firmware

<figure><img src="/files/bXRKEImHbpOfaMCLdgeY" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/OHGgHJS20v4cwdicDy90" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/eYQX1hMvNX37poUvPqyq" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/3Hpiqlp3n5jYbieEvk0h" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/OSbRCjcuyNiIaptQCegi" alt=""><figcaption></figcaption></figure>

## MCP Setup

<figure><img src="/files/QxGIKQsM5MIaAcIeNr4v" alt=""><figcaption></figcaption></figure>

## Parse C Source Files

<figure><img src="/files/8rrbWj7kJdSvZPENdDYl" alt=""><figcaption></figcaption></figure>

## BSim Parsing

Gave it to Claude to generate the H2 file since bulk imports haven't arrived yet

{% embed url="<https://github.com/NationalSecurityAgency/ghidra/discussions/5530>" %}

Also generated a custom Ghidra script to run the similarity scoring across the functions in the firmware

<figure><img src="/files/J1zNLkWiTqxR95PKmEHG" alt=""><figcaption></figcaption></figure>

{% embed url="<https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases/tag/arc-2026.03-release>" %}

Good progress — I confirmed FUN\_ram\_00000e48 really is memset (matches BSim's guess), but also found FUN\_ram\_000010a0 is memmove (BSim guessed wrong: timingsafe\_bcmp/wmemset) and FUN\_ram\_000010e8 looks like memchr (BSim guessed wmemchr — same family, wrong width). No strlen yet in this cluster. Let me check a few more neighbors.

Called ghidra-mcp 8 times, ran 1 shell command

Here's what the manual verification turned up. I decompiled the cluster of small early functions (0x500–0x1610) that BSim's earlier full-corpus run flagged as libc-shaped, and checked each against BSim's own guess for it:

<figure><img src="/files/39q5gQ7yLsLH5wCKlPVV" alt=""><figcaption></figcaption></figure>

I never found a bare strlen in this cluster — its neighbors turned out to be the rest of the str\*/mem\* family (strcmp, strncmp, memset, memmove, memchr, memcpy), just not strlen itself. It may be dead-code-eliminated if nothing calls it directly (common if the code always tracks lengths itself or only uses sizeof), or it's sitting elsewhere and I haven't found it yet.

The more important finding: similarity score is not a reliable filter here. The one correct match (memset) scored lower (0.303) than two of the wrong matches (strncmp→0.338, memmove→0.328). Significance tracked correctness better in this small sample (16.79 for the correct memset match vs. 6–10 for the wrong ones), but that's only 6 data points. This validates the plan's caveat — MetaWare vs. GCC codegen drift is real, and scores need decompilation checked, not trusted directly.

Want me to keep hunting for strlen specifically (e.g. tracing backward from %s-handling printf internals), or move on to running the full 478-function query properly (with the self-significance filter added, to kill the degenerate sim=1.000 stub-collision noise I hit earlier) and rank by significance instead of similarity?

## Finding %gp

Here's my plan, if I can't find gp in this or other sections, then obviously I must be parsing something wrong or not getting the full picture, I still haven't even found the entry point yet...

### Idea 1

Ask claude... lol, no luck same thing that I found, gp gets restored on thread context restore

### Idea 2

identify printf and other libc functions. can discover where strings are loaded and, if we know what the string address is and what the calculation / offset is to get to the string, then perhaps we can calculate %gp. I discovered strlen totally by accident by picking a random function and telling claude to analyze it for similarity. I told claude to run a binary search type of search for printf, assuming it appears in the same offset relative to other functions but I'm actually not certain that's how that works or if it differs depending on the compiler

<figure><img src="/files/ViTC60OeoqiPHYpdbKQt" alt=""><figcaption></figcaption></figure>

No luck... gonna reach out to the God himself

#### Idea 3

You have access to Ghidra MCP. I have a firmware from segment1 in blink182 project but the base address if off. There are some hardcoded strings in memory that are the ground truth for what the base address is. The idea is to find a decompiled library function from libc, netxduo, or threadx with an expected and predictable error message, such that we can find the offset between the expected message in memory and what the hardcoded value is. For example, take the following string:&#x20;

p\_misc\_string                                   XREF\[1]: build\_device\_report:000382e4(R)

ram:000384ec 20 05 2b 27 undefined4 20052B27h

The ground truth address is 20052b27, however, it doesn't line up with the start of a proper string or match the context.

WORKED

I'd want to pull the exact filename string for the source file containing `build_device_report` (it's probably not literally "misc.c" — that name is a Ghidra auto-generated placeholder) and cross-reference against the `.c` file list already pulled, plus check bytes at `0x428f4`/`0x42564`/`0x45c08` directly, before committing to a base-address fix. Want me to continue from here?

Continue

Validated memory offsets through cross-referencing multiple anchor pointsValidated memory offsets through cross-referencing multiple anchor points

That's a strong lead. Let me verify precisely:

* `0x2005d40e` (used as an isolated `%d` format string in `build_device_report`) → local `0x2005d40e − 0x20002400 = 0x5b00e`. I inspected that address and it is **exactly** a standalone, null-terminated `"%d\0"` string with a clean boundary before and after.
* `p_misc_string` (`0x20052b27`, used as a `__FILE__`-style arg to `error_s(file, line, ...)`) → local `0x20052b27 − 0x20002400 = 0x50727`. That address is **exactly** `"../../src/app/onboard/onboard.c"` in the filename-string list I pulled earlier — a perfect semantic match, since `build_device_report` (an onboarding/device-report function) plausibly lives in `onboard.c`.

Two independent, exact hits on delta = **`0x20002400`**. Let me get a third confirmation from a totally different function before I commit to it — I'll pull another `error_s(file, line, ...)` call site and check its file pointer against the known `.c` filename list.

Decompile functionDecompile function

That third check also lines up beautifully — but it revealed an important nuance. Let me confirm it precisely.

Used ghidra-mcp integrationUsed ghidra-mcp integration

Let's check these two more embedded absolute addresses I just spotted (`0x20054f6c`, `0x20054f86`) against the same delta.

Validated memory offset findings and deliberated rebase application strategyValidated memory offset findings and deliberated rebase application strategy

Confirmed. Both new checks land exactly on clean string boundaries:

* `0x20054f6c → local 0x52b6c` → starts precisely at `"Unsupported accessory %d.\n"`
* `0x20054f86 → local 0x52b86` → starts precisely at `"Accessory "..."` (next string in the same error-message block)

That's **four independent, exact confirmations** of the same delta, each landing on a clean string/data boundary with contextually sensible content:

| Literal (ground truth)         | `− 0x20002400` | Local address | Content found                                                                        |
| ------------------------------ | -------------- | ------------- | ------------------------------------------------------------------------------------ |
| `0x2005d40e`                   |                | `0x5b00e`     | standalone `"%d\0"`                                                                  |
| `0x20052b27` (`p_misc_string`) |                | `0x50727`     | `"../../src/app/onboard/onboard.c"` — matches `error_s(file,line,...)` usage exactly |
| `0x20054f6c`                   |                | `0x52b6c`     | start of `"Unsupported accessory %d.\n"`                                             |
| `0x20054f86`                   |                | `0x52b86`     | start of `"Accessory ..."`                                                           |

**The correct base address offset for `segment_1_00000160_app` is `+0x20002400`.** Currently Ghidra has it loaded at `ram:00000000`; it should be `ram:20002400`.

One useful side-finding: not every pointer needs this correction. A few `.data`-section pointer variables I spotted (e.g. the one Ghidra auto-named `PTR_DAT_ram_00054020...` / `PTR_DAT_ram_00056b60...`) already store small, zero-based values that resolve fine as-is. That's expected — those are `.data` pointers, evidently pre-relocation placeholders patched at boot, whereas the values that need the `+0x20002400` correction are **absolute 32-bit immediates embedded directly in code** (ARCompact long-immediate operands), which are position-dependent and already encode the true final load address. So after rebasing, don't be surprised if a handful of `.data` pointers look "off" relative to the new base — that's a separate, expected quirk, not a sign the rebase is wrong.

Want me to go ahead and apply this now with `set_image_base` (rebasing `segment_1_00000160_app` to `0x20002400`), or would you rather review first? Rebasing will shift every address in the program, so I'd suggest saving/snapshotting the project first if you haven't already.

### Decompilation Issues

#### Empty-Body Functions

<figure><img src="/files/Dqx7mJ72C8dpWWfMr9RF" alt=""><figcaption></figcaption></figure>

What do? Huh?

What are called saved register? Registers from the callee that need to be saved? Give me an example of why we’d need these and a sample caller and callee. How does blink and bl work? I’m not familiar with this assembly language

Orchestrated explanation of register preservation, function calls, and assembly mechanicsOrchestrated explanation of register preservation, function calls, and assembly mechanics

Good, let's build this up from scratch.

**Callee-saved registers**

In any calling convention, the set of CPU registers gets split into two buckets:

* **Caller-saved (a.k.a. "volatile"):** the callee is free to trash these. If the caller has a value in one of these it still needs *after* the call, the caller must save it itself (usually to the stack) *before* calling.
* **Callee-saved (a.k.a. "non-volatile" or "saved"):** the callee is allowed to *use* these registers, but if it does, it must restore their original values before returning. The caller can trust that these registers survive a function call untouched.

So yes — your instinct is right: "callee-saved" means "saved *by* the callee, on behalf of the caller." The callee is the one doing the saving (pushing at entry, popping before return); the caller is the one who benefits (its values in those registers are guaranteed to still be there after the call returns).

**Why do we need this split at all?**

Without it, every function call would be a minefield — you'd never know which of your register values survived a call without reading the callee's source. Splitting registers into two groups gives a *contract*: "these registers might change, so save what you need before calling" vs "these registers are yours to rely on across calls."

**Concrete example**

```c
// caller
int compute(int x) {
    int total = x * 2;       // say this lands in r13 (callee-saved)
    total += helper(x);      // call helper — r13 must survive this
    return total;
}

// callee
int helper(int x) {
    int y = x + 1;           // helper wants to use r13 as scratch too
    ...
    return y;
}
```

If `helper` wants to use `r13` internally, it *cannot* just clobber it — `compute` is relying on `r13` still holding `total` when `helper` returns. So `helper`'s prologue must:

1. Push (save) `r13` to the stack on entry.
2. Do whatever it wants with `r13` in its body.
3. Pop (restore) `r13` before returning.

That push/pop is exactly the thunk we looked at — `helper` doesn't write those 4 store instructions itself, it just `bl`s into the shared thunk which does `st.a r13,[sp,-4]` etc., then the thunk's own `blink` return lands back inside `helper`'s prologue, and `helper` continues.

Created a custom script for this, you just highlight and it does the renaming for you...

<figure><img src="/files/4J2An5zQcTqBBc5j0AFt" alt=""><figcaption></figcaption></figure>

#### **How `bl` and `blink` work**

This is a "branch-and-link" call mechanism, common on RISC-y architectures (ARC, ARM, MIPS, etc. all have some version of it) — much simpler than x86's `call`/`ret` which push a return address to the stack automatically.

* **`bl target`** (branch-and-link): jump to `target`, and **before jumping**, copy the address of the *next* instruction (the return address) into a dedicated register called the **link register** — on ARC that's `blink`. Nothing touches the stack; it's just: `blink = PC + (instruction size); PC = target`.
* **`j blink`** (jump to blink) or **`j_s.d blink`** (the short/delay-slot form we saw): jump *back* to whatever address is sitting in `blink`. This is how the callee returns — it doesn't need to know the address, it just jumps to wherever `blink` says.

So a call/return pair looks like:

```
        bl   helper        ; blink := address of next instr; jump to helper
        ...                ; execution resumes here after helper returns
helper:
        ...
        j    blink          ; jump back to the saved return address
```

The catch: `blink` is a *single* register, so it can only hold *one* return address at a time. If `helper` itself calls another function before returning, that inner call's `bl` would overwrite `blink` — clobbering `helper`'s own return address. That's why you saw `push_s blink` at the top of `submit_device_report` (address `20045810`) before it does any `bl`s of its own: it's saving *its own* return address to the stack because it's about to make calls, and needs to restore `blink` before it can `j blink` to return to *its* caller.

This is also why `blink` itself is technically a callee-saved register in the ARC convention — any function that calls other functions has to preserve its incoming `blink` value across those calls, which is exactly what that `push_s blink` / (eventually) `pop_s blink` bracket around `submit_device_report`'s body does.

## JADX-GUI

1. Downloading the APK

{% embed url="<https://www.youtube.com/watch?v=0VN9UDi3Lwo>" %}

2. Setting up JADX

{% embed url="<https://github.com/skylot/jadx>" %}

3. Analysis

<figure><img src="/files/GtEik0vxJjCJKn3kVYYg" alt=""><figcaption></figcaption></figure>

## Diff'ing the Latest Update

...

## Reversing Resources

* [ ] Ghidra MCP (bennington) <https://github.com/bethington/ghidra-mcp>
* [ ] Ongoing PR for ARC support in Ghidra master <https://github.com/NationalSecurityAgency/ghidra/pull/3006>
* [ ] Jackson Donaldson - <https://www.linkedin.com/in/jackson-donaldson/>
* [ ] <https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases/tag/arc-2026.03-release>
*
* [ ] [Blink XT2 Specification / Review](https://techaeris.com/2019/10/10/blink-xt2-review-an-outstanding-small-outdoor-security-camera-system/)
* [ ] [Extracting Firmware from Embedded Devices (SPI NOR Flash) - Flashback Team](https://www.youtube.com/watch?v=nruUuDalNR0)
* [ ] [Astrid's Blink Mini Reverse Engineering](https://astrid.tech/projects/blink-mini-re/)
  * [ ] [Part 3: Firmware Analysis](https://astrid.tech/2022/08/03/0/blink-mini-fw-analysis/)
  * [ ] [Part 4: More Firmware Analysis](https://astrid.tech/2022/08/06/0/blink-mini-4/)
* [ ] [Fuzzy Matching w/ Ghidra BSim](https://www.pentestpartners.com/security-blog/fuzzy-matching-with-ghidra-bsim-a-guide/)
* [ ] [Ghidra w/ ARC](https://gitlab.com/hacking9773320/ghidra-with-arc)
* [ ] [Analyzing ARCompact Firmware with Ghidra - Nicolas Iooss](https://www.sstic.org/media/SSTIC2021/SSTIC-actes/analyzing_arcompact_firmware_with_ghidra/SSTIC2021-Article-analyzing_arcompact_firmware_with_ghidra-iooss.pdf)
* [ ] [Blink Fact Sheet](https://www.askalexa.com/pluginfile.php/2534/local_category_languages/factsheets/0/Blink%20Factsheet)

{% file src="/files/EwlnR71E55yGmvmL3RtV" %}

## API Resources

* [ ] [Blink WiFi Network FAQ](https://support.blinkforhome.com/en_US/wi-fi-or-network-issues/what-is-the-blink-wifi-connection?utm_source=chatgpt.com)
* [ ] [Reddit: Is anyone using the Blink API](https://www.reddit.com/r/blinkcameras/comments/11jnb4f/is_anyone_using_the_blink_api/)
* [ ] [Blink Integration Problem](https://community.home-assistant.io/t/113-2-blink-intergration-problem/216616/14)
* [ ] [fronzbot/blinkpy](https://github.com/fronzbot/blinkpy): Python library for Blink camera system
* [ ] [MattTW/BlinkMonitorProtocol](https://github.com/MattTW/BlinkMonitorProtocol)
* [ ] [lurume84/bling-viewer](https://github.com/lurume84/bling-viewer): Blink web-viewer
* [ ] [lurume84/bling-desktop](https://github.com/lurume84/bling-desktop): application container of Blink-Viewer project
  * [ ] <https://github.com/lurume84/bling-viewer/issues/7>
* [ ] [Blink Camera API Issue](https://community.home-assistant.io/t/looking-at-blink-cameras/26173/32?page=2)
* [ ] API Endpoints
  * [ ] <https://rest-prde.immedia-semi.com/>
  * [ ] <https://api.oauth.blink.com/>
  * [ ] <https://rest-prod.immedia-semi.com/>
  * [ ] <https://rest-u062.immedia-semi.com/>
