mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 11:18:11 -07:00
Merge pull request #3292 from YoungJules/legic_improvements
Some improvements to a couple lua scripts for Legic.
This commit is contained in:
@@ -102,6 +102,7 @@ Known issues; needs to be fixed:
|
||||
local utils = require('utils')
|
||||
local getopt = require('getopt')
|
||||
local ansicolors = require('ansicolors')
|
||||
local json = require('dkjson')
|
||||
|
||||
---
|
||||
-- global variables / defines
|
||||
@@ -194,7 +195,7 @@ it's kinda interactive with following commands in three categories:
|
||||
without the need of changing anything - MCD,MSN,MCC will be read from the tag
|
||||
before and applied to the output.
|
||||
|
||||
lf: 'load file' - load a (xored) binary file (*.bin) from the local Filesystem into the 'virtual inTag'
|
||||
lf: 'load file' - load a (xored) binary file (*.bin) or Proxmark JSON dump (*.json) into the 'virtual inTag'
|
||||
sf: 'save file' - saves the 'virtual inTag' to the local Filesystem as eml and bin (xored with Tag-MCC)
|
||||
xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with chosen MCC - use '00' for plain values)
|
||||
|
||||
@@ -351,6 +352,11 @@ end
|
||||
function file_check(file_name)
|
||||
if not file_name then return false, "" end
|
||||
|
||||
local home = os.getenv("HOME") or os.getenv("USERPROFILE")
|
||||
if home and (file_name == "~" or file_name:sub(1, 2) == "~/" or file_name:sub(1, 2) == "~\\") then
|
||||
file_name = home .. file_name:sub(2)
|
||||
end
|
||||
|
||||
local arr = split(file_name, ".")
|
||||
local ext = table.remove(arr)
|
||||
local name = join(arr, '.')
|
||||
@@ -395,20 +401,72 @@ function getInputBytes(infile)
|
||||
local line
|
||||
local bytes = {}
|
||||
|
||||
local home = os.getenv("HOME") or os.getenv("USERPROFILE")
|
||||
if home and (infile == "~" or infile:sub(1, 2) == "~/" or infile:sub(1, 2) == "~\\") then
|
||||
infile = home .. infile:sub(2)
|
||||
end
|
||||
|
||||
local arr = split(infile, ".")
|
||||
local ext = table.remove(arr)
|
||||
local name = join(arr, '.')
|
||||
local path = core.search_file(name, "."..ext)
|
||||
if (path == nil) then oops("failed to read from file ".. infile); return false; end
|
||||
|
||||
local fhi,err = io.open(path,"rb")
|
||||
if err then oops("failed to read from file ".. path); return false; end
|
||||
if ext:lower() == "json" then
|
||||
local fhi, err = io.open(path, "r")
|
||||
if err then oops("failed to read from file ".. path); return false; end
|
||||
|
||||
file_data = fhi:read("*a");
|
||||
for i = 1, #file_data do
|
||||
bytes[i] = string.format("%x",file_data:byte(i))
|
||||
local file_data = fhi:read("*a")
|
||||
fhi:close()
|
||||
|
||||
local obj, pos, jerr = json.decode(file_data, 1, nil)
|
||||
if jerr then
|
||||
oops("failed to parse json dump ".. path ..": ".. jerr)
|
||||
return false
|
||||
end
|
||||
|
||||
if type(obj) ~= "table" or type(obj.blocks) ~= "table" then
|
||||
oops("json dump does not contain a blocks table: ".. path)
|
||||
return false
|
||||
end
|
||||
|
||||
local keys = {}
|
||||
for k in pairs(obj.blocks) do
|
||||
local n = tonumber(k)
|
||||
if n ~= nil then
|
||||
keys[#keys + 1] = n
|
||||
end
|
||||
end
|
||||
table.sort(keys)
|
||||
|
||||
for _, key in ipairs(keys) do
|
||||
local block = obj.blocks[tostring(key)] or obj.blocks[key]
|
||||
if type(block) ~= "string" then
|
||||
oops("block ".. key .." is missing or invalid in json dump ".. path)
|
||||
return false
|
||||
end
|
||||
|
||||
block = block:gsub("%s", "")
|
||||
if (#block % 2) ~= 0 then
|
||||
oops("block ".. key .." has an odd number of hex digits in json dump ".. path)
|
||||
return false
|
||||
end
|
||||
|
||||
for c in block:gmatch("..") do
|
||||
bytes[#bytes + 1] = c:lower()
|
||||
end
|
||||
end
|
||||
else
|
||||
local fhi,err = io.open(path,"rb")
|
||||
if err then oops("failed to read from file ".. path); return false; end
|
||||
|
||||
file_data = fhi:read("*a");
|
||||
for i = 1, #file_data do
|
||||
bytes[i] = string.format("%x",file_data:byte(i))
|
||||
end
|
||||
fhi:close()
|
||||
end
|
||||
fhi:close()
|
||||
|
||||
if (bytes[7]=='00') then return false end
|
||||
print(#bytes .. " bytes from "..path.." loaded")
|
||||
return bytes
|
||||
@@ -668,7 +726,7 @@ local function readFile(filename)
|
||||
end
|
||||
|
||||
bytes = getInputBytes(path)
|
||||
if bytes == false then return oops('couldnt get input bytes') end
|
||||
if bytes == false then return oops('could not get input bytes') end
|
||||
|
||||
-- make plain bytes
|
||||
bytes = xorBytes(bytes,bytes[5])
|
||||
@@ -2835,6 +2893,7 @@ function main(args)
|
||||
-- set init colors/switch (can be toggled with 'tac' => 'toggle ansicolors')
|
||||
load_colors(colored_output)
|
||||
if (#args == 0 ) then modifyMode() end
|
||||
if args and args:match('^%-%-help%s*$') then return help() end
|
||||
--- variables
|
||||
local inTAG, backupTAG, outTAG, outfile, interactive, crc
|
||||
local ofs=false
|
||||
|
||||
@@ -2,23 +2,23 @@ local utils = require('utils')
|
||||
local cmds = require('commands')
|
||||
local getopt = require('getopt')
|
||||
local ansicolors = require('ansicolors')
|
||||
local json = require('dkjson')
|
||||
|
||||
--[[
|
||||
script to create a clone-dump with new crc
|
||||
script to rewrite a LEGIC Prime dump for a different target tag
|
||||
Author: mosci
|
||||
my Fork: https://github.com/icsom/proxmark3.git
|
||||
|
||||
1. read tag-dump, xor byte 22..end with byte 0x05 of the inputfile
|
||||
2. write to outfile
|
||||
3. set byte 0x05 to newcrc
|
||||
4. until byte 0x21 plain like in inputfile
|
||||
5. from 0x22..end xored with newcrc
|
||||
6. calculate new crc on each segment (needs to know the new MCD & MSN0..2)
|
||||
1. read tag-dump and load the raw bytes
|
||||
2. if the dump contains parseable segments, recalculate the dependent CRCs
|
||||
3. update the target card identity bytes from the new tag
|
||||
4. xor the writable image with the target MCC
|
||||
5. write the resulting dump to disk and/or restore it to the target tag
|
||||
|
||||
simplest usage:
|
||||
Dump a legic tag with 'hf legic dump'
|
||||
place your 'empty' tag on the reader and run
|
||||
'script run hf_legic_clone -i orig.bin -w'
|
||||
simplest usage:
|
||||
Dump a legic tag with 'hf legic dump'
|
||||
place the target tag on the reader and run
|
||||
'script run hf_legic_clone -i orig.bin -w'
|
||||
|
||||
you will see some output like:
|
||||
|
||||
@@ -52,8 +52,8 @@ local ansicolors = require('ansicolors')
|
||||
#db# write successful
|
||||
proxmark3>
|
||||
|
||||
the default value (number of bytes to write) is calculated over all valid segments and should be ok - just hit enter, wait until write has finished
|
||||
and your clone should be ready (except there has to be a additional KGH-CRC to be calculated - which credentials are unknown until yet)
|
||||
when the dump has parseable segments, the script recalculates the dependent CRCs
|
||||
for raw/blank images, it simply restores the image content to the target tag
|
||||
|
||||
the '-w' switch will only work with my fork - it needs the binary legic_crc8 which is not part of the proxmark3-master-branch
|
||||
also the ability to write DCF is not possible with the proxmark3-master-branch
|
||||
@@ -91,19 +91,21 @@ copyright = ''
|
||||
author = 'Mosci'
|
||||
version = 'v1.0.2'
|
||||
desc = [[
|
||||
This is a script which creates a clone-dump of a dump from a LEGIC Prime Tag (MIM256 or MIM1024)
|
||||
Create a dump by running `hf legic dump`.
|
||||
This script rewrites a LEGIC Prime dump so it can be written to a different tag (MIM256 or MIM1024).
|
||||
It handles both segmented dumps and raw/blank LEGIC images.
|
||||
Create the source dump by running `hf legic dump`.
|
||||
]]
|
||||
example = [[
|
||||
script run hf_legic_clone -i my_dump.bin -o my_clone.bin -c f8
|
||||
script run hf_legic_clone -i my_dump.json -d -s
|
||||
script run hf_legic_clone -i my_dump.bin -d -s
|
||||
]]
|
||||
usage = [[
|
||||
script run hf_legic_clone [-h] [-i <file>] [-o <file>] [-c <crc>] [-d] [-s] [-w]
|
||||
]]
|
||||
arguments = [[
|
||||
required :
|
||||
-i <input file> - file to read data from, must be in binary format (*.bin)
|
||||
required :
|
||||
-i <input file> - file to read data from, binary (*.bin) or Proxmark JSON (*.json)
|
||||
|
||||
optional :
|
||||
-h - Help text
|
||||
@@ -169,13 +171,25 @@ local function readlegicinfo()
|
||||
end
|
||||
|
||||
-- Check availability of file
|
||||
local function expand_user_path(path)
|
||||
if path == nil then return nil end
|
||||
|
||||
local home = os.getenv("HOME") or os.getenv("USERPROFILE")
|
||||
if home and (path == "~" or path:sub(1, 2) == "~/" or path:sub(1, 2) == "~\\") then
|
||||
return home .. path:sub(2)
|
||||
end
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
local function file_check(file_name)
|
||||
local exists = io.open(file_name, "r")
|
||||
if not exists then
|
||||
exists = false
|
||||
file_name = expand_user_path(file_name)
|
||||
local exists = io.open(file_name, "r")
|
||||
if not exists then
|
||||
exists = false
|
||||
else
|
||||
exists = true
|
||||
end
|
||||
end
|
||||
return exists
|
||||
end
|
||||
|
||||
@@ -192,6 +206,56 @@ end
|
||||
-- read input-file into array
|
||||
local function getInputBytes(infile)
|
||||
local bytes = {}
|
||||
infile = expand_user_path(infile)
|
||||
local lower = infile:lower()
|
||||
|
||||
if lower:sub(-5) == ".json" then
|
||||
local f = io.open(infile, "r")
|
||||
if f == nil then print("OOps ... failed to read from file ".. infile); return false; end
|
||||
|
||||
local str = f:read("*all")
|
||||
f:close()
|
||||
|
||||
local obj, pos, err = json.decode(str, 1, nil)
|
||||
if err then
|
||||
print("OOps ... failed to parse json dump ".. infile ..": ".. err)
|
||||
return false
|
||||
end
|
||||
|
||||
if type(obj) ~= "table" or type(obj.blocks) ~= "table" then
|
||||
print("OOps ... json dump does not contain a blocks table: ".. infile)
|
||||
return false
|
||||
end
|
||||
|
||||
local keys = {}
|
||||
for k in pairs(obj.blocks) do
|
||||
local n = tonumber(k)
|
||||
if n ~= nil then
|
||||
keys[#keys + 1] = n
|
||||
end
|
||||
end
|
||||
table.sort(keys)
|
||||
|
||||
for _, key in ipairs(keys) do
|
||||
local block = obj.blocks[tostring(key)] or obj.blocks[key]
|
||||
if type(block) ~= "string" then
|
||||
print("OOps ... block ".. key .." is missing or invalid in json dump ".. infile)
|
||||
return false
|
||||
end
|
||||
block = block:gsub("%s", "")
|
||||
if (#block % 2) ~= 0 then
|
||||
print("OOps ... block ".. key .." has an odd number of hex digits in json dump ".. infile)
|
||||
return false
|
||||
end
|
||||
for c in block:gmatch("..") do
|
||||
bytes[#bytes + 1] = c:lower()
|
||||
end
|
||||
end
|
||||
|
||||
print("\nread ".. #bytes .." bytes from "..ansicolors.yellow..infile..ansicolors.reset)
|
||||
return bytes
|
||||
end
|
||||
|
||||
local f = io.open(infile, "rb")
|
||||
if f == nil then print("OOps ... failed to read from file ".. infile); return false; end
|
||||
|
||||
@@ -251,10 +315,10 @@ local function getSegmentData(bytes, start, index)
|
||||
segment[3] = str_bit_extract(bytes[start + 1], 7, 1)
|
||||
|
||||
-- len = (byte 0)+(bit0-3 of byte 1)
|
||||
segment[4] = str_bit_extract(bytes[start + 1], 0, 3) << 8 + tonumber(bytes[start], 16)
|
||||
segment[4] = (str_bit_extract(bytes[start + 1], 0, 3) << 8) + tonumber(bytes[start], 16)
|
||||
|
||||
-- wrp (write proteted) = byte 2
|
||||
segment[5] = tonumber(bytes[start + 2])
|
||||
segment[5] = tonumber(bytes[start + 2], 16)
|
||||
|
||||
-- wrc (write control) - bit 4-6 of byte 3
|
||||
segment[6] = str_bit_extract(bytes[start + 3], 4, 3)
|
||||
@@ -273,6 +337,19 @@ local function getSegmentData(bytes, start, index)
|
||||
return segment
|
||||
end
|
||||
|
||||
local function segmentLooksValid(bytes, segment, start)
|
||||
if segment == nil or segment[4] == nil or segment[4] < 5 then
|
||||
return false
|
||||
end
|
||||
|
||||
return (start + segment[4] - 1) <= #bytes
|
||||
end
|
||||
|
||||
local function hasParseableSegments(bytes)
|
||||
local seg = getSegmentData(bytes, 23, 0)
|
||||
return segmentLooksValid(bytes, seg, 23)
|
||||
end
|
||||
|
||||
--- Kaba Group Header
|
||||
-- checks if a segment does have a kghCRC
|
||||
-- returns boolean false if no kgh has being detected or the kghCRC if a kgh was detected
|
||||
@@ -306,6 +383,11 @@ local function getSegmentCrcBytes(bytes)
|
||||
local start = 23
|
||||
local index = 0
|
||||
local crcbytes = {}
|
||||
|
||||
if not hasParseableSegments(bytes) then
|
||||
return nil
|
||||
end
|
||||
|
||||
repeat
|
||||
seg = getSegmentData(bytes, start, index)
|
||||
crcbytes[index] = seg[10]
|
||||
@@ -332,6 +414,19 @@ end
|
||||
-- print segment-data (hf legic info like)
|
||||
local function displaySegments(bytes)
|
||||
|
||||
local function appendByte(out, idx, label)
|
||||
local b = bytes[idx]
|
||||
if b == nil then
|
||||
return nil, oops(label.." is out of range at byte "..idx.." in input dump")
|
||||
end
|
||||
return out .. b .. ' '
|
||||
end
|
||||
|
||||
if not hasParseableSegments(bytes) then
|
||||
print("No parseable LEGIC Prime segments found; treating dump as raw data.")
|
||||
return
|
||||
end
|
||||
|
||||
--display segment header(s)
|
||||
start = 23
|
||||
index = 0
|
||||
@@ -344,8 +439,11 @@ local function displaySegments(bytes)
|
||||
Seg = getSegmentData(bytes, start, index)
|
||||
if Seg == nil then return oops("segment is nil") end
|
||||
|
||||
KGH = CheckKgh(bytes, start, (start + Seg[4]))
|
||||
if not segmentLooksValid(bytes, Seg, start) then
|
||||
return oops("invalid segment length at segment "..Seg[9].." in input dump")
|
||||
end
|
||||
|
||||
KGH = CheckKgh(bytes, start, (start + Seg[4]))
|
||||
printSegment(Seg)
|
||||
|
||||
-- wrc
|
||||
@@ -354,7 +452,9 @@ local function displaySegments(bytes)
|
||||
-- length of wrc = wrc
|
||||
for i = 1, Seg[6] do
|
||||
-- starts at (segment-start + segment-header + segment-crc)-1
|
||||
wrc = wrc..bytes[(start + 4 + 1 + i) - 1]..' '
|
||||
local updated, err = appendByte(wrc, (start + 4 + 1 + i) - 1, "WRC protected area")
|
||||
if not updated then return err end
|
||||
wrc = updated
|
||||
end
|
||||
print(wrc)
|
||||
elseif (Seg[5] > 0) then
|
||||
@@ -362,7 +462,9 @@ local function displaySegments(bytes)
|
||||
-- length of wrp = (wrp-wrc)
|
||||
for i = 1, (Seg[5] - Seg[6]) do
|
||||
-- starts at (segment-start + segment-header + segment-crc + wrc)-1
|
||||
wrp = wrp..bytes[(start + 4 + 1 + Seg[6] + i) - 1]..' '
|
||||
local updated, err = appendByte(wrp, (start + 4 + 1 + Seg[6] + i) - 1, "write protected area")
|
||||
if not updated then return err end
|
||||
wrp = updated
|
||||
end
|
||||
print(wrp)
|
||||
end
|
||||
@@ -372,7 +474,9 @@ local function displaySegments(bytes)
|
||||
--length of payload = segment-len - segment-header - segment-crc - wrp -wrc
|
||||
for i = 1, (Seg[4] - 4 - 1 - Seg[5] - Seg[6]) do
|
||||
-- starts at (segment-start + segment-header + segment-crc + segment-wrp + segemnt-wrc)-1
|
||||
pld = pld..bytes[(start + 4 + 1 + Seg[5] + Seg[6] + i) - 1]..' '
|
||||
local updated, err = appendByte(pld, (start + 4 + 1 + Seg[5] + Seg[6] + i) - 1, "segment payload")
|
||||
if not updated then return err end
|
||||
pld = updated
|
||||
end
|
||||
print(pld)
|
||||
if (KGH) then
|
||||
@@ -414,17 +518,21 @@ local function writeToTag(plainBytes)
|
||||
|
||||
-- calculate new Segment-CRC for each valid segment
|
||||
SegCrcs = getSegmentCrcBytes(plainBytes)
|
||||
for i = 0, (#SegCrcs - 1) do
|
||||
-- SegCrcs[i]-4 = address of first byte of segmentHeader (low byte segment-length)
|
||||
segLen = tonumber(("%1x"):format(bit32.extract("0x"..plainBytes[(SegCrcs[i] - 3)], 0, 3))..("%02x"):format(tonumber(plainBytes[SegCrcs[i] - 4], 16)), 16)
|
||||
segStart = (SegCrcs[i] - 4)
|
||||
segEnd = (SegCrcs[i] - 4 + segLen)
|
||||
KGH = CheckKgh(plainBytes, segStart, segEnd)
|
||||
if (KGH) then
|
||||
print("'Kaba Group Header' detected - re-calculate...")
|
||||
if SegCrcs ~= nil then
|
||||
for i = 0, (#SegCrcs - 1) do
|
||||
-- SegCrcs[i]-4 = address of first byte of segmentHeader (low byte segment-length)
|
||||
segLen = tonumber(("%1x"):format(bit32.extract("0x"..plainBytes[(SegCrcs[i] - 3)], 0, 3))..("%02x"):format(tonumber(plainBytes[SegCrcs[i] - 4], 16)), 16)
|
||||
segStart = (SegCrcs[i] - 4)
|
||||
segEnd = (SegCrcs[i] - 4 + segLen)
|
||||
KGH = CheckKgh(plainBytes, segStart, segEnd)
|
||||
if (KGH) then
|
||||
print("'Kaba Group Header' detected - re-calculate...")
|
||||
end
|
||||
cmd = MCD..MSN0..MSN1..MSN2..plainBytes[SegCrcs[i]-4]..plainBytes[SegCrcs[i]-3]..plainBytes[SegCrcs[i]-2]..plainBytes[SegCrcs[i]-1]
|
||||
plainBytes[SegCrcs[i]] = ("%02x"):format(utils.Crc8Legic(cmd))
|
||||
end
|
||||
cmd = MCD..MSN0..MSN1..MSN2..plainBytes[SegCrcs[i]-4]..plainBytes[SegCrcs[i]-3]..plainBytes[SegCrcs[i]-2]..plainBytes[SegCrcs[i]-1]
|
||||
plainBytes[SegCrcs[i]] = ("%02x"):format(utils.Crc8Legic(cmd))
|
||||
else
|
||||
print("No parseable segments found; restoring raw LEGIC image.")
|
||||
end
|
||||
|
||||
-- apply MCD & MSN to plain data
|
||||
@@ -453,6 +561,8 @@ local function main(args)
|
||||
local bytes = {}
|
||||
local segments = {}
|
||||
|
||||
if args and args:match('^%-%-help%s*$') then return help() end
|
||||
|
||||
-- parse arguments for the script
|
||||
for o, a in getopt.getopt(args, 'hwsdc:i:o:') do
|
||||
-- output file
|
||||
@@ -466,13 +576,13 @@ local function main(args)
|
||||
end
|
||||
-- input file
|
||||
if o == 'i' then
|
||||
infile = a
|
||||
infile = expand_user_path(a)
|
||||
if (file_check(infile) == false) then return oops('input file: '..infile..' not found') end
|
||||
|
||||
bytes = getInputBytes(infile)
|
||||
if (bytes == false) then return oops('could not read file') end
|
||||
oldcrc = bytes[5]
|
||||
ifs = true
|
||||
if (bytes == false) then return oops('couldnt read file') end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
@@ -514,7 +624,7 @@ local function main(args)
|
||||
res = res .."\ncreated clone_dump from\n\t"..infile.." crc: "..oldcrc.."\ndump_file:"
|
||||
res = res .."\n\t"..outfile.." crc: "..string.sub(newcrc, -2)
|
||||
res = res .."\nyou may load the new file with:"
|
||||
res = res ..ansicolors.yellow.."hf legic eload -f "..outfile..ansicolors.reset
|
||||
res = res ..ansicolors.yellow.."hf legic restore -f "..outfile..ansicolors.reset
|
||||
res = res .."\n\nif you don't write to tag immediately ('-w' switch) you will need to recalculate each segmentCRC"
|
||||
res = res .."\nafter writing this dump to a tag!"
|
||||
res = res .."\n\na segmentCRC gets calculated over MCD,MSN0..3, Segment-Header0..3"
|
||||
@@ -527,10 +637,14 @@ local function main(args)
|
||||
end
|
||||
else
|
||||
if (ss) then
|
||||
-- show why the output-file was not written
|
||||
print("\nnew file not written - some arguments are missing ..")
|
||||
print("output file: ".. (ofs and outfile or "not given"))
|
||||
print("new crc: ".. (ncs and newcrc or "not given"))
|
||||
if (ofs or ncs) then
|
||||
-- show why the output-file was not written
|
||||
print("\nnew file not written - some arguments are missing ..")
|
||||
print("output file: ".. (ofs and outfile or "not given"))
|
||||
print("new crc: ".. (ncs and newcrc or "not given"))
|
||||
else
|
||||
print("\ndisplay-only mode - no output file or target CRC requested")
|
||||
end
|
||||
end
|
||||
end
|
||||
-- write to tag
|
||||
|
||||
Reference in New Issue
Block a user