From 4707ac12771ac1b7a1e80e42068ad5d6042229c1 Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:28:19 +0000 Subject: [PATCH 01/11] Add files via upload Script for cloning Paxton fobs Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/luascripts/paxton_clone.lua.txt | 423 +++++++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 client/luascripts/paxton_clone.lua.txt diff --git a/client/luascripts/paxton_clone.lua.txt b/client/luascripts/paxton_clone.lua.txt new file mode 100644 index 000000000..daa7df38b --- /dev/null +++ b/client/luascripts/paxton_clone.lua.txt @@ -0,0 +1,423 @@ +-------------------------------------------------------------------- +-- Author - jareckib - 30.01.2025 +-- Based on Equipter's tutorial - Downgrade Paxton to EM4102 +-- version v 1.14 +--------------------------------------------------------------------- +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 command = core.console + +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 + 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) + 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.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 + print(dash) + print(" 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) + 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) + 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") + 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() + while true do + command('clear') + print() + print(dash) + local input_option + 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 + print(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..') + print(dash) + end + io.read() + command('lf hitag read --ht2 -k BDF5E846') + command('clear') + 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 + print(dash) + print(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..') + print(dash) + show_place_message = false + else + print(dash) + 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(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) + 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 = 0 + while retries < 3 do + io.write(" Enter the name to search (3 attempts) : "..ac.yellow) + local user_input = io.read() + io.write(ac.reset..'') + 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) + 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 == 3 then + print(ac.yellow .. " Name not found after 3 attempts." .. ac.reset) + end + end + print(dash) + print(' Exiting script Lua...') + return + end +end + +main() \ No newline at end of file From ca9f4341105e86619b50a516887b73ccf5c69f46 Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:32:43 +0000 Subject: [PATCH 02/11] Delete client/luascripts/paxton_clone.lua.txt Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/luascripts/paxton_clone.lua.txt | 423 ------------------------- 1 file changed, 423 deletions(-) delete mode 100644 client/luascripts/paxton_clone.lua.txt diff --git a/client/luascripts/paxton_clone.lua.txt b/client/luascripts/paxton_clone.lua.txt deleted file mode 100644 index daa7df38b..000000000 --- a/client/luascripts/paxton_clone.lua.txt +++ /dev/null @@ -1,423 +0,0 @@ --------------------------------------------------------------------- --- Author - jareckib - 30.01.2025 --- Based on Equipter's tutorial - Downgrade Paxton to EM4102 --- version v 1.14 ---------------------------------------------------------------------- -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 command = core.console - -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 - 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) - 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.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 - print(dash) - print(" 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) - 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) - 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") - 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() - while true do - command('clear') - print() - print(dash) - local input_option - 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 - print(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..') - print(dash) - end - io.read() - command('lf hitag read --ht2 -k BDF5E846') - command('clear') - 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 - print(dash) - print(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..') - print(dash) - show_place_message = false - else - print(dash) - 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(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) - 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 = 0 - while retries < 3 do - io.write(" Enter the name to search (3 attempts) : "..ac.yellow) - local user_input = io.read() - io.write(ac.reset..'') - 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) - 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 == 3 then - print(ac.yellow .. " Name not found after 3 attempts." .. ac.reset) - end - end - print(dash) - print(' Exiting script Lua...') - return - end -end - -main() \ No newline at end of file From 6e572141a3583f0932687e479cadc70eb660050f Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:34:55 +0000 Subject: [PATCH 03/11] Add files via upload Script for automatic clone Paxton fob or convert to EM4102 Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/luascripts/Paxton_clone.lua | 427 +++++++++++++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 client/luascripts/Paxton_clone.lua diff --git a/client/luascripts/Paxton_clone.lua b/client/luascripts/Paxton_clone.lua new file mode 100644 index 000000000..7c62671fc --- /dev/null +++ b/client/luascripts/Paxton_clone.lua @@ -0,0 +1,427 @@ +-------------------------------------------------------------------- +-- Author - jareckib - 30.01.2025 +-- Based on Equipter's tutorial - Downgrade Paxton to EM4102 +-- version v 1.17 +--------------------------------------------------------------------- +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 command = core.console + +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 + 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) + 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.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 + print(dash) + print(" 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) + 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) + 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") + 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() + while true do + command('clear') + print() + print(dash) + local input_option + 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 + print(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..') + print(dash) + end + io.read() + command('lf hitag read --ht2 -k BDF5E846') + command('clear') + 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 + print(dash) + print(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..') + print(dash) + show_place_message = false + else + print(dash) + 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(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) + 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() \ No newline at end of file From 989b59511922ed709a0ba3965e75230d85289dfc Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:38:04 +0000 Subject: [PATCH 04/11] Add files via upload Converters Paxton Switch2 and Net2 to EM4102 Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/pyscripts/PAXTON_NET.py | 69 +++++++++++++++++++++++ client/pyscripts/Paxton_convert.py | 89 ++++++++++++++++++++++++++++++ client/pyscripts/Paxton_switch.py | 57 +++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 client/pyscripts/PAXTON_NET.py create mode 100644 client/pyscripts/Paxton_convert.py create mode 100644 client/pyscripts/Paxton_switch.py diff --git a/client/pyscripts/PAXTON_NET.py b/client/pyscripts/PAXTON_NET.py new file mode 100644 index 000000000..8b2920b77 --- /dev/null +++ b/client/pyscripts/PAXTON_NET.py @@ -0,0 +1,69 @@ +# Author - Jareckib +# Based on Equipter's tutorial - Downgrade Paxton Net to EM410x +import sys +def hex_to_bin(hex_string): + return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) +def remove_last_two_bits(binary_str): + return binary_str[:-2] +def split_into_5bit_chunks(binary_str): + return [binary_str[i:i+5] for i in range(0, len(binary_str), 5)] +def remove_parity_bit(chunks): + return [chunk[1:] for chunk in chunks if len(chunk) == 5] +def convert_to_hex(chunks): + return [format(int(chunk, 2), 'X') for chunk in chunks] +def find_until_before_f(hex_values): + result = [] + for value in hex_values: + if value == 'F': + break + result.append(value) + return result +def process_block(block): + binary_str = hex_to_bin(block) + binary_str = remove_last_two_bits(binary_str) + chunks = split_into_5bit_chunks(binary_str) + no_parity_chunks = remove_parity_bit(chunks) + hex_values = convert_to_hex(no_parity_chunks) + return hex_values +def calculate_id(blocks): + all_hex_values = [] + for block in blocks: + hex_values = process_block(block) + all_hex_values.extend(hex_values) + selected_hex_values = find_until_before_f(all_hex_values) + if not selected_hex_values: + raise ValueError("Error: No valid data found in blocks 4 and 5.") + combined_hex = ''.join(selected_hex_values) + if not combined_hex.isdigit(): + raise ValueError("Error: Invalid data in blocks 4 and 5.") + decimal_id = int(combined_hex) + stripped_hex_id = format(decimal_id, 'X').upper() + padded_hex_id = stripped_hex_id.zfill(10) + return combined_hex, decimal_id, stripped_hex_id, padded_hex_id +def input_block_data(block_number): + while True: + block_data = input("Enter data for block {} (4 bytes in hex): ".format(block_number)).strip() + if len(block_data) != 8 or not all(c in '0123456789abcdefABCDEF' for c in block_data): + print("Error: Data must be 4 bytes (8 characters) in hex. Try again.") + else: + return block_data +block_4 = input_block_data(4) +block_5 = input_block_data(5) + +# Check if the second nibble of the second byte in block 5 is 'F' +if block_5[3] != 'F' and block_5[3] != 'f': + print("This is not a Paxton Net2") + sys.exit() + +blocks = [ + block_4, + block_5, +] +try: + result_hex, result_decimal, stripped_hex_id, padded_hex_id = calculate_id(blocks) + print('Calculations for block 4 and block 5:') + print('Result (Net2 ID - decimal): {}'.format(result_decimal)) + print('Result (Net2 ID - hex): {}'.format(padded_hex_id)) + print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) +except ValueError as e: + print(e) \ No newline at end of file diff --git a/client/pyscripts/Paxton_convert.py b/client/pyscripts/Paxton_convert.py new file mode 100644 index 000000000..37633b8ff --- /dev/null +++ b/client/pyscripts/Paxton_convert.py @@ -0,0 +1,89 @@ +# Author - jareckib +# Based on Equipter's tutorial - Downgrade Paxton Switch2 to EM410x +import sys +def hex_to_bin(hex_string): + return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) +def remove_last_two_bits(binary_str): + return binary_str[:-2] +def split_into_5bit_chunks(binary_str): + return [binary_str[i:i+5] for i in range(0, len(binary_str), 5)] +def remove_parity_bit(chunks): + return [chunk[1:] for chunk in chunks if len(chunk) == 5] +def convert_to_hex(chunks): + return [format(int(chunk, 2), 'X') for chunk in chunks] +def convert_to_decimal(chunks): + return [int(chunk, 2) for chunk in chunks] +def find_until_before_f(hex_values): + result = [] + for value in hex_values: + if value == 'F': + break + result.append(value) + return result +def process_block(block): + binary_str = hex_to_bin(block) + binary_str = remove_last_two_bits(binary_str) + chunks = split_into_5bit_chunks(binary_str) + no_parity_chunks = remove_parity_bit(chunks) + return no_parity_chunks +def calculate_id_net(blocks): + all_hex_values = [] + for block in blocks: + hex_values = convert_to_hex(process_block(block)) + all_hex_values.extend(hex_values) + selected_hex_values = find_until_before_f(all_hex_values) + if not selected_hex_values: + raise ValueError("Error: No valid data found in blocks 4 and 5.") + combined_hex = ''.join(selected_hex_values) + if not combined_hex.isdigit(): + raise ValueError("Error: Invalid data in blocks 4 and 5.") + decimal_id = int(combined_hex) + stripped_hex_id = format(decimal_id, 'X').upper() + padded_hex_id = stripped_hex_id.zfill(10) + return decimal_id, padded_hex_id +def calculate_id_switch(blocks): + all_decimal_values = [] + for block in blocks: + decimal_values = convert_to_decimal(process_block(block)) + all_decimal_values.extend(decimal_values) + if len(all_decimal_values) < 15: + raise ValueError("Error: Not enough data after processing blocks 4, 5, 6, and 7.") + id_positions = [9, 11, 13, 15, 2, 4, 6, 8] + id_numbers = [all_decimal_values[pos-1] for pos in id_positions] + decimal_id = int(''.join(map(str, id_numbers))) + padded_hex_id = format(decimal_id, 'X').upper().zfill(10) + return decimal_id, padded_hex_id +def input_block_data(block_number): + while True: + block_data = input("Enter data for block {} (4 bytes in hex): ".format(block_number)).strip() + if len(block_data) != 8 or not all(c in '0123456789abcdefABCDEF' for c in block_data): + print("Error: Data must be 4 bytes (8 characters) in hex. Try again.") + else: + return block_data +block_4 = input_block_data(4) +block_5 = input_block_data(5) +if block_5[3] == 'F' or block_5[3] == 'f': + print("Identified Paxton Net2") + blocks = [block_4, block_5] + try: + decimal_id, padded_hex_id = calculate_id_net(blocks) + print('Calculations for block 4 and block 5:') + print('Net2 ID - decimal: {}'.format(decimal_id)) + print('Net2 ID - hex: {}'.format(padded_hex_id)) + print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) + except ValueError as e: + print(e) +else: + print("Identified Paxton Switch2") + block_6 = input_block_data(6) + block_7 = input_block_data(7) + blocks = [block_4, block_5, block_6, block_7] + try: + decimal_id, padded_hex_id = calculate_id_switch(blocks) + print('Calculated data from blocks 4, 5, 6, 7:') + print('Switch2 ID - decimal: {}'.format(decimal_id)) + print('Switch2 ID - hex: {}'.format(padded_hex_id)) + print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) + except ValueError as e: + print(e) +print('If EM4102 does not work, this option is probably disabled. Sorry for the inconvenience.') \ No newline at end of file diff --git a/client/pyscripts/Paxton_switch.py b/client/pyscripts/Paxton_switch.py new file mode 100644 index 000000000..df9ea2955 --- /dev/null +++ b/client/pyscripts/Paxton_switch.py @@ -0,0 +1,57 @@ +# Author - jareckib +# Based on Equipter's tutorial - Downgrade Paxton Switch2 to EM410x +import sys +def hex_to_bin(hex_string): + return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) +def remove_last_two_bits(binary_str): + return binary_str[:-2] +def split_into_5bit_chunks(binary_str): + return [binary_str[i:i+5] for i in range(0, len(binary_str), 5)] +def remove_parity_bit(chunks): + return [chunk[1:] for chunk in chunks if len(chunk) == 5] +def convert_to_decimal(chunks): + return [int(chunk, 2) for chunk in chunks] +def process_block(block): + binary_str = hex_to_bin(block) + binary_str = remove_last_two_bits(binary_str) + chunks = split_into_5bit_chunks(binary_str) + no_parity_chunks = remove_parity_bit(chunks) + decimal_values = convert_to_decimal(no_parity_chunks) + return decimal_values +def calculate_id(blocks): + all_decimal_values = [] + for block in blocks: + decimal_values = process_block(block) + all_decimal_values.extend(decimal_values) + if len(all_decimal_values) < 15: + raise ValueError("Error: Not enough data after processing blocks 4, 5, 6, and 7.") + id_positions = [9, 11, 13, 15, 2, 4, 6, 8] + id_numbers = [all_decimal_values[pos-1] for pos in id_positions] + decimal_id = int(''.join(map(str, id_numbers))) + padded_hex_id = format(decimal_id, 'X').upper().zfill(10) + return decimal_id, padded_hex_id +def input_block_data(block_number): + while True: + block_data = input("Enter data for block {} (4 bytes in hex): ".format(block_number)).strip() + if len(block_data) != 8 or not all(c in '0123456789abcdefABCDEF' for c in block_data): + print("Error: Data must be 4 bytes (8 characters) in hex. Try again.") + else: + return block_data +block_4 = input_block_data(4) +block_5 = input_block_data(5) +block_6 = input_block_data(6) +block_7 = input_block_data(7) +blocks = [ + block_4, + block_5, + block_6, + block_7, +] +try: + decimal_id, padded_hex_id = calculate_id(blocks) + print('Calculated data from blocks 4, 5, 6, 7:') + print('Switch2 ID - decimal: {}'.format(decimal_id)) + print('Switch2 ID - hex: {}'.format(padded_hex_id)) + print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) +except ValueError as e: + print(e) \ No newline at end of file From e8c1ecf1baf3c97707fbccbd46dd0b247f669af7 Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:50:44 +0000 Subject: [PATCH 05/11] Update PAXTON_NET.py Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/pyscripts/PAXTON_NET.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/client/pyscripts/PAXTON_NET.py b/client/pyscripts/PAXTON_NET.py index 8b2920b77..912cedea0 100644 --- a/client/pyscripts/PAXTON_NET.py +++ b/client/pyscripts/PAXTON_NET.py @@ -1,5 +1,21 @@ -# Author - Jareckib -# Based on Equipter's tutorial - Downgrade Paxton Net to EM410x +# paxton_net.py - Convert Paxton Net2 to EM4102 +# Author jareckib +# Based on Equipter's tutorial - Downgrade Paxton Net to EM410x +# +# This code is copyright (c) jareckib, 2025, All rights reserved. +# For non-commercial use only, the following terms apply - for all other +# uses, please contact the author. +# +# This code 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 code 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. + import sys def hex_to_bin(hex_string): return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) @@ -66,4 +82,4 @@ try: print('Result (Net2 ID - hex): {}'.format(padded_hex_id)) print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) except ValueError as e: - print(e) \ No newline at end of file + print(e) From f1354e57154f9e447962c0fa88bc22099670aeb0 Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:52:12 +0000 Subject: [PATCH 06/11] Update Paxton_convert.py Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/pyscripts/Paxton_convert.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/client/pyscripts/Paxton_convert.py b/client/pyscripts/Paxton_convert.py index 37633b8ff..d4a1331c5 100644 --- a/client/pyscripts/Paxton_convert.py +++ b/client/pyscripts/Paxton_convert.py @@ -1,5 +1,21 @@ -# Author - jareckib -# Based on Equipter's tutorial - Downgrade Paxton Switch2 to EM410x +# paxton_convert.py - Convert Paxton Net2 and Switch2 to EM4102 +# Author jareckib +# Based on Equipter's tutorial - Downgrade Paxton Net to EM410x +# +# This code is copyright (c) jareckib, 2025, All rights reserved. +# For non-commercial use only, the following terms apply - for all other +# uses, please contact the author. +# +# This code 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 code 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. + import sys def hex_to_bin(hex_string): return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) @@ -86,4 +102,4 @@ else: print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) except ValueError as e: print(e) -print('If EM4102 does not work, this option is probably disabled. Sorry for the inconvenience.') \ No newline at end of file +print('If EM4102 does not work, this option is probably disabled. Sorry for the inconvenience.') From f78ab02498e22b520fe54d73cf12aa63db30d5bb Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:53:08 +0000 Subject: [PATCH 07/11] Update Paxton_switch.py Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/pyscripts/Paxton_switch.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/client/pyscripts/Paxton_switch.py b/client/pyscripts/Paxton_switch.py index df9ea2955..275e3c33f 100644 --- a/client/pyscripts/Paxton_switch.py +++ b/client/pyscripts/Paxton_switch.py @@ -1,5 +1,20 @@ -# Author - jareckib -# Based on Equipter's tutorial - Downgrade Paxton Switch2 to EM410x +# paxton_switch.py - Convert Paxton Switch2 to EM4102 +# Author jareckib +# Based on Equipter's tutorial - Downgrade Paxton Net to EM410x +# +# This code is copyright (c) jareckib, 2025, All rights reserved. +# For non-commercial use only, the following terms apply - for all other +# uses, please contact the author. +# +# This code 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 code 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. import sys def hex_to_bin(hex_string): return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) @@ -54,4 +69,4 @@ try: print('Switch2 ID - hex: {}'.format(padded_hex_id)) print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) except ValueError as e: - print(e) \ No newline at end of file + print(e) From aeb75ab43ed9f3f83222f6557707c22276203688 Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:59:09 +0000 Subject: [PATCH 08/11] Update PAXTON_NET.py Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/pyscripts/PAXTON_NET.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/pyscripts/PAXTON_NET.py b/client/pyscripts/PAXTON_NET.py index 912cedea0..532ca276e 100644 --- a/client/pyscripts/PAXTON_NET.py +++ b/client/pyscripts/PAXTON_NET.py @@ -9,7 +9,7 @@ # This code 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. +# any later version. # # This code is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of From b8fb8d763ce291f5a48e5301557780f72c568536 Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:59:46 +0000 Subject: [PATCH 09/11] Update Paxton_convert.py Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/pyscripts/Paxton_convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/pyscripts/Paxton_convert.py b/client/pyscripts/Paxton_convert.py index d4a1331c5..9c00d5326 100644 --- a/client/pyscripts/Paxton_convert.py +++ b/client/pyscripts/Paxton_convert.py @@ -9,7 +9,7 @@ # This code 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. +# any later version. # # This code is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of From 8d461f6542eff0a0bf676af0ede6875f6ffac507 Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:00:19 +0000 Subject: [PATCH 10/11] Update Paxton_switch.py Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/pyscripts/Paxton_switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/pyscripts/Paxton_switch.py b/client/pyscripts/Paxton_switch.py index 275e3c33f..b65b1d39b 100644 --- a/client/pyscripts/Paxton_switch.py +++ b/client/pyscripts/Paxton_switch.py @@ -9,7 +9,7 @@ # This code 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. +# any later version. # # This code is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of From 24265b0ea711bad28719c7eb55d202a17cc2888d Mon Sep 17 00:00:00 2001 From: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:14:24 +0000 Subject: [PATCH 11/11] Update Paxton_clone.lua Signed-off-by: Jarek Barwinski <116510448+jareckib@users.noreply.github.com> --- client/luascripts/Paxton_clone.lua | 58 +++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/client/luascripts/Paxton_clone.lua b/client/luascripts/Paxton_clone.lua index 7c62671fc..dcf99e5bf 100644 --- a/client/luascripts/Paxton_clone.lua +++ b/client/luascripts/Paxton_clone.lua @@ -1,8 +1,4 @@ --------------------------------------------------------------------- --- Author - jareckib - 30.01.2025 --- Based on Equipter's tutorial - Downgrade Paxton to EM4102 --- version v 1.17 ---------------------------------------------------------------------- + local getopt = require('getopt') local utils = require('utils') local ac = require('ansicolors') @@ -14,6 +10,50 @@ local log_file_path = dir .. "Paxton_log.txt" local nam = "" local command = core.console +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 @@ -257,12 +297,14 @@ local function is_valid_hex(input) return #input == 8 and input:match("^[0-9A-Fa-f]+$") end -local function main() +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() print(dash) - local input_option 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') @@ -424,4 +466,4 @@ local function main() end end -main() \ No newline at end of file +main(args)