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 readlegicdata(offset
, length
, iv
)
173 local command
= Command
:newMIX
{
174 cmd
= cmds
.CMD_HF_LEGIC_READER
180 local result
, err
= command
:sendMIX()
181 if not result
then return oops(err
) end
182 -- result is a packed data structure, data starts at offset 33
186 -- Check availability of file
187 local function file_check(file_name
)
188 local exists
= io
.open(file_name
, "r")
198 -- xor all from addr 0x22 (start counting from 1 => 23)
199 local function xorme(hex
, xor
, index
)
200 if ( index
>= 23 ) then
201 return ('%02x'):format(bxor( tonumber(hex
, 16) , tonumber(xor
, 16) ))
207 -- read input-file into array
208 local function getInputBytes(infile
)
210 local f
= io
.open(infile
, "rb")
211 if f
== nil then print("OOps ... failed to read from file ".. infile
); return false; end
213 local str
= f
:read("*all")
216 for c
in (str
or ''):gmatch
'.' do
217 bytes
[#bytes
+ 1] = ('%02x'):format(c
:byte())
220 print("\nread ".. #bytes
.." bytes from "..ansicolors
.yellow
..infile
..ansicolors
.reset
)
225 local function writeOutputBytes(bytes
, outfile
)
226 local fho
,err
= io
.open(outfile
, "wb")
227 if err
then print("OOps ... faild to open output-file ".. outfile
); return false; end
230 fho
:write(string.char(tonumber(bytes
[i
], 16)))
233 print("\nwrote ".. #bytes
.." bytes to " .. outfile
)
237 -- xore certain bytes
238 local function xorBytes(inBytes
, crc
)
240 for index
= 1, #inBytes
do
241 bytes
[index
] = xorme(inBytes
[index
], crc
, index
)
243 if (#inBytes
== #bytes
) then
245 bytes
[5] = string.sub(crc
, -2)
248 print("error: byte-count missmatch")
253 -- get raw segment-data
254 local function getSegmentData(bytes
, start
, index
)
255 local raw
, len
, valid
, last
, wrp
, wrc
, rd
, crc
257 segment
[0] = bytes
[start
]..' '..bytes
[start
+ 1]..' '..bytes
[start
+ 2]..' '..bytes
[start
+ 3]
258 -- flag = high nibble of byte 1
259 segment
[1] = string.sub(bytes
[start
+ 1], 0, 1)
261 -- valid = bit 6 of byte 1
262 segment
[2] = tonumber(bit32
.extract('0x'..bytes
[start
+ 1], 6, 1), 16)
264 -- last = bit 7 of byte 1
265 segment
[3] = tonumber(bit32
.extract('0x'..bytes
[start
+ 1], 7, 1), 16)
267 -- len = (byte 0)+(bit0-3 of byte 1)
268 segment
[4] = tonumber(('%03x'):format(tonumber(bit32
.extract('0x'..bytes
[start
+ 1], 0, 3), 16)..tonumber(bytes
[start
], 16)), 16)
270 -- wrp (write proteted) = byte 2
271 segment
[5] = tonumber(bytes
[start
+ 2])
273 -- wrc (write control) - bit 4-6 of byte 3
274 segment
[6] = tonumber(bit32
.extract('0x'..bytes
[start
+ 3], 4, 3), 16)
276 -- rd (read disabled) - bit 7 of byte 3
277 segment
[7] = tonumber(bit32
.extract('0x'..bytes
[start
+ 3], 7, 1), 16)
280 segment
[8] = bytes
[start
+ 4]
286 segment
[10] = start
+ 4
290 --- Kaba Group Header
291 -- checks if a segment does have a kghCRC
292 -- returns boolean false if no kgh has being detected or the kghCRC if a kgh was detected
293 local function CheckKgh(bytes
, segStart
, segEnd
)
294 if (bytes
[8] == '9f' and bytes
[9] == 'ff' and bytes
[13] == '11') then
297 segStart
= tonumber(segStart
, 10)
298 segEnd
= tonumber(segEnd
, 10)
299 local dataLen
= segEnd
- segStart
- 5
300 --- gather creadentials for verify
301 local WRP
= bytes
[(segStart
+ 2)]
302 local WRC
= ("%02x"):format(tonumber(bit32
.extract("0x"..bytes
[segStart
+3], 4, 3), 16))
303 local RD
= ("%02x"):format(tonumber(bit32
.extract("0x"..bytes
[segStart
+3], 7, 1), 16))
305 cmd
= bytes
[1]..bytes
[2]..bytes
[3]..bytes
[4]..WRP
..WRC
..RD
..XX
306 for i
= (segStart
+ 5), (segStart
+ 5 + dataLen
- 2) do
309 local KGH
= ("%02x"):format(utils
.Crc8Legic(cmd
))
310 if (KGH
== bytes
[segEnd
- 1]) then
320 -- get only the addresses of segemnt-crc's and the length of bytes
321 local function getSegmentCrcBytes(bytes
)
326 seg
= getSegmentData(bytes
, start
, index
)
327 crcbytes
[index
] = seg
[10]
328 start
= start
+ seg
[4]
330 until (seg
[3] == 1 or tonumber(seg
[9]) == 126 )
331 crcbytes
[index
] = start
335 -- print Segment values
336 local function printSegment(SegmentData
)
337 res
= "\nSegment "..SegmentData
[9]..": "
338 res
= res
.. "raw header="..SegmentData
[0]..", "
339 res
= res
.. "flag="..SegmentData
[1].." (valid="..SegmentData
[2].." last="..SegmentData
[3].."), "
340 res
= res
.. "len="..("%04d"):format(SegmentData
[4])..", "
341 res
= res
.. "WRP="..prepend_zero(SegmentData
[5])..", "
342 res
= res
.. "WRC="..prepend_zero(SegmentData
[6])..", "
343 res
= res
.. "RD="..SegmentData
[7]..", "
344 res
= res
.. "crc="..SegmentData
[8]
348 -- print segment-data (hf legic info like)
349 local function displaySegments(bytes
)
351 --display segment header(s)
355 --repeat until last-flag ist set to 1 or segment-index has reached 126
360 Seg
= getSegmentData(bytes
, start
, index
)
361 if Seg
== nil then return OOps("segment is nil") end
363 KGH
= CheckKgh(bytes
, start
, (start
+ tonumber(Seg
[4], 10)))
369 print("WRC protected area:")
370 -- length of wrc = wrc
372 -- starts at (segment-start + segment-header + segment-crc)-1
373 wrc
= wrc
..bytes
[(start
+ 4 + 1 + i
) - 1]..' '
376 elseif (Seg
[5] > 0) then
377 print("Remaining write protected area:")
378 -- length of wrp = (wrp-wrc)
379 for i
= 1, (Seg
[5] - Seg
[6]) do
380 -- starts at (segment-start + segment-header + segment-crc + wrc)-1
381 wrp
= wrp
..bytes
[(start
+ 4 + 1 + Seg
[6] + i
) - 1]..' '
387 print("Remaining segment payload:")
388 --length of payload = segment-len - segment-header - segment-crc - wrp -wrc
389 for i
= 1, (Seg
[4] - 4 - 1 - Seg
[5] - Seg
[6]) do
390 -- starts at (segment-start + segment-header + segment-crc + segment-wrp + segemnt-wrc)-1
391 pld
= pld
..bytes
[(start
+ 4 + 1 + Seg
[5] + Seg
[6] + i
) - 1]..' '
395 print(ansicolors
.yellow
.."'Kaba Group Header' detected"..ansicolors
.reset
)
397 start
= start
+ Seg
[4]
398 index
= prepend_zero(tonumber(Seg
[9]) + 1)
400 until (Seg
[3] == 1 or tonumber(Seg
[9]) == 126 )
403 -- write clone-data to tag
404 local function writeToTag(plainBytes
)
408 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
412 readbytes
= readlegicdata(0, 4, 0x55)
413 -- gather MCD & MSN from new Tag - this must be enterd manually
414 print("\nthese are the MCD MSN0 MSN1 MSN2 from the Tag that has being read:")
416 -- readbytes is a usbcommandOLD package, hence 32 bytes offset until data.
417 plainBytes
[1] = ('%02x'):format(readbytes
:byte(33))
418 plainBytes
[2] = ('%02x'):format(readbytes
:byte(34))
419 plainBytes
[3] = ('%02x'):format(readbytes
:byte(35))
420 plainBytes
[4] = ('%02x'):format(readbytes
:byte(36))
426 -- calculate crc8 over MCD & MSN
427 cmd
= MCD
..MSN0
..MSN1
..MSN2
428 MCC
= ("%02x"):format(utils
.Crc8Legic(cmd
))
429 print("MCD:"..ansicolors
.green
..MCD
..ansicolors
.reset
..", MSN:"..ansicolors
.green
..MSN0
.." "..MSN1
.." "..MSN2
..ansicolors
.reset
..", MCC:"..MCC
)
431 -- calculate new Segment-CRC for each valid segment
432 SegCrcs
= getSegmentCrcBytes(plainBytes
)
433 for i
= 0, (#SegCrcs
- 1) do
434 -- SegCrcs[i]-4 = address of first byte of segmentHeader (low byte segment-length)
435 segLen
= tonumber(("%1x"):format(tonumber(bit32
.extract("0x"..plainBytes
[(SegCrcs
[i
] - 3)], 0, 3), 16))..("%02x"):format(tonumber(plainBytes
[SegCrcs
[i
] - 4], 16)), 16)
436 segStart
= (SegCrcs
[i
] - 4)
437 segEnd
= (SegCrcs
[i
] - 4 + segLen
)
438 KGH
= CheckKgh(plainBytes
, segStart
, segEnd
)
440 print("'Kaba Group Header' detected - re-calculate...")
442 cmd
= MCD
..MSN0
..MSN1
..MSN2
..plainBytes
[SegCrcs
[i
]-4]..plainBytes
[SegCrcs
[i
]-3]..plainBytes
[SegCrcs
[i
]-2]..plainBytes
[SegCrcs
[i
]-1]
443 plainBytes
[SegCrcs
[i]]
= ("%02x"):format(utils
.Crc8Legic(cmd
))
446 -- apply MCD & MSN to plain data
453 -- prepare plainBytes for writing (xor plain data with new MCC)
454 bytes
= xorBytes(plainBytes
, MCC
)
456 -- write data to file
457 if (writeOutputBytes(bytes
, "hf-legic-UID-dump.bin")) then
458 -- write pm3-buffer to Tag
459 cmd
= ('hf legic restore -f hf-legic-UID-dump')
465 local function main(args
)
468 local oldcrc
, newcrc
, infile
, outfile
472 -- parse arguments for the script
473 for o
, a
in getopt
.getopt(args
, 'hwsdc:i:o:') do
478 if (file_check(a
)) then
479 local answer
= utils
.confirm('\nthe output-file '..a
..' already exists!\nthis will delete the previous content!\ncontinue?')
480 if (answer
== false) then return oops('quiting') end
486 if (file_check(infile
) == false) then return oops('input file: '..infile
..' not found') end
488 bytes
= getInputBytes(infile
)
491 if (bytes
== false) then return oops('couldnt read file') end
500 -- display segments switch
501 if o
== 'd' then ds
= true; end
502 -- display summary switch
503 if o
== 's' then ss
= true; end
504 -- write to tag switch
505 if o
== 'w' then ws
= true; end
507 if o
== 'h' then return help() end
510 if (not ifs
) then return oops('option -i <input file> is required') end
513 bytes
= xorBytes(bytes
, oldcrc
)
515 -- show segments (works only on plain bytes)
517 print("+------------------------------------------- Segments -------------------------------------------+")
518 displaySegments(bytes
);
521 if (ofs
and ncs
) then
522 -- xor bytes with new crc
523 newBytes
= xorBytes(bytes
, newcrc
)
525 if (writeOutputBytes(newBytes
, outfile
)) then
526 -- show summary if requested
529 res
= "\n+-------------------------------------------- Summary -------------------------------------------+"
530 res
= res
.."\ncreated clone_dump from\n\t"..infile
.." crc: "..oldcrc
.."\ndump_file:"
531 res
= res
.."\n\t"..outfile
.." crc: "..string.sub(newcrc
, -2)
532 res
= res
.."\nyou may load the new file with:"
533 res
= res
..ansicolors
.yellow
.."hf legic eload -f "..outfile
..ansicolors
.reset
534 res
= res
.."\n\nif you don't write to tag immediately ('-w' switch) you will need to recalculate each segmentCRC"
535 res
= res
.."\nafter writing this dump to a tag!"
536 res
= res
.."\n\na segmentCRC gets calculated over MCD,MSN0..3, Segment-Header0..3"
537 res
= res
.."\ne.g. (based on Segment00 of the data from "..infile
.."):"
539 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
540 -- this can not be calculated without knowing the new MCD, MSN0..2
546 -- show why the output-file was not written
547 print("\nnew file not written - some arguments are missing ..")
548 print("output file: ".. (ofs
and outfile
or "not given"))
549 print("new crc: ".. (ncs
and newcrc
or "not given"))
553 if (ws
and ( #bytes
== 1024 or #bytes
== 256)) then
558 -- call main with arguments