docs: stateful response table firmware implementation plan
9 tasks: command IDs, iso15_tag_t extensions, sim_crypto, sim_table header + implementation, appmain dispatch, native handler access control + random UID, table fallback in default case, full build. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
795
docs/plans/2026-03-17-stateful-table-firmware-plan.md
Normal file
795
docs/plans/2026-03-17-stateful-table-firmware-plan.md
Normal file
@@ -0,0 +1,795 @@
|
||||
# Stateful Response Table Firmware Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Add a stateful response table engine to PM3 firmware for custom/vendor 15693 commands, with access control in the native handler and shared AES-CMAC crypto.
|
||||
|
||||
**Architecture:** New `sim_table.c/h` provides table storage, lookup with group-based state machine, and action execution (static response, EML read/write, AES-CMAC). The existing `SimTagIso15693()` native handler gains per-block access control and falls through to the table for unrecognized commands. Shared `sim_crypto.c/h` wraps existing `ulaes_cmac()`.
|
||||
|
||||
**Tech Stack:** C (ARM7 firmware), existing PM3 build system, existing `cmac_calc.c` for AES
|
||||
|
||||
**Working directory:** `/home/work/proxmark3/` on branch `sim-relay`
|
||||
|
||||
**Build command:** `make armsrc/obj/fullimage.elf` (firmware only, no client)
|
||||
|
||||
**Important:** All commits use `git commit --no-gpg-sign`. Firmware has no unit test framework — we verify by successful compilation. Python-side tests come later in a separate plan.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Command IDs
|
||||
|
||||
**Files:**
|
||||
- Modify: `/home/work/proxmark3/include/pm3_cmd.h`
|
||||
|
||||
**Step 1: Add table command IDs**
|
||||
|
||||
After the `CMD_HF_SEOS_SIMULATE` line (~930), add:
|
||||
|
||||
```c
|
||||
// Sim response table management
|
||||
#define CMD_SIM_TABLE_UPLOAD 0x0950 // Host→FW: upload table (chunked)
|
||||
#define CMD_SIM_TABLE_CLEAR 0x0951 // Host→FW: clear table
|
||||
#define CMD_SIM_TABLE_UPDATE 0x0952 // Host→FW: add/modify single entry
|
||||
```
|
||||
|
||||
**Step 2: Verify build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
|
||||
Expected: Build succeeds
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/work/proxmark3
|
||||
git add include/pm3_cmd.h
|
||||
git commit --no-gpg-sign -m "feat: add CMD_SIM_TABLE_* command IDs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: iso15_tag_t extensions
|
||||
|
||||
**Files:**
|
||||
- Modify: `/home/work/proxmark3/include/iso15.h`
|
||||
|
||||
**Step 1: Add new fields to iso15_tag_t**
|
||||
|
||||
Add these fields before the closing `} PACKED iso15_tag_t;` (after `trace_enabled`):
|
||||
|
||||
```c
|
||||
// Stateful table support
|
||||
uint8_t random_uid; // if nonzero, inventory returns random UID
|
||||
uint8_t auth_state; // bitmask: authenticated passwords
|
||||
// bit 0: read, bit 1: write, bit 2: privacy, bit 3: destroy
|
||||
uint8_t lock_mode[ISO15693_TAG_MAX_PAGES];
|
||||
// per-block access control:
|
||||
// 0x00 = open
|
||||
// 0x01 = permanently locked
|
||||
// 0x02 = read needs auth (check auth_state bit 0)
|
||||
// 0x04 = write needs auth (check auth_state bit 1)
|
||||
// 0x06 = read+write need auth
|
||||
```
|
||||
|
||||
**Step 2: Verify build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
|
||||
Expected: Build succeeds
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/work/proxmark3
|
||||
git add include/iso15.h
|
||||
git commit --no-gpg-sign -m "feat: extend iso15_tag_t with auth_state, lock_mode, random_uid"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: sim_crypto module
|
||||
|
||||
**Files:**
|
||||
- Create: `/home/work/proxmark3/armsrc/sim_crypto.h`
|
||||
- Create: `/home/work/proxmark3/armsrc/sim_crypto.c`
|
||||
- Modify: `/home/work/proxmark3/armsrc/Makefile`
|
||||
|
||||
**Step 1: Create sim_crypto.h**
|
||||
|
||||
```c
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// Shared crypto primitives for card simulation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __SIM_CRYPTO_H
|
||||
#define __SIM_CRYPTO_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
// AES-CMAC: compute MAC over msg using 16-byte key
|
||||
// mac_out receives up to mac_len bytes of the 16-byte MAC
|
||||
// Returns true on success
|
||||
bool sim_aes_cmac(const uint8_t key[16], const uint8_t *msg,
|
||||
uint8_t msg_len, uint8_t *mac_out, uint8_t mac_len);
|
||||
|
||||
#endif // __SIM_CRYPTO_H
|
||||
```
|
||||
|
||||
**Step 2: Create sim_crypto.c**
|
||||
|
||||
```c
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// Shared crypto primitives for card simulation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "sim_crypto.h"
|
||||
#include "cmac_calc.h"
|
||||
#include <string.h>
|
||||
|
||||
bool sim_aes_cmac(const uint8_t key[16], const uint8_t *msg,
|
||||
uint8_t msg_len, uint8_t *mac_out, uint8_t mac_len) {
|
||||
if (mac_len > 16) mac_len = 16;
|
||||
uint8_t full_mac[16];
|
||||
ulaes_cmac(key, 16, msg, msg_len, full_mac);
|
||||
memcpy(mac_out, full_mac, mac_len);
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Add to Makefile**
|
||||
|
||||
In `armsrc/Makefile`, find the `SRC_ASM` or `SRC_FIRM` source list (where `cmac_calc.c` appears). Add `sim_crypto.c \` on the line after `cmac_calc.c \`.
|
||||
|
||||
**Step 4: Verify build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
|
||||
Expected: Build succeeds
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/work/proxmark3
|
||||
git add armsrc/sim_crypto.h armsrc/sim_crypto.c armsrc/Makefile
|
||||
git commit --no-gpg-sign -m "feat: add sim_crypto module (AES-CMAC wrapper)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: sim_table header
|
||||
|
||||
**Files:**
|
||||
- Create: `/home/work/proxmark3/armsrc/sim_table.h`
|
||||
|
||||
**Step 1: Create sim_table.h**
|
||||
|
||||
```c
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// Stateful response table for card simulation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __SIM_TABLE_H
|
||||
#define __SIM_TABLE_H
|
||||
|
||||
#include "common.h"
|
||||
#include "iso15.h"
|
||||
|
||||
#define SIM_TABLE_MAX_MATCH 32
|
||||
#define SIM_TABLE_MAX_RESPONSE 64
|
||||
#define SIM_TABLE_MAX_ENTRIES 300
|
||||
|
||||
// EML action types
|
||||
#define SIM_EML_NONE 0 // static response only
|
||||
#define SIM_EML_READ 1 // read from EML into response
|
||||
#define SIM_EML_WRITE 2 // write command data to EML
|
||||
#define SIM_EML_AES_CMAC 3 // compute AES-CMAC, key from EML, challenge from cmd
|
||||
|
||||
// Match modes
|
||||
#define SIM_MATCH_EXACT 0
|
||||
#define SIM_MATCH_PREFIX 1
|
||||
|
||||
// Entry flags
|
||||
#define SIM_FLAG_APPEND_CRC 0x01
|
||||
#define SIM_FLAG_CONSUME 0x02 // one-shot: disable entry after use
|
||||
|
||||
typedef struct {
|
||||
// Match
|
||||
uint8_t match[SIM_TABLE_MAX_MATCH];
|
||||
uint8_t match_len;
|
||||
uint8_t match_mode; // SIM_MATCH_EXACT or SIM_MATCH_PREFIX
|
||||
|
||||
// Response
|
||||
uint8_t response[SIM_TABLE_MAX_RESPONSE];
|
||||
uint8_t response_len;
|
||||
uint8_t response_flags; // SIM_FLAG_APPEND_CRC
|
||||
|
||||
// EML actions
|
||||
uint8_t eml_action; // SIM_EML_*
|
||||
uint16_t eml_offset; // EML byte offset for read/write/key
|
||||
uint8_t eml_len; // bytes to read/write / MAC output length
|
||||
uint8_t eml_resp_insert; // position in response to insert data (read/CMAC)
|
||||
uint8_t cmd_data_offset; // position in command for write data / challenge
|
||||
uint8_t cmd_data_len; // length of data to write / challenge length
|
||||
|
||||
// State
|
||||
uint8_t group; // group this entry belongs to (0 = always active)
|
||||
uint32_t activate_groups; // bitmask: groups to activate on match
|
||||
uint32_t deactivate_groups; // bitmask: groups to deactivate on match
|
||||
uint8_t set_auth; // auth_state bits to SET on match
|
||||
uint8_t clear_auth; // auth_state bits to CLEAR on match
|
||||
|
||||
// Flags
|
||||
uint8_t flags; // SIM_FLAG_CONSUME etc.
|
||||
|
||||
uint8_t _pad[1]; // alignment
|
||||
} PACKED sim_table_entry_t; // ~128 bytes
|
||||
|
||||
// Initialize table in BigBuf with initial active group mask
|
||||
void sim_table_init(uint32_t initial_groups);
|
||||
|
||||
// Clear all entries, reset state
|
||||
void sim_table_clear(void);
|
||||
|
||||
// Add entry from serialized data (128 bytes)
|
||||
// Returns true if entry was added
|
||||
bool sim_table_add(const uint8_t *data, uint16_t len);
|
||||
|
||||
// Current entry count
|
||||
uint16_t sim_table_count(void);
|
||||
|
||||
// Find first matching entry for command
|
||||
// Returns NULL if no match
|
||||
sim_table_entry_t *sim_table_lookup(const uint8_t *cmd, uint8_t cmd_len);
|
||||
|
||||
// Execute matched entry: build response, apply state changes
|
||||
// tag pointer needed for EML access and auth_state updates
|
||||
void sim_table_execute(sim_table_entry_t *e,
|
||||
const uint8_t *cmd, uint8_t cmd_len,
|
||||
uint8_t *resp_buf, uint16_t *resp_len,
|
||||
iso15_tag_t *tag);
|
||||
|
||||
// Get current active groups mask
|
||||
uint32_t sim_table_get_active_groups(void);
|
||||
|
||||
#endif // __SIM_TABLE_H
|
||||
```
|
||||
|
||||
**Step 2: Verify build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
|
||||
Expected: Build succeeds (header only, no .c yet)
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/work/proxmark3
|
||||
git add armsrc/sim_table.h
|
||||
git commit --no-gpg-sign -m "feat: add sim_table.h — stateful response table header"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: sim_table implementation
|
||||
|
||||
**Files:**
|
||||
- Create: `/home/work/proxmark3/armsrc/sim_table.c`
|
||||
- Modify: `/home/work/proxmark3/armsrc/Makefile`
|
||||
|
||||
**Step 1: Create sim_table.c**
|
||||
|
||||
```c
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// Stateful response table for card simulation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "sim_table.h"
|
||||
#include "sim_crypto.h"
|
||||
#include "BigBuf.h"
|
||||
#include "dbprint.h"
|
||||
#include <string.h>
|
||||
|
||||
static struct {
|
||||
sim_table_entry_t *entries;
|
||||
uint16_t count;
|
||||
uint16_t max_entries;
|
||||
uint32_t active_groups;
|
||||
} g_table;
|
||||
|
||||
void sim_table_init(uint32_t initial_groups) {
|
||||
// Allocate table from BigBuf
|
||||
// We use a dedicated region at the end of BigBuf
|
||||
uint32_t table_size = SIM_TABLE_MAX_ENTRIES * sizeof(sim_table_entry_t);
|
||||
g_table.entries = (sim_table_entry_t *)BigBuf_calloc(table_size);
|
||||
g_table.count = 0;
|
||||
g_table.max_entries = SIM_TABLE_MAX_ENTRIES;
|
||||
g_table.active_groups = initial_groups;
|
||||
|
||||
if (g_dbglevel >= DBG_INFO)
|
||||
Dbprintf("sim_table_init: %d entries max, initial groups=0x%08X",
|
||||
g_table.max_entries, initial_groups);
|
||||
}
|
||||
|
||||
void sim_table_clear(void) {
|
||||
if (g_table.entries) {
|
||||
memset(g_table.entries, 0, g_table.count * sizeof(sim_table_entry_t));
|
||||
}
|
||||
g_table.count = 0;
|
||||
g_table.active_groups = 0;
|
||||
}
|
||||
|
||||
bool sim_table_add(const uint8_t *data, uint16_t len) {
|
||||
if (g_table.entries == NULL) return false;
|
||||
if (g_table.count >= g_table.max_entries) return false;
|
||||
if (len < sizeof(sim_table_entry_t)) return false;
|
||||
|
||||
memcpy(&g_table.entries[g_table.count], data, sizeof(sim_table_entry_t));
|
||||
g_table.count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t sim_table_count(void) {
|
||||
return g_table.count;
|
||||
}
|
||||
|
||||
uint32_t sim_table_get_active_groups(void) {
|
||||
return g_table.active_groups;
|
||||
}
|
||||
|
||||
sim_table_entry_t *sim_table_lookup(const uint8_t *cmd, uint8_t cmd_len) {
|
||||
for (uint16_t i = 0; i < g_table.count; i++) {
|
||||
sim_table_entry_t *e = &g_table.entries[i];
|
||||
|
||||
// Skip empty entries (consumed one-shots)
|
||||
if (e->match_len == 0) continue;
|
||||
|
||||
// Skip if entry's group is not active (group 0 = always active)
|
||||
if (e->group != 0 && !(g_table.active_groups & (1u << e->group))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match
|
||||
bool matched = false;
|
||||
if (e->match_mode == SIM_MATCH_EXACT) {
|
||||
if (cmd_len == e->match_len &&
|
||||
memcmp(cmd, e->match, e->match_len) == 0) {
|
||||
matched = true;
|
||||
}
|
||||
} else if (e->match_mode == SIM_MATCH_PREFIX) {
|
||||
if (cmd_len >= e->match_len &&
|
||||
memcmp(cmd, e->match, e->match_len) == 0) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) continue;
|
||||
|
||||
// Match found — apply state changes
|
||||
if (e->activate_groups)
|
||||
g_table.active_groups |= e->activate_groups;
|
||||
if (e->deactivate_groups)
|
||||
g_table.active_groups &= ~e->deactivate_groups;
|
||||
|
||||
// Consume if one-shot
|
||||
if (e->flags & SIM_FLAG_CONSUME) {
|
||||
e->match_len = 0; // mark as empty
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sim_table_execute(sim_table_entry_t *e,
|
||||
const uint8_t *cmd, uint8_t cmd_len,
|
||||
uint8_t *resp_buf, uint16_t *resp_len,
|
||||
iso15_tag_t *tag) {
|
||||
|
||||
// Apply auth state changes
|
||||
if (e->set_auth) tag->auth_state |= e->set_auth;
|
||||
if (e->clear_auth) tag->auth_state &= ~e->clear_auth;
|
||||
|
||||
// Start with static response
|
||||
memcpy(resp_buf, e->response, e->response_len);
|
||||
*resp_len = e->response_len;
|
||||
|
||||
switch (e->eml_action) {
|
||||
case SIM_EML_NONE:
|
||||
break;
|
||||
|
||||
case SIM_EML_READ: {
|
||||
// Read from tag EML data into response
|
||||
uint16_t off = e->eml_offset;
|
||||
uint8_t len = e->eml_len;
|
||||
uint8_t ins = e->eml_resp_insert;
|
||||
if (off + len <= sizeof(tag->data)) {
|
||||
memcpy(resp_buf + ins, tag->data + off, len);
|
||||
if (ins + len > *resp_len)
|
||||
*resp_len = ins + len;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SIM_EML_WRITE: {
|
||||
// Write command data to tag EML
|
||||
uint8_t src_off = e->cmd_data_offset;
|
||||
uint8_t len = e->eml_len;
|
||||
uint16_t dst_off = e->eml_offset;
|
||||
if (src_off + len <= cmd_len && dst_off + len <= sizeof(tag->data)) {
|
||||
memcpy(tag->data + dst_off, cmd + src_off, len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SIM_EML_AES_CMAC: {
|
||||
// Key from EML, challenge from command, MAC into response
|
||||
uint8_t key[16];
|
||||
uint16_t key_off = e->eml_offset;
|
||||
if (key_off + 16 > sizeof(tag->data)) break;
|
||||
memcpy(key, tag->data + key_off, 16);
|
||||
|
||||
uint8_t challenge_off = e->cmd_data_offset;
|
||||
uint8_t challenge_len = e->cmd_data_len;
|
||||
if (challenge_off + challenge_len > cmd_len) break;
|
||||
|
||||
uint8_t mac_len = e->eml_len;
|
||||
uint8_t mac_ins = e->eml_resp_insert;
|
||||
|
||||
sim_aes_cmac(key, cmd + challenge_off, challenge_len,
|
||||
resp_buf + mac_ins, mac_len);
|
||||
|
||||
if (mac_ins + mac_len > *resp_len)
|
||||
*resp_len = mac_ins + mac_len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Add to Makefile**
|
||||
|
||||
Add `sim_table.c \` after the `sim_crypto.c \` line added in Task 3.
|
||||
|
||||
**Step 3: Verify build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
|
||||
Expected: Build succeeds
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/work/proxmark3
|
||||
git add armsrc/sim_table.c armsrc/Makefile
|
||||
git commit --no-gpg-sign -m "feat: add sim_table.c — stateful response table engine"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Command dispatch in appmain.c
|
||||
|
||||
**Files:**
|
||||
- Modify: `/home/work/proxmark3/armsrc/appmain.c`
|
||||
|
||||
**Step 1: Add include**
|
||||
|
||||
Near the top of `appmain.c`, with the other includes, add:
|
||||
|
||||
```c
|
||||
#include "sim_table.h"
|
||||
```
|
||||
|
||||
**Step 2: Add dispatch cases**
|
||||
|
||||
Find the `CMD_HF_SEOS_SIMULATE` case (around line 1571). After it, add:
|
||||
|
||||
```c
|
||||
case CMD_SIM_TABLE_UPLOAD: {
|
||||
// First chunk: init table, then add entries
|
||||
uint32_t initial_groups = 0xFFFFFFFF; // all groups active by default
|
||||
if (packet->length >= 4) {
|
||||
// First 4 bytes = initial_groups mask
|
||||
memcpy(&initial_groups, packet->data.asBytes, 4);
|
||||
}
|
||||
sim_table_init(initial_groups);
|
||||
// Remaining bytes = entries
|
||||
uint16_t data_off = 4;
|
||||
while (data_off + sizeof(sim_table_entry_t) <= packet->length) {
|
||||
sim_table_add(packet->data.asBytes + data_off, sizeof(sim_table_entry_t));
|
||||
data_off += sizeof(sim_table_entry_t);
|
||||
}
|
||||
reply_ng(CMD_SIM_TABLE_UPLOAD, PM3_SUCCESS, NULL, 0);
|
||||
break;
|
||||
}
|
||||
case CMD_SIM_TABLE_CLEAR: {
|
||||
sim_table_clear();
|
||||
reply_ng(CMD_SIM_TABLE_CLEAR, PM3_SUCCESS, NULL, 0);
|
||||
break;
|
||||
}
|
||||
case CMD_SIM_TABLE_UPDATE: {
|
||||
// Add single entry (or chunk of entries)
|
||||
uint16_t off = 0;
|
||||
while (off + sizeof(sim_table_entry_t) <= packet->length) {
|
||||
sim_table_add(packet->data.asBytes + off, sizeof(sim_table_entry_t));
|
||||
off += sizeof(sim_table_entry_t);
|
||||
}
|
||||
reply_ng(CMD_SIM_TABLE_UPDATE, PM3_SUCCESS, NULL, 0);
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Verify build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
|
||||
Expected: Build succeeds
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/work/proxmark3
|
||||
git add armsrc/appmain.c
|
||||
git commit --no-gpg-sign -m "feat: dispatch CMD_SIM_TABLE_* in appmain"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Native handler access control + random UID
|
||||
|
||||
**Files:**
|
||||
- Modify: `/home/work/proxmark3/armsrc/iso15693.c`
|
||||
|
||||
**Step 1: Add includes**
|
||||
|
||||
Near the top of `iso15693.c`, add:
|
||||
|
||||
```c
|
||||
#include "sim_table.h"
|
||||
```
|
||||
|
||||
**Step 2: Initialize new fields in SimTagIso15693**
|
||||
|
||||
In `SimTagIso15693()`, after the existing field initializations (around line 2193, after `tag->trace_enabled`), add:
|
||||
|
||||
```c
|
||||
tag->random_uid = 0;
|
||||
tag->auth_state = 0;
|
||||
memset(tag->lock_mode, 0, sizeof(tag->lock_mode));
|
||||
```
|
||||
|
||||
**Step 3: Add random UID to inventory response**
|
||||
|
||||
Find the inventory response block (around line 2412-2416):
|
||||
|
||||
```c
|
||||
// No error: Answer
|
||||
recv[0] = ISO15_NOERROR;
|
||||
recv[1] = tag->dsfid;
|
||||
memcpy(&recv[2], tag->uid, 8);
|
||||
recvLen = 10;
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```c
|
||||
// No error: Answer
|
||||
recv[0] = ISO15_NOERROR;
|
||||
recv[1] = tag->dsfid;
|
||||
if (tag->random_uid) {
|
||||
// Privacy mode: return random UID
|
||||
for (int i = 0; i < 8; i++)
|
||||
recv[2 + i] = prand() & 0xFF;
|
||||
} else {
|
||||
memcpy(&recv[2], tag->uid, 8);
|
||||
}
|
||||
recvLen = 10;
|
||||
```
|
||||
|
||||
Also do the same for the second inventory path (around line 2450-2453 in the `switch(cmd[1])` case `ISO15693_INVENTORY`):
|
||||
|
||||
```c
|
||||
case ISO15693_INVENTORY:
|
||||
if (g_dbglevel >= DBG_DEBUG) Dbprintf("Inventory cmd");
|
||||
recv[0] = ISO15_NOERROR;
|
||||
recv[1] = tag->dsfid;
|
||||
if (tag->random_uid) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
recv[2 + i] = prand() & 0xFF;
|
||||
} else {
|
||||
memcpy(&recv[2], tag->uid, 8);
|
||||
}
|
||||
recvLen = 10;
|
||||
break;
|
||||
```
|
||||
|
||||
**Step 4: Add access control to READBLOCK**
|
||||
|
||||
Find the `ISO15693_READBLOCK` case (around line 2459). After the `pageNum` extraction and before the existing bounds check, add access control:
|
||||
|
||||
```c
|
||||
case ISO15693_READBLOCK:
|
||||
if (g_dbglevel >= DBG_DEBUG) Dbprintf("ReadBlock cmd");
|
||||
pageNum = cmd[cmdCpt++];
|
||||
// Access control check
|
||||
if (pageNum < ISO15693_TAG_MAX_PAGES) {
|
||||
uint8_t lm = tag->lock_mode[pageNum];
|
||||
if (lm == 0x01) {
|
||||
error = ISO15_ERROR_BLOCK_LOCKED;
|
||||
break;
|
||||
}
|
||||
if ((lm & 0x02) && !(tag->auth_state & 0x01)) {
|
||||
error = ISO15_ERROR_BLOCK_UNAVAILABLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pageNum >= tag->pagesCount)
|
||||
error = ISO15_ERROR_BLOCK_UNAVAILABLE;
|
||||
else {
|
||||
// ... existing read logic unchanged ...
|
||||
```
|
||||
|
||||
**Step 5: Add access control to WRITEBLOCK**
|
||||
|
||||
Find the `ISO15693_WRITEBLOCK` case (around line 2476). After `pageNum` extraction, add:
|
||||
|
||||
```c
|
||||
case ISO15693_WRITEBLOCK:
|
||||
if (g_dbglevel >= DBG_DEBUG) Dbprintf("WriteBlock cmd");
|
||||
pageNum = cmd[cmdCpt++];
|
||||
// Access control check
|
||||
if (pageNum < ISO15693_TAG_MAX_PAGES) {
|
||||
uint8_t lm = tag->lock_mode[pageNum];
|
||||
if (lm == 0x01) {
|
||||
error = ISO15_ERROR_BLOCK_LOCKED;
|
||||
break;
|
||||
}
|
||||
if ((lm & 0x04) && !(tag->auth_state & 0x02)) {
|
||||
error = ISO15_ERROR_BLOCK_UNAVAILABLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pageNum >= tag->pagesCount)
|
||||
error = ISO15_ERROR_BLOCK_UNAVAILABLE;
|
||||
else {
|
||||
// ... existing write logic unchanged ...
|
||||
```
|
||||
|
||||
**Step 6: Verify build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
|
||||
Expected: Build succeeds
|
||||
|
||||
**Step 7: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/work/proxmark3
|
||||
git add armsrc/iso15693.c
|
||||
git commit --no-gpg-sign -m "feat: add access control + random UID to 15693 sim handler"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Table fallback in default case
|
||||
|
||||
**Files:**
|
||||
- Modify: `/home/work/proxmark3/armsrc/iso15693.c`
|
||||
|
||||
**Step 1: Replace default case with table lookup + relay fallback**
|
||||
|
||||
Find the `default:` case in the command switch (around line 2629). Replace the entire case body:
|
||||
|
||||
```c
|
||||
default:
|
||||
// --- Table lookup for custom/vendor commands ---
|
||||
if (sim_table_count() > 0) {
|
||||
sim_table_entry_t *entry = sim_table_lookup(cmd, cmd_len);
|
||||
if (entry) {
|
||||
sim_table_execute(entry, cmd, cmd_len, recv, &recvLen, tag);
|
||||
if (g_dbglevel >= DBG_DEBUG)
|
||||
Dbprintf("Table match for CMD 0x%02X, resp %d bytes",
|
||||
cmd[1], recvLen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// No table match — fall through to relay
|
||||
if (tag->relay_resp_ready &&
|
||||
tag->relay_cmd_len == cmd_len &&
|
||||
memcmp(tag->relay_cmd, cmd, cmd_len) == 0) {
|
||||
memcpy(recv, tag->relay_resp, tag->relay_resp_len);
|
||||
recvLen = tag->relay_resp_len;
|
||||
tag->relay_resp_ready = false;
|
||||
if (g_dbglevel >= DBG_DEBUG)
|
||||
Dbprintf("Serving cached relay response for CMD 0x%02X", cmd[1]);
|
||||
} else {
|
||||
memcpy(tag->relay_cmd, cmd, MIN(cmd_len, sizeof(tag->relay_cmd)));
|
||||
tag->relay_cmd_len = cmd_len;
|
||||
tag->relay_pending = true;
|
||||
tag->relay_resp_ready = false;
|
||||
reply_ng(CMD_HF_ISO15693_SIM_RELAY, PM3_SUCCESS, cmd, cmd_len);
|
||||
if (g_dbglevel >= DBG_DEBUG)
|
||||
Dbprintf("Relayed CMD 0x%02X to host (%d bytes)", cmd[1], cmd_len);
|
||||
recvLen = 0;
|
||||
}
|
||||
break;
|
||||
```
|
||||
|
||||
**Step 2: Verify build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
|
||||
Expected: Build succeeds
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/work/proxmark3
|
||||
git add armsrc/iso15693.c
|
||||
git commit --no-gpg-sign -m "feat: table lookup before relay fallback in 15693 sim default case"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Verify full build + flash size
|
||||
|
||||
**Step 1: Clean build**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && make clean && make -j`
|
||||
Expected: Full build succeeds (client + firmware)
|
||||
|
||||
**Step 2: Check firmware size**
|
||||
|
||||
Run: `cd /home/work/proxmark3 && arm-none-eabi-size armsrc/obj/fullimage.elf`
|
||||
Expected: text section fits in 512KB flash. Note the size for comparison with upstream.
|
||||
|
||||
**Step 3: Commit (if any Makefile adjustments needed)**
|
||||
|
||||
If build revealed issues, fix and commit. Otherwise, this task is just verification.
|
||||
Reference in New Issue
Block a user