Merge branch 'master' into patch-1

Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com>
This commit is contained in:
Jarek Barwinski 2025-03-18 09:11:38 +00:00 committed by GitHub
commit ddd148329e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 3342 additions and 420 deletions

View file

@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
- Major update to `lf em 4x70` internals on ARM side; Enabling improved debugging and reliability (@henrygab)
- Improved `pcf7931` generic readability of the code. Unified datatypes and added documentation/explainations (@tinooo)
- Improved `lf pcf7931` read code - fixed some checks for more stability (@tinooo)
- Changed `trace list -t seos` - improved annotation (@iceman1001)

File diff suppressed because it is too large Load diff

View file

@ -19,12 +19,11 @@
#ifndef EM4x70_H
#define EM4x70_H
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include "../include/em4x70.h"
typedef struct {
uint8_t data[32];
} em4x70_tag_t;
typedef enum {
RISING_EDGE,
FALLING_EDGE

View file

@ -320,7 +320,7 @@ static void hitag2_handle_reader_command(uint8_t *rx, const size_t rxlen, uint8_
// reader/writer
// returns how long it took
static uint32_t hitag_reader_send_bit(int bit) {
static uint32_t hitag2_reader_send_bit(int bit) {
// Binary pulse length modulation (BPLM) is used to encode the data stream
// This means that a transmission of a one takes longer than that of a zero
@ -349,13 +349,13 @@ static uint32_t hitag_reader_send_bit(int bit) {
// reader / writer commands
// frame_len is in number of bits?
static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len) {
static uint32_t hitag2_reader_send_frame(const uint8_t *frame, size_t frame_len) {
WDT_HIT();
uint32_t wait = 0;
// Send the content of the frame
for (size_t i = 0; i < frame_len; i++) {
wait += hitag_reader_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1);
wait += hitag2_reader_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1);
}
// Send EOF
@ -378,14 +378,14 @@ static uint32_t hitag_reader_send_frame(const uint8_t *frame, size_t frame_len)
// reader / writer commands
// frame_len is in number of bits?
static uint32_t hitag_reader_send_framebits(const uint8_t *frame, size_t frame_len) {
static uint32_t hitag2_reader_send_framebits(const uint8_t *frame, size_t frame_len) {
WDT_HIT();
uint32_t wait = 0;
// Send the content of the frame
for (size_t i = 0; i < frame_len; i++) {
wait += hitag_reader_send_bit(frame[i]);
wait += hitag2_reader_send_bit(frame[i]);
}
// Send EOF
@ -1863,7 +1863,7 @@ void ReaderHitag(const lf_hitag_data_t *payload, bool ledcontrol) {
}
// Transmit the reader frame
command_duration = hitag_reader_send_frame(tx, txlen);
command_duration = hitag2_reader_send_frame(tx, txlen);
response_start = command_start + command_duration;
// Let the antenna and ADC values settle
@ -2214,7 +2214,7 @@ void WriterHitag(const lf_hitag_data_t *payload, bool ledcontrol) {
}
// Transmit the reader frame
command_duration = hitag_reader_send_frame(tx, txlen);
command_duration = hitag2_reader_send_frame(tx, txlen);
// global write state variable used
// tearoff occurred
@ -2434,9 +2434,9 @@ static void ht2_send(bool turn_on, uint32_t *cmd_start
// Transmit the reader frame
if (send_bits) {
*cmd_duration = hitag_reader_send_framebits(tx, txlen);
*cmd_duration = hitag2_reader_send_framebits(tx, txlen);
} else {
*cmd_duration = hitag_reader_send_frame(tx, txlen);
*cmd_duration = hitag2_reader_send_frame(tx, txlen);
}
*resp_start = (*cmd_start + *cmd_duration);

View file

@ -419,7 +419,7 @@ static void hts_init_clock(void) {
static int check_select(const uint8_t *rx, uint32_t uid) {
// global var?
concatbits((uint8_t *)&reader_selected_uid, 0, rx, 5, 32);
concatbits((uint8_t *)&reader_selected_uid, 0, rx, 5, 32, false);
reader_selected_uid = BSWAP_32(reader_selected_uid);
if (reader_selected_uid == uid) {
@ -1090,7 +1090,7 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
protocol_mode = packet->mode;
uint8_t cmd = protocol_mode;
txlen = concatbits(tx, txlen, &cmd, 0, 5);
txlen = concatbits(tx, txlen, &cmd, 0, 5, false);
hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, t_wait, ledcontrol, true);
if (rxlen != 32) {
@ -1105,10 +1105,10 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
// select uid
txlen = 0;
cmd = HITAGS_SELECT;
txlen = concatbits(tx, txlen, &cmd, 0, 5);
txlen = concatbits(tx, txlen, rx, 0, 32);
txlen = concatbits(tx, txlen, &cmd, 0, 5, false);
txlen = concatbits(tx, txlen, rx, 0, 32, false);
uint8_t crc = CRC8Hitag1Bits(tx, txlen);
txlen = concatbits(tx, txlen, &crc, 0, 8);
txlen = concatbits(tx, txlen, &crc, 0, 8, false);
hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false);
@ -1140,8 +1140,8 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
}
txlen = 0;
txlen = concatbits(tx, txlen, rnd, 0, 32);
txlen = concatbits(tx, txlen, auth_ks, 0, 32);
txlen = concatbits(tx, txlen, rnd, 0, 32, false);
txlen = concatbits(tx, txlen, auth_ks, 0, 32, false);
DBG DbpString("Authenticating using key:");
DBG Dbhexdump(6, packet->key, false);
@ -1173,13 +1173,13 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
// send write page request
txlen = 0;
cmd = HITAGS_WRITE_PAGE;
txlen = concatbits(tx, txlen, &cmd, 0, 4);
txlen = concatbits(tx, txlen, &cmd, 0, 4, false);
uint8_t addr = 64;
txlen = concatbits(tx, txlen, &addr, 0, 8);
txlen = concatbits(tx, txlen, &addr, 0, 8, false);
crc = CRC8Hitag1Bits(tx, txlen);
txlen = concatbits(tx, txlen, &crc, 0, 8);
txlen = concatbits(tx, txlen, &crc, 0, 8, false);
hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false);
@ -1189,9 +1189,9 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
}
txlen = 0;
txlen = concatbits(tx, txlen, packet->pwd, 0, 32);
txlen = concatbits(tx, txlen, packet->pwd, 0, 32, false);
crc = CRC8Hitag1Bits(tx, txlen);
txlen = concatbits(tx, txlen, &crc, 0, 8);
txlen = concatbits(tx, txlen, &crc, 0, 8, false);
hts_send_receive(tx, txlen, rx, sizeofrx, &rxlen, HITAG_T_WAIT_SC, ledcontrol, false);
@ -1287,11 +1287,11 @@ void hts_read(const lf_hitag_data_t *payload, bool ledcontrol) {
//send read request
size_t txlen = 0;
uint8_t cmd = HITAGS_READ_PAGE;
txlen = concatbits(tx, txlen, &cmd, 0, 4);
txlen = concatbits(tx, txlen, &cmd, 0, 4, false);
uint8_t addr = page_addr;
txlen = concatbits(tx, txlen, &addr, 0, 8);
txlen = concatbits(tx, txlen, &addr, 0, 8, false);
uint8_t crc = CRC8Hitag1Bits(tx, txlen);
txlen = concatbits(tx, txlen, &crc, 0, 8);
txlen = concatbits(tx, txlen, &crc, 0, 8, false);
hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, false);
@ -1396,13 +1396,13 @@ void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol) {
txlen = 0;
uint8_t cmd = HITAGS_WRITE_PAGE;
txlen = concatbits(tx, txlen, &cmd, 0, 4);
txlen = concatbits(tx, txlen, &cmd, 0, 4, false);
uint8_t addr = payload->page;
txlen = concatbits(tx, txlen, &addr, 0, 8);
txlen = concatbits(tx, txlen, &addr, 0, 8, false);
uint8_t crc = CRC8Hitag1Bits(tx, txlen);
txlen = concatbits(tx, txlen, &crc, 0, 8);
txlen = concatbits(tx, txlen, &crc, 0, 8, false);
hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_SC, ledcontrol, false);
@ -1430,9 +1430,9 @@ void hts_write_page(const lf_hitag_data_t *payload, bool ledcontrol) {
// }
txlen = 0;
txlen = concatbits(tx, txlen, payload->data, 0, 32);
txlen = concatbits(tx, txlen, payload->data, 0, 32, false);
crc = CRC8Hitag1Bits(tx, txlen);
txlen = concatbits(tx, txlen, &crc, 0, 8);
txlen = concatbits(tx, txlen, &crc, 0, 8, false);
enable_page_tearoff = g_tearoff_enabled;
@ -1493,7 +1493,7 @@ int hts_read_uid(uint32_t *uid, bool ledcontrol, bool send_answer) {
size_t txlen = 0;
uint8_t tx[HITAG_FRAME_LEN] = { 0x00 };
txlen = concatbits(tx, txlen, &cmd, 0, 5);
txlen = concatbits(tx, txlen, &cmd, 0, 5, false);
hts_send_receive(tx, txlen, rx, ARRAYLEN(rx), &rxlen, HITAG_T_WAIT_FIRST, ledcontrol, true);

View file

@ -87,15 +87,15 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) {
samplePosLastEdge = 0;
block_done = 0;
bitPos = 0;
lastClockDuration=0;
lastClockDuration = 0;
for (sample = 1 ; sample < g_GraphTraceLen-4; sample++) {
for (sample = 1 ; sample < g_GraphTraceLen - 4; sample++) {
// condition is searching for the next edge, in the expected diretion.
//todo: without flouz
dest[sample] = (uint8_t)(dest[sample-1] * IIR_CONST1 + dest[sample] * IIR_CONST2); // apply IIR filter
dest[sample] = (uint8_t)(dest[sample - 1] * IIR_CONST1 + dest[sample] * IIR_CONST2); // apply IIR filter
if ( ((dest[sample] + THRESHOLD) < dest[sample-1] && expectedNextEdge == FALLING ) ||
((dest[sample] - THRESHOLD) > dest[sample-1] && expectedNextEdge == RISING )) {
if (((dest[sample] + THRESHOLD) < dest[sample - 1] && expectedNextEdge == FALLING) ||
((dest[sample] - THRESHOLD) > dest[sample - 1] && expectedNextEdge == RISING)) {
//okay, next falling/rising edge found
expectedNextEdge = (expectedNextEdge == FALLING) ? RISING : FALLING; //toggle the next expected edge
@ -193,7 +193,7 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) {
half_switch = 0;
}
}else {
} else {
// Dbprintf("%d, %d", sample, dest[sample]);
}
@ -403,7 +403,7 @@ void ReadPCF7931(bool ledcontrol) {
end:
/*
/*
Dbprintf("-----------------------------------------");
Dbprintf("Memory content:");
Dbprintf("-----------------------------------------");
@ -424,7 +424,7 @@ end:
Dbprintf("-----------------------------------------");
}
*/
*/
reply_mix(CMD_ACK, 0, 0, 0, 0, 0);
}
@ -434,7 +434,7 @@ static void RealWritePCF7931(
uint16_t init_delay,
int8_t offsetPulseWidth, int8_t offsetPulsePosition,
uint8_t address, uint8_t byte, uint8_t data,
bool ledcontrol){
bool ledcontrol) {
uint32_t tab[1024] = {0}; // data times frame
uint32_t u = 0;

View file

@ -19,7 +19,7 @@
#include "common.h"
typedef enum{
typedef enum {
FALLING,
RISING
} EdgeType;

View file

@ -1,4 +1,3 @@
local getopt = require('getopt')
local utils = require('utils')
local ac = require('ansicolors')
@ -8,11 +7,14 @@ local dir = os.getenv('HOME') .. '/.proxmark3/logs/'
local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+"))
local log_file_path = dir .. "Paxton_log.txt"
local nam = ""
local pm3 = require('pm3')
p = pm3.pm3()
local command = core.console
command('clear')
author = ' Author: jareckib - 30.01.2025'
tutorial = ' Based on Equipter tutorial - Downgrade Paxton to EM4102'
version = ' version v1.18'
version = ' version v1.19'
desc = [[
The script automates the copying of Paxton fobs read - write.
It also allows manual input of data for blocks 4-7.
@ -228,13 +230,10 @@ end
local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
while true do
print(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2" .. ac.reset)
print(dash)
io.write(" Your choice "..ac.cyan.."(1/2): "..ac.reset)
io.write(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2 " .. ac.reset)
local choice = io.read()
if choice == "1" then
print(dash)
print(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.write(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read()
print(dash)
command("lf hitag wrbl --ht2 -p 4 -d " .. blocks[4] .. " -k BDF5E846")
@ -242,20 +241,18 @@ local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
command("lf hitag wrbl --ht2 -p 6 -d " .. blocks[6] .. " -k BDF5E846")
command("lf hitag wrbl --ht2 -p 7 -d " .. blocks[7] .. " -k BDF5E846")
elseif choice == "2" then
print(dash)
print(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.write(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read()
print(dash)
command("lf em 410x clone --id " .. padded_hex_id)
p:console("lf em 410x clone --id " .. padded_hex_id)
print(' Cloned EM4102 to T5577 with ID ' ..ac.green.. padded_hex_id ..ac.reset)
else
print(ac.yellow .. " Invalid choice." .. ac.reset .. " Please enter " .. ac.cyan .. "1" .. ac.reset .. " or " .. ac.cyan .. "2" .. ac.reset)
goto ask_again
end
while true do
print(dash)
io.write(" Make next RFID Fob"..ac.cyan.." (y/n)"..ac.reset.." > "..ac.yellow)
io.write(" Make next RFID Fob"..ac.cyan.." (y/n) "..ac.reset)
local another = io.read()
io.write(ac.reset..'')
if another:lower() == "n" then
if was_option_3 then
print(" No writing to Paxton_log.txt - Name: " ..ac.green.. nam .. ac.reset.. " exist")
@ -303,7 +300,6 @@ local function main(args)
if o == 'h' then return help() end
end
command('clear')
print()
print(dash)
print(ac.green .. ' Select option: ' .. ac.reset)
print(ac.cyan .. ' 1' .. ac.reset .. ' - Read Paxton blocks 4-7 to make a copy')
@ -324,12 +320,11 @@ local function main(args)
local show_place_message = true
while true do
if show_place_message then
print(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..')
print(dash)
io.write(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..')
end
io.read()
command('lf hitag read --ht2 -k BDF5E846')
command('clear')
print(dash)
p:console('lf hitag read --ht2 -k BDF5E846')
if not logfile then
error(" No files in this directory")
end
@ -343,12 +338,11 @@ local function main(args)
end
end
if empty_block then
print(dash)
print(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..')
print(dash)
io.write(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..')
show_place_message = false
else
print(dash)
print(' Readed blocks:')
print()
for i = 4, 7 do
if blocks[i] then
print(string.format(" Block %d: %s%s%s", i, ac.yellow, blocks[i], ac.reset))
@ -364,7 +358,6 @@ local function main(args)
print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]})
end
print(dash)
print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset))
print(dash)
handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)

View file

@ -11,7 +11,7 @@ local command = core.console
command('clear')
author = ' Author: jareckib - 15.02.2025'
version = ' version v1.01'
version = ' version v1.02'
desc = [[
This simple script first checks if a password has been set for the T5577.
It uses the dictionary t55xx_default_pwds.dic for this purpose. If a password
@ -111,20 +111,23 @@ local function main(args)
if o == 'h' then return help() end
end
p:console('clear')
print(' I am initiating the repair process for '..ac.cyan..'T5577'..ac.reset)
print(dash)
print('I am initiating the repair process for '..ac.cyan..'T5577'..ac.reset)
io.write("Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read()
print(dash)
print("::: "..ac.cyan.."Hold on, I'm searching for a password in the dictionary"..ac.reset.." :::")
print(dash)
p:console('lf t55 chk')
timer(5)
local log_content = read_log_file(logfile)
local password = log_content and extract_password(log_content) or nil
reanimate_t5577(password)
p:console('lf t55 detect')
p:console('lf t55 read -b 0')
timer(5)
local success = false
for line in p.grabbed_output:gmatch("[^\r\n]+") do
if line:find("000880E0") then
if line:find("00 | 000880E0 |") then
success = true
break
end

View file

@ -0,0 +1,462 @@
local getopt = require('getopt')
local utils = require('utils')
local ac = require('ansicolors')
local os = require('os')
local dash = string.rep('--', 32)
local dir = os.getenv('HOME') .. '/.proxmark3/logs/'
local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+"))
local log_file_path = dir .. "Paxton_log.txt"
local nam = ""
local pm3 = require('pm3')
p = pm3.pm3()
local command = core.console
command('clear')
author = ' Author: jareckib - 30.01.2025'
tutorial = ' Based on Equipter tutorial - Downgrade Paxton to EM4102'
version = ' version v1.18'
desc = [[
The script automates the copying of Paxton fobs read - write.
It also allows manual input of data for blocks 4-7.
The third option is reading data stored in the log file and create new fob.
Additionally, the script calculates the ID for downgrading Paxton to EM4102.
]]
usage = [[
script run paxton_clone
]]
arguments = [[
script run paxton_clone -h : this help
]]
local debug = true
local function dbg(args)
if not DEBUG then return end
if type(args) == 'table' then
local i = 1
while args[i] do
dbg(args[i])
i = i+1
end
else
print('###', args)
end
end
local function help()
print()
print(author)
print(tutorial)
print(version)
print(desc)
print(ac.cyan..' Usage'..ac.reset)
print(usage)
print(ac.cyan..' Arguments'..ac.reset)
print(arguments)
end
local function read_log_file(logfile)
local file = io.open(logfile, "r")
if not file then
error(" Could not open the file")
end
local content = file:read("*all")
file:close()
return content
end
local function parse_blocks(result)
local blocks = {}
for line in result:gmatch("[^\r\n]+") do
local block_num, block_data = line:match("%[%=%]%s+%d/0x0([4-7])%s+%|%s+([0-9A-F ]+)")
if block_num and block_data then
block_num = tonumber(block_num)
block_data = block_data:gsub("%s+", "")
blocks[block_num] = block_data
end
end
return blocks
end
local function hex_to_bin(hex_string)
local bin_string = ""
local hex_to_bin_map = {
['0'] = "0000", ['1'] = "0001", ['2'] = "0010", ['3'] = "0011",
['4'] = "0100", ['5'] = "0101", ['6'] = "0110", ['7'] = "0111",
['8'] = "1000", ['9'] = "1001", ['A'] = "1010", ['B'] = "1011",
['C'] = "1100", ['D'] = "1101", ['E'] = "1110", ['F'] = "1111"
}
for i = 1, #hex_string do
bin_string = bin_string .. hex_to_bin_map[hex_string:sub(i, i)]
end
return bin_string
end
local function remove_last_two_bits(binary_str)
return binary_str:sub(1, #binary_str - 2)
end
local function split_into_5bit_chunks(binary_str)
local chunks = {}
for i = 1, #binary_str, 5 do
table.insert(chunks, binary_str:sub(i, i + 4))
end
return chunks
end
local function remove_parity_bit(chunks)
local no_parity_chunks = {}
for _, chunk in ipairs(chunks) do
if #chunk == 5 then
table.insert(no_parity_chunks, chunk:sub(2))
end
end
return no_parity_chunks
end
local function convert_to_hex(chunks)
local hex_values = {}
for _, chunk in ipairs(chunks) do
if #chunk > 0 then
table.insert(hex_values, string.format("%X", tonumber(chunk, 2)))
end
end
return hex_values
end
local function convert_to_decimal(chunks)
local decimal_values = {}
for _, chunk in ipairs(chunks) do
table.insert(decimal_values, tonumber(chunk, 2))
end
return decimal_values
end
local function find_until_before_f(hex_values)
local result = {}
for _, value in ipairs(hex_values) do
if value == 'F' then
break
end
table.insert(result, value)
end
return result
end
local function process_block(block)
local binary_str = hex_to_bin(block)
binary_str = remove_last_two_bits(binary_str)
local chunks = split_into_5bit_chunks(binary_str)
local no_parity_chunks = remove_parity_bit(chunks)
return no_parity_chunks
end
local function calculate_id_net(blocks)
local all_hex_values = {}
for _, block in ipairs(blocks) do
local hex_values = convert_to_hex(process_block(block))
for _, hex in ipairs(hex_values) do
table.insert(all_hex_values, hex)
end
end
local selected_hex_values = find_until_before_f(all_hex_values)
if #selected_hex_values == 0 then
error(ac.red..' Error: '..ac.reset..'No valid data found in blocks 4 and 5')
end
local combined_hex = table.concat(selected_hex_values)
if not combined_hex:match("^%x+$") then
error(ac.red..' Error: '..ac.reset..'Invalid data in blocks 4 and 5')
end
local decimal_id = tonumber(combined_hex)
local stripped_hex_id = string.format("%X", decimal_id)
local padded_hex_id = string.format("%010X", decimal_id)
return decimal_id, padded_hex_id
end
local function calculate_id_switch(blocks)
local all_decimal_values = {}
for _, block in ipairs(blocks) do
local decimal_values = convert_to_decimal(process_block(block))
for _, dec in ipairs(decimal_values) do
table.insert(all_decimal_values, dec)
end
end
if #all_decimal_values < 15 then
error(ac.red..' Error:'..ac.reset..' Not enough data after processing blocks 4, 5, 6, and 7')
end
local id_positions = {9, 11, 13, 15, 2, 4, 6, 8}
local id_numbers = {}
for _, pos in ipairs(id_positions) do
table.insert(id_numbers, all_decimal_values[pos])
end
local decimal_id = tonumber(table.concat(id_numbers))
local padded_hex_id = string.format("%010X", decimal_id)
return decimal_id, padded_hex_id
end
local function name_exists_in_log(name)
local file = io.open(log_file_path, "r")
if not file then
return false
end
local pattern = "^Name:%s*" .. name .. "%s*$"
for line in file:lines() do
if line:match(pattern) then
file:close()
return true
end
end
file:close()
return false
end
local function log_result(blocks, em410_id, name)
local log_file = io.open(log_file_path, "a")
if log_file then
log_file:write("Name: " .. name .. "\n")
log_file:write("Date: ", os.date("%Y-%m-%d %H:%M:%S"), "\n")
for i = 4, 7 do
log_file:write(string.format("Block %d: %s\n", i, blocks[i] or "nil"))
end
log_file:write(string.format('EM4102 ID: %s\n', em410_id or "nil"))
log_file:write('--------------------------\n')
log_file:close()
print(' Log saved as: pm3/.proxmark3/logs/' ..ac.yellow..' Paxton_log.txt'..ac.reset)
else
print(" Failed to open log file for writing.")
end
end
local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
while true do
io.write(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2 " .. ac.reset)
local choice = io.read()
if choice == "1" then
io.write(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read()
print(dash)
command("lf hitag wrbl --ht2 -p 4 -d " .. blocks[4] .. " -k BDF5E846")
command("lf hitag wrbl --ht2 -p 5 -d " .. blocks[5] .. " -k BDF5E846")
command("lf hitag wrbl --ht2 -p 6 -d " .. blocks[6] .. " -k BDF5E846")
command("lf hitag wrbl --ht2 -p 7 -d " .. blocks[7] .. " -k BDF5E846")
elseif choice == "2" then
io.write(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read()
p:console("lf em 410x clone --id " .. padded_hex_id)
print(' Cloned EM4102 to T5577 with ID ' ..ac.green.. padded_hex_id ..ac.reset)
else
print(ac.yellow .. " Invalid choice." .. ac.reset .. " Please enter " .. ac.cyan .. "1" .. ac.reset .. " or " .. ac.cyan .. "2" .. ac.reset)
goto ask_again
end
while true do
print(dash)
io.write(" Make next RFID Fob"..ac.cyan.." (y/n) "..ac.reset)
local another = io.read()
if another:lower() == "n" then
if was_option_3 then
print(" No writing to Paxton_log.txt - Name: " ..ac.green.. nam .. ac.reset.. " exist")
return
end
print()
print(ac.green .. " Saving Paxton_log file..." .. ac.reset)
while true do
io.write(" Enter a name for database (cannot be empty/duplicate): "..ac.yellow)
name = io.read()
io.write(ac.reset..'')
if name == nil or name:match("^%s*$") then
print(ac.red .. ' ERROR:'..ac.reset..' Name cannot be empty.')
else
if name_exists_in_log(name) then
print(ac.yellow .. ' Name exists!!! '..ac.reset.. 'Please choose a different name.')
else
break
end
end
end
log_result(blocks, padded_hex_id, name)
print(ac.green .. " Log saved successfully!" .. ac.reset)
local file = io.open(logfile, "w+")
file:write("")
file:close()
return
elseif another:lower() == "y" then
goto ask_again
else
print(ac.yellow.." Invalid response."..ac.reset.." Please enter"..ac.cyan.." y"..ac.reset.." or"..ac.cyan.." n"..ac.reset)
end
end
::ask_again::
end
end
local function is_valid_hex(input)
return #input == 8 and input:match("^[0-9A-Fa-f]+$")
end
local function main(args)
while true do
for o, a in getopt.getopt(args, 'h') do
if o == 'h' then return help() end
end
command('clear')
print(dash)
print(ac.green .. ' Select option: ' .. ac.reset)
print(ac.cyan .. ' 1' .. ac.reset .. ' - Read Paxton blocks 4-7 to make a copy')
print(ac.cyan .. ' 2' .. ac.reset .. ' - Manually input data for Paxton blocks 4-7')
print(ac.cyan .. " 3" .. ac.reset .. " - Search in Paxton_log by name and use the data")
print(dash)
while true do
io.write(' Your choice '..ac.cyan..'(1/2/3): ' .. ac.reset)
input_option = io.read()
if input_option == "1" or input_option == "2" or input_option == "3" then
break
else
print(ac.yellow .. ' Invalid choice.' .. ac.reset .. ' Please enter ' .. ac.cyan .. '1' .. ac.reset .. ' or ' .. ac.cyan .. '2' .. ac.reset..' or'..ac.cyan..' 3'..ac.reset)
end
end
local was_option_3 = false
if input_option == "1" then
local show_place_message = true
while true do
if show_place_message then
io.write(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..')
end
io.read()
print(dash)
p:console('lf hitag read --ht2 -k BDF5E846')
if not logfile then
error(" No files in this directory")
end
local result = read_log_file(logfile)
local blocks = parse_blocks(result)
local empty_block = false
for i = 4, 7 do
if not blocks[i] then
empty_block = true
break
end
end
if empty_block then
io.write(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..')
show_place_message = false
else
print(' Readed blocks:')
print()
for i = 4, 7 do
if blocks[i] then
print(string.format(" Block %d: %s%s%s", i, ac.yellow, blocks[i], ac.reset))
end
end
local decimal_id, padded_hex_id
if blocks[5] and (blocks[5]:sub(4, 4) == 'F' or blocks[5]:sub(4, 4) == 'f') then
print(dash)
print(' Identified Paxton ' .. ac.cyan .. 'Net2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_net({blocks[4], blocks[5]})
else
print(dash)
print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]})
end
print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset))
print(dash)
handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
break
end
end
elseif input_option == "2" then
local blocks = {}
for i = 4, 7 do
while true do
io.write(ac.reset..' Enter data for block ' .. i .. ': ' .. ac.yellow)
local input = io.read()
input = input:upper()
if is_valid_hex(input) then
blocks[i] = input
break
else
print(ac.yellow .. ' Invalid input.' .. ac.reset .. ' Each block must be 4 bytes (8 hex characters).')
end
end
end
local decimal_id, padded_hex_id
if blocks[5] and (blocks[5]:sub(4, 4) == 'F' or blocks[5]:sub(4, 4) == 'f') then
print(ac.reset.. dash)
print(' Identified Paxton ' .. ac.cyan .. 'Net2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_net({blocks[4], blocks[5]})
else
print(ac.reset.. dash)
print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]})
end
print(dash)
print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset))
print(dash)
if not padded_hex_id then
print(ac.red..' ERROR: '..ac.reset.. 'Invalid block data provided')
return
end
handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
break
elseif input_option == "3" then
was_option_3 = true
local retries = 3
while retries > 0 do
io.write(' Enter the name to search ('..retries..' attempts) : '..ac.yellow)
local user_input = io.read()
io.write(ac.reset..'')
if user_input == nil or user_input:match("^%s*$") then
print(ac.yellow..' Error: '..ac.reset.. 'Empty name !!!')
end
local name_clean = "^Name:%s*" .. user_input:gsub("%s", "%%s") .. "%s*$"
local file = io.open(log_file_path, "r")
if not file then
print(ac.red .. ' Error:'..ac.reset.. 'Could not open log file.')
return
end
local lines = {}
for line in file:lines() do
table.insert(lines, line)
end
file:close()
local found = false
for i = 1, #lines do
if lines[i]:match(name_clean) then
nam = user_input
local blocks = {
[4] = lines[i + 2]:match("Block 4: (.+)"),
[5] = lines[i + 3]:match("Block 5: (.+)"),
[6] = lines[i + 4]:match("Block 6: (.+)"),
[7] = lines[i + 5]:match("Block 7: (.+)")
}
local em4102_id = lines[i + 6]:match("EM4102 ID: (.+)")
print(dash)
print(' I found the data under the name: '..ac.yellow ..nam.. ac.reset)
for j = 4, 7 do
print(string.format(" Block %d: %s%s%s", j, ac.yellow, blocks[j] or "N/A", ac.reset))
end
print(" EM4102 ID: " .. ac.green .. (em4102_id or "N/A") .. ac.reset)
print(dash)
local decimal_id, padded_hex_id = em4102_id, em4102_id
handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3, nam)
found = true
break
end
end
if not found then
retries = retries - 1
else
break
end
end
if retries == 0 then
print(ac.yellow .. " Name not found after 3 attempts." .. ac.reset)
end
end
print(dash)
print(' Exiting script Lua...')
return
end
end
main(args)

View file

@ -688,11 +688,11 @@ static int CmdEM410xClone(const char *Cmd) {
uint8_t r_parity = 0;
uint8_t nibble = id >> i & 0xF;
databits = concatbits(data, databits, &nibble, 4, 4);
databits = concatbits(data, databits, &nibble, 4, 4, false);
for (size_t j = 0; j < 4; j++) {
r_parity ^= nibble >> j & 1;
}
databits = concatbits(data, databits, &r_parity, 7, 1);
databits = concatbits(data, databits, &r_parity, 7, 1, false);
c_parity ^= nibble;
}
data[7] |= c_parity << 1;

View file

@ -34,9 +34,15 @@
// TODO: Optional: use those unique structures in a union, call it em4x70_data_t, but add a first
// common header field that includes the command itself (to improve debugging / validation).
typedef struct _em4x70_tag_info_t {
/// <summary>
/// The full data on an em4x70 the tag.
/// The full data on an em4170 tag.
/// For V4070 tags:
/// * UM2 does not exist on the tag
/// * Pin does not exist on the tag
/// * UM1 (including the lock bits) might be one-time programmable (OTP)
///
/// [31] == Block 15 MSB == UM2₆₃..UM2₅₆
/// [30] == Block 15 LSB == UM2₅₅..UM2₄₈
/// [29] == Block 14 MSB == UM2₄₇..UM2₄₀
@ -168,6 +174,7 @@ typedef struct _em4x70_cmd_input_calculate_t {
ID48LIB_KEY key;
ID48LIB_NONCE rn;
} em4x70_cmd_input_calculate_t;
typedef struct _em4x70_cmd_output_calculate_t {
ID48LIB_FRN frn;
ID48LIB_GRN grn;

View file

@ -832,7 +832,7 @@ static bool ht2_get_uid(uint32_t *uid) {
}
if (resp.status != PM3_SUCCESS) {
PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting UID");
PrintAndLogEx(DEBUG, "DEBUG: Error - failed getting Hitag 2 UID");
return false;
}

View file

@ -186,6 +186,7 @@ static void fill_grabber(const char *string) {
g_grabbed_output.ptr = tmp;
g_grabbed_output.size += MAX_PRINT_BUFFER;
}
int len = snprintf(g_grabbed_output.ptr + g_grabbed_output.idx, MAX_PRINT_BUFFER, "%s", string);
if (len < 0 || len > MAX_PRINT_BUFFER) {
// We leave current g_grabbed_output_len untouched
@ -196,25 +197,35 @@ static void fill_grabber(const char *string) {
}
void PrintAndLogOptions(const char *str[][2], size_t size, size_t space) {
char buff[2000] = "Options:\n";
char format[2000] = "";
size_t counts[2] = {0, 0};
for (size_t i = 0; i < size; i++)
for (size_t j = 0 ; j < 2 ; j++)
for (size_t i = 0; i < size; i++) {
for (size_t j = 0 ; j < 2 ; j++) {
if (counts[j] < strlen(str[i][j])) {
counts[j] = strlen(str[i][j]);
}
}
}
for (size_t i = 0; i < size; i++) {
for (size_t j = 0; j < 2; j++) {
if (j == 0)
if (j == 0) {
snprintf(format, sizeof(format), "%%%zus%%%zus", space, counts[j]);
else
} else {
snprintf(format, sizeof(format), "%%%zus%%-%zus", space, counts[j]);
}
snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), format, " ", str[i][j]);
}
if (i < size - 1)
if (i < size - 1) {
strncat(buff, "\n", sizeof(buff) - strlen(buff) - 1);
}
}
PrintAndLogEx(NORMAL, "%s", buff);
}
@ -223,12 +234,14 @@ static uint8_t PrintAndLogEx_spinidx = 0;
void PrintAndLogEx(logLevel_t level, const char *fmt, ...) {
// skip debug messages if client debugging is turned off i.e. 'DATA SETDEBUG -0'
if (g_debugMode == 0 && level == DEBUG)
if (g_debugMode == 0 && level == DEBUG) {
return;
}
// skip HINT messages if client has hints turned off i.e. 'HINT 0'
if (g_session.show_hints == false && level == HINT)
if (g_session.show_hints == false && level == HINT) {
return;
}
char prefix[40] = {0};
char buffer[MAX_PRINT_BUFFER] = {0};
@ -242,17 +255,19 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) {
};
switch (level) {
case ERR:
if (g_session.emoji_mode == EMO_EMOJI)
if (g_session.emoji_mode == EMO_EMOJI) {
strncpy(prefix, "[" _RED_("!!") "] :rotating_light: ", sizeof(prefix) - 1);
else
} else {
strncpy(prefix, "[" _RED_("!!") "] ", sizeof(prefix) - 1);
}
stream = stderr;
break;
case FAILED:
if (g_session.emoji_mode == EMO_EMOJI)
if (g_session.emoji_mode == EMO_EMOJI) {
strncpy(prefix, "[" _RED_("-") "] :no_entry: ", sizeof(prefix) - 1);
else
} else {
strncpy(prefix, "[" _RED_("-") "] ", sizeof(prefix) - 1);
}
break;
case DEBUG:
strncpy(prefix, "[" _BLUE_("#") "] ", sizeof(prefix) - 1);
@ -264,10 +279,11 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) {
strncpy(prefix, "[" _GREEN_("+") "] ", sizeof(prefix) - 1);
break;
case WARNING:
if (g_session.emoji_mode == EMO_EMOJI)
if (g_session.emoji_mode == EMO_EMOJI) {
strncpy(prefix, "[" _CYAN_("!") "] :warning: ", sizeof(prefix) - 1);
else
} else {
strncpy(prefix, "[" _CYAN_("!") "] ", sizeof(prefix) - 1);
}
break;
case INFO:
strncpy(prefix, "[" _YELLOW_("=") "] ", sizeof(prefix) - 1);
@ -276,14 +292,16 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) {
if (g_session.emoji_mode == EMO_EMOJI) {
strncpy(prefix, spinner_emoji[PrintAndLogEx_spinidx], sizeof(prefix) - 1);
PrintAndLogEx_spinidx++;
if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner_emoji))
if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner_emoji)) {
PrintAndLogEx_spinidx = 0;
}
} else {
strncpy(prefix, spinner[PrintAndLogEx_spinidx], sizeof(prefix) - 1);
PrintAndLogEx_spinidx++;
if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner))
if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner)) {
PrintAndLogEx_spinidx = 0;
}
}
break;
case NORMAL:
// no prefixes for normal
@ -306,8 +324,9 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) {
const char delim[2] = "\n";
// line starts with newline
if (buffer[0] == '\n')
if (buffer[0] == '\n') {
fPrintAndLog(stream, "");
}
token = strtok_r(buffer, delim, &tmp_ptr);
@ -315,16 +334,21 @@ void PrintAndLogEx(logLevel_t level, const char *fmt, ...) {
size_t size = strlen(buffer2);
if (strlen(token))
if (strlen(token)) {
snprintf(buffer2 + size, sizeof(buffer2) - size, "%s%s\n", prefix, token);
else
} else {
snprintf(buffer2 + size, sizeof(buffer2) - size, "\n");
}
token = strtok_r(NULL, delim, &tmp_ptr);
}
fPrintAndLog(stream, "%s", buffer2);
} else {
snprintf(buffer2, sizeof(buffer2), "%s%s", prefix, buffer);
if (level == INPLACE) {
// ignore INPLACE if rest of output is grabbed
if (!(g_printAndLog & PRINTANDLOG_GRAB)) {
@ -354,6 +378,7 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) {
if (logging && g_session.incognito) {
logging = 0;
}
if ((g_printAndLog & PRINTANDLOG_LOG) && logging && !logfile) {
char *my_logfile_path = NULL;
char filename[40];
@ -361,11 +386,15 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) {
time_t now = time(NULL);
timenow = gmtime(&now);
strftime(filename, sizeof(filename), PROXLOG, timenow);
if (searchHomeFilePath(&my_logfile_path, LOGS_SUBDIR, filename, true) != PM3_SUCCESS) {
printf(_YELLOW_("[-]") " Logging disabled!\n");
my_logfile_path = NULL;
logging = 0;
} else {
logfile = fopen(my_logfile_path, "a");
if (logfile == NULL) {
printf(_YELLOW_("[-]") " Can't open logfile %s, logging disabled!\n", my_logfile_path);
@ -411,14 +440,17 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) {
linefeed = false;
buffer[strlen(buffer) - 1] = 0;
}
bool filter_ansi = !g_session.supports_colors;
memcpy_filter_ansi(buffer2, buffer, sizeof(buffer), filter_ansi);
if (g_printAndLog & PRINTANDLOG_PRINT) {
if ((g_printAndLog & PRINTANDLOG_PRINT) == PRINTANDLOG_PRINT) {
memcpy_filter_emoji(buffer3, buffer2, sizeof(buffer2), g_session.emoji_mode);
fprintf(stream, "%s", buffer3);
if (linefeed)
if (linefeed) {
fprintf(stream, "\n");
}
}
#ifdef RL_STATE_READCMD
// We are using GNU readline. libedit (OSX) doesn't support this flag.
@ -433,33 +465,44 @@ static void fPrintAndLog(FILE *stream, const char *fmt, ...) {
if (((g_printAndLog & PRINTANDLOG_LOG) && logging && logfile) ||
(g_printAndLog & PRINTANDLOG_GRAB)) {
memcpy_filter_emoji(buffer3, buffer2, sizeof(buffer2), EMO_ALTTEXT);
if (filter_ansi == false) {
memcpy_filter_ansi(buffer, buffer3, sizeof(buffer3), true);
}
}
if ((g_printAndLog & PRINTANDLOG_LOG) && logging && logfile) {
if (filter_ansi) {
fprintf(logfile, "%s", buffer3);
} else {
fprintf(logfile, "%s", buffer);
}
if (linefeed)
if (linefeed) {
fprintf(logfile, "\n");
}
fflush(logfile);
}
if (g_printAndLog & PRINTANDLOG_GRAB) {
if (filter_ansi) {
fill_grabber(buffer3);
} else {
fill_grabber(buffer);
}
if (linefeed)
if (linefeed) {
fill_grabber("\n");
}
}
if (flushAfterWrite)
if (flushAfterWrite) {
fflush(stdout);
}
//release lock
pthread_mutex_unlock(&g_print_lock);
@ -478,9 +521,10 @@ void memcpy_filter_rlmarkers(void *dest, const void *src, size_t n) {
uint8_t *rsrc = (uint8_t *)src;
uint16_t si = 0;
for (size_t i = 0; i < n; i++) {
if ((rsrc[i] == '\001') || (rsrc[i] == '\002'))
if ((rsrc[i] == '\001') || (rsrc[i] == '\002')) {
// skip readline special markers
continue;
}
rdest[si++] = rsrc[i];
}
}

View file

@ -555,9 +555,13 @@ void reverse_arraybytes_copy(uint8_t *arr, uint8_t *dest, size_t len) {
}
// TODO: Boost performance by copying in chunks of 1, 2, or 4 bytes when feasible.
size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits) {
/**
* @brief Concatenate bits from src to dest, bitstream is stored MSB first
* which means that the dest_offset=0 is the MSB of the dest[0]
*
*/
size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits, bool src_lsb) {
int i, end, step;
// overlap
if ((src - dest) * 8 + src_offset - dest_offset > 0) {
i = 0;
@ -571,8 +575,8 @@ size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_of
for (; i != end; i += step) {
// equiv of dest_bits[dest_offset + i] = src_bits[src_offset + i]
CLEAR_BIT(dest, dest_offset + i);
if (TEST_BIT(src, src_offset + i)) SET_BIT(dest, dest_offset + i);
CLEAR_BIT_MSB(dest, dest_offset + i);
if (src_lsb ? TEST_BIT_LSB(src, src_offset + i) : TEST_BIT_MSB(src, src_offset + i)) SET_BIT_MSB(dest, dest_offset + i);
}
return dest_offset + nbits;

View file

@ -150,7 +150,7 @@ bool hexstr_to_byte_array(const char *hexstr, uint8_t *d, size_t *n);
void reverse_arraybytes(uint8_t *arr, size_t len);
void reverse_arraybytes_copy(uint8_t *arr, uint8_t *dest, size_t len);
size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits);
size_t concatbits(uint8_t *dest, int dest_offset, const uint8_t *src, int src_offset, size_t nbits, bool src_lsb);
int char2int(char c);
int hexstr2ByteArr(const char *hexstr, unsigned char *array, size_t asize);
#endif

View file

@ -5047,7 +5047,7 @@
"-v, --verbose verbose output",
"-f, --file <fn> Specify a filename for dump file",
"--emu from emulator memory",
"--start <dec> index of block to start writing (default 0)",
"--start <dec> index of block to start writing (def 0)",
"--end <dec> index of block to end writing (default last block)"
],
"usage": "hf mf gload [-hv] [--mini] [--1k] [--1k+] [--2k] [--4k] [-p <hex>] [-f <fn>] [--emu] [--start <dec>] [--end <dec>]"
@ -10992,8 +10992,8 @@
"-r, --reset Reset configuration to default values",
"-p, --pwd <hex> Password, 7bytes, LSB-order",
"-d, --delay <dec> Tag initialization delay (in us)",
"--lw <dec> offset, low pulses width (in us)",
"--lp <dec> offset, low pulses position (in us)"
"--lw <dec> offset, low pulses width (in us), optional!",
"--lp <dec> offset, low pulses position (in us), optional!"
],
"usage": "lf pcf7931 config [-hr] [-p <hex>] [-d <dec>] [--lw <dec>] [--lp <dec>]"
},
@ -13232,6 +13232,6 @@
"metadata": {
"commands_extracted": 760,
"extracted_by": "PM3Help2JSON v1.00",
"extracted_on": "2025-03-12T15:46:33"
"extracted_on": "2025-03-18T06:54:58"
}
}

View file

@ -0,0 +1,147 @@
# arbitrary lf em commands
Goals:
1. Improved logging of `lf em` commands and responses
2. Greater certainty in command sequences
3. Easier testing of new commands
## Methodology
This is documenting the actual commands used by existing code. Phases include:
* Document the existing command sequences
* Document the existing logging APIs
* Define small set of timing-sensitive functions as abstractions
* Implement the abstractions
* Add logging
The goal is to improve logging and debugging, and allow easily testing new LF commands.
## EM4x70 (aka ID48, aka Megamos)
Only six command sequences currently used:
#define EM4X70_COMMAND_ID 0x01
#define EM4X70_COMMAND_UM1 0x02
#define EM4X70_COMMAND_AUTH 0x03
#define EM4X70_COMMAND_PIN 0x04
#define EM4X70_COMMAND_WRITE 0x05
#define EM4X70_COMMAND_UM2 0x07
### ID Command
Wait for `LIW` (listen window), and start transmission at next `LIW`:
source | bits | comment
----------|---------|---------
tag | LIW | listen window sync
reader | `0b00` | RM
reader | `0b001` | CMD
reader | `0b1` | command parity bit
tag | HEADER | HEADER (0b1111'1111'1111'0000)
tag | 32-bits | ID (D31..D0)
tag | LIW | tag reverts to be ready for next command
### UM1 Command
source | bits | comment
----------|---------|---------
tag | LIW | listen window
reader | `0b00` | RM
reader | `0b010` | CMD
reader | `0b1` | command parity bit
tag | 16-bits | HEADER
tag | 32-bits | UM1 data
tag | LIW | tag reverts to be ready for next command
### UM2 Command
source | bits | comment
----------|---------|---------
tag | LIW | listen window
reader | `0b00` | RM
reader | `0b111` | CMD
reader | `0b1` | command parity bit
tag | 16-bits | HEADER
tag | 64-bits | UM2 data
tag | LIW | tag reverts to be ready for next command
### Auth Command
source | bits | comment
----------|---------|---------
tag | LIW | listen window
reader | `0b00` | RM
reader | `0b011` | CMD
reader | `0b0` | command parity bit
reader | 56-bits | RN
reader | 7-bits | Tdiv == 0b0000000 (always zero)
reader | 28-bits | f(RN)
tag | 16-bits | HEADER
tag | 20-bits | g(RN)
tag | LIW | tag reverts to be ready for next command
### Write Word
source | bits | comment
----------|---------|---------
tag | LIW | listen window
reader | `0b00` | RM
reader | `0b101` | CMD
reader | `0b0` | command parity bit
reader | 4-bits | address/block to write
reader | 1-bit | address/block parity bit
reader | 25-bits | 5x5 data w/ row and column parity
tag | ACK | Wait (TWA) for ACK ... time to wait before searching for ACK
tag | ACK | Wait (WEE) for ACK ... time to wait before searching for ACK
tag | LIW | tag reverts to be ready for next command
### PIN Command
source | bits | comment
----------|---------|---------
tag | LIW | listen window
reader | `0b00` | RM
reader | `0b100` | CMD
reader | `0b1` | command parity bit
reader | 32-bits | ID of the tag
reader | 32-bits | PIN
tag | ACK | Wait (TWALB) for ACK ... time to wait before searching for ACK
tag | HEADER | DELAYED (TWEE) header ... time to wait before searching for header
tag | 32-bits | ID of the tag
tag | LIW | tag reverts to be ready for next command
### Abstraction required
Possible items to abstract:
* bits to send: quantity of bits to be sent + storage containing those bits
* bits to receive: expected bits to receive + storage to receive those bits
* LIW: special-case handling to synchronize next command
* ACK: special-case handling to wait for ACK
* HEADER: special-case handling to wait for HEADER
* DELAY: ticks to delay before processing next item
Special handling required for:
* `HEADER` --> 12-bits of zero, 4-bits of one. Consider a timeout: if tag disappears, no pulse found, while sometimes expect long time before HEADER appears (as in SEND_PIN). Read of header may miss the first few bits during transition, so need to special-case handling of this detection.
* `LIW` --> Timing-sensitive, syncs reader with tag ... reader must send during 32 period where chip's modulator is ON.
* `ACK` --> This is currently a time-to-delay.
Should this be a maximum time to wait for ACK?
Currently, could sit waiting for long time
if no tag present, as `check_ack()` has no timeout.
```C
WaitTicks(EM4X70_T_TAG_TWA);
if (check_ack())
WaitTicks(EM4X70_T_TAG_WEE);
if (check_ack())
return PM3_SUCCESS;
```

File diff suppressed because it is too large Load diff

View file

@ -202,10 +202,15 @@ extern bool g_tearoff_enabled;
#endif
// bit stream operations
#define TEST_BIT(data, i) (*((data) + ((i) / 8)) >> (7 - ((i) % 8))) & 1
#define SET_BIT(data, i) *((data) + ((i) / 8)) |= (1 << (7 - ((i) % 8)))
#define CLEAR_BIT(data, i) *((data) + ((i) / 8)) &= ~(1 << (7 - ((i) % 8)))
#define FLIP_BIT(data, i) *((data) + ((i) / 8)) ^= (1 << (7 - ((i) % 8)))
#define TEST_BIT_MSB(data, i) ((*((data) + ((i) / 8)) >> (7 - ((i) % 8))) & 1)
#define SET_BIT_MSB(data, i) (*((data) + ((i) / 8)) |= (1 << (7 - ((i) % 8))))
#define CLEAR_BIT_MSB(data, i) (*((data) + ((i) / 8)) &= ~(1 << (7 - ((i) % 8))))
#define FLIP_BIT_MSB(data, i) (*((data) + ((i) / 8)) ^= (1 << (7 - ((i) % 8))))
#define TEST_BIT_LSB(data, i) ((*((data) + ((i) / 8)) >> ((i) % 8)) & 1)
#define SET_BIT_LSB(data, i) (*((data) + ((i) / 8)) |= (1 << ((i) % 8)))
#define CLEAR_BIT_LSB(data, i) (*((data) + ((i) / 8)) &= ~(1 << ((i) % 8)))
#define FLIP_BIT_LSB(data, i) (*((data) + ((i) / 8)) ^= (1 << ((i) % 8)))
// time for decompressing and loading the image to the FPGA
#define FPGA_LOAD_WAIT_TIME (1500)

View file

@ -21,6 +21,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#define EM4X70_NUM_BLOCKS 16
@ -28,24 +29,36 @@
#define EM4X70_PIN_WORD_LOWER 10
#define EM4X70_PIN_WORD_UPPER 11
/// @brief Command transport structure for EM4x70 commands.
/// @details
/// This structure is used to transport data from the PC
/// to the proxmark3, and contain all data needed for
/// a given `lf em 4x70 ...` command to be processed
/// on the proxmark3.
/// The only requirement is that this structure remain
/// smaller than the NG buffer size (256 bytes).
typedef struct {
// ISSUE: `bool` type does not have a standard-defined size.
// therefore, compatibility between architectures /
// compilers is not guaranteed.
// ISSUE: C99 has no _Static_assert() ... was added in C11
// TODO: add _Static_assert(sizeof(bool)==1);
// TODO: add _Static_assert(sizeof(em4x70_data_t)==36);
bool parity;
// Used for writing address
uint8_t address;
// ISSUE: Presumes target is little-endian
// BUGBUG: Non-portable ... presumes stored in little-endian form!
uint16_t word;
// PIN to unlock
// BUGBUG: Non-portable ... presumes stored in little-endian form!
uint32_t pin;
// Used for authentication
//
// IoT safe subset of C++ would be helpful here,
// to support variable-bit-length integer types
// as integral integer types.
//
// Even C23 would work for this (GCC14+, Clang15+):
// _BitInt(56) rnd;
// _BitInt(28) frnd;
// _BitInt(20) grnd;
uint8_t frnd[4];
uint8_t grnd[3];
uint8_t rnd[7];
@ -54,9 +67,20 @@ typedef struct {
uint8_t crypt_key[12];
// used for bruteforce the partial key
// ISSUE: Presumes target is little-endian
// BUGBUG: Non-portable ... presumes stored in little-endian form!
uint16_t start_key;
} em4x70_data_t;
//_Static_assert(sizeof(em4x70_data_t) == 36);
// ISSUE: `bool` type does not have a standard-defined size.
// therefore, compatibility between architectures /
// compilers is not guaranteed.
// TODO: verify alignof(bool) == 1
//_Static_assert(sizeof(bool) == 1, "bool size mismatch");
typedef union {
uint8_t data[32];
} em4x70_tag_t;
//_Static_assert(sizeof(em4x70_tag_t) == 32, "em4x70_tag_t size mismatch");
#endif /* EM4X70_H__ */