-------------------------------------------------------------------- -- Author - jareckib - 25.01.2025 -- Based on Equipter's tutorial - Downgrade Paxton to EM410x --------------------------------------------------------------------- local getopt = require('getopt') local utils = require('utils') local ac = require('ansicolors') local os = require('os') local dash = string.rep('--', 32) 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 log_result(blocks, em410_id) local dir = os.getenv('HOME')..'/.proxmark3/logs/' local log_file_path = dir .. 'Paxton_log.txt' local log_file = io.open(log_file_path, "a") if log_file then 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)) log_file:close() else print("Failed to open log file for writing.") end end local function handle_cloning(decimal_id, padded_hex_id, blocks) local previous_choice = nil while true do if not previous_choice then io.write(' Create Paxton choose '..ac.cyan..'1'..ac.reset..' or EM4102 choose '..ac.cyan..'2'..ac.reset..' >') previous_choice = io.read() end if previous_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) core.console('lf hitag wrbl --ht2 -p 4 -d ' .. blocks[4] .. ' -k BDF5E846') core.console('lf hitag wrbl --ht2 -p 5 -d ' .. blocks[5] .. ' -k BDF5E846') core.console('lf hitag wrbl --ht2 -p 6 -d ' .. blocks[6] .. ' -k BDF5E846') core.console('lf hitag wrbl --ht2 -p 7 -d ' .. blocks[7] .. ' -k BDF5E846') break elseif previous_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() print(dash) core.console('lf em 410x clone --id ' .. padded_hex_id) break else print(ac.yellow..' Invalid choice.'..ac.reset..' Please enter'..ac.cyan..' 1'..ac.reset..' or'..ac.cyan..' 2'..ac.reset) previous_choice = nil end end while true do print(dash) io.write(' Make next RFID Fob'..ac.green..' (y/n)'..ac.reset..' >') local another = io.read() if another:lower() == 'n' then print() print(ac.yellow..' Paxton_log.txt file created in directory: '..ac.green..'logs'..ac.reset) print(dash) print() os.exit(1) elseif another:lower() == 'y' then if previous_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) core.console('lf hitag wrbl --ht2 -p 4 -d ' .. blocks[4] .. ' -k BDF5E846') core.console('lf hitag wrbl --ht2 -p 5 -d ' .. blocks[5] .. ' -k BDF5E846') core.console('lf hitag wrbl --ht2 -p 6 -d ' .. blocks[6] .. ' -k BDF5E846') core.console('lf hitag wrbl --ht2 -p 7 -d ' .. blocks[7] .. ' -k BDF5E846') elseif previous_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() print(dash) core.console('lf em 410x clone --id ' .. padded_hex_id) end else print(ac.yellow..' Invalid response.'..ac.reset..' Please enter'..ac.cyan..' y'..ac.reset..' or'..ac.cyan..' n'..ac.reset) end 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 core.console('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(dash) while true do io.write(' Your choice'..ac.cyan..' (1/2) '..ac.reset..'>') input_option = io.read() if input_option == "1" or input_option == "2" then break else print(ac.yellow .. ' Invalid choice.' .. ac.reset .. ' Please enter ' .. ac.cyan .. '1' .. ac.reset .. ' or ' .. ac.cyan .. '2' .. ac.reset) end end 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() core.console('lf hitag read --ht2 -k BDF5E846') core.console('clear') 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+")) 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) log_result(blocks, padded_hex_id) handle_cloning(decimal_id, padded_hex_id, blocks) 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) log_result(blocks, padded_hex_id) if not padded_hex_id then print(ac.red..' ERROR: '..ac.reset.. 'Invalid block data provided') os.exit(1) end handle_cloning(decimal_id, padded_hex_id, blocks) break end end end main()