Skip to content

Fix lf em 4x05 dump writing corrupted (byte-swapped) dump files#3409

Merged
iceman1001 merged 1 commit into
RfidResearchGroup:masterfrom
munzzyy:fix/em4x05-dump-corruption
Jul 6, 2026
Merged

Fix lf em 4x05 dump writing corrupted (byte-swapped) dump files#3409
iceman1001 merged 1 commit into
RfidResearchGroup:masterfrom
munzzyy:fix/em4x05-dump-corruption

Conversation

@munzzyy

@munzzyy munzzyy commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

What

lf em 4x05 dump writes a corrupted dump file. Every 32-bit block in the saved .bin/.json comes out byte-reversed, so reloading it (with lf em 4x05 view or anything else) gives the wrong data.

Why

em4x05_print_blocks() byte-swaps its buffer in place to print the blocks:

uint32_t *d = (void *)data;
for (i = 0; i < (dlen >> 2); i++)
    d[i] = BSWAP_32(d[i]);

In CmdEM4x05Dump() that print call runs before pm3_save_dump(). The read loop stores each word as data[addr] = BSWAP_32(word), which is the order the loader in CmdEM4x05View() expects — it reads blocks MSB-first via bytes_to_num(). The extra in-place swap in the print routine undoes that right before the save, so the file lands in the wrong byte order.

It also corrupts the auto-generated filename, which is built from BSWAP_32(data[1]) after the print call.

Symmetric words like 0x00000000 survive a byte-swap unchanged, which is why this is easy to miss at a glance.

Fix

Give em4x05_print_blocks() a private copy to swap and print, and take the input as const so it can't mutate the caller's buffer. On-screen output is byte-for-byte identical; the dump path now saves the untouched buffer. Both call sites stay correct — view frees its buffer right after and never relied on the mutation.

Checking it

Modelling the exact transforms (BSWAP_32, the little-endian save, and the loader's MSB-first bytes_to_num) and round-tripping a full 16-block tag:

block tag word reloads before reloads after
1 01020304 04030201 01020304
4 DEADBEEF EFBEADDE DEADBEEF
8 00000000 00000000 00000000

Before the fix the saved dump reloads byte-swapped; after, it round-trips cleanly.

Possible follow-up (not part of this change)

While in here I noticed CmdEM4x05View() passes a size_t bytes_read into the uint8_t dlen parameter, which silently truncates on an oversized or malformed dump file. That's pre-existing and untouched by this PR — flagging it as a separate hardening item so it doesn't look overlooked.

Fixes #3403

em4x05_print_blocks() byte-swaps its buffer in place to display the blocks.
In CmdEM4x05Dump() that call runs before pm3_save_dump(), so the buffer
handed to the saver has been swapped out of the canonical on-disk order and
the saved .bin/.json reloads with every block reversed.

The read loop stores each word as data[addr] = BSWAP_32(word), which is what
the loader in CmdEM4x05View() expects (it reads blocks MSB-first via
bytes_to_num()). The extra in-place swap in the print routine undoes that
right before the save. It also corrupts the auto-generated filename, which is
built from BSWAP_32(data[1]) after the print call. Symmetric words such as
0x00000000 survive the swap, so it's easy to miss.

Give em4x05_print_blocks() a private copy to swap and print, and take the
input as const so it can't mutate the caller's buffer. On-screen output is
unchanged; the dump path now saves the untouched buffer.

Fixes RfidResearchGroup#3403
@munzzyy

munzzyy commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

The proxspace failure isn't from this change. The last two Windows Build and Test runs on master fail the same job: https://github.com/RfidResearchGroup/proxmark3/actions/runs/28683540784 and https://github.com/RfidResearchGroup/proxmark3/actions/runs/28682164863. Ubuntu, macOS and CodeQL are green here.

Comment thread client/src/cmdlfem4x05.c
// dlen is a uint8_t, so 64 words is enough to hold any valid input.
uint32_t d[64] = {0};
memcpy(d, src, dlen);
uint8_t *data = (uint8_t *)d;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why fiddle with the extra pointer if you gonna use a local copy?
and whats up with the uint32_t ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both are needed, not extra: the rest of the function reads the buffer two different ways. d[i] is used for the byte-swapped 32-bit words (block values, lock bits, protection checks), and data + (i * EM4X05_BLOCK_SIZE) is used a few lines down for reverse_array_copy(), which wants a byte pointer for the ASCII display column. That dual usage already existed before this PR - I just moved it from operating on the caller's buffer to operating on a local copy, so the byte-swap doesn't corrupt what CmdEM4x05Dump() later writes to disk.

uint32_t specifically because d[i] = BSWAP_32(d[i]) is a 32-bit word swap - has to be a 4-byte type for that to mean anything, and the block-indexed reads (d[EM4305_PROT1_BLOCK] etc.) further down are all word-sized too.

data is just (uint8_t *)d - a byte-typed alias over the same local array, not a second buffer.

@iceman1001
iceman1001 merged commit ee8a5cc into RfidResearchGroup:master Jul 6, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EM4x05: dump saves modified buffer after refactor to shared print routine

2 participants