1 local utils
= require('utils')
2 local cmds
= require('commands')
3 local getopt
= require('getopt')
4 local ansicolors
= require('ansicolors')
7 script to create a clone-dump with new crc
9 my Fork: https://github.com/icsom/proxmark3.git
11 1. read tag-dump, xor byte 22..end with byte 0x05 of the inputfile
13 3. set byte 0x05 to newcrc
14 4. until byte 0x21 plain like in inputfile
15 5. from 0x22..end xored with newcrc
16 6. calculate new crc on each segment (needs to know the new MCD & MSN0..2)
19 Dump a legic tag with 'hf legic dump'
20 place your 'empty' tag on the reader and run
21 'script run hf_legic_clone -i orig.bin -w'
23 you will see some output like:
25 read 1024 bytes from orig.bin
27 place your empty tag onto the PM3 to read and display the MCD & MSN0..2
28 the values will be shown below
29 confirm when ready [y/n] ?y
31 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 !!
32 type in MCD as 2-digit value - e.g.: 00 (default: 79 )
34 type in MSN0 as 2-digit value - e.g.: 01 (default: 28 )
36 type in MSN1 as 2-digit value - e.g.: 02 (default: d1 )
38 type in MSN2 as 2-digit value - e.g.: 03 (default: 43 )
40 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
42 wrote 1024 bytes to myLegicClone.hex
43 enter number of bytes to write? (default: 86 )
46 #db# setting up legic card
47 #db# MIM 256 card found, writing 0x00 - 0x01 ...
50 #db# setting up legic card
51 #db# MIM 256 card found, writing 0x56 - 0x01 ...
55 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
56 and your clone should be ready (except there has to be a additional KGH-CRC to be calculated - which credentials are unknown until yet)
58 the '-w' switch will only work with my fork - it needs the binary legic_crc8 which is not part of the proxmark3-master-branch
59 also the ability to write DCF is not possible with the proxmark3-master-branch
60 but creating dumpfile-clone files will be possible (without valid segment-crc - this has to done manually with)
63 (example) Legic-Prime Layout with 'Kaba Group Header'
64 +----+----+----+----+----+----+----+----+
65 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
66 +----+----+----+----+----+----+----+----+
67 0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
68 +----+----+----+----+----+----+----+----+
69 0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
70 +----+----+----+----+----+----+----+----+
71 0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
72 +----+----+----+----+----+----+----+----+
76 MCD= ManufacturerID (1 Byte)
77 MSN0..2= ManufactureSerialNumber (3 Byte)
78 MCC= CRC (1 Byte) calculated over MCD,MSN0..2
79 DCF= DecrementalField (2 Byte) 'credential' (enduser-Tag) seems to have always DCF-low=0x60 DCF-high=0xea
80 Bck0..5= Backup (6 Byte) Bck0 'dirty-flag', Bck1..5 SegmentHeader-Backup
81 BCC= BackupCRC (1 Byte) CRC calculated over Bck1..5
82 Seg0..3= SegmentHeader (on MIM 4 Byte )
83 SegC= SegmentCRC (1 Byte) calculated over MCD,MSN0..2,Seg0..3
84 Stp0..n= Stamp0... (variable length) length = Segment-Len - UserData - 1
85 UID0..n= UserDater (variable length - with KGH hex 0x00-0x63 / dec 0-99) length = Segment-Len - WRP - WRC - 1
86 kghC= KabaGroupHeader (1 Byte + addr 0x0c must be 0x11)
87 as seen on this example: addr 0x05..0x08 & 0x0c must have been set to this values - otherwise kghCRC will not be created by a official reader (not accepted)
94 This is a script which creates a clone-dump of a dump from a LEGIC Prime Tag (MIM256 or MIM1024)
95 Create a dump by running `hf legic dump`.
98 script run hf_legic_clone -i my_dump.bin -o my_clone.bin -c f8
99 script run hf_legic_clone -i my_dump.bin -d -s
102 script run hf_legic_clone [-h] [-i <file>] [-o <file>] [-c <crc>] [-d] [-s] [-w]
106 -i <input file> - file to read data from, must be in binary format (*.bin)
110 -o <output file> - requires option -c to be given
111 -c <new-tag crc> - requires option -o to be given
112 -d - Display content of found Segments
113 -s - Display summary at the end
114 -w - write directly to tag - a file hf-legic-UID-dump.bin will also be generated
117 hint: using the CRC '00' will result in a plain dump ( -c 00 )
120 local bxor
= bit32
.bxor
122 -- This is only meant to be used when errors occur
123 local function dbg(args
)
124 if not DEBUG
then return end
125 if type(args
) == 'table' then
135 -- we need always 2 digits
136 local function prepend_zero(s
)
137 if s
== nil then return '..' end
150 -- This is only meant to be used when errors occur
151 local function oops(err
)
153 core
.clearCommandBuffer()
158 local function help()
163 print(ansicolors
.cyan
..'Usage'..ansicolors
.reset
)
165 print(ansicolors
.cyan
..'Arguments'..ansicolors
.reset
)
167 print(ansicolors
.cyan
..'Example usage'..ansicolors
.reset
)
171 local function readlegicinfo()
173 local c
= Command
:newNG
{cmd
= cmds
.CMD_HF_LEGIC_INFO
, data
= nil}
174 local result
, err
= c
:sendNG(false, 2000)
175 if not result
then return oops(err
) end
176 -- result is a packed data structure, data starts at offset 33
180 -- Check availability of file
181 local function file_check(file_name
)
182 local exists
= io
.open(file_name
, "r")
192 -- xor all from addr 0x22 (start counting from 1 => 23)
193 local function xorme(hex
, xor
, index
)
194 if ( index
>= 23 ) then
195 return ('%02x'):format(bxor( tonumber(hex
, 16) , tonumber(xor
, 16) ))
201 -- read input-file into array
202 local function getInputBytes(infile
)
204 local f
= io
.open(infile
, "rb")
205 if f
== nil then print("OOps ... failed to read from file ".. infile
); return false; end
207 local str
= f
:read("*all")
210 for c
in (str
or ''):gmatch
'.' do
211 bytes
[#bytes
+ 1] = ('%02x'):format(c
:byte())
214 print("\nread ".. #bytes
.." bytes from "..ansicolors
.yellow
..infile
..ansicolors
.reset
)
219 local function writeOutputBytes(bytes
, outfile
)
220 local fho
,err
= io
.open(outfile
, "wb")
221 if err
then print("OOps ... failed to open output-file ".. outfile
); return false; end
224 fho
:write(string.char(tonumber(bytes
[i
], 16)))
227 print("\nwrote ".. #bytes
.." bytes to " .. outfile
)
231 -- xore certain bytes
232 local function xorBytes(inBytes
, crc
)
234 for index
= 1, #inBytes
do
235 bytes
[index
] = xorme(inBytes
[index
], crc
, index
)
237 if (#inBytes
== #bytes
) then
239 bytes
[5] = string.sub(crc
, -2)
242 print("error: byte-count missmatch")
247 -- get raw segment-data
248 local function getSegmentData(bytes
, start
, index
)
249 local raw
, len
, valid
, last
, wrp
, wrc
, rd
, crc
251 segment
[0] = bytes
[start
]..' '..bytes
[start
+ 1]..' '..bytes
[start
+ 2]..' '..bytes
[start
+ 3]
252 -- flag = high nibble of byte 1
253 segment
[1] = string.sub(bytes
[start
+ 1], 0, 1)
255 -- valid = bit 6 of byte 1
256 segment
[2] = tonumber(bit32
.extract('0x'..bytes
[start
+ 1], 6, 1), 16)
258 -- last = bit 7 of byte 1
259 segment
[3] = tonumber(bit32
.extract('0x'..bytes
[start
+ 1], 7, 1), 16)
261 -- len = (byte 0)+(bit0-3 of byte 1)
262 segment
[4] = tonumber(('%03x'):format(tonumber(bit32
.extract('0x'..bytes
[start
+ 1], 0, 3), 16)..tonumber(bytes
[start
], 16)), 16)
264 -- wrp (write proteted) = byte 2
265 segment
[5] = tonumber(bytes
[start
+ 2])
267 -- wrc (write control) - bit 4-6 of byte 3
268 segment
[6] = tonumber(bit32
.extract('0x'..bytes
[start
+ 3], 4, 3), 16)
270 -- rd (read disabled) - bit 7 of byte 3
271 segment
[7] = tonumber(bit32
.extract('0x'..bytes
[start
+ 3], 7, 1), 16)
274 segment
[8] = bytes
[start
+ 4]
280 segment
[10] = start
+ 4
284 --- Kaba Group Header
285 -- checks if a segment does have a kghCRC
286 -- returns boolean false if no kgh has being detected or the kghCRC if a kgh was detected
287 local function CheckKgh(bytes
, segStart
, segEnd
)
288 if (bytes
[8] == '9f' and bytes
[9] == 'ff' and bytes
[13] == '11') then
291 segStart
= tonumber(segStart
, 10)
292 segEnd
= tonumber(segEnd
, 10)
293 local dataLen
= segEnd
- segStart
- 5
294 --- gather creadentials for verify
295 local WRP
= bytes
[(segStart
+ 2)]
296 local WRC
= ("%02x"):format(tonumber(bit32
.extract("0x"..bytes
[segStart
+3], 4, 3), 16))
297 local RD
= ("%02x"):format(tonumber(bit32
.extract("0x"..bytes
[segStart
+3], 7, 1), 16))
299 cmd
= bytes
[1]..bytes
[2]..bytes
[3]..bytes
[4]..WRP
..WRC
..RD
..XX
300 for i
= (segStart
+ 5), (segStart
+ 5 + dataLen
- 2) do
303 local KGH
= ("%02x"):format(utils
.Crc8Legic(cmd
))
304 if (KGH
== bytes
[segEnd
- 1]) then
314 -- get only the addresses of segemnt-crc's and the length of bytes
315 local function getSegmentCrcBytes(bytes
)
320 seg
= getSegmentData(bytes
, start
, index
)
321 crcbytes
[index
] = seg
[10]
322 start
= start
+ seg
[4]
324 until (seg
[3] == 1 or tonumber(seg
[9]) == 126 )
325 crcbytes
[index
] = start
329 -- print Segment values
330 local function printSegment(SegmentData
)
331 res
= "\nSegment "..SegmentData
[9]..": "
332 res
= res
.. "raw header="..SegmentData
[0]..", "
333 res
= res
.. "flag="..SegmentData
[1].." (valid="..SegmentData
[2].." last="..SegmentData
[3].."), "
334 res
= res
.. "len="..("%04d"):format(SegmentData
[4])..", "
335 res
= res
.. "WRP="..prepend_zero(SegmentData
[5])..", "
336 res
= res
.. "WRC="..prepend_zero(SegmentData
[6])..", "
337 res
= res
.. "RD="..SegmentData
[7]..", "
338 res
= res
.. "crc="..SegmentData
[8]
342 -- print segment-data (hf legic info like)
343 local function displaySegments(bytes
)
345 --display segment header(s)
349 --repeat until last-flag ist set to 1 or segment-index has reached 126
354 Seg
= getSegmentData(bytes
, start
, index
)
355 if Seg
== nil then return OOps("segment is nil") end
357 KGH
= CheckKgh(bytes
, start
, (start
+ tonumber(Seg
[4], 10)))
363 print("WRC protected area:")
364 -- length of wrc = wrc
366 -- starts at (segment-start + segment-header + segment-crc)-1
367 wrc
= wrc
..bytes
[(start
+ 4 + 1 + i
) - 1]..' '
370 elseif (Seg
[5] > 0) then
371 print("Remaining write protected area:")
372 -- length of wrp = (wrp-wrc)
373 for i
= 1, (Seg
[5] - Seg
[6]) do
374 -- starts at (segment-start + segment-header + segment-crc + wrc)-1
375 wrp
= wrp
..bytes
[(start
+ 4 + 1 + Seg
[6] + i
) - 1]..' '
381 print("Remaining segment payload:")
382 --length of payload = segment-len - segment-header - segment-crc - wrp -wrc
383 for i
= 1, (Seg
[4] - 4 - 1 - Seg
[5] - Seg
[6]) do
384 -- starts at (segment-start + segment-header + segment-crc + segment-wrp + segemnt-wrc)-1
385 pld
= pld
..bytes
[(start
+ 4 + 1 + Seg
[5] + Seg
[6] + i
) - 1]..' '
389 print(ansicolors
.yellow
.."'Kaba Group Header' detected"..ansicolors
.reset
)
391 start
= start
+ Seg
[4]
392 index
= prepend_zero(tonumber(Seg
[9]) + 1)
394 until (Seg
[3] == 1 or tonumber(Seg
[9]) == 126 )
397 -- write clone-data to tag
398 local function writeToTag(plainBytes
)
402 if (utils
.confirm("\nplace your empty tag onto the PM3 to restore the data of the input file\nthe CRCs will be calculated as needed\n confirm when ready") == false) then
406 readbytes
= readlegicinfo()
407 -- gather MCD & MSN from new Tag - this must be enterd manually
408 print("\nthese are the MCD MSN0 MSN1 MSN2 from the Tag that has being read:")
410 -- readbytes is a table with uid data as hex string in Data key
411 plainBytes
[1] = readbytes
.Data
:sub(1,2)
412 plainBytes
[2] = readbytes
.Data
:sub(3,4)
413 plainBytes
[3] = readbytes
.Data
:sub(5,6)
414 plainBytes
[4] = readbytes
.Data
:sub(7,8)
420 -- calculate crc8 over MCD & MSN
421 cmd
= MCD
..MSN0
..MSN1
..MSN2
422 MCC
= ("%02x"):format(utils
.Crc8Legic(cmd
))
423 print("MCD:"..ansicolors
.green
..MCD
..ansicolors
.reset
..", MSN:"..ansicolors
.green
..MSN0
.." "..MSN1
.." "..MSN2
..ansicolors
.reset
..", MCC:"..MCC
)
425 -- calculate new Segment-CRC for each valid segment
426 SegCrcs
= getSegmentCrcBytes(plainBytes
)
427 for i
= 0, (#SegCrcs
- 1) do
428 -- SegCrcs[i]-4 = address of first byte of segmentHeader (low byte segment-length)
429 segLen
= tonumber(("%1x"):format(tonumber(bit32
.extract("0x"..plainBytes
[(SegCrcs
[i
] - 3)], 0, 3), 16))..("%02x"):format(tonumber(plainBytes
[SegCrcs
[i
] - 4], 16)), 16)
430 segStart
= (SegCrcs
[i
] - 4)
431 segEnd
= (SegCrcs
[i
] - 4 + segLen
)
432 KGH
= CheckKgh(plainBytes
, segStart
, segEnd
)
434 print("'Kaba Group Header' detected - re-calculate...")
436 cmd
= MCD
..MSN0
..MSN1
..MSN2
..plainBytes
[SegCrcs
[i
]-4]..plainBytes
[SegCrcs
[i
]-3]..plainBytes
[SegCrcs
[i
]-2]..plainBytes
[SegCrcs
[i
]-1]
437 plainBytes
[SegCrcs
[i]]
= ("%02x"):format(utils
.Crc8Legic(cmd
))
440 -- apply MCD & MSN to plain data
447 -- prepare plainBytes for writing (xor plain data with new MCC)
448 bytes
= xorBytes(plainBytes
, MCC
)
450 -- write data to file
451 if (writeOutputBytes(bytes
, "hf-legic-UID-dump.bin")) then
452 -- write pm3-buffer to Tag
453 cmd
= ('hf legic restore -f hf-legic-UID-dump')
459 local function main(args
)
462 local oldcrc
, newcrc
, infile
, outfile
466 -- parse arguments for the script
467 for o
, a
in getopt
.getopt(args
, 'hwsdc:i:o:') do
472 if (file_check(a
)) then
473 local answer
= utils
.confirm('\nthe output-file '..a
..' already exists!\nthis will delete the previous content!\ncontinue?')
474 if (answer
== false) then return oops('quiting') end
480 if (file_check(infile
) == false) then return oops('input file: '..infile
..' not found') end
482 bytes
= getInputBytes(infile
)
485 if (bytes
== false) then return oops('couldnt read file') end
494 -- display segments switch
495 if o
== 'd' then ds
= true; end
496 -- display summary switch
497 if o
== 's' then ss
= true; end
498 -- write to tag switch
499 if o
== 'w' then ws
= true; end
501 if o
== 'h' then return help() end
504 if (not ifs
) then return oops('option -i <input file> is required') end
507 bytes
= xorBytes(bytes
, oldcrc
)
509 -- show segments (works only on plain bytes)
511 print("+------------------------------------------- Segments -------------------------------------------+")
512 displaySegments(bytes
);
515 if (ofs
and ncs
) then
516 -- xor bytes with new crc
517 newBytes
= xorBytes(bytes
, newcrc
)
519 if (writeOutputBytes(newBytes
, outfile
)) then
520 -- show summary if requested
523 res
= "\n+-------------------------------------------- Summary -------------------------------------------+"
524 res
= res
.."\ncreated clone_dump from\n\t"..infile
.." crc: "..oldcrc
.."\ndump_file:"
525 res
= res
.."\n\t"..outfile
.." crc: "..string.sub(newcrc
, -2)
526 res
= res
.."\nyou may load the new file with:"
527 res
= res
..ansicolors
.yellow
.."hf legic eload -f "..outfile
..ansicolors
.reset
528 res
= res
.."\n\nif you don't write to tag immediately ('-w' switch) you will need to recalculate each segmentCRC"
529 res
= res
.."\nafter writing this dump to a tag!"
530 res
= res
.."\n\na segmentCRC gets calculated over MCD,MSN0..3, Segment-Header0..3"
531 res
= res
.."\ne.g. (based on Segment00 of the data from "..infile
.."):"
533 res
= res
..ansicolors
.yellow
.."hf legic crc -d "..bytes
[1]..bytes
[2]..bytes
[3]..bytes
[4]..bytes
[23]..bytes
[24]..bytes
[25]..bytes
[26].." --mcc "..newcrc
.." -t 8"..ansicolors
.reset
534 -- this can not be calculated without knowing the new MCD, MSN0..2
540 -- show why the output-file was not written
541 print("\nnew file not written - some arguments are missing ..")
542 print("output file: ".. (ofs
and outfile
or "not given"))
543 print("new crc: ".. (ncs
and newcrc
or "not given"))
547 if (ws
and ( #bytes
== 1024 or #bytes
== 256)) then
552 -- call main with arguments