Files

272 lines
9.4 KiB
Lua
Raw Permalink Normal View History

2025-03-13 08:01:31 +00:00
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 command = core.console
2025-03-21 15:13:02 +00:00
local pm3 = require('pm3')
p = pm3.pm3()
2025-03-13 08:01:31 +00:00
command('clear')
2025-03-21 15:13:02 +00:00
author = ' jareckib - 12.03.2025'
version = ' v1.06'
mod = ' 20.03.2025'
2025-03-18 08:11:06 +01:00
desc = [[
2025-03-21 15:13:02 +00:00
2025-03-13 08:01:31 +00:00
This simple script stores 1, 2 or 3 different EM4102 on a single T5577.
There is an option to enter the number engraved on the fob in decimal form.
2025-03-18 08:11:06 +01:00
The script can therefore be useful if the original EM4102 doesn't work but
has an engraved ID number. By entering such an ID as a single EM4102, we
2025-03-13 08:01:31 +00:00
can create a working copy of our damaged fob.
A tag T5577 created in this way works with the following USB readers:
2025-03-18 08:11:06 +01:00
2025-03-13 08:01:31 +00:00
- ACM08Y
- ACM26C
- Sycreader R60D
- Elatech Multitech TWN4
]]
usage = [[
script run lf_t55xx_multiwriter
]]
arguments = [[
script run lf_t55xx_multiwriter -h : this help
]]
local function help()
print()
2025-03-21 15:13:02 +00:00
print(ac.yellow..' Author:'..ac.reset..author)
print(ac.yellow..' Version:'..ac.reset..version)
2025-03-23 23:22:23 +01:00
print(ac.yellow..' Modification date:'..ac.reset..mod)
2025-03-13 08:01:31 +00:00
print(desc)
print(ac.cyan .. ' Usage' .. ac.reset)
print(usage)
print(ac.cyan .. ' Arguments' .. ac.reset)
print(arguments)
end
2025-03-21 15:13:02 +00:00
local function sleep(n)
os.execute("sleep " ..tonumber(n))
end
function wait(msec)
local t = os.clock()
repeat
until os.clock() > t + msec * 1e-3
end
local function timer(n)
while n > 0 do
io.write(ac.cyan.."::::: "..ac.yellow.. tonumber(n) ..ac.yellow.." sec "..ac.cyan..":::::\r"..ac.reset)
sleep(1)
io.flush()
n = n-1
end
end
2025-03-13 08:01:31 +00:00
local function reset_log_file()
local file = io.open(logfile, "w+")
file:write("")
file:close()
end
local function read_log_file(logfile)
local file = io.open(logfile, "r")
if not file then
return nil
end
local content = file:read("*all")
file:close()
return content
end
local function extract_uid(log_content)
for line in log_content:gmatch("[^\r\n]+") do
local uid = line:match("%[%s*%+%]%s*EM%s*410x%s*ID%s*([A-F0-9]+)")
if uid then
return uid
end
end
return nil
end
local function hex_to_bin(hex_value)
if not hex_value:match("^[A-Fa-f0-9]+$") or #hex_value ~= 10 then
error("Invalid UID format. Must be a valid 5-byte HEX value.")
end
local decimal_value = tonumber(hex_value, 16)
if not decimal_value then
error("Error: Invalid HEX conversion.")
end
local binary = ""
for i = 39, 0, -1 do
binary = binary .. ((decimal_value & (1 << i)) ~= 0 and "1" or "0")
end
if #binary ~= 40 then
error("Unexpected UID length after conversion to binary.")
end
return binary
end
local function even_parity(bits)
return (bits:gsub("0", ""):len() % 2 == 0) and "0" or "1"
end
local function encode_uid(uid)
local uid_bin = hex_to_bin(uid)
local start_bits = '1' .. string.rep('1', 8)
local data_with_parity = ''
for i = 1, 40, 4 do
local nibble = uid_bin:sub(i, i + 3)
local parity_bit = even_parity(nibble)
data_with_parity = data_with_parity .. nibble .. parity_bit
end
local col_parity_bits = ''
for i = 1, 4 do
local col_bits = ''
for j = i, #data_with_parity, 5 do
col_bits = col_bits .. data_with_parity:sub(j, j)
end
col_parity_bits = col_parity_bits .. even_parity(col_bits)
end
local stop_bit = '0'
local full_bin = start_bits .. data_with_parity .. col_parity_bits .. stop_bit
return string.format("%X", tonumber(full_bin, 2))
end
local function get_uid_from_user()
while true do
print(dash)
io.write(ac.cyan .. '(1)' .. ac.reset .. ' Manual entry UID |' .. ac.cyan .. ' (2)' .. ac.reset .. ' Read via Proxmark3 ')
2025-03-18 08:11:06 +01:00
2025-03-13 08:01:31 +00:00
local choice
repeat
choice = io.read()
if choice ~= "1" and choice ~= "2" then
io.write(ac.yellow .. "Invalid choice. Please enter (1) or (2) " .. ac.reset)
end
until choice == "1" or choice == "2"
if choice == "1" then
local format
repeat
io.write("Choose format HEX or DEC (engraved ID) "..ac.cyan.."(h/d) "..ac.reset)
format = io.read():lower()
if format ~= "h" and format ~= "d" then
print(ac.yellow .. "Invalid choice. Choose format HEX or DEC" .. ac.reset)
end
until format == "h" or format == "d"
while true do
io.write("Enter 10-character UID: "..ac.green)
local uid = io.read():upper()
if format == "h" and uid:match("^[A-F0-9]+$") and #uid == 10 then
return uid
elseif format == "d" and uid:match("^%d%d%d%d%d%d%d%d%d%d$") then
return string.format("%010X", tonumber(uid))
else
2025-03-21 15:13:02 +00:00
print(ac.yellow .. "Invalid UID format. Enter exactly 10 characters in selected format." .. ac.reset)
2025-03-13 08:01:31 +00:00
end
end
elseif choice == "2" then
io.write("Place original FOB on coil for reading and press" ..ac.cyan.." Enter..." .. ac.reset)
io.read()
while true do
2025-03-18 08:11:06 +01:00
reset_log_file()
2025-03-21 15:13:02 +00:00
p:console('lf em 410x read')
2025-03-13 08:01:31 +00:00
local log_content = read_log_file(logfile)
local uid = extract_uid(log_content)
if uid and #uid == 10 then
2025-03-23 23:22:23 +01:00
print("Readed EM4102 ID: " ..ac.green.. uid ..ac.reset)
2025-03-23 23:14:18 +01:00
return uid
2025-03-13 08:01:31 +00:00
else
io.write(ac.yellow .. "Error reading UID. Please adjust FOB position and press Enter..." .. ac.reset)
io.read()
end
end
end
end
end
2025-03-21 15:13:02 +00:00
local function verify_written_data(blocks, block0_value, uid_count)
reset_log_file()
p:console('lf t55 detect')
for i = 0, #blocks do
p:console('lf t55 read -b ' .. i)
end
local log_content = read_log_file(logfile)
local verified = true
local pattern_block0 = "%[%s*%+%]%s*00%s*|%s*([A-F0-9]+)"
local found_block0 = log_content:match(pattern_block0)
if not found_block0 or found_block0:upper() ~= block0_value:upper() then
print("Error in block 0 ...expected: " .. ac.green .. block0_value .. ac.reset .. " ...found: " .. ac.green .. (found_block0 or "N/A") .. ac.reset)
verified = false
end
for i = 1, #blocks do
local expected_block = blocks[i]
local pattern = "%[%s*%+%]%s*" .. string.format("%02X", i) .. " |%s*([A-F0-9]+)"
local found_block = log_content:match(pattern)
if not found_block or found_block:upper() ~= expected_block:upper() then
print("Error in block " .. i .. " ...expected: " .. ac.green .. expected_block .. ac.reset .. " ...found: " .. ac.green .. (found_block or "N/A") .. ac.reset)
verified = false
end
end
return verified
end
local function write(blocks, block0_value)
p:console('lf t55xx write -b 0 -d ' .. block0_value)
for i = 1, #blocks do
p:console('lf t55xx write -b ' .. i .. ' -d ' .. blocks[i])
end
end
2025-03-13 08:01:31 +00:00
local function main(args)
for o, a in getopt.getopt(args, 'h') do
2025-03-23 23:14:18 +01:00
if o == 'h' then
return help()
2025-03-21 15:13:02 +00:00
end
2025-03-18 08:11:06 +01:00
end
2025-03-13 08:01:31 +00:00
local blocks = {}
local uid_count = 0
for i = 1, 3 do
local uid = get_uid_from_user()
local encoded_hex = encode_uid(uid)
blocks[#blocks + 1] = encoded_hex:sub(1, 8)
blocks[#blocks + 1] = encoded_hex:sub(9, 16)
uid_count = uid_count + 1
if i < 3 then
local next_choice
repeat
io.write(ac.reset.."Do you want to add another UID? "..ac.cyan.."(y/n) "..ac.reset)
next_choice = io.read():lower()
if next_choice ~= "y" and next_choice ~= "n" then
print(ac.yellow .. "Invalid choice. Please enter (y) for yes or (n) for no." .. ac.reset)
end
until next_choice == "y" or next_choice == "n"
if next_choice == "y" then
print(dash)
print(ac.yellow .. (i == 1 and "::: Second UID :::" or "::: Third UID :::") .. ac.reset)
elseif next_choice == "n" then
break
end
end
end
local block0_value = (uid_count == 1) and "00148040" or (uid_count == 2) and "00148080" or "001480C0"
2025-03-21 15:13:02 +00:00
io.write("Place the " .. ac.cyan .. "T5577" .. ac.reset .. " tag on the coil for writing and press " .. ac.cyan .. "Enter..." .. ac.reset)
2025-03-13 08:01:31 +00:00
io.read()
2025-03-21 15:13:02 +00:00
write(blocks, block0_value)
2025-03-23 23:14:18 +01:00
print(dash)
2025-03-21 15:13:02 +00:00
timer(3)
local verified = verify_written_data(blocks, block0_value, uid_count)
while not verified do
print("Verification failed." .. ac.reset .. " Please adjust the " .. ac.cyan .. "T5577" .. ac.reset .. " position and try again.")
io.write("Press " .. ac.cyan .. "Enter" .. ac.reset .. " to retry...")
io.read()
write(blocks, block0_value)
timer(3)
verified = verify_written_data(blocks, block0_value, uid_count)
2025-03-13 08:01:31 +00:00
end
2025-03-21 15:13:02 +00:00
print(ac.green .. "Successfully written " .. ac.reset .. uid_count .. ac.green .. " EM4102 UID(s) to T5577" .. ac.reset)
2025-03-13 08:01:31 +00:00
end
2025-03-18 08:11:06 +01:00
main(args)