sec1.dk // research log nieldk
← back to blog
RFID & Hardware Hacking
Reverse Engineering RFID AT91SAM7S · STM32L4 Proxmark3 · ChameleonMini Firmware Analysis

PM3 SE Hub Mini — Tearing Apart a Closed-Source RFID Combo Device

date Jun. 2026 author nieldk read ~12 min

A card-sized device showed up on Chinese RFID research channels, the PM3 SE Hub Mini. It claims to combine a Proxmark3 and a ChameleonMini RevH into something the size of a thick credit card, controllable entirely over BLE from a phone. No public source code. No English documentation. Sold exclusively through Taobao and WeChat seller groups to a domestic market that doesn't particularly care about either of those things.

I had an installer, a firmware blob, and some board photos. That's enough.


What's on the board

The promotional board illustration shows a compact PCB with a 7-segment slot display across the middle, an NFC coil at the bottom labelled RF, a status LED cluster at the top labelled STA, and exactly two small QFN-packaged ICs side by side in the centre. The back shows two tactile buttons (A and B) and the PM3 SE Hub mini silkscreen.

The left IC is readable: 32L433C6 / 7AA7R 9R / PHL 129 with the ST logo, consistent with an STM32L4 series ARM Cortex-M4. The right IC has partially legible markings consistent with a second small QFN. Both packages appear similar in footprint, roughly 5–7 mm square.

The architectural paradox: firmware telemetry vs. board illustration

Here the analysis hits a direct contradiction. The firmware binaries shipped in the installer insist on a specific, well-understood hardware architecture:

The AT91SAM7S512 in its standard LQFP-64 package has a 10×10 mm body. The Xilinx XC2S30 in TQFP-100 has a 14×14 mm body with gull-wing pins extending on all four sides, it would be the most visually dominant component on any board. Neither appears in the board illustration, which shows only two compact QFNs.

Important caveat
The available board imagery is promotional product art, not a photograph or X-ray of a physical unit. Marketing renders for Chinese electronics routinely simplify or idealise PCB layouts, replacing complex component arrays with uniform clean boxes, removing test pads, and resizing packages to make the board look sleek. Treating this illustration as a reliable physical schematic would be a methodological error. The firmware telemetry is the primary evidence; the render is an anomaly to be noted, not a ground truth.

With that caveat stated, the contradiction is real and worth documenting for any researcher who obtains a physical unit. Three hypotheses account for the discrepancy:

Hypothesis A — The render is simply stylised. Opening a physical casing reveals a conventional layout with discrete LQFP-64 (AT91SAM7S512) and a compact FPGA in BGA or WLCSP packaging. The board art is marketing collateral that bears no resemblance to the actual PCB. This is the most conservative and statistically most likely explanation, Chinese product renders routinely bear no physical resemblance to the boards they represent.

Hypothesis B — System-in-Package or Multi-Chip Module. The vendor used a custom SiP or MCM housing the AT91SAM7 core and FPGA fabric on a shared substrate beneath a single unassuming QFN shell. This would explain why only two packages are visible if one of them is a multi-die assembly. SiP integration is common in Shenzhen contract manufacturing for high-density portable products.

Hypothesis C — Modern SoC with soft-core emulation. The subsystem runs on a modern Chinese FPGA or SoC (Gowin, Anlogic, or similar) using an internal soft-core processor and FPGA fabric to natively execute the legacy AT91SAM7 binary and Spartan-II bitstream. The firmware flashes to 0x00100000 because the SoC maps its internal flash to that address range to maintain binary compatibility with the unmodified Iceman client. This is technically sophisticated but not unprecedented in the Chinese clone hardware ecosystem.

All three hypotheses are consistent with the firmware evidence. Only physical decapsulation, X-ray, or a board-level trace would distinguish between them. The firmware analysis is the same regardless of which hypothesis is correct, the AT91SAM7 instruction set executes somewhere, the FPGA logic runs somewhere, and the Iceman codebase operates as designed.

The companion app screenshot reveals the device name is ProxMini on Android. The Windows GUI is called ChameleonMiniRevH_GUI.exe. Both are clues about the software architecture regardless of the physical implementation.


Cracking open the installer

The Windows installer is a 7-zip self-extracting archive. Extracting it yields:

ChameleonMiniRevH_GUI.exe          <-- main GUI (x64)
ChameleonMiniRevH_GUI_x86.exe
client/
  proxmark3.exe                    <-- Iceman PM3 client
  bootrom.elf                      <-- AT91SAM7 bootrom
  fullimage.elf                    <-- AT91SAM7 firmware
  Chameleon-Mini-Update.bin        <-- STM32 firmware blob
  PM3_SE_Hub_BLE.dll               <-- Windows BLE bridge
  PM3_SE_Hub_BLE_x86.dll
  resources/hardnested_tables/     <-- 300+ bitflip LZ4 tables
setup.bat

The setup.bat installs to %LOCALAPPDATA%\PM3_SE_Hub\install and launches the x86 GUI with --install. No registry entries. Typical small-workshop distribution pattern.

The ELF firmware files

The vendor ships both ELF files inside the installer so the GUI can flash the PM3-side over USB. They are the actual device firmware, not stripped-down stubs. Both are extracted and provided here for independent analysis:

FileSizeSHA256Notes
bootrom.elf14 KB76ec740f…2a3e457AT91SAM7 bootrom, not stripped, debug symbols present
fullimage.elf408 KB16e75b86…6bc4a5eAT91SAM7 PM3 firmware, stripped, contains embedded FPGA bitstreams
Chameleon-Mini-Update.bin370 KB75a5b2a7…43cfcf58STM32 Chameleon firmware, AES-128-ECB encrypted, key unknown

Both ELF files target ARM EABI5, soft-float, statically linked. The version section of fullimage.elf is unambiguous:

PM3V\x01\x01\x02Iceman/master/v4.21128-124-g190f6265c-dirty
2026-03-15 01:52:00
482b41e4b

So the firmware is a private dirty fork of Iceman v4.21128, built 2026-03-15. The memory map confirms the target:

Entry point:    0x102001      # bootrom at 0x100000, main at 0x102000
.text           0x00102000    # AT91SAM7 flash range
.data           0x00200000    # AT91SAM7 internal SRAM

bootrom.elf (not stripped) contains AT91F_USB_SendZlp, AT91F_USB_SendData and other Atmel AT91 HAL symbols. This is definitively an AT91SAM7S512 (512 KB flash confirmed by the flash client output showing Available memory: 512K and flash range 0x00100000–0x00180000).

The FPGA bitstream strings confirm the rest:

fpga_pm3_hf.ncd     image 2s30vq100  15-03-2026 01:15:32
fpga_pm3_lf.ncd     image 2s30vq100  15-03-2026 01:15:32
fpga_pm3_felica.ncd image 2s30vq100  15-03-2026 01:15:32
fpga_pm3_hf_15.ncd  image 2s30vq100  15-03-2026 01:15:32

Xilinx Spartan 2s30vq100 — the same FPGA used in the RDV4. This is a full PM3 RDV4 clone at the hardware level, not a simplified variant.

Hardware identification — primary evidence (firmware-derived)

The following is derived entirely from the firmware binaries, independent of any board illustration or physical observation.

AT91SAM7S512 — confirmed from six independent sources:

# 1. AT91 HAL symbols in bootrom.elf (not stripped, debug symbols present)
AT91F_USB_SendZlp, AT91F_USB_SendData   # Atmel AT91 HAL — not present on any other MCU family

# 2. Entry point in bootrom.elf
0x00100000                               # AT91SAM7S boot vector address

# 3. Flash range from live flash client output (screenshot)
Permitted flash range: 0x001000000x00180000   # 0x80000 = 512 KB → S512
Available memory: 512K

# 4. SSC peripheral base in fullimage.elf (Synchronous Serial Controller — FPGA comms)
0xFFFD4000   85 code references           # AT91SAM7S SSC — not present on other MCUs

# 5. PIOA peripheral base in fullimage.elf
0xFFFFF400   217 code references

# 6. No PIOB in fullimage.elf — diagnostic for S-series specifically
0xFFFFF600   0 references                 # AT91SAM7S = PIOA only; AT91SAM7X has PIOB

The absence of PIOB is particularly diagnostic. The AT91SAM7S family has a single PIO controller (PIOA only), while the AT91SAM7X has both. 217 PIOA references and zero PIOB references identifies the S-series unambiguously.

Xilinx XC2S30 FPGA (2s30vq100) — confirmed from fullimage.elf:

# Xilinx bitstream sync word at fullimage.elf file offset 0x4b8d6
ff ff ff ff aa 99 55 66 30 00 80 01 00 00 00 07 ...

# Preceding Xilinx bitstream header (parsed standard format)
design = 'fpga_pm3_hf.ncd'   # field tag 0x61
part   = '2s30vq100'        # field tag 0x62
size   = 42096 bytes        # field tag 0x65

The sync word 0xFFFFFFFF_AA995566 is hardcoded in silicon, it is the Xilinx Spartan-II configuration bitstream synchronisation sequence, present in every XC2S30 bitstream ever produced. It cannot appear by coincidence. The header fields follow standard Xilinx .bit file format and parse cleanly to the expected part number and design name.

The 85 SSC peripheral references connect the two findings directly: the AT91SAM7S SSC is used both to configure the FPGA on boot (SPI slave mode) and to stream RF sample data during operation (DMA mode). The error string FpgaSetupSscDma failed. Exiting and the function reference FpgaDownload confirm the firmware actively manages the FPGA lifecycle at runtime.

The bitstream target string 2s30vq100 is a compilation artifact from the Iceman toolchain, it identifies the FPGA logic the bitstream was compiled for. What physical silicon implements that logic is addressed in the architectural paradox section above. The firmware evidence confirms the logic unambiguously; the physical implementation remains an open question pending hardware decapsulation.


Dual-MCU architecture

The PDB path embedded in PM3_SE_Hub_BLE.dll gives away the developer's machine:

C:\Users\w065\STM32CubeIDE\workspace_1.18.0\ChameleonMiniRevH_GUI\BLE_DEV\PM3_SE_Hub_BLE\

Built in STM32CubeIDE. The DLL exports a clean BLE GATT interface (BLE_Initialize, BLE_ScanDevices, BLE_ConnectDevice, BLE_SendData, BLE_EnableTXNotification) implemented over Windows BT GATT APIs using C++/WinRT.

The architecture resolves cleanly:

LayerComponentRole
AT91SAM7S512Proxmark3 engineRFID protocol stack, FPGA control, USB CDC
FPGA (logic confirmed as 2s30vq100-equivalent)FPGA analog front-endHF/LF/Felica analog signal processing
STM32L4 seriesBLE bridge + ChameleonBLE GATT, ChameleonMini RevH emulator
SPI NOR flash (board photo: G5V20A)SPI NOR flashCard data, keys, SPIFFS filesystem (PM3 side)

The STM32 is not a passive BLE adapter, it also runs full ChameleonMini RevH firmware, making this a genuine dual-mode device. The GUI bottom tab strip shows PM3 | 变色龙 (Chameleon) | 配置信息 (Config).

The inter-MCU link is confirmed by the REBOOTPM3 command string in the GUI binary, the STM32 Chameleon side can command the AT91SAM7 to reboot.


What the GUI reveals

Extracting all command strings from the GUI binary shows a standard ChameleonMini RevH serial protocol plus some interesting additions. The STM32 DFU memory layout strings are particularly informative:

@Internal Flash   /0x08000000/12*02Ka,372*02Kg

12 × 2 KB read-protected (bootloader at 0x08000000), 372 × 2 KB writeable from 0x08006000. Total addressable flash implied by this descriptor: 384 × 2 KB = 768 KB. This is worth noting: no STM32L433 variant has 768 KB of flash (the largest L433 is 256 KB). The board photo marking suggests an L433C6 but the DFU descriptor contradicts it, the actual chip may be a different STM32L4 variant (L476, L452, or similar), or the DFU descriptor may be non-standard. The GUI version string confirms STM32L4xx series; the specific part number remains uncertain.

Custom commands beyond stock ChameleonMini protocol include:

FM1208_09         # FM1208 CPU-only (ISO14443-4, no Classic interface)
FM1208_09_BYPASS  # CPU-only with authentication bypass
FM1208_10         # FM1208 CPU + Mifare Classic 1K dual-interface
FM1208_10_BYPASS
FM1208_TID        # Tag ID read
FM1208_SID        # Sector ID
FM1208_BOMB       # Irreversible card write — bricking command
REBOOTPM3         # STM32 → AT91SAM7 reboot signal
SHOW_BAT_LEVEL    # Battery status display on device

The FM1208 is Fudan Microelectronics' CPU card used extensively in Chinese transit and building access systems. Having dedicated _BYPASS and _BOMB commands makes the target market very clear.

The GUI terminal output from a live session shows a Gen 4 GDM/USCUID magic card being probed, with the card fingerprinted as Fudan-based (FM11RF08 clone), PRNG as weak, and slot 3 configured for ISO15693 / 特斯联 (Tesilon), a major Chinese apartment access control brand.


The firmware encryption scheme

The most interesting artifact in the package is Chameleon-Mini-Update.bin. It ships inside the installer and is deployed to %LOCALAPPDATA%\PM3_SE_Hub\install\client\Chameleon-Mini-Update.bin on the user's machine. New firmware versions arrive via new installers, there is no separate download path for the blob. The file from V1.1.5 is provided here for independent analysis:

Chameleon-Mini-Update.bin, 370 KB, SHA256 75a5b2a781b01940fefc9bca4c440f6c7ab24da488483dceac7c45ba43cfcf58

It's 378,608 bytes (exactly divisible by 16) and has essentially perfect entropy:

0x000000–0x001000: entropy = 7.944 bits/byte
0x001000–0x002000: entropy = 7.955 bits/byte
...
Overall:           entropy = 7.998 bits/byte  (max 8.0)

The "vector table" at offset 0 doesn't contain 0x20xxxxxx stack pointers or 0x0800xxxx reset vectors, it's random-looking bytes from byte zero. This is not a raw STM32 flash image. It's AES-128 encrypted ciphertext.

The GUI binary confirms this. The strings _KEY_128L and _KEY_128H appear as field identifiers in the APDU packet construction code, and the function at offset 0x6919 handles upgrade_H / key.bin. The upgrade flow is:

  1. User enters a 32-character hex string (16 bytes) in the GUI "升级密钥" field
  2. GUI caches the raw bytes to key.bin in %LOCALAPPDATA%\PM3_SE_Hub\install\client\
  3. GUI sends the encrypted firmware blob over BLE or USB
  4. STM32 bootloader decrypts with its hardcoded AES-128 key and flashes the result
Architectural constraint

The STM32 bootloader must decrypt the firmware update before it can flash it. This means the AES-128 key is hardcoded in the STM32 bootloader at 0x080000000x08006000, it cannot be per-user or per-device. Every unit shipped has the same key burned into its bootloader. The "upgrade key" the seller distributes out-of-band is simply that same single global key, presented as if it were individual, it is not.

The key is not in the installer package, not in ChameleonMiniRevH_GUI.exe, and not in Chameleon-Mini-Update.bin. The GUI merely caches a key string the user types, it has no knowledge of whether that string is correct until the STM32 accepts or rejects the decrypted result. The only authoritative copy of the key is in the STM32 bootloader, which was never distributed and exists only on the physical hardware.

Note: bootrom.elf included in this package is the AT91SAM7S512 PM3 bootrom, the Proxmark3 side. It does not contain the STM32 key.

The key is distributed out-of-band by the seller via WeChat/QQ to paying customers. The protection is real but architecturally fragile, a single key for all devices means anyone who extracts it from one unit has it for all units forever.

⚠ Vendor lock-in
No key, no OTA firmware update over BLE. The AES protection gates the BLE UPGRADE path entirely. The key is only recoverable from: (1) the seller, (2) key.bin on a machine where a valid key was previously entered, or (3) a DFU dump of the STM32 bootloader from a physical device.

The bypass that was already there

The GUI binary also contains:

0x08000000:force   # Chameleon serial command — force STM32 into DFU
0x08006000:leave   # DFU leave address after flash
READ_UNPROTECT     # STM32 RDP removal tool — presence implies vendor expects RDP may need clearing

The 0x08000000:force command is a ChameleonMini serial protocol extension that reboots the STM32 into its native USB DFU bootloader. Once in DFU mode, the device enumerates as "STM32 Bootloader" on USB and accepts firmware via standard DFU protocol, no key, no vendor authentication whatsoever.

# trigger DFU mode (via GUI button or raw serial)
echo -e "0x08000000:force\r" > /dev/ttyACM0

# flash your own STM32 firmware directly — no key needed
dfu-util -a 0 -s 0x08006000:leave -D your_chameleon_firmware.bin

# dump the STM32 bootloader (0x08000000-0x08006000 = 24KB)
# THIS DUMP CONTAINS THE AES-128 KEY
dfu-util -a 0 -s 0x08000000:0x6000 -U stm32_bootloader.bin

The presence of READ_UNPROTECT in the GUI suggests the vendor anticipated needing to clear RDP, whether RDP is actually enabled on shipped units is unknown without a physical device. If RDP is absent, the bootloader dump above succeeds and the key is directly recoverable from it.

Why the bootloader dump gives the key: the STM32 bootloader is the only place the AES-128 key exists, it must be hardcoded there to decrypt incoming firmware updates before flashing them. The GUI never had the key; it only cached whatever string the seller provided. The installer package never contained the key. The bootloader is the single authoritative source.

Once you have stm32_bootloader.bin, scan for the key:

from Crypto.Cipher import AES

TARGET = bytes.fromhex('4a58b82f45e46cbb3e9d45256be2a0f8')  # AES(key, 0x00*16)
data   = open('stm32_bootloader.bin', 'rb').read()

for i in range(0, len(data) - 16, 4):
    k = data[i:i+16]
    if AES.new(k, AES.MODE_ECB).encrypt(b'\x00' * 16) == TARGET:
        print(f'KEY FOUND at offset 0x{i:x}: {k.hex()}')
Note, GUI binary patch
If you want to bypass the key check in the Windows GUI itself (e.g. to send a custom-keyed firmware blob via BLE), patch the JNZ at file offset 0x68c7: change 0f 85 61 01 00 00 to e9 62 01 00 00 90. This forces the code to always take the "key loaded" branch. The STM32 still needs the correct key to decrypt, this only removes the PC-side prompt.

Key derivation, what's actually in the binary

A full sliding-window scan of the 2.99 MB GUI binary testing every 16-byte offset as a candidate AES-128 key against the oracle confirms: the key is not stored as a raw 16-byte constant anywhere in the executable.

The binary imports several Windows CryptoAPI hash functions (CryptCreateHash, CryptHashData, CryptGetHashParam) and contains CALG_MD5, CALG_SHA1, CALG_SHA256 constants, which initially suggested hash-based key derivation. Disassembly proves this wrong, those imports have zero call sites in the binary. They are dead imports, likely pulled in by a statically linked library.

The actual key handling is in the function at file offset 0x68b8 / VA 0x1400074b8. Disassembly of this function shows:

  1. GetWindowTextW reads the upgrade key string from the UI input field (max 0x21 = 33 chars)
  2. A loop calls an internal formatter with format string %.2hhx, parsing each pair of hex characters into one raw byte, iterating 16 times
  3. The resulting 16-byte buffer is written to key.bin via fwrite(buf, 1, 16, fp)

There is no hashing, no KDF, no derivation. The upgrade key the seller distributes is a 32-character hex string. The GUI converts it directly to 16 raw bytes and uses those as the AES-128-ECB key. The key.bin file at %LOCALAPPDATA%\PM3_SE_Hub\install\client\key.bin stores exactly these 16 bytes.

This means:

A custom AES implementation is also present in the binary (S-box at file offset 0x1fa300, Rcon at 0x1fa2e0), used for the actual decryption of Chameleon-Mini-Update.bin.

Extracting the key

This device is built on GPL-licensed firmware from emsec/ChameleonMini and the RfidResearchGroup Iceman fork. Both licenses require that users can study, modify and reflash the firmware. Encrypting the update channel is a GPL violation. The following documents how to recover the key you are entitled to.

There are three realistic paths, in order of practicality:

Method 1, STM32 bootloader DFU dump (definitive, works without the seller)

The key is hardcoded in the STM32 bootloader at 0x08000000. If RDP is not set on the device:

# Trigger DFU mode
echo -e "0x08000000:force\r" > /dev/ttyACM0

# Dump the 24KB bootloader region
dfu-util -a 0 -s 0x08000000:0x6000 -U stm32_bootloader.bin

# Scan for the key
python3 -c "
from Crypto.Cipher import AES
T = bytes.fromhex('4a58b82f45e46cbb3e9d45256be2a0f8')
d = open('stm32_bootloader.bin','rb').read()
for i in range(0,len(d)-16,4):
    k=d[i:i+16]
    if AES.new(k,AES.MODE_ECB).encrypt(b'\x00'*16)==T:
        print(f'KEY: {k.hex()}')
"

Method 2, read key.bin (only useful if seller has already provided the key string)

If the GUI has previously been run with a valid key string from the seller, the decoded 16 raw bytes are cached at:

%LOCALAPPDATA%\PM3_SE_Hub\install\client\key.bin
with open(r'%LOCALAPPDATA%\PM3_SE_Hub\install\client\key.bin', 'rb') as f:
    key = f.read()   # 16 bytes — the AES-128 key
print(key.hex())

Method 3, x64dbg / Frida (only useful if you already have the seller's key string)

Important, this is not independent key recovery
The GUI is a blind push client. It takes whatever 32 hex characters the user types, decodes them to 16 raw bytes, and pushes them to the STM32. The STM32 is the only component that validates whether the key is correct. If you enter random hex characters and break at 0x14000756b, rcx will point to exactly those random bytes, not the vendor's master key. The oracle assertion will fail. These methods are only useful to verify or observe a key string you already received from the seller.

If you have the seller's 32-char hex key string and want to verify it before trusting it, the breakpoint confirms the bytes being cached:

XOR analysis, structural plaintext recovery without the key

In AES-ECB, identical ciphertext blocks reveal identical plaintext blocks. Algebraic recovery of plaintext from ciphertext without the key is impossible, but that is not what is happening here. Structural constraint recovery is plaintext recovery. Because firmware follows rigid architectural rules, deterministic padding, fixed vector table layouts, predictable memory map, the ciphertext block frequencies directly reveal the exact plaintext values of the most common regions. The firmware surrenders its core landmarks before a single key byte is known. This is not inference or approximation; it is exact plaintext knowledge derived from engineering constraints rather than cryptanalysis.

Known plaintext blocks (confirmed by structural necessity):

Ciphertext blockCountKnown plaintextBasis
4a58b82f45e46cbb3e9d45256be2a0f8 255× 0x00 × 16 Flash padding at sector tails is always null bytes. 255 repetitions is structurally impossible to be anything else. This block is the primary AES oracle: AES(key, 0x00×16).
ff685328e8138e9770ea754072063b48 67× 0xFF × 16 Unprogrammed STM32 flash erases to 0xFF. Blocks of this value at predictable boundaries are the secondary oracle: AES(key, 0xFF×16).
c386f3f9260617088da67a4ba51e999f 56× [Default_Handler_addr] × 4 Located at 0x08040d000x08041130 inside the interrupt vector table region. Every unused IRQ vector in an STM32L4 image points to the same Default_Handler address. A single 32-bit Thumb address repeated 4 times fills a 16-byte block. 56 consecutive identical blocks = 56 unused IRQ slots all pointing to the same handler. The encrypted block leaks the handler's address indirectly, once the key is recovered, this block immediately reveals Default_Handler's exact location.

This gives us two confirmed plaintext/ciphertext pairs that can serve as oracle values for key verification:

AES(key, 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00) = 4a58b82f45e46cbb3e9d45256be2a0f8
AES(key, ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff) = ff685328e8138e9770ea754072063b48

Any candidate key must satisfy both. This does not reduce the 128-bit key space, but it does mean a single DFU bootloader dump scan can be validated against two independent oracles rather than one, eliminating false positives with certainty.

Scanning for adjacent identical ciphertext blocks (XOR = 0x00×16) across all 23,663 blocks:

STM32 addressBlocksBytesKnown/inferred plaintext
0x08006040, 0x08006070, 0x080060c02×332×3Vector table, IRQ entries, all pointing to same handler address (9f7f443f...)
0x08006160348Vector table, another repeated handler address (3ca875f3...)
0x08013120464Repeated 64-byte region, lookup table or compiler padding
0x080270a08128Repeated 128-byte region, large table or memset block
0x08030390464Repeated pattern
0x08040d000x0804113056896Default_Handler address ×4, repeated across all unused IRQ slots
0x0803f8c00x080400101151840Known: 0x00×16, BSS/uninitialised data (null padding)

From the block positions the firmware occupies approximately 0x080060000x08055000 = ~316 KB. This is larger than any STM32L433 variant (max 256 KB), further evidence the chip is a larger STM32L4 variant, L452, L476, or similar.

Block statistics across the 23,663-block (378,608-byte) encrypted image:

Total blocks:                    23663
0x00×16 (null padding):            255  (1.1%)  — known plaintext, primary oracle
0xFF×16 (erased flash):             67  (0.3%)  — known plaintext, secondary oracle
Default_Handler ×4 (IRQ table):     56  (0.2%)  — known structure, unknown address value
Ciphertext blocks appearing once: 22660 (95.7%)  — appear unique in ciphertext space
Other repeated structural blocks:  681  (2.9%)  — partially inferred

The 95.7% figure requires careful interpretation. It does not mean 95.7% of the firmware consists of structurally unique 16-byte sequences. AES-ECB encryption randomises output distributions, identical plaintexts produce identical ciphertexts, but similar or related plaintexts produce completely unrelated ciphertexts. In unencrypted ARM Cortex-M firmware, a large fraction of blocks would be repeated: common instruction sequences, zero-initialised struct fields, function prologues, alignment padding, jump tables. After AES-ECB encryption, all of those become statistically indistinguishable from unique high-entropy blocks. The 95.7% figure reflects encryption-induced ciphertext diversity, not underlying plaintext diversity. The actual code and data blocks have substantial repetition that is invisible until the key is recovered.

; Key-save function: RVA 0x074b8  file 0x68b8
; Only executes after user provides key string — does NOT contain or reveal the key independently

RVA 074e2  call    [GetWindowTextW]         ; reads seller-provided hex string from UI -> [rsp+0x50]
RVA 07505  call    0x1400016d3             ; sscanf loop x16: "%.2hhx" -> raw byte at [rsp+0x40]
RVA 0756b  call    0x1400f21e8             ; fwrite([rsp+0x40], 1, 16, fp) — *** BREAKPOINT HERE: rcx = key buffer pointer ***
; At this CALL, rcx = key buffer (Windows x64 fastcall arg1) — stable read point
; Do NOT break at 0x7570 — fwrite may have altered local stack frame
RVA 07573  call    0x1400f2178             ; fclose

x64dbg: set breakpoint at VA 0x14000756b (the CALL fwrite instruction, before it executes). When it fires, rcx holds the pointer to the 16-byte key buffer, dump [rcx] for 16 bytes. Do not break after fwrite returns at 0x7570 since fwrite may alter the local stack frame making [rsp+0x40] unreliable.

Frida, hook at the fwrite call site (RVA 0x756b) and read rcx directly. This is stable regardless of stack frame changes inside fwrite. Hooking after fwrite returns at 0x7570 risks reading a corrupted stack offset since fwrite may have altered the local frame:

// frida -l hook.js -f ChameleonMiniRevH_GUI.exe
// Hook at RVA 0x756b — the CALL to fwrite, before it executes
// At this point rcx holds the pointer to the 16-byte key buffer (Windows x64 fastcall arg1)
const base = Module.getBaseAddress('ChameleonMiniRevH_GUI.exe');
Interceptor.attach(base.add(0x756b), {
    onEnter(args) {
        const key = this.context.rcx.readByteArray(16);
        const hex = Array.from(new Uint8Array(key))
            .map(b => b.toString(16).padStart(2, '0')).join('');
        console.log('[+] AES-128 key:', hex);
    }
});

Verify and decrypt (whichever method obtained the key):

from Crypto.Cipher import AES

key    = bytes.fromhex('<16 bytes from bootloader scan, key.bin, or debugger>')
ORACLE = bytes.fromhex('4a58b82f45e46cbb3e9d45256be2a0f8')

assert AES.new(key, AES.MODE_ECB).encrypt(b'\x00' * 16) == ORACLE, 'wrong key'

with open('Chameleon-Mini-Update.bin', 'rb') as f:
    enc = f.read()

dec = b''.join(
    AES.new(key, AES.MODE_ECB).decrypt(enc[i:i+16])
    for i in range(0, len(enc), 16)
)

with open('Chameleon-Mini-Update-decrypted.bin', 'wb') as f:
    f.write(dec)

print(f'Decrypted {len(dec)} bytes')

The decrypted binary is a raw STM32 flash image. Load it in Ghidra as ARM Cortex-M little-endian with base address 0x08006000 to analyse the FM1208 BYPASS implementation, TID/SID field layout, and BOMB command sequencing.


FM1208 magic card deep-dive

Beyond standard Mifare Classic support, the most operationally interesting feature of this device is its FM1208 attack suite. Fudan Microelectronics FM1208 is a CPU card (ISO7816-4 compliant) used widely in Chinese apartment building and elevator access control systems. The device exposes seven FM1208-specific Chameleon emulation modes.

Correction
An earlier version of this post incorrectly described FM1208_09 and FM1208_10 as firmware versions (v0.9 and v1.0). A reader correctly pointed out that they are hardware variants, not versions. FM1208_09 is the CPU-only variant (ISO14443-4, SAK 0x20); FM1208_10 is the dual-interface variant adding a Mifare Classic 1K sector array (SAK 0x28). This is confirmed by the Iceman upstream source (cmdhf14a.c line 2408: FM1208-10 with MIFARE Classic 1K) and the SAK semantics, 0x28 has both the ISO14443-4 bit and the Classic-compatible bit set, while 0x20 has only the ISO14443-4 bit.

The emulation modes

These are set via the standard ChameleonMini CONFIG command and select which FM1208 variant the STM32 presents to a reader:

CONFIG modeMeaning
FM1208_09Emulate FM1208 CPU-only card (ISO14443-4, no Classic interface), SAK 0x20
FM1208_09_BYPASSCPU-only with authentication bypass magic
FM1208_10Emulate FM1208 CPU + Mifare Classic 1K dual-interface card, SAK 0x28
FM1208_10_BYPASSCPU+Classic dual-interface with authentication bypass magic
FM1208_TIDTag ID magic, freely settable TID returned to reader
FM1208_SIDSector ID magic, freely settable SID
FM1208_BOMBCard-bricking mode, sends 00 DD non-standard command to a real card. The irreversible nature of this command is inferred from the mode name and the non-standard CLA; a Fudan datasheet would confirm.

The BYPASS modes, based on the mode name alone, as the mechanism lives in the encrypted STM32 ChameleonMini firmware, exist because Chinese building access readers perform a cryptographic challenge-response authentication against the FM1208 before reading any data sectors. A reader sends 00 88 (INTERNAL AUTHENTICATE) with a random challenge; the card must respond with a mathematically valid signature derived from a secret key. Simply replaying a captured response from a previous session will fail because the challenge is different each time. The BYPASS modes contain a hardcoded exploit of a specific cryptographic weakness, static key, or reader implementation flaw in the target reader brands, not a passive data table. This is why the encrypted STM32 firmware is the critical artifact: the bypass engine is proprietary reverse-engineered logic, not a configuration value.

Proprietary APDU command set

By scanning the GUI binary's data section I recovered the complete FM1208 APDU transaction table. FM1208 uses standard ISO7816-4 commands alongside Fudan proprietary extensions (CLA=0x80):

# Standard ISO7816-4 commands used
00 88 P1 00 09   # INTERNAL AUTHENTICATE
00 A4 00 00 0F   # SELECT FILE by ID
00 A4 04 00 0F   # SELECT by AID (15-byte AID)
00 B0 00 00 0E   # READ BINARY
00 B2 01 04 0E   # READ RECORD
00 D6 00 00 0D   # UPDATE BINARY
00 DC 01 04 0D   # UPDATE RECORD
00 E2 00 04 09   # APPEND RECORD
00 20 00 00 09   # VERIFY (PIN)
00 C0 00 00 02   # GET RESPONSE

# Fudan proprietary (CLA=0x80)
80 0E 00 00 10   # ERASE (Fudan proprietary)
80 D4 01 00 0D   # WRITE — 13-byte payload, 12 file addresses targeted
80 E0 00 00 0D   # READ base file
80 E0 00 02 0D   # READ file 0x02
80 E0 00 10 0D   # READ file 0x10 (TID region?)
80 E0 00 20 0D   # READ file 0x20
80 E0 00 21 0D   # READ file 0x21
80 E0 00 22 0D   # READ file 0x22 (SID region?)
80 E0 10 00 0D   # READ group 0x10, offset 0
80 E0 3F 00 01   # READ MF root (P1=3F)
80 E0 P1 P2 01   # READ single byte at P1/P2 (0x00,0x01,0x02)
80 E8 5F 50 xx   # Unknown — possibly security status / personalization

# Fudan non-standard (CLA=0x00, INS not in ISO7816)
00 DD 00 00 01   # *** BOMB — 1-byte payload, irreversible lock
00 DD 00 01 03   # *** BOMB variant — 3-byte payload

# Secure messaging variant
84 0E 00 00 02   # ERASE with SM (CLA=84)

The 00 DD commands are not in any published ISO7816 standard. The name BOMB, combined with the non-standard CLA/INS pair, strongly suggests this is Fudan's personalization lock command, an irreversible write that transitions the card from factory mode to production mode. Once sent, the card's security configuration is permanently frozen. The two variants (1-byte and 3-byte payload) likely correspond to different lock levels or lock targets.

⚠ BOMB warning
00 DD 00 00 / 00 DD 00 01 sent to a real FM1208 card in factory mode will permanently lock it. This is not recoverable. The Chameleon BOMB emulation mode likely uses this to provision cloned cards into a locked production state, or to brick cards intentionally.

Encryption analysis, key material for further research

The STM32 ChameleonMini firmware blob is the most valuable artifact in the package. The FM1208 magic protocol implementation, the bypass authentication logic, and the BOMB sequencing all live in the ChameleonMini (STM32) side, this is the correct architecture, since the ChameleonMini handles card emulation while the PM3 side handles reader attacks. The BYPASS logic in particular is proprietary: it exploits specific cryptographic weaknesses in target reader brands and cannot be reconstructed from APDU traces alone.

Firmware blob facts

File:    Chameleon-Mini-Update.bin
Size:    378608 bytes  (0x5c6f0)
SHA256:  75a5b2a781b01940fefc9bca4c440f6c7ab24da488483dceac7c45ba43cfcf58
Blocks:  23663 × 16 bytes  (exactly divisible by 16, no IV prepended)
Cipher:  AES-128-ECB  (confirmed by repeated ciphertext blocks)
Key:     unknown — distributed out-of-band by vendor

AES-ECB confirmation and known-plaintext oracle

The firmware is AES-128-ECB (not CBC). This was determined by finding 334 repeated 16-byte ciphertext blocks, including 181 consecutive pairs, which is impossible in CBC mode where each ciphertext block depends on the previous one. In ECB, identical plaintext blocks always produce identical ciphertext blocks, so zeroed flash regions are directly visible as repeated ciphertext.

The most frequent ciphertext block appears 255 times, with the longest run being 115 consecutive blocks (1840 bytes) starting at file offset 0x398c0. In the decrypted firmware this will be a 1840-byte region of 0x00 bytes, blank flash padding.

This gives us a known-plaintext oracle. The most common ciphertext block is AES-ECB(key, 0x00 × 16):

# Known plaintext oracle:
# AES-ECB-128(key, b'\x00' * 16) ==
4a58b82f45e46cbb3e9d45256be2a0f8

# Use this to verify any candidate key:
from Crypto.Cipher import AES

TARGET = bytes.fromhex('4a58b82f45e46cbb3e9d45256be2a0f8')

def check_key(candidate_key: bytes) -> bool:
    cipher = AES.new(candidate_key, AES.MODE_ECB)
    return cipher.encrypt(b'\x00' * 16) == TARGET

# Decrypt once key is found:
def decrypt_firmware(key: bytes):
    cipher = AES.new(key, AES.MODE_ECB)
    with open('Chameleon-Mini-Update.bin', 'rb') as f:
        ct = f.read()
    return cipher.decrypt(ct)

Additional ciphertext-of-zero candidates (from other frequently repeated blocks) provide further oracle points, useful for validating partial key candidates in a search:

# All repeated blocks and their occurrence counts:
4a58b82f45e46cbb3e9d45256be2a0f8  × 255  ← AES(key, 0x00*16) — primary oracle
ff685328e8138e9770ea754072063b48  ×  67  ← likely AES(key, another padding pattern)
c386f3f9260617088da67a4ba51e999f  ×  56
2421836069078c3a1da194b431e8fd9b  ×  20
5449140914c18e3e4472099b3c0bb601  ×  20

ECB structural leakage

Because ECB is used, the ciphertext leaks firmware structure without the key. The 23663-block file contains 22662 unique ciphertext blocks, meaning only ~4.2% of blocks repeat. The repeated blocks correspond to blank flash regions in the STM32. The distribution of these regions reveals the approximate layout:

Offset      Length    Notes
0x398c0     1840 B    largest blank region — likely end of .text/.rodata padding
0x38810      128 B    blank region
0x38c00      128 B    blank region
0x38cb0      128 B    blank region (8 × 16)
0x38d60      128 B    blank region

The clustering of blank regions around 0x38000–0x3A000 suggests the firmware code ends around that address in flash. With base 0x08006000, that places code end around 0x083E000–0x083F000, roughly 220–230 KB of actual firmware. The specific STM32 flash size is uncertain given the contradiction between the board marking and the DFU descriptor.

Obtaining the key

Three practical paths to the decryption key:

1. USB DFU dump, if RDP is not set on the target device. The GUI includes READ_UNPROTECT tooling; whether shipped units have RDP enabled is unknown without physical access. If open, trigger DFU mode and dump flash directly:

# Trigger DFU via Chameleon serial (USB connection)
echo -e "0x08000000:force\r" > /dev/ttyACM0

# Full flash dump — includes bootloader + firmware
dfu-util -a 0 -s 0x08000000:0x40000 -U stm32_dump.bin

# The hardcoded AES key will be in the bootloader (0x08000000–0x08006000)
# Scan for it by testing against the oracle:
python3 -c "
from Crypto.Cipher import AES
TARGET = bytes.fromhex('4a58b82f45e46cbb3e9d45256be2a0f8')
with open('stm32_dump.bin','rb') as f:
    data = f.read()
for i in range(0, len(data)-16, 4):
    k = data[i:i+16]
    if AES.new(k, AES.MODE_ECB).encrypt(b'\x00'*16) == TARGET:
        print(f'KEY FOUND at offset 0x{i:x}: {k.hex()}')
"

2. key.bin on an existing installation, any Windows machine that has run the GUI after a valid upgrade key was entered will have the key cached at:

%LOCALAPPDATA%\PM3_SE_Hub\install\client\key.bin

3. Contact the seller directly, the GUI embeds the seller's QQ contact number:

QQ: 601676629
http://wpa.qq.com/msgrd?v=3&uin=601676629
Note, phone-home
The GUI calls http://ip-api.com/json/?lang=zh-CN on startup to geolocate the user. Worth being aware of if running the software for analysis.
Note, ECB is a gift here
Because AES-ECB is used, a single known plaintext/ciphertext pair (e.g. from the zero-block oracle) is sufficient to verify any candidate key. If you obtain the key through any channel, the oracle block confirms it immediately before attempting a full decrypt.

Summary

The PM3 SE Hub Mini is a legitimate dual-MCU RFID research device with genuine engineering depth. The hardware is a proper AT91SAM7S512 + Spartan-2s30vq100-compatible FPGA PM3 RDV4 clone running a current Iceman fork, paired with an STM32L4 series MCU running ChameleonMini RevH firmware that also handles BLE. The two MCUs communicate directly, with the STM32 able to command PM3 reboots.

The target market is the Chinese access control grey market, FM1208 CPU card bypass and bricking commands, Tesilon ISO15693 emulation slots, Mifare Classic autopwn, and hardcoded key dictionaries all point to a tool designed for apartment/elevator access cloning workflows, sold informally through Taobao.

The firmware encryption on the BLE OTA path is real and the key is not in the installer. The USB DFU path is unprotected, and the ECB mode used means the ciphertext leaks firmware structure. Anyone with physical access to a device and a Linux box can dump the STM32 flash and find the key without needing to contact the vendor.

ComponentFinding
PM3 firmwareIceman v4.21128-124 dirty fork, built 2026-03-15
Main CPUAT91SAM7S512, flash 0x100000–0x180000, 512 KB
FPGA2s30vq100-compatible logic (bitstream confirmed); physical package unknown, TQFP-100 is geometrically impossible in card form, likely BGA/WLCSP or modern Chinese equivalent
BLE/Chameleon MCUSTM32L4 series (specific variant uncertain, board photo suggests L433, DFU map contradicts it)
External flashSPI NOR flash, ~2 Mbit (board photo: G5V20A, unconfirmed from binary)
OTA encryptionAES-128-ECB, no IV, key out-of-band. File: Chameleon-Mini-Update.bin
ECB oracleAES(key, 0×00×16) = 4a58b82f45e46cbb3e9d45256be2a0f8
Firmware SHA25675a5b2a7…43cfcf58 (378608 bytes)
USB DFURDP status unknown, READ_UNPROTECT tooling present in GUI; if unset, dfu-util -s 0x08000000 dumps flash
Key gate patchGUI offset 0x68c7: 0f85→e962 + NOP
FM1208 BOMB00 DD 00 00 01 / 00 DD 00 01 03, irreversible lock
Developer identityw065@outlook.com (Authenticode cert CN)

Implementing FM1208 support on ChameleonUltra

ChameleonUltra already has everything needed at the infrastructure level. The TAG_TYPE_HF14A_4 = 3000 slot type provides a full ISO14443-4 T=CL emulator with I/R/S-block handling, a 12-entry static APDU response table (flash-backed via FDS), and configurable anticollision responses (UID, ATQA, SAK, ATS). The RC522 handles the reader role. Nothing needs to be built from scratch, this is purely a matter of wiring known values into the existing framework.

What is confirmed vs what is missing

Working from the PM3 SE Hub installer, the upstream Iceman source (cmdhf14a.c, armsrc/iso14443a.c), and the APDU table extracted from the GUI binary:

ValueFM1208SourceStatus
SAK0x28Iceman cmdhf14a.c line 2406–2410confirmed
ATQA00 04 (or 00 44 CL2)SAK=0x28 branch, no ATQA filterprobable
ATSunknownnot in any binary examinedneed sniff
AID (SELECT)15-byte, value unknownplaceholder zeros in APDU tableneed sniff
All Fudan proprietary APDUscomplete setGUI APDU tableconfirmed
BYPASS auth responseunknownin encrypted STM32 firmwareneed FW dump
TID file address80 E0 00 10 likelyAPDU table P2 sequenceprobable
SID file address80 E0 00 22 likelyAPDU table P2 sequenceprobable
BOMB payload bytesunknownLc=1 or Lc=3, value unknownneed FW dump

For reference: the sibling card FM11RF005SH (Fudan Shanghai Metro, a simpler card with proprietary crypto) uses ATQA 03 00, SAK 0x0A, confirmed from armsrc/iso14443a.c line 1427–1431. That card does not use ISO14443-4 and is a separate implementation target.

Reader-side commands (implementable now)

The complete APDU set for reading and probing a real FM1208 card is known. These map directly to hf 14a raw sequences and can be wrapped as a hf fm1208 command group in the Python CLI:

# Probe / info
00 A4 04 00 0F [15-byte AID]  # SELECT by AID — AID value unknown, try broadcast
00 A4 00 00 0F [file_id]      # SELECT FILE by ID
00 88 01 00 09 [8-byte chal]  # INTERNAL AUTHENTICATE — P1=key index

# Read file addresses (Fudan proprietary READ, CLA=0x80)
80 E0 00 00 0D  # READ base — 13-byte response
80 E0 00 02 0D  # READ file 0x02
80 E0 00 10 0D  # READ 0x10 — probable TID location
80 E0 00 20 0D  # READ 0x20
80 E0 00 21 0D  # READ 0x21
80 E0 00 22 0D  # READ 0x22 — probable SID location
80 E0 10 00 0D  # READ group 0x10
80 E0 3F 00 01  # READ MF root (1 byte)

# Read single bytes (version / status flags)
80 E0 00 00 01  # READ 1 byte at base
80 E0 01 00 01  # READ 1 byte at 0x01
80 E0 02 00 01  # READ 1 byte at 0x02

# Write (Fudan proprietary WRITE — 13-byte payload)
80 D4 01 00 0D [13 bytes]    # WRITE to current file

# Erase
80 0E 00 00 10  # ERASE — 16-byte payload
84 0E 00 00 02  # ERASE with Secure Messaging

# Standard ISO7816
00 B0 00 00 0E  # READ BINARY
00 B2 01 04 0E  # READ RECORD
00 D6 00 00 0D  # UPDATE BINARY
00 20 00 00 09  # VERIFY PIN — 8-byte payload
00 C0 00 00 02  # GET RESPONSE

# ⚠ Destructive — do not send to real access cards
00 DD 00 00 01 [1 byte]     # BOMB — irreversible personalization lock
00 DD 00 01 03 [3 bytes]    # BOMB variant

Emulator-side: tag_base_type.h additions

Six new tag types slot naturally into the HF 14443-4 range above TAG_TYPE_HF14A_4 = 3000:

// firmware/application/src/rfid/nfctag/tag_base_type.h

// FM1208 series (Fudan CPU card — Chinese access control)  3100
TAG_TYPE_FM1208_09        = 3100,  // FM1208 CPU-only (ISO14443-4, SAK=0x20)
TAG_TYPE_FM1208_09_BYPASS = 3101,  // CPU-only with auth bypass magic
TAG_TYPE_FM1208_10        = 3102,  // FM1208 CPU + Classic 1K dual-interface (SAK=0x28)
TAG_TYPE_FM1208_10_BYPASS = 3103,  // CPU+Classic dual-interface with auth bypass magic
TAG_TYPE_FM1208_TID       = 3104,  // magic: freely settable TID
TAG_TYPE_FM1208_SID       = 3105,  // magic: freely settable SID

Each type maps to the nfc_tag_14a_4 backend with different factory data. The anticollision parameters for FM1208_09 and FM1208_10 (standard emulation) should default to SAK 0x28, ATQA 00 04, with the ATS left as a placeholder until a real card is sniffed.

Anticollision factory defaults

// nfc_tag_fm1208.c — factory data for standard FM1208 emulation
// Values confirmed: SAK from Iceman fingerprint tree, ATQA probable
// ATS: placeholder — UPDATE after sniffing a real card

static const nfc_tag_14a_coll_res_entity_t fm1208_coll_res_default = {
    .atqa  = { 0x00, 0x04 },          // ATQA — probable, verify with real card
    .sak   = 0x28,                     // SAK — confirmed (ISO14443-4 + Classic)
    .uid   = { 0x01, 0x02, 0x03, 0x04 }, // placeholder UID
    .uid_len = 4,
    // ATS — unknown, placeholder until real card sniffed:
    .ats   = { 0x0A, 0x78, 0x80, 0x82, 0x02, 0x00, 0x00, 0x00 },
    .ats_len = 8,
};

For comparison, MIFARE DESFire uses ATS 06 75 77 81 02 80 and the JCOP/EMV type uses 13 78 80 72 02 80 31 80 66 b1 84 0c 01 6e 01 83 00. FM1208 ATS historical bytes will identify the Fudan applet version and should become clear from any real-card sniff trace.

Static APDU response table for normal emulation

The nfc_tag_14a_4 engine supports 12 pre-loaded command/response pairs stored in FDS flash. For FM1208 normal emulation, the minimum viable table needs responses to SELECT, READ (at each probed address), and INTERNAL AUTHENTICATE. With a real card and hf 14a sniff these can be captured in a single reader interaction and loaded directly:

# On CU with a real FM1208 in field, capture the exchange:
hf 14a sniff

# Then load the captured responses as the static APDU table:
hf 14a 4 apdu add --cmd 00A4040F[AID] --resp [select_response]
hf 14a 4 apdu add --cmd 80E000000D   --resp [read_base_response]
hf 14a 4 apdu add --cmd 80E000100D   --resp [tid_response]
hf 14a 4 apdu add --cmd 80E000220D   --resp [sid_response]
hf 14a 4 apdu add --cmd 008801[chal] --resp [auth_response]  # BYPASS mode only

The BYPASS and magic modes

The BYPASS modes are where the real value lies, they likely allow authentication to succeed without knowing the card key, though the exact mechanism is in the encrypted STM32 firmware and unconfirmed. The probable magic auth response (what the emulator sends back to 00 88) lives inside it. Two paths to get it:

Path 1, Sniff a successful bypass: if you have a PM3 SE Hub with a BYPASS slot configured and a compatible reader, sniff the exchange with hf 14a sniff. The reader sends 00 88 01 00 09 [challenge] and the magic card responds with some response + 90 00. Capture that, it may be static (simplest case) or challenge-derived (harder).

Path 2, STM32 firmware dump: DFU mode may be accessible if RDP is not set. Dump flash, search for the auth response handler. The oracle value 4a58b82f45e46cbb3e9d45256be2a0f8 confirms the key once found.

The TID and SID magic modes are simpler, they return a freely programmable value from a specific file address. Based on the APDU P2 sequence in the GUI table, the TID likely lives at 80 E0 00 10 and the SID at 80 E0 00 22, but these addresses are inferred from the address sequence in the APDU table, not confirmed from firmware. These modes still require passing the reader's authentication step, but may target reader brands where the authentication uses a known static key rather than a per-device secret, making them tractable without the BYPASS engine.

What a sniff trace would give you

A single hf 14a sniff session against a building door reader with a real FM1208 card would yield: the correct ATS (historical bytes), the AID used in SELECT, and the exact file addresses the reader queries. However, the authentication challenge-response pair captured from one session cannot be replayed, readers issue a fresh random challenge each time. For the basic TID/SID emulation modes (which don't require passing authentication), the sniff data is sufficient. For BYPASS emulation, the bypass logic itself is needed, which lives in the encrypted STM32 firmware.

Implementation checklist
Can implement now: reader-side hf fm1208 commands (info, dump, write, auth probe) using the known APDU set extracted from the GUI binary.
After real-card sniff: ATS/historical bytes and AID for correct anticollision response. Useful for reconnaissance, not sufficient for emulation, since the reader will issue a random authentication challenge the emulator cannot answer without either the card key or a bypass.
After STM32 firmware dump: the BYPASS exploit code itself, which implements an active cryptographic attack against specific reader brands. This is not a static response value to replay, it is a state-machine exploit that must be ported to the target platform. The dump also reveals the TID/SID file addresses and the BOMB command sequence.
For full emulation (non-bypass): requires either the FM1208 card's authentication key (to respond correctly to challenges) or knowledge of a static-key reader brand where the key is known. Neither is derivable from a sniff alone.