Firmware reverse engineering

What a 2 MiB blob can tell you

Reverse-engineering the Charmera keychain camera
with AI and radare2

SHA-256 d8797ced94fc2aaf9c72f3ffcff91af4ac584b77c36437daccc276dc0f422008

The object

The Kodak Charmera — from shelf to flash chip

teardown

Teardown: PCB, battery, sensor, speaker

product line

Product line (7 styles)

programmer and clip

SPI programmer + SOIC8 clip on the flash

flash close-up

Flash chip under the clip

Method

How to look at it with radare2

dump inspection

file only says "data". The byte pattern at 0x200 (ldr pc,[pc,#..], the exception vector table) identifies ARM32 little-endian — that is why you pass -a arm -b 32. r2 defaults to x86 and reports havecode=false.

The problem

3,900 functions you will never read by hand

function wall and disassembly

Left: the function list from afl. Right: the raw ARM of one function (pd 38 @ 0x1d714). This is the scale no human holds in their head — which is why the method and the AI come next.

Method

AI + radare2, iterated

the commands: radare2, driven headless

radare2 commands

a slice of the AI chat that guided the analysis

AI session excerpt

Loop: the AI proposes a hypothesis (e.g. "fcn.00021208 is a hex parser"); we check it in radare2 — seek, pd/pdf, axt, string search — then disassemble the next function. Every claim is tied to a concrete file offset (e.g. the checksum loop at 0x21458), so it stays verifiable.

Disassembly

Functions reveal their domain

function map

No symbols, so r2 names them only fcn.XXXXXXXX. Counts are approximate (nearest domain anchor): 3,562 code functions, plus 352 aaa hits that land inside data (compressed resources, tables) — false recognitions.

Method & result

Finding data — and where it is used

data lifecycle
logo frame internal photo

Same method for every asset: search the signature, validate it, find the consumer, land on the runtime moment. Found: 4 JPEG, 5 WAV, 12 GPZP. GP+ is Generalplus's own container for these UI assets (a name/ID table plus GPZP / JPEG / WAV payloads).

The object

Facts before interpretation

image structure

2 MiB · SHA-256 d8797ced… · byte-sum 113C7C08 · no ELF · MMIO init table in the header · GP+ package at 0x120000

Rendering

The blitter — and the frames it draws

blitter and frame layers

All four frames, decoded — each 800×600, YUV 4:2:0 stacked from 3 layers.

Custom content

Swap a frame, or build your own

frame workflow

Custom content

Custom sounds

sound resources

Hardware from code

The firmware describes the board

hardware blocks

Logical blocks, not a physical PCB layout: sensor, JPEG, SPI-NOR, SD, display, buttons, audio, USB, battery.

Input

Buttons and states

navigation states playback states

Firmware IDs and MMIO bit numbers — not physical pinout.

Input

How a press reaches display, capture and sound

event flow

Capture

One photo, end to end

capture flow captured photo

Trigger → IMG.WAV → pipeline → C:\DCIM\PICT%04d.jpg

Audio

Sound IDs to resources

sound ids

A keypress can trigger an action even when no sound plays — the selector checks options and state first.

Highlight

The update path

update search and verify update flash

Search on the SD card, verify the byte-sum, then erase, program and read back — and what it forgets to check.

Update

The filename is the whole "security"

filename schema
search / verify sequence flash sequence

The expected value comes only from the 8 hex chars in the filename; what is checked is the byte-sum of the whole file (mod 232) — not a CRC, not a signature.

Hidden features

Formats and paths the menu never shows

formats

Hidden feature

SDK module inventory

sdk modules

~780 KiB of SDK source in six layers (SDIO → driver → TCP/IP → WPA → app/HTTP). Debug log strings leaked the original function names — free reconnaissance.

Takeaways

  • One dump, no symbols → recovered media, UI, behavior and hardware hints.
  • Hidden formats matter: photo frames are YUV 4:2:0, not RGB565 — misread they give streaks and wrong colours.
  • Copied SDK leftovers reveal hidden features and the exact build.
  • The updater trusts a filename: expected value = 8 hex chars, checked against a plain byte-sum of the file — no signature.
  • So a custom image (logo, frames, sounds, strings) is achievable.
  • AI + radare2 move faster, but every claim is pinned to an offset and verified against ground truth.

Thank you

Questions?

Tools: extract_gpz.py · jh_sum.py · check_update_candidate.py · verify_update.py (Unicorn) · radare2

Reproducible: hash-pinned scripts, exported evidence, no firmware written without a backup.