mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2024-11-11 01:55:38 +08:00
541 lines
No EOL
17 KiB
Lua
541 lines
No EOL
17 KiB
Lua
--[[
|
|
script to create a clone-dump with new crc
|
|
Author: mosci
|
|
my Fork: https://github.com/icsom/proxmark3.git
|
|
Upstream: https://github.com/Proxmark/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)
|
|
|
|
simplest usage:
|
|
read a valid legic tag with 'hf legic reader'
|
|
save the dump with 'hf legic save orig.hex'
|
|
place your 'empty' tag on the reader and run 'script run Legic_clone -i orig.hex -w'
|
|
you will see some output like:
|
|
read 1024 bytes from legic_dumps/j_0000.hex
|
|
|
|
place your empty tag onto the PM3 to read and display the MCD & MSN0..2
|
|
the values will be shown below
|
|
confirm whnen ready [y/n] ?y
|
|
#db# setting up legic card
|
|
#db# MIM 256 card found, reading card ...
|
|
#db# Card read, use 'hf legic decode' or
|
|
#db# 'data hexsamples 8' to view results
|
|
0b ad c0 de <- !! here you'll see the MCD & MSN of your empty tag, which has to be typed in manually as seen below !!
|
|
type in MCD as 2-digit value - e.g.: 00 (default: 79 )
|
|
> 0b
|
|
type in MSN0 as 2-digit value - e.g.: 01 (default: 28 )
|
|
> ad
|
|
type in MSN1 as 2-digit value - e.g.: 02 (default: d1 )
|
|
> c0
|
|
type in MSN2 as 2-digit value - e.g.: 03 (default: 43 )
|
|
> de
|
|
MCD:0b, MSN:ad c0 de, MCC:79 <- this crc is calculated from the MCD & MSN and must match the one on yout empty tag
|
|
|
|
wrote 1024 bytes to myLegicClone.hex
|
|
enter number of bytes to write? (default: 86 )
|
|
|
|
loaded 1024 samples
|
|
#db# setting up legic card
|
|
#db# MIM 256 card found, writing 0x00 - 0x01 ...
|
|
#db# write successful
|
|
...
|
|
#db# setting up legic card
|
|
#db# MIM 256 card found, writing 0x56 - 0x01 ...
|
|
#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)
|
|
|
|
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
|
|
but creating dumpfile-clone files will be possible (without valid segment-crc - this has to done manually with)
|
|
|
|
|
|
(example) Legic-Prime Layout with 'Kaba Group Header'
|
|
+----+----+----+----+----+----+----+----+
|
|
0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
|
|
+----+----+----+----+----+----+----+----+
|
|
0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
|
|
+----+----+----+----+----+----+----+----+
|
|
0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
|
|
+----+----+----+----+----+----+----+----+
|
|
0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
|
|
+----+----+----+----+----+----+----+----+
|
|
0x20|UID1|UID2|kghC|
|
|
+----+----+----+
|
|
|
|
MCD= ManufacturerID (1 Byte)
|
|
MSN0..2= ManufactureSerialNumber (3 Byte)
|
|
MCC= CRC (1 Byte) calculated over MCD,MSN0..2
|
|
DCF= DecrementalField (2 Byte) 'credential' (enduser-Tag) seems to have always DCF-low=0x60 DCF-high=0xea
|
|
Bck0..5= Backup (6 Byte) Bck0 'dirty-flag', Bck1..5 SegmentHeader-Backup
|
|
BCC= BackupCRC (1 Byte) CRC calculated over Bck1..5
|
|
Seg0..3= SegmentHeader (on MIM 4 Byte )
|
|
SegC= SegmentCRC (1 Byte) calculated over MCD,MSN0..2,Seg0..3
|
|
Stp0..n= Stamp0... (variable length) length = Segment-Len - UserData - 1
|
|
UID0..n= UserDater (variable length - with KGH hex 0x00-0x63 / dec 0-99) length = Segment-Len - WRP - WRC - 1
|
|
kghC= KabaGroupHeader (1 Byte + addr 0x0c must be 0x11)
|
|
as seen on ths example: addr 0x05..0x08 & 0x0c must have been set to this values - otherwise kghCRC will not be created by a official reader (not accepted)
|
|
--]]
|
|
|
|
example = "Script create a clone-dump of a dump from a Legic Prime Tag"
|
|
author = "Mosci"
|
|
desc =
|
|
[[
|
|
This is a script which create a clone-dump of a dump from a Legic Prime Tag (MIM256 or MIM1024)
|
|
(created with 'hf legic save my_dump.hex')
|
|
requiered arguments:
|
|
-i <input file> (file to read data from)
|
|
|
|
optional arguments :
|
|
-h - Help text
|
|
-o <output file> - requieres option -c to be given
|
|
-c <new-tag crc> - requieres option -o to be given
|
|
-d - Display content of found Segments
|
|
-s - Display summary at the end
|
|
-w - write directly to Tag - a file myLegicClone.hex wille be generated also
|
|
|
|
e.g.:
|
|
hint: using the CRC '00' will result in a plain dump ( -c 00 )
|
|
|
|
Examples :
|
|
script run legic_clone -i my_dump.hex -o my_clone.hex -c f8
|
|
script run legic_clone -i my_dump.hex -d -s
|
|
]]
|
|
|
|
local utils = require('utils')
|
|
local getopt = require('getopt')
|
|
local bxor = bit32.bxor
|
|
|
|
-- we need always 2 digits
|
|
function prepend_zero(s)
|
|
if (string.len(s)==1) then return "0" .. s
|
|
else
|
|
if (string.len(s)==0) then return "00"
|
|
else return s
|
|
end
|
|
end
|
|
end
|
|
|
|
---
|
|
-- This is only meant to be used when errors occur
|
|
function oops(err)
|
|
print("ERROR: ",err)
|
|
return nil, err
|
|
end
|
|
|
|
---
|
|
-- Usage help
|
|
function help()
|
|
print(desc)
|
|
print("Example usage")
|
|
print(example)
|
|
end
|
|
|
|
-- Check availability of file
|
|
function file_check(file_name)
|
|
local file_found=io.open(file_name, "r")
|
|
if file_found==nil then
|
|
file_found=false
|
|
else
|
|
file_found=true
|
|
end
|
|
return file_found
|
|
end
|
|
|
|
--- xor-wrapper
|
|
-- xor all from addr 0x22 (start counting from 1 => 23)
|
|
function xorme(hex, xor, index)
|
|
if ( index >= 23 ) then
|
|
return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
|
|
else
|
|
return hex
|
|
end
|
|
end
|
|
|
|
-- read input-file into array
|
|
function getInputBytes(infile)
|
|
local line
|
|
local bytes = {}
|
|
|
|
local fhi,err = io.open(infile)
|
|
if err then print("OOps ... faild to read from file ".. infile); return false; end
|
|
|
|
while true do
|
|
line = fhi:read()
|
|
if line == nil then break end
|
|
|
|
for byte in line:gmatch("%w+") do
|
|
table.insert(bytes, byte)
|
|
end
|
|
end
|
|
|
|
fhi:close()
|
|
|
|
print("\nread ".. #bytes .." bytes from ".. infile)
|
|
return bytes
|
|
end
|
|
|
|
-- write to file
|
|
function writeOutputBytes(bytes, outfile)
|
|
local line
|
|
local bcnt=0
|
|
local fho,err = io.open(outfile,"w")
|
|
if err then print("OOps ... faild to open output-file ".. outfile); return false; end
|
|
|
|
for i = 1, #bytes do
|
|
if (bcnt == 0) then
|
|
line=bytes[i]
|
|
elseif (bcnt <= 7) then
|
|
line=line.." "..bytes[i]
|
|
end
|
|
if (bcnt == 7) then
|
|
-- write line to new file
|
|
fho:write(line.."\n")
|
|
-- reset counter & line
|
|
bcnt=-1
|
|
line=""
|
|
end
|
|
bcnt=bcnt+1
|
|
end
|
|
fho:close()
|
|
print("\nwrote ".. #bytes .." bytes to " .. outfile)
|
|
return true
|
|
end
|
|
|
|
-- xore certain bytes
|
|
function xorBytes(inBytes, crc)
|
|
local bytes = {}
|
|
for index = 1, #inBytes do
|
|
bytes[index] = xorme(inBytes[index], crc, index)
|
|
end
|
|
if (#inBytes == #bytes) then
|
|
-- replace crc
|
|
bytes[5] = string.sub(crc,-2)
|
|
return bytes
|
|
else
|
|
print("error: byte-count missmatch")
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- get raw segment-data
|
|
function getSegmentData(bytes, start, index)
|
|
local raw, len, valid, last, wrp, wrc, rd, crc
|
|
local segment = {}
|
|
segment[0] = bytes[start].." "..bytes[start+1].." "..bytes[start+2].." "..bytes[start+3]
|
|
-- flag = high nibble of byte 1
|
|
segment[1] = string.sub(bytes[start+1],0,1)
|
|
|
|
-- valid = bit 6 of byte 1
|
|
segment[2] = tonumber(bit32.extract("0x"..bytes[start+1],6,1),16)
|
|
|
|
-- last = bit 7 of byte 1
|
|
segment[3] = tonumber(bit32.extract("0x"..bytes[start+1],7,1),16)
|
|
|
|
-- len = (byte 0)+(bit0-3 of byte 1)
|
|
segment[4] = tonumber(("%03x"):format(tonumber(bit32.extract("0x"..bytes[start+1],0,3),16)..tonumber(bytes[start],16)),16)
|
|
|
|
-- wrp (write proteted) = byte 2
|
|
segment[5] = tonumber(bytes[start+2])
|
|
|
|
-- wrc (write control) - bit 4-6 of byte 3
|
|
segment[6] = tonumber(bit32.extract("0x"..bytes[start+3],4,3),16)
|
|
|
|
-- rd (read disabled) - bit 7 of byte 3
|
|
segment[7] = tonumber(bit32.extract("0x"..bytes[start+3],7,1),16)
|
|
|
|
-- crc byte 4
|
|
segment[8] = bytes[start+4]
|
|
|
|
-- segment index
|
|
segment[9] = index
|
|
|
|
-- # crc-byte
|
|
segment[10] = start+4
|
|
return segment
|
|
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
|
|
function CheckKgh(bytes, segStart, segEnd)
|
|
if (bytes[8]=='9f' and bytes[9]=='ff' and bytes[13]=='11') then
|
|
local i
|
|
local data = {}
|
|
segStart=tonumber(segStart,10)
|
|
segEnd=tonumber(segEnd,10)
|
|
local dataLen = segEnd-segStart-5
|
|
--- gather creadentials for verify
|
|
local WRP = bytes[(segStart+2)]
|
|
local WRC = ("%02x"):format(tonumber(bit32.extract("0x"..bytes[segStart+3],4,3),16))
|
|
local RD = ("%02x"):format(tonumber(bit32.extract("0x"..bytes[segStart+3],7,1),16))
|
|
local XX = "00"
|
|
cmd = bytes[1]..bytes[2]..bytes[3]..bytes[4]..WRP..WRC..RD..XX
|
|
for i=(segStart+5), (segStart+5+dataLen-2) do
|
|
cmd = cmd..bytes[i]
|
|
end
|
|
local KGH=("%02x"):format(utils.Crc8Legic(cmd))
|
|
if (KGH==bytes[segEnd-1]) then
|
|
return KGH
|
|
else
|
|
return false
|
|
end
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- get only the addresses of segemnt-crc's and the length of bytes
|
|
function getSegmentCrcBytes(bytes)
|
|
local start=23
|
|
local index=0
|
|
local crcbytes = {}
|
|
repeat
|
|
seg = getSegmentData(bytes,start,index)
|
|
crcbytes[index]= seg[10]
|
|
start = start + seg[4]
|
|
index = index + 1
|
|
until (seg[3] == 1 or tonumber(seg[9]) == 126 )
|
|
crcbytes[index] = start
|
|
return crcbytes
|
|
end
|
|
|
|
-- print segment-data (hf legic decode like)
|
|
function displaySegments(bytes)
|
|
--display segment header(s)
|
|
start=23
|
|
index="00"
|
|
|
|
--repeat until last-flag ist set to 1 or segment-index has reached 126
|
|
repeat
|
|
wrc=""
|
|
wrp=""
|
|
pld=""
|
|
Seg = getSegmentData(bytes,start,index)
|
|
KGH = CheckKgh(bytes,start,(start+tonumber(Seg[4],10)))
|
|
printSegment(Seg)
|
|
|
|
-- wrc
|
|
if(Seg[6]>0) then
|
|
print("WRC protected area:")
|
|
-- 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].." "
|
|
end
|
|
print(wrc)
|
|
elseif(Seg[5]>0) then
|
|
print("Remaining write protected area:")
|
|
-- 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].." "
|
|
end
|
|
print(wrp)
|
|
end
|
|
|
|
-- payload
|
|
print("Remaining segment payload:")
|
|
--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].." "
|
|
end
|
|
print(pld)
|
|
if (KGH) then print("'Kaba Group Header' detected"); end
|
|
start = start+Seg[4]
|
|
index = prepend_zero(tonumber(Seg[9])+1)
|
|
|
|
until (Seg[3] == 1 or tonumber(Seg[9]) == 126 )
|
|
end
|
|
|
|
-- print Segment values
|
|
function printSegment(SegmentData)
|
|
res = "\nSegment "..SegmentData[9]..": "
|
|
res = res.. "raw header="..SegmentData[0]..", "
|
|
res = res.. "flag="..SegmentData[1].." (valid="..SegmentData[2].." last="..SegmentData[3].."), "
|
|
res = res.. "len="..("%04d"):format(SegmentData[4])..", "
|
|
res = res.. "WRP="..prepend_zero(SegmentData[5])..", "
|
|
res = res.. "WRC="..prepend_zero(SegmentData[6])..", "
|
|
res = res.. "RD="..SegmentData[7]..", "
|
|
res = res.. "crc="..SegmentData[8]
|
|
print(res)
|
|
end
|
|
|
|
-- write clone-data to tag
|
|
function writeToTag(plainBytes)
|
|
local SegCrcs = {}
|
|
if(utils.confirm("\nplace your empty tag onto the PM3 to read and display the MCD & MSN0..2\nthe values will be shown below\n confirm when ready") == false) then
|
|
return
|
|
end
|
|
|
|
-- gather MCD & MSN from new Tag - this must be enterd manually
|
|
cmd = 'hf legic read 0x00 0x04'
|
|
core.console(cmd)
|
|
print("\nthese are the MCD MSN0 MSN1 MSN2 from the Tag that has being read:")
|
|
cmd = 'data hexsamples 4'
|
|
core.console(cmd)
|
|
print("^^ use this values as input for the following answers (one 2-digit-value per question/answer):")
|
|
-- enter MCD & MSN (in hex)
|
|
MCD = utils.input("type in MCD as 2-digit value - e.g.: 00", plainBytes[1])
|
|
MSN0 = utils.input("type in MSN0 as 2-digit value - e.g.: 01", plainBytes[2])
|
|
MSN1 = utils.input("type in MSN1 as 2-digit value - e.g.: 02", plainBytes[3])
|
|
MSN2 = utils.input("type in MSN2 as 2-digit value - e.g.: 03", plainBytes[4])
|
|
|
|
-- calculate crc8 over MCD & MSN
|
|
cmd = MCD..MSN0..MSN1..MSN2
|
|
MCC = ("%02x"):format(utils.Crc8Legic(cmd))
|
|
print("MCD:"..MCD..", MSN:"..MSN0.." "..MSN1.." "..MSN2..", MCC:"..MCC)
|
|
|
|
-- 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(tonumber(bit32.extract("0x"..plainBytes[(SegCrcs[i]-3)],0,3),16))..("%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
|
|
|
|
-- apply MCD & MSN to plain data
|
|
plainBytes[1] = MCD
|
|
plainBytes[2] = MSN0
|
|
plainBytes[3] = MSN1
|
|
plainBytes[4] = MSN2
|
|
plainBytes[5] = MCC
|
|
|
|
-- prepare plainBytes for writing (xor plain data with new MCC)
|
|
bytes = xorBytes(plainBytes, MCC)
|
|
|
|
-- write data to file
|
|
if (writeOutputBytes(bytes, "myLegicClone.hex")) then
|
|
WriteBytes = utils.input("enter number of bytes to write?", SegCrcs[#SegCrcs])
|
|
|
|
-- load file into pm3-buffer
|
|
cmd = 'hf legic load myLegicClone.hex'
|
|
core.console(cmd)
|
|
|
|
-- write pm3-buffer to Tag
|
|
for i=0, WriteBytes do
|
|
if ( i<5 or i>6) then
|
|
cmd = ('hf legic write 0x%02x 0x01'):format(i)
|
|
core.console(cmd)
|
|
elseif (i == 6) then
|
|
-- write DCF in reverse order (requires 'mosci-patch')
|
|
cmd = 'hf legic write 0x05 0x02'
|
|
core.console(cmd)
|
|
else
|
|
print("skipping byte 0x05 - will be written next step")
|
|
end
|
|
utils.Sleep(0.2)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- main function
|
|
function main(args)
|
|
-- some variables
|
|
local i=0
|
|
local oldcrc, newcrc, infile, outfile
|
|
local bytes = {}
|
|
local segments = {}
|
|
|
|
-- parse arguments for the script
|
|
for o, a in getopt.getopt(args, 'hwsdc:i::o:') do
|
|
-- output file
|
|
if o == "o" then
|
|
outfile = a
|
|
ofs = true
|
|
if (file_check(a)) then
|
|
local answer = utils.confirm("\nthe output-file "..a.." alredy exists!\nthis will delete the previous content!\ncontinue?")
|
|
if (answer==false) then return oops("quiting") end
|
|
end
|
|
end
|
|
-- input file
|
|
if o == "i" then
|
|
infile = a
|
|
if (file_check(infile)==false) then
|
|
return oops("input file: "..infile.." not found")
|
|
else
|
|
bytes = getInputBytes(infile)
|
|
oldcrc = bytes[5]
|
|
ifs = true
|
|
if (bytes == false) then return oops('couldnt get input bytes') end
|
|
end
|
|
i = i+1
|
|
end
|
|
-- new crc
|
|
if o == "c" then
|
|
newcrc = a
|
|
ncs = true
|
|
end
|
|
-- display segments switch
|
|
if o == "d" then ds = true; end
|
|
-- display summary switch
|
|
if o == "s" then ss = true; end
|
|
-- write to tag switch
|
|
if o == "w" then ws = true; end
|
|
-- help
|
|
if o == "h" then return help() end
|
|
end
|
|
|
|
if (not ifs) then return oops("option -i <input file> is required but missing") end
|
|
|
|
-- bytes to plain
|
|
bytes = xorBytes(bytes, oldcrc)
|
|
|
|
-- show segments (works only on plain bytes)
|
|
if (ds) then
|
|
print("+------------------------------------------- Segments -------------------------------------------+")
|
|
displaySegments(bytes);
|
|
end
|
|
|
|
if (ofs and ncs) then
|
|
-- xor bytes with new crc
|
|
newBytes = xorBytes(bytes, newcrc)
|
|
-- write output
|
|
if (writeOutputBytes(newBytes, outfile)) then
|
|
-- show summary if requested
|
|
if (ss) then
|
|
-- information
|
|
res = "\n+-------------------------------------------- Summary -------------------------------------------+"
|
|
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: hf legic load "..outfile
|
|
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"
|
|
res = res .."\ne.g. (based on Segment00 of the data from "..infile.."):"
|
|
res = res .."\nhf legic crc8 "..bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[23]..bytes[24]..bytes[25]..bytes[26]
|
|
-- this can not be calculated without knowing the new MCD, MSN0..2
|
|
print(res)
|
|
end
|
|
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"))
|
|
end
|
|
end
|
|
-- write to tag
|
|
if (ws and #bytes == 1024) then
|
|
writeToTag(bytes)
|
|
end
|
|
end
|
|
|
|
-- call main with arguments
|
|
main(args) |