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