2 script to create a clone-dump with new crc
4 my Fork: https://github.com/icsom/proxmark3.git
5 Upstream: https://github.com/Proxmark/proxmark3.git
7 1. read tag-dump, xor byte 22..end with byte 0x05 of the inputfile
9 3. set byte 0x05 to newcrc
10 4. until byte 0x21 plain like in inputfile
11 5. from 0x22..end xored with newcrc
12 6. calculate new crc on each segment (needs to know the new MCD & MSN0..2)
15 read a valid legic tag with 'hf legic reader'
16 save the dump with 'hf legic save orig.hex'
17 place your 'empty' tag on the reader and run 'script run Legic_clone -i orig.hex -w'
18 you will see some output like:
19 read 1024 bytes from legic_dumps/j_0000.hex
21 place your empty tag onto the PM3 to read and display the MCD & MSN0..2
22 the values will be shown below
23 confirm whnen ready [y/n] ?y
24 #db# setting up legic card
25 #db# MIM 256 card found, reading card ...
26 #db# Card read, use 'hf legic decode' or
27 #db# 'data hexsamples 8' to view results
28 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 !!
29 type in MCD as 2-digit value - e.g.: 00 (default: 79 )
31 type in MSN0 as 2-digit value - e.g.: 01 (default: 28 )
33 type in MSN1 as 2-digit value - e.g.: 02 (default: d1 )
35 type in MSN2 as 2-digit value - e.g.: 03 (default: 43 )
37 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
39 wrote 1024 bytes to myLegicClone.hex
40 enter number of bytes to write? (default: 86 )
43 #db# setting up legic card
44 #db# MIM 256 card found, writing 0x00 - 0x01 ...
47 #db# setting up legic card
48 #db# MIM 256 card found, writing 0x56 - 0x01 ...
52 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
53 and your clone should be ready (except there has to be a additional KGH-CRC to be calculated - which credentials are unknown until yet)
55 the '-w' switch will only work with my fork - it needs the binary legic_crc8 which is not part of the proxmark3-master-branch
56 also the ability to write DCF is not possible with the proxmark3-master-branch
57 but creating dumpfile-clone files will be possible (without valid segment-crc - this has to done manually with)
60 (example) Legic-Prime Layout with 'Kaba Group Header'
61 +----+----+----+----+----+----+----+----+
62 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
63 +----+----+----+----+----+----+----+----+
64 0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
65 +----+----+----+----+----+----+----+----+
66 0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
67 +----+----+----+----+----+----+----+----+
68 0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
69 +----+----+----+----+----+----+----+----+
73 MCD= ManufacturerID (1 Byte)
74 MSN0..2= ManufactureSerialNumber (3 Byte)
75 MCC= CRC (1 Byte) calculated over MCD,MSN0..2
76 DCF= DecrementalField (2 Byte) 'credential' (enduser-Tag) seems to have always DCF-low=0x60 DCF-high=0xea
77 Bck0..5= Backup (6 Byte) Bck0 'dirty-flag', Bck1..5 SegmentHeader-Backup
78 BCC= BackupCRC (1 Byte) CRC calculated over Bck1..5
79 Seg0..3= SegmentHeader (on MIM 4 Byte )
80 SegC= SegmentCRC (1 Byte) calculated over MCD,MSN0..2,Seg0..3
81 Stp0..n= Stamp0... (variable length) length = Segment-Len - UserData - 1
82 UID0..n= UserDater (variable length - with KGH hex 0x00-0x63 / dec 0-99) length = Segment-Len - WRP - WRC - 1
83 kghC= KabaGroupHeader (1 Byte + addr 0x0c must be 0x11)
84 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)
87 example = "Script create a clone-dump of a dump from a Legic Prime Tag"
91 This is a script which create a clone-dump of a dump from a Legic Prime Tag (MIM256 or MIM1024)
92 (created with 'hf legic save my_dump.hex')
94 -i <input file> (file to read data from)
98 -o <output file> - requieres option -c to be given
99 -c <new-tag crc> - requieres option -o to be given
100 -d - Display content of found Segments
101 -s - Display summary at the end
102 -w - write directly to Tag - a file myLegicClone.hex wille be generated also
105 hint: using the CRC '00' will result in a plain dump ( -c 00 )
108 script run legic_clone -i my_dump.hex -o my_clone.hex -c f8
109 script run legic_clone -i my_dump.hex -d -s
112 local utils = require('utils')
113 local getopt = require('getopt')
114 local bxor = bit32.bxor
116 -- we need always 2 digits
117 function prepend_zero(s)
118 if (string.len(s)==1) then return "0" .. s
120 if (string.len(s)==0) then return "00"
127 -- This is only meant to be used when errors occur
137 print("Example usage")
141 -- Check availability of file
142 function file_check(file_name)
143 local file_found=io.open(file_name, "r")
144 if file_found==nil then
153 -- xor all from addr 0x22 (start counting from 1 => 23)
154 function xorme(hex, xor, index)
155 if ( index >= 23 ) then
156 return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
162 -- read input-file into array
163 function getInputBytes(infile)
167 local fhi,err = io.open(infile)
168 if err then print("OOps ... faild to read from file ".. infile); return false; end
172 if line == nil then break end
174 for byte in line:gmatch("%w+") do
175 table.insert(bytes, byte)
181 print("\nread ".. #bytes .." bytes from ".. infile)
186 function writeOutputBytes(bytes, outfile)
189 local fho,err = io.open(outfile,"w")
190 if err then print("OOps ... faild to open output-file ".. outfile); return false; end
195 elseif (bcnt <= 7) then
196 line=line.." "..bytes[i]
199 -- write line to new file
200 fho:write(line.."\n")
201 -- reset counter & line
208 print("\nwrote ".. #bytes .." bytes to " .. outfile)
212 -- xore certain bytes
213 function xorBytes(inBytes, crc)
215 for index = 1, #inBytes do
216 bytes[index] = xorme(inBytes[index], crc, index)
218 if (#inBytes == #bytes) then
220 bytes[5] = string.sub(crc,-2)
223 print("error: byte-count missmatch")
228 -- get raw segment-data
229 function getSegmentData(bytes, start, index)
230 local raw, len, valid, last, wrp, wrc, rd, crc
232 segment[0] = bytes[start].." "..bytes[start+1].." "..bytes[start+2].." "..bytes[start+3]
233 -- flag = high nibble of byte 1
234 segment[1] = string.sub(bytes[start+1],0,1)
236 -- valid = bit 6 of byte 1
237 segment[2] = tonumber(bit32.extract("0x"..bytes[start+1],6,1),16)
239 -- last = bit 7 of byte 1
240 segment[3] = tonumber(bit32.extract("0x"..bytes[start+1],7,1),16)
242 -- len = (byte 0)+(bit0-3 of byte 1)
243 segment[4] = tonumber(("%03x"):format(tonumber(bit32.extract("0x"..bytes[start+1],0,3),16)..tonumber(bytes[start],16)),16)
245 -- wrp (write proteted) = byte 2
246 segment[5] = tonumber(bytes[start+2])
248 -- wrc (write control) - bit 4-6 of byte 3
249 segment[6] = tonumber(bit32.extract("0x"..bytes[start+3],4,3),16)
251 -- rd (read disabled) - bit 7 of byte 3
252 segment[7] = tonumber(bit32.extract("0x"..bytes[start+3],7,1),16)
255 segment[8] = bytes[start+4]
261 segment[10] = start+4
265 --- Kaba Group Header
266 -- checks if a segment does have a kghCRC
267 -- returns boolean false if no kgh has being detected or the kghCRC if a kgh was detected
268 function CheckKgh(bytes, segStart, segEnd)
269 if (bytes[8]=='9f' and bytes[9]=='ff' and bytes[13]=='11') then
272 segStart=tonumber(segStart,10)
273 segEnd=tonumber(segEnd,10)
274 local dataLen = segEnd-segStart-5
275 --- gather creadentials for verify
276 local WRP = bytes[(segStart+2)]
277 local WRC = ("%02x"):format(tonumber(bit32.extract("0x"..bytes[segStart+3],4,3),16))
278 local RD = ("%02x"):format(tonumber(bit32.extract("0x"..bytes[segStart+3],7,1),16))
280 cmd = bytes[1]..bytes[2]..bytes[3]..bytes[4]..WRP..WRC..RD..XX
281 for i=(segStart+5), (segStart+5+dataLen-2) do
284 local KGH=("%02x"):format(utils.Crc8Legic(cmd))
285 if (KGH==bytes[segEnd-1]) then
295 -- get only the addresses of segemnt-crc's and the length of bytes
296 function getSegmentCrcBytes(bytes)
301 seg = getSegmentData(bytes,start,index)
302 crcbytes[index]= seg[10]
303 start = start + seg[4]
305 until (seg[3] == 1 or tonumber(seg[9]) == 126 )
306 crcbytes[index] = start
310 -- print segment-data (hf legic decode like)
311 function displaySegments(bytes)
312 --display segment header(s)
316 --repeat until last-flag ist set to 1 or segment-index has reached 126
321 Seg = getSegmentData(bytes,start,index)
322 KGH = CheckKgh(bytes,start,(start+tonumber(Seg[4],10)))
327 print("WRC protected area:")
328 -- length of wrc = wrc
330 -- starts at (segment-start + segment-header + segment-crc)-1
331 wrc = wrc..bytes[(start+4+1+i)-1].." "
334 elseif(Seg[5]>0) then
335 print("Remaining write protected area:")
336 -- length of wrp = (wrp-wrc)
337 for i=1, (Seg[5]-Seg[6]) do
338 -- starts at (segment-start + segment-header + segment-crc + wrc)-1
339 wrp = wrp..bytes[(start+4+1+Seg[6]+i)-1].." "
345 print("Remaining segment payload:")
346 --length of payload = segment-len - segment-header - segment-crc - wrp -wrc
347 for i=1, (Seg[4]-4-1-Seg[5]-Seg[6]) do
348 -- starts at (segment-start + segment-header + segment-crc + segment-wrp + segemnt-wrc)-1
349 pld = pld..bytes[(start+4+1+Seg[5]+Seg[6]+i)-1].." "
352 if (KGH) then print("'Kaba Group Header' detected"); end
354 index = prepend_zero(tonumber(Seg[9])+1)
356 until (Seg[3] == 1 or tonumber(Seg[9]) == 126 )
359 -- print Segment values
360 function printSegment(SegmentData)
361 res = "\nSegment "..SegmentData[9]..": "
362 res = res.. "raw header="..SegmentData[0]..", "
363 res = res.. "flag="..SegmentData[1].." (valid="..SegmentData[2].." last="..SegmentData[3].."), "
364 res = res.. "len="..("%04d"):format(SegmentData[4])..", "
365 res = res.. "WRP="..prepend_zero(SegmentData[5])..", "
366 res = res.. "WRC="..prepend_zero(SegmentData[6])..", "
367 res = res.. "RD="..SegmentData[7]..", "
368 res = res.. "crc="..SegmentData[8]
372 -- write clone-data to tag
373 function writeToTag(plainBytes)
375 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
379 -- gather MCD & MSN from new Tag - this must be enterd manually
380 cmd = 'hf legic read 0x00 0x04'
382 print("\nthese are the MCD MSN0 MSN1 MSN2 from the Tag that has being read:")
383 cmd = 'data hexsamples 4'
385 print("^^ use this values as input for the following answers (one 2-digit-value per question/answer):")
386 -- enter MCD & MSN (in hex)
387 MCD = utils.input("type in MCD as 2-digit value - e.g.: 00", plainBytes[1])
388 MSN0 = utils.input("type in MSN0 as 2-digit value - e.g.: 01", plainBytes[2])
389 MSN1 = utils.input("type in MSN1 as 2-digit value - e.g.: 02", plainBytes[3])
390 MSN2 = utils.input("type in MSN2 as 2-digit value - e.g.: 03", plainBytes[4])
392 -- calculate crc8 over MCD & MSN
393 cmd = MCD..MSN0..MSN1..MSN2
394 MCC = ("%02x"):format(utils.Crc8Legic(cmd))
395 print("MCD:"..MCD..", MSN:"..MSN0.." "..MSN1.." "..MSN2..", MCC:"..MCC)
397 -- calculate new Segment-CRC for each valid segment
398 SegCrcs = getSegmentCrcBytes(plainBytes)
399 for i=0, (#SegCrcs-1) do
400 -- SegCrcs[i]-4 = address of first byte of segmentHeader (low byte segment-length)
401 segLen=tonumber(("%1x"):format(tonumber(bit32.extract("0x"..plainBytes[(SegCrcs[i]-3)],0,3),16))..("%02x"):format(tonumber(plainBytes[SegCrcs[i]-4],16)),16)
402 segStart=(SegCrcs[i]-4)
403 segEnd=(SegCrcs[i]-4+segLen)
404 KGH=CheckKgh(plainBytes,segStart,segEnd)
406 print("'Kaba Group Header' detected - re-calculate...")
408 cmd = MCD..MSN0..MSN1..MSN2..plainBytes[SegCrcs[i]-4]..plainBytes[SegCrcs[i]-3]..plainBytes[SegCrcs[i]-2]..plainBytes[SegCrcs[i]-1]
409 plainBytes[SegCrcs[i]] = ("%02x"):format(utils.Crc8Legic(cmd))
412 -- apply MCD & MSN to plain data
419 -- prepare plainBytes for writing (xor plain data with new MCC)
420 bytes = xorBytes(plainBytes, MCC)
422 -- write data to file
423 if (writeOutputBytes(bytes, "myLegicClone.hex")) then
424 WriteBytes = utils.input("enter number of bytes to write?", SegCrcs[#SegCrcs])
426 -- load file into pm3-buffer
427 cmd = 'hf legic load myLegicClone.hex'
430 -- write pm3-buffer to Tag
431 for i=0, WriteBytes do
432 if ( i<5 or i>6) then
433 cmd = ('hf legic write 0x%02x 0x01'):format(i)
436 -- write DCF in reverse order (requires 'mosci-patch')
437 cmd = 'hf legic write 0x05 0x02'
440 print("skipping byte 0x05 - will be written next step")
451 local oldcrc, newcrc, infile, outfile
455 -- parse arguments for the script
456 for o, a in getopt.getopt(args, 'hwsdc:i::o:') do
461 if (file_check(a)) then
462 local answer = utils.confirm("\nthe output-file "..a.." alredy exists!\nthis will delete the previous content!\ncontinue?")
463 if (answer==false) then return oops("quiting") end
469 if (file_check(infile)==false) then
470 return oops("input file: "..infile.." not found")
472 bytes = getInputBytes(infile)
475 if (bytes == false) then return oops('couldnt get input bytes') end
484 -- display segments switch
485 if o == "d" then ds = true; end
486 -- display summary switch
487 if o == "s" then ss = true; end
488 -- write to tag switch
489 if o == "w" then ws = true; end
491 if o == "h" then return help() end
494 if (not ifs) then return oops("option -i <input file> is required but missing") end
497 bytes = xorBytes(bytes, oldcrc)
499 -- show segments (works only on plain bytes)
501 print("+------------------------------------------- Segments -------------------------------------------+")
502 displaySegments(bytes);
505 if (ofs and ncs) then
506 -- xor bytes with new crc
507 newBytes = xorBytes(bytes, newcrc)
509 if (writeOutputBytes(newBytes, outfile)) then
510 -- show summary if requested
513 res = "\n+-------------------------------------------- Summary -------------------------------------------+"
514 res = res .."\ncreated clone_dump from\n\t"..infile.." crc: "..oldcrc.."\ndump_file:"
515 res = res .."\n\t"..outfile.." crc: "..string.sub(newcrc,-2)
516 res = res .."\nyou may load the new file with: hf legic load "..outfile
517 res = res .."\n\nif you don't write to tag immediately ('-w' switch) you will need to recalculate each segmentCRC"
518 res = res .."\nafter writing this dump to a tag!"
519 res = res .."\n\na segmentCRC gets calculated over MCD,MSN0..3,Segment-Header0..3"
520 res = res .."\ne.g. (based on Segment00 of the data from "..infile.."):"
521 res = res .."\nhf legic crc8 "..bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[23]..bytes[24]..bytes[25]..bytes[26]
522 -- this can not be calculated without knowing the new MCD, MSN0..2
528 -- show why the output-file was not written
529 print("\nnew file not written - some arguments are missing ..")
530 print("output file: ".. (ofs and outfile or "not given"))
531 print("new crc: ".. (ncs and newcrc or "not given"))
535 if (ws and #bytes == 1024) then
540 -- call main with arguments