Add an opt-in simulator probe that checks a reader's AES authentication continuation against a supplied candidate and stops before the final card response. Expose the result through the firmware protocol and etest bench command, and document the evidence and handshake scope.
Co-Authored-By: OpenAI Codex <noreply@openai.com>
New CLI command `lf cotag sim` with COTAG tag simulation implementation.
Detects COTAG reader activation sequence and emulates a COTAG tag.
Signed-off-by: andrej@towelbyte.net
RF over CEP works now that the FPGA bitstream step is done - it was
never actually CEP-blocked. Note CMD_CEP_STATUS (Hardware Status
dashboard) and the real open bug (Read Hitag2 over CEP fails with
stale cached data, firmware-side). SPI bit-order/phase is confirmed,
not pending.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`lf t55xx detect -p <pwd>` reached for the password whenever detection
failed without one, and sent it across every downlink mode and again
after a wake. On a tag that is not password protected those frames can
be taken for a write: the PWD bit gets set and the tag's data is
destroyed, leaving it locked behind whatever block 7 already held.
`lf t55xx read` and `lf t55xx dump` already guard against this. They
verify the PWD bit in the configuration block before using a password,
and expose -o/--override to bypass the check deliberately. detect had
neither, and T55xxReadBlockEx() treats a live `detect -p` as proof the
bit is set.
detect cannot run the same check -- reading the configuration is what it
is for, and the password is only reached for once that has failed -- so
the risk has to be the user's to accept. Gate it behind -o/--override to
match read and dump, and carry read's warning banner into the help.
No change in behaviour for anyone not passing -p.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stage and verify a valid 4-byte UID before committing the clean configuration, propagate write failures, and preserve recovery when cleanup is incomplete. Correct the sector trailer template.
Co-Authored-By: OpenAI Codex <noreply@openai.com>
doegox asked on PR #3668 whether CEP really needs to be guarded by a
compile flag, suggesting it ship on by default with a SKIP_CEP opt-out
instead - mirrors the existing NO_LOWBATT_SHUTDOWN/NO_LOWBATT_BEEP
opt-out pattern in common_arm/Makefile.hal. WITH_CEP is now baked into
PM5's base PLATFORM_DEFS; PLATFORM_EXTRAS=SKIP_CEP filters it back out.
No change to armsrc/Makefile (already keys off the WITH_CEP define, not
the old extras token) or to the CEP transport logic itself. Updated the
PM5-FLIPPER-USAGE doc, the CHANGELOG entry, and one stale comment in
include/pm3_cmd.h that referenced the old opt-in token.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add --sys to the FeliCa reader so repeated polling can activate an Express Transit card while keeping the RF field on.
Co-Authored-By: Codex (GPT-6 Sol) <noreply@openai.com>
Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Wire up the previously-dead Flipper Zero handshake/SPI code from
at32_unit_test.c into a real WITH_CEP platform extra, independent of
BWM (new g_reply_via_cep flag, not reusing BWM's g_reply_via_fpc, so
both transports can run concurrently).
Fixes a real reconnect bug found during hardware bring-up: the
handshake listener was gated on a CC-controller attach edge that
didn't reliably fire on every FAP relaunch, requiring a physical
cable replug to reconnect. Now watches for a fresh incoming byte any
time the link reports attached, with a bounded receive timeout so a
stray byte can never hang the main loop.
RF/FPGA-dependent functionality (hf/lf reads) is out of scope here -
tracked separately. Closes#3667.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The module ships with BLE open: anyone in range can connect and run
commands, and there was no way to turn the radio off or to require
pairing from the PM5, although the module already implements bonding
with a static passkey (LE Secure Connections + MITM, SPP characteristic
encrypted) behind commands nothing exposed.
New CMD_PM5_BWM_BLE (0x0183): one action byte, then a full status
snapshot back (bwm_ble_status_t: switch, state, bonding, passkey, TX
power, address, name, bonded devices). Client menu `hw bwm ble`:
status everything above, with a warning while open
on | off persisted radio switch (needs the companion
Proxmark5_BWM_esp32 PR; off =
nothing can connect, USB/WiFi only)
pairing on|off [-k] require the 6-digit passkey, set the passkey; a
change of on/off restarts the stack (drops a
connected client), the factory key 123456 is
flagged
forget -i N | --all remove bonded devices
txpower -a/-c dBm advertising / connection power, -24..18 and 20
The firmware replies before restarting the stack so the reply survives
when the command itself arrived over BLE. Fields a module without the
switch cannot answer read 0xFF and print as unknown. A module that does
not answer at all ends the status snapshot after its first query instead
of running into the client's timeout.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The USB-unplug power-off left two ways to drain the battery: a PM5
woken by the button and forgotten, and one left idle after the phone
disconnected.
Idle trigger (opt-in, PM5_AUTOOFF_IDLE_MS 0 by default so out of the box
only the unplug trigger exists, as before): on battery the board powers
off after the idle timeout with no interaction and no wireless client.
Interaction is a received command over any transport, any button press,
or a client connecting or leaving; a standalone mode or a long-running
command holds the main loop, so neither can be cut short. The BWM
reports its clients with the LINK_STATE broadcast (8092, companion
Proxmark5_BWM_esp32 PR) which bwm_forward.c tracks; at boot the BLE
half is seeded with a status query since the module only reports
changes. When the timer is due but a client is still tracked, the module
is asked for its BLE state (at most every 10 s), so a lost "client left"
broadcast cannot pin the board on. And right before going off the module
is asked once more: older module firmware never sends the broadcast and
a "connected" one can get lost, and neither may power the board off
under a silent BLE client. No answer means off.
--unplug off: the unplug trigger always won, so "survive the unplug, go
off after N idle seconds" was not reachable. It is its own setting
(default on = unchanged). With it off an unplug only restarts the idle
clock - from the unplug, not from the last command, or a board that sat
idle on USB for an hour would still die with the cable. Unplug detection
is edge-based (VUSB present at the previous poll, absent at this one)
instead of "absent and seen since boot", which would fire on every poll
after a tolerated unplug. VUSB is followed while the feature is switched
off too, so switching it on again on battery is not taken for an unplug.
Stored on the BWM: runtime-only settings reset at every boot, which is
exactly when the idle trigger matters. The PM5 has no settings store, so
the switch, the idle timeout and the unplug switch live in the module's
host value slots (APP_CMD_SET/GET_SYS_HOST_VALUE 1021/1022, companion
PR; slot 0 switch, 1 idle seconds, 2 unplug), loaded at the first
auto-off poll and saved by CMD_PM5_BWM_AUTOOFF with 1 s per write, so a
silent or older module still leaves the reply inside the client's 5 s
wait. Without a module, or with an older one, the defaults apply and the
status says "not stored".
hw bwm autooff [on|off] [--idle <sec>] [--unplug <on|off>]; no argument
shows the state, one fact per line: switch, idle timeout, unplug
power-off, stored on module, USB power, USB seen since boot, unplug
trigger armed, tracked client, the module's BLE state, idle time.
Payload [action][enabled][idle_s u32][unplug, optional] with keep
sentinels; both actions are non-zero so an old firmware, which reads
byte 0 as the enable flag, can only be left enabled. hw status prints
the module's live BLE state and the auto power-off setting; at debug
level also the tracked client, which should agree with the live state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
New `hw bwm wifipower [off|none|min|max]`. `off` turns the BWM's WiFi
fully off (BLE-only, persisted; the same path as `hw bwm wifi stop`,
and the module's default state). The other three set the ESP's persisted
WiFi modem power-save type (CMD_PM5_BWM_WIFI_PS 0x0182, module commands
2052/2053, esp_wifi_set_ps()) used while WiFi is up: min (the ESP-IDF
default, the modem sleeps between DTIM beacons), max (sleeps for the
whole listen interval, lowest current, slower to react), none (modem
always on, lowest latency). With no argument it reports whether WiFi is
up at all and the stored type. Independent of `hw bwm powersave`.
The link helper's "cmd failed" diagnostic is now printed only at debug
level: a status query while WiFi is off fails by design and was
cluttering every call.
Verified on hardware: default reads min with WiFi off; none, max and
min each set and read back; off tears WiFi down; an unknown value is
rejected; max survives a module reboot. Not measured with WiFi
associated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
New `hw bwm powersave [on|off]` (CMD_PM5_BWM_POWERSAVE 0x0181) shows or
sets the ESP's persisted power-save switch (companion Proxmark5_BWM_esp32
PR: DFS, light sleep and slow advertising after a 30 s fast phase, or
the stock always-on behaviour), so the two can be A/B measured and the
saving turned off when chasing a link problem. `hw status` prints the
state next to the BWM firmware version.
Both module queries in `hw status` are time-bounded (version 2 x 800 ms,
power save 300 ms): a module that is silent, or a firmware without the
command, cannot push the report past the client's timeout.
Verified on hardware: state read/set/persist across an ESP reboot,
`hw status` after 4, 15 and 40 s idle in power-save mode and after 6 s
idle with it off.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Port the "reduce idle power" feature from the Fantasi firmware
(soeinova/Fantasi @ 9671309, plus its later ACC-SOF fix) to the PM5
(AT32F435) target.
Problem
-------
The PM5 firmware ran the ARM core at 288 MHz with a 1.3 V LDO and a
main loop that busy-polled at full speed even when the device was idle.
The 288 MHz PLL and the 1.3 V LDO dominate idle draw, and the busy loop
adds to it. Nothing scaled back when there was no work.
Change
------
New armsrc/pm5_power.c drives a refcounted clock/voltage governor:
- Idle (nothing pending, boost count 0, boot grace elapsed, FPGA off):
take SCLK off the PLL onto HICK-48, power the PLL down, drop the LDO
to 1.1 V, park the FPGA 24 MHz clock (PA8 low), and halt the core in
WFI until the next IRQ or the 1 ms SysTick wake.
- Any timing-critical work holds a 288 MHz boost: AppMain wraps
PacketReceived (which on the PM5 also covers a standalone mode, since
it is only entered through a command there); the BWM low-batt poll
and the RGB indicator wrap their I2C. Boost is refcounted and
IRQ-guarded.
- USB (OS image) moves to the crystal-less HICK-48 clock, ACC-trimmed
against the SOF, so powering the PLL down never touches USB. The
bootrom keeps the HEXT/PLL USB path (AS_BOOTROM), so DFU flashing is
unchanged.
- bwm_uart_clock_update() re-inits UART4 through the SDK usart_init()
on each clock switch (the divider set at 288 MHz is 6x off at 48 MHz).
- FpgaIsOff() (fpga_core.c, PM5 only) tracks the last conf word so the
governor never downclocks while a reader field / emulation is running.
- Wake preamble on the BWM link: the companion module firmware
(Proxmark5_BWM_esp32) light-sleeps 2 s after the last byte on the link
and its UART wakes on RX edges but loses the bytes that carried them.
bwm_uart_write() therefore leads with four 0x55 bytes and a 10 ms
settle on its first write and after 1 s without a write of its own
(RX is not tracked: the ring can be drained long after the bytes
came in). The module only starts light-sleeping once it has seen a
preamble, so the one sent while it was still booting is repeated
after the boot-time link negotiation. A module firmware without light
sleep drops the preamble as noise.
New `hw powersave on|off` toggles the governor at runtime (default on);
`hw status` reports the idle-clock and WFI-asleep fractions.
Behaviour change
----------------
- PM5 idles at 48 MHz with the PLL off and the CPU halted; it boosts to
288 MHz for commands and BWM housekeeping. Command timing and
throughput are unchanged (intra-command work is always boosted).
- PM5 USB now runs off HICK-48 (ACC-trimmed) instead of the HEXT PLL.
- New command id CMD_PM5_POWERSAVE (0x0180).
Testing
-------
Built warning-free (gcc) for PLATFORM=PM5 (with and without
PLATFORM_EXTRAS=BWM), PM3RDV4 and PM3GENERIC, armsrc and bootrom. Client
built via the default Makefile. Not built with clang or on
Windows/macOS; nothing outside PM5/AT32 guards changes.
Flashed to a PM5 and exercised against a MIFARE Classic 4K card:
hw version/status/ping (4000 & 512 B), hw tune, hf search, hf 14a
info/reader (x12 across idle gaps), hf mf info/chk/rdbl, lf search/read,
hw powersave off/on, held-field (raw -sk) correctly blocks the downclock,
hw reset + re-enumerate. All pass; USB stays up across downclocks. Idle
reaches ~80-95% at 48 MHz and ~90%+ WFI-halted after a few seconds idle.
Measured at the USB port: idle draw 283 mA -> 207 mA with the stock
BWM firmware.
Co-authored-by: Soei Nova <soeinova@proton.me>
Co-authored-by: noproto <noproto@zeroday.engineering>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`make commands` on current master: the two hf ntag424 commands had not
made it into the generated docs, and `hw bwm help` is gated by IfBwm
since 339db44cf but the docs still list it as offline-capable. No other
change. Kept separate so the commits that follow only carry their own
doc changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Corrects the claim that every block reads back as 00000000 after finalize.
On the tag tested, block 0x38 held 11223344 before finalize and block 0x20
held it afterwards, a shift of 0x18, while blocks 0x10/0x11 and 0x14/0x15
were cleared.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Picks up the hf 15 csetuid and hf 15 cfinalize help text changes, plus the
hf ntag424 commands added earlier on this branch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The notes said block 0x15 reads back as 69 E2 5D 00 after finalize. It reads
back as 00 00 00 00, and so does every other block.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
restore refused UL-AES outright. The generic page plan assumes the last
four pages are cfg0/cfg1/PWD/PACK, which on UL-AES are the AES keys, so
the data loop walked over the lock page, both config pages and the key
lock bits at 0x2D. Give UL-AES its own plan: user memory 0x04..0x27,
and lock/cfg0/cfg1 behind -s. Key pages 0x30..0x37 are left alone, the
dump only ever carries AESKey0 and 0x34..0x37 read back as zeros.
info never printed the UL-AES config. ulaes_print_configuration() is
called with 0x29 but its first branch tested for 0x2C, so cfg0 and cfg1
fell through to the bare return and only the CMAC page showed up.
info also read page 0x34 as if it were an EV1 config page. That is the
UIDRetrKey. Genuine silicon NAKs the read, but a magic or simulated
card would have had its key printed as cfg0/cfg1/PWD/PACK.
Tested on a UL-AES with AUTH0=4, PROT set and secure messaging on.
Restore of an unmodified dump comes back byte identical; a dump carrying
DEADBEEF at 0x27 and CAFEBABE at 0x2B writes 0x27 and leaves 0x2B alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add bidirectional conversion between DFC v4 credentials and mfdes v1 JSON, with documentation and round-trip coverage. Include H10301 FC69 CN420 factory and field fixtures and an external HID reader test target.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
The regenerated `hf mfdes etest` object had been spliced inside the
`hf mfdes esave` object in doc/commands.json, so catalog consumers did
not see it. Moved by hand to its place among the commands, matching what
`make commands` produces for that entry.
Help and how-to said a recorded session replays byte for byte. It does
only when the queue is filled in draw order: the random UID takes 3 bytes
at --scan on a random-id image, then each authentication takes its RndB.
Say so.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
One command with one action per call: --begin, --scan, --apdu <hex>,
--random <hex>, --fieldoff, --state, --end. Text output by default,
-j gives one line of JSON per call for a script to read back.
Distinct failures are reported as such: no image in emulator memory,
not activated (--scan first), random queue full, and a firmware that
does not answer.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>