2 (example) Legic-Prime Layout with 'Kaba Group Header'
3 +----+----+----+----+----+----+----+----+
4 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
5 +----+----+----+----+----+----+----+----+
6 0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
7 +----+----+----+----+----+----+----+----+
8 0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
9 +----+----+----+----+----+----+----+----+
10 0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
11 +----+----+----+----+----+----+----+----+
16 example = "script run legic"
22 This script helps you to read, create and modify Legic Prime Tags (MIM22, MIM256, MIM1024)
23 it's kinda interactive with following commands in three categories:
25 Data I/O Segment Manipulation File I/O
26 ------------------ -------------------- ---------------
27 rt => read Tag ds => dump Segments lf => load File
28 wt => write Tag as => add Segment sf => save File
29 es => edit Segment xf => xor File
30 ct => copy io Tag ed => edit Data
31 tc => copy oi Tag rs => remove Segment
32 di => dump inTag cc => check Segment-CRC
33 do => dump outTag ck => check KGH
36 q => quit et => edit Token h => this Help
39 rt: 'read tag' - reads a tag placed near to the PM3
40 wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3
41 without the need of changing anything - MCD,MSN,MCC will be read from the tag
42 before and applied to the output.
43 ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not usefull yet, but inernally needed
44 tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not usefull yet, but inernally needed
45 di: 'dump inTag' - shows the current content of the 'virtual Tag'
46 do: 'dump outTag' - shows the current content of the 'virtual outTag'
49 (all manipulations happens only in the 'virtual inTAG' - they need to be written with 'wt' to take effect)
50 ds: 'dump Segments' - will show the content of a selected Segment
51 as: 'add Segment' - will add a 'empty' Segment to the inTag
52 es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid)
53 all other Segment-Header-Values are either calculated or not needed to edit (yet)
54 ed: 'edit data' - edit the Data of a Segment (ADF-Aera / Stamp & Payload specific Data)
55 et: 'edit Token' - edit Data of a Token (CDF-Area / SAM, SAM64, SAM63, IAM, GAM specific Data)
56 mt: 'make Token' - create a Token 'from scratch' (guided)
57 rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token)
58 cc: 'check Segment-CRC'- checks & calculates (if check failed) the Segment-CRC of all Segments
59 ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected
60 'Kaba Group Header CRC calculation'
61 tk: 'toggle KGH' - toglle the (script-internal) flag for kgh-calculation for a segment
62 xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment
65 lf: 'load file' - load a (xored) file from the local Filesystem into the 'virtual inTag'
66 sf: 'save file' - saves the 'virtual inTag' to the local Filesystem (xored with Tag-MCC)
67 xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with choosen MCC - use '00' for plain values)
73 local utils = require('utils')
74 local getopt = require('getopt')
77 -- global variables / defines
78 local bxor = bit32.bxor
79 local bbit = bit32.extract
80 local input = utils.input
81 local confirm = utils.confirm
84 -- This is only meant to be used when errors occur
95 print("Example usage")
100 -- table check helper
102 return type(t) == 'table'
107 function xorme(hex, xor, index)
108 if ( index >= 23 ) then
109 return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
116 -- (de)obfuscate bytes
117 function xorBytes(inBytes, crc)
119 for index = 1, #inBytes do
120 bytes[index] = xorme(inBytes[index], crc, index)
122 if (#inBytes == #bytes) then
124 bytes[5] = string.sub(crc,-2)
127 print("error: byte-count missmatch")
133 -- check availability of file
134 function file_check(file_name)
135 local file_found=io.open(file_name, "r")
136 if file_found==nil then
144 -- read file into virtual-tag
145 function readFile(filename)
148 if (file_check(filename)==false) then
149 return oops("input file: "..filename.." not found")
151 bytes = getInputBytes(filename)
152 if (bytes == false) then return oops('couldnt get input bytes')
155 bytes = xorBytes(bytes,bytes[5])
156 -- create Tag for plain bytes
158 -- load plain bytes to tag-table
159 tag=bytesToTag(bytes, tag)
166 -- write bytes to file
167 function writeFile(bytes, filename)
168 if (filename~='MylegicClone.hex') then
169 if (file_check(filename)) then
170 local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
171 if (answer==false) then return print("user abort") end
176 local fho,err = io.open(filename, "w")
177 if err then oops("OOps ... faild to open output-file ".. filename) end
178 bytes=xorBytes(bytes, bytes[5])
182 elseif (bcnt <= 7) then
183 line=line.." "..bytes[i]
186 -- write line to new file
187 fho:write(line.."\n")
188 -- reset counter & line
195 print("\nwrote ".. #bytes .." bytes to " .. filename)
200 -- put certain bytes into a new table
201 function bytesToTable(bytes, bstart, bend)
203 for i=0, (bend-bstart) do
210 -- read from pm3 into virtual-tag
211 function readFromPM3()
212 local tag, bytes, infile
213 --if (confirm("is the Tag placed onto the Proxmak3 and ready for reading?")) then
214 --print("reading Tag ...")
215 --infile=input("input a name for the temp-file:", "legic.temp")
216 --if (file_check(infile)) then
217 -- local answer = confirm("\nthe output-file "..infile.." alredy exists!\nthis will delete the previous content!\ncontinue?")
218 -- if (answer==false) then return print("user abort") end
221 core.console("hf legic reader")
222 core.console("hf legic save "..infile)
223 --print("read temp-file into virtual Tag ...")
226 --else return print("user abort"); end
230 -- write virtual Tag to real Tag
231 function writeToTag(plainBytes, taglen, filename)
233 if(utils.confirm("\nplace your empty tag onto the PM3 to read & write\n") == false) then
237 -- write data to file
239 WriteBytes = utils.input("enter number of bytes to write?", taglen)
241 -- load file into pm3-buffer
242 if (type(filename)~="string") then filename=input("filename to load to pm3-buffer?","legic.temp") end
243 cmd = 'hf legic load '..filename
246 -- write pm3-buffer to Tag
247 for i=0, WriteBytes do
248 if ( i<5 or i>6) then
249 cmd = ('hf legic write 0x%02x 0x01'):format(i)
253 -- write DCF in reverse order (requires 'mosci-patch')
254 cmd = 'hf legic write 0x05 0x02'
258 print("skipping byte 0x05 - will be written next step")
266 -- read file into table
267 function getInputBytes(infile)
270 local fhi,err = io.open(infile)
271 if err then oops("faild to read from file ".. infile); return false; end
274 if line == nil then break end
275 for byte in line:gmatch("%w+") do
276 table.insert(bytes, byte)
280 print(#bytes .. " bytes from "..infile.." loaded")
285 -- create tag-table helper
286 function createTagTable()
312 -- put bytes into tag-table
313 function bytesToTag(bytes, tag)
314 if(istable(tag)) then
324 tag.Type=getTokenType(tag.DCFl);
325 tag.OLE=bbit("0x"..tag.DCFl,7,1)
326 tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4))
327 tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3))
328 tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1))
329 tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10))
330 tag.data=bytesToTable(bytes, 10, 13)
331 tag.Bck=bytesToTable(bytes, 14, 20)
332 tag.MTC=bytesToTable(bytes, 21, 22)
334 print("Tag-Type: ".. tag.Type)
335 if (tag.Type=="SAM" and #bytes>23) then
336 tag=segmentsToTag(bytes, tag)
337 print((#tag.SEG+1).." Segment(s) found")
338 -- unsegmented Master-Token
342 table.insert(tag.data, tag.Bck[i])
344 tag.data[#tag.data]=tag.MTC[0]
346 --tag.MTC[0]=tag.MTC[1]
349 print(#bytes.." bytes for Tag processed")
352 return oops("tag is no table in: bytesToTag ("..type(tag)..")")
356 -- read Tag-Table in bytes-table
357 function tagToBytes(tag)
358 if (istable(tag)) then
362 table.insert(bytes, tag.MCD)
363 table.insert(bytes, tag.MSN0)
364 table.insert(bytes, tag.MSN1)
365 table.insert(bytes, tag.MSN2)
366 table.insert(bytes, tag.MCC)
367 table.insert(bytes, tag.DCFl)
368 table.insert(bytes, tag.DCFh)
369 table.insert(bytes, tag.raw)
370 table.insert(bytes, tag.SSC)
372 for i=0, #tag.data do
373 table.insert(bytes, tag.data[i])
376 if(istable(tag.Bck)) then
378 table.insert(bytes, tag.Bck[i])
381 -- token-create-time / master-token crc
383 table.insert(bytes, tag.MTC[i])
386 if (type(tag.SEG[0])=='table') then
388 for i2=1, #tag.SEG[i].raw+1 do
389 table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2])
391 table.insert(bytes, #bytes+1, tag.SEG[i].crc)
392 for i2=0, #tag.SEG[i].data-1 do
393 table.insert(bytes, #bytes+1, tag.SEG[i].data[i2])
398 for i=#bytes+1, 1024 do
399 table.insert(bytes, i, '00')
401 print(#bytes.." bytes of Tag dumped")
404 return oops("tag is no table in tagToBytes ("..type(tag)..")")
411 ['Type'] = {"SAM", "SAM63", "SAM64", "IAM", "GAM"},
412 ['DCF'] = {"60ea", "31fa", "30fa", "80fa", "f0fa"},
413 ['WRP'] = {"15", "2", "2", "2", "2"},
414 ['WRC'] = {"01", "02", "02", "00", "00"},
415 ['RD'] = {"01", "00", "00", "00", "00"},
416 ['Stamp'] = {"00", "00", "00", "00", "00"},
417 ['Segment'] = {"0d", "c0", "04", "00", "be", "01", "02", "03", "04", "01", "02", "03", "04"}
420 for k, v in pairs(mt.Type) do
421 ttype=ttype..k..") "..v.." "
423 mtq=tonumber(input("select number for Token-Type\n"..ttype, '1'), 10)
424 if (type(mtq)~="number") then return print("selection invalid!")
425 elseif (mtq>#mt.Type) then return print("selection invalid!")
426 else print("Token-Type '"..mt.Type[mtq].."' selected") end
427 local raw=calcHeaderRaw(mt.WRP[mtq], mt.WRC[mtq], mt.RD[mtq])
430 bytes={"01", "02", "03", "04", "cb", string.sub(mt.DCF[mtq], 0, 2), string.sub(mt.DCF[mtq], 3), raw,
431 "00", "00", "00", "00", "00", "00", "00", "00",
432 "00", "00", "00", "00", "00", "00"}
434 for i=0, #mt.Segment do
435 table.insert(bytes, mt.Segment[i])
440 for i=#bytes, 1023 do table.insert(bytes, "00") end
441 -- if Master-Token -> calc Master-Token-CRC
442 if (mtq>1) then bytes[22]=calcMtCrc(bytes) end
443 local tempTag=createTagTable()
444 -- remove segment if MasterToken
445 if (mtq>1) then tempTag.SEG[0]=nil end
446 return bytesToTag(bytes, tempTag)
451 function editTag(tag)
452 -- for simulation it makes sense to edit everything
453 local edit_sim="MCD MSN0 MSN2 MSN2 MCC DCFl DCFh WRP WRC RD"
454 -- on real tags it makes only sense to edit DCF, WRP, WRC, RD
455 local edit_real="DCFl DCFh WRP WRC RD"
456 if (confirm("do you want to edit non-writeable values (e.g. for simulation)?")) then
458 else edit_tag=edit_real end
460 if(istable(tag)) then
461 for k,v in pairs(tag) do
462 if(type(v)~="table" and type(v)~="boolean" and string.find(edit_tag, k)) then
463 tag[k]=input("value for: "..k, v)
467 if (tag.Type=="SAM") then ttype="Header"; else ttype="Stamp"; end
468 if (confirm("do you want to edit "..ttype.." Data?")) then
469 -- master-token specific
470 if(istable(tag.Bck)==false) then
471 -- stamp-data length=(0xfc-DCFh)
472 -- on MT: SSC holds the Starting Stamp Character (Stamp0)
473 tag.SSC=input(ttype.."0: ", tag.SSC)
474 -- rest of stamp-bytes are in tag.data 0..n
475 for i=0, (tonumber(0xfc ,10)-("%d"):format('0x'..tag.DCFh))-2 do
476 tag.data[i]=input(ttype.. i+1 ..": ", tag.data[i])
479 --- on credentials byte7 should always be 9f and byte8 ff
480 -- on Master-Token not (even on SAM63/64 not)
481 -- tag.SSC=input(ttype.."0: ", tag.SSC)
482 for i=0, #tag.data do
483 tag.data[i]=input(ttype.. i ..": ", tag.data[i])
488 bytes=tagToBytes(tag)
490 --- check data-consistency (calculate tag.raw)
491 bytes[8]=calcHeaderRaw(tag.WRP, tag.WRC, tag.RD)
493 --- Master-Token specific
494 -- should be triggered if a SAM was converted to a non-SAM (user-Token to Master-Token)
495 -- or a Master-Token has being edited (also SAM64 & SAM63 - which are in fact Master-Token)
496 if(tag.Type~="SAM" or bytes[6]..bytes[7]~="60ea") then
497 -- calc new Master-Token crc
498 bytes[22]=calcMtCrc(bytes)
500 -- ensure tag.SSC set to 'ff' on credential-token (SAM)
502 -- if a Master-Token was converted to a Credential-Token
503 -- lets unset the Time-Area to 00 00 (will contain Stamp-Data on MT)
508 tag=bytesToTag(bytes, tag)
513 -- calculates header-byte (addr 0x07)
514 function calcHeaderRaw(wrp, wrc, rd)
516 wrp=("%02x"):format(tonumber(wrp, 10))
518 res=("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0))
523 -- dump virtual Tag-Data
524 function dumpTag(tag)
530 res ="\nCDF: System Area"
531 res= res.."\n"..dumpCDF(tag)
532 -- segments (user-token area)
533 if(tag.Type=="SAM") then
534 res = res.."\n\nADF: User Area"
536 res=res.."\n"..dumpSegment(tag, i).."\n"
543 -- get segmemnt-data from byte-table
544 function getSegmentData(bytes, start, index)
551 ['raw'] = {'00', '00', '00', '00'},
559 if (bytes[start]) then
562 segment.index = index
563 -- flag = high nibble of byte 1
564 segment.flag = string.sub(bytes[start+1],0,1)
565 -- valid = bit 6 of byte 1
566 segment.valid = bbit("0x"..bytes[start+1],6,1)
567 -- last = bit 7 of byte 1
568 segment.last = bbit("0x"..bytes[start+1],7,1)
569 -- len = (byte 0)+(bit0-3 of byte 1)
570 segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16)
571 -- raw segment header
572 segment.raw = {bytes[start], bytes[start+1], bytes[start+2], bytes[start+3]}
573 -- wrp (write proteted) = byte 2
574 segment.WRP = tonumber(bytes[start+2],16)
575 -- wrc (write control) - bit 4-6 of byte 3
576 segment.WRC = tonumber(bbit("0x"..bytes[start+3],4,3),16)
577 -- rd (read disabled) - bit 7 of byte 3
578 segment.RD = tonumber(bbit("0x"..bytes[start+3],7,1),16)
580 segment.crc = bytes[start+4]
581 -- segment-data starts at segment.len - segment.header - segment.crc
582 for i=0, (segment.len-5) do
583 segment.data[i]=bytes[start+5+i]
591 -- put segments from byte-table to tag-table
592 function segmentsToTag(bytes, tag)
596 if (istable(tag)) then
599 tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i))
600 if (tag.Type=="SAM") then
601 if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end
603 start=start+tag.SEG[i].len
604 until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126)
606 else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end
607 else print("no Segments: must be a MIM22") end
611 -- regenerate segment-header (after edit)
612 function regenSegmentHeader(segment)
614 local raw = segment.raw
616 -- len bit0..7 | len=12bit=low nibble of byte1..byte0
617 raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8))
618 -- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh?
619 raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),4.4)..bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8))
621 raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8))
623 raw[4]=("%02x"):format(bbit("0x"..("%03x"):format(seg.WRC),4,3)..bbit("0x"..("%02x"):format(seg.RD*128),0,8))
625 seg.flag=string.sub(raw[2],0,1)
626 --print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4])
627 if(#seg.data>(seg.len-5)) then
628 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
629 print("Data-Length has being reduced: removing ".. #seg.data-(seg.len-5) .." bytes from Payload");
630 for i=(seg.len-5), #seg.data-1 do
631 table.remove(seg.data)
633 elseif (#seg.data<(seg.len-5)) then
634 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
635 print("Data-Length has being extended: adding "..(seg.len-5)-#seg.data.." bytes to Payload");
636 for i=#seg.data, (seg.len-5)-1 do
637 table.insert(seg.data, '00')
644 -- determine TagType (bits 0..6 of DCFlow)
645 function getTokenType(DCFl)
651 local tt = tonumber(bbit("0x"..DCFl,0,7),10)
652 if (tt >= 0 and tt <= 47) then tt = "IAM"
653 elseif (tt == 49) then tt = "SAM63"
654 elseif (tt == 48) then tt = "SAM64"
655 elseif (tt >= 50 and tt <= 111) then tt = "SAM"
656 elseif (tt >= 112 and tt <= 127) then tt = "GAM"
662 -- dump tag-system area
663 function dumpCDF(tag)
668 if (istable(tag)) then
669 res = res.."MCD: "..tag.MCD..", MSN: "..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..", MCC: "..tag.MCC.."\n"
670 res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n"
671 res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..((tag.raw=='9f') and (", SSC="..tag.SSC.."\n") or "\n")
673 -- credential (end-user tag)
674 if (tag.Type=="SAM") then
675 res = res.."Remaining Header Area\n"
676 for i=0, (#tag.data) do
677 res = res..tag.data[i].." "
679 res = res.."\nBackup Area\n"
680 for i=0, (#tag.Bck) do
681 res = res..tag.Bck[i].." "
683 res = res.."\nTime Area\n"
684 for i=0, (#tag.MTC) do
685 res = res..tag.MTC[i].." "
688 -- Master Token specific
690 res = res .."Master-Token Area\nStamp: "
691 res= res..tag.SSC.." "
692 for i=0, tag.Stamp_len-2 do
693 res = res..tag.data[i].." "
695 res=res.."\nunused payload\n"
696 for i=0, (#tag.data-tag.Stamp_len-1) do
697 res = res..tag.data[i].." "
699 bytes=tagToBytes(tag)
700 local mtcrc=calcMtCrc(bytes)
701 res=res.."\nMaster-Token CRC: "
702 res = res ..tag.MTC[1].." ("..((tag.MTC[1]==mtcrc) and "valid" or "error")..")"
705 else print("no valid Tag in dumpCDF") end
709 -- dump single segment
710 function dumpSegment(tag, index)
713 local dp=0 --data-position in table
714 local res="" --result
715 local raw="" --raw-header
717 if ( (istable(tag.SEG[i])) and tag.Type=="SAM") then
718 if (istable(tag.SEG[i].raw)) then
719 for k,v in pairs(tag.SEG[i].raw) do
725 res = res.."Segment "..("%02d"):format(tag.SEG[i].index)..": "
726 res = res .."raw header:"..string.sub(raw,0,-2)..", flag="..tag.SEG[i].flag..", (valid="..("%x"):format(tag.SEG[i].valid)..", last="..("%x"):format(tag.SEG[i].last).."), "
727 res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", "
728 res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." "
729 res = res .."("..(checkSegmentCrc(tag, i) and "valid" or "error")..")"
733 if (tag.SEG[i].WRC>0) then
734 res = res .."\nWRC protected area (Stamp):\n"
735 for i2=dp, tag.SEG[i].WRC-1 do
736 res = res..tag.SEG[i].data[dp].." "
742 if (tag.SEG[i].WRP>tag.SEG[i].WRC) then
743 res = res .."\nRemaining write protected area (Stamp):\n"
744 for i2=dp, tag.SEG[i].WRP-tag.SEG[i].WRC-1 do
745 res = res..tag.SEG[i].data[dp].." "
751 if (#tag.SEG[i].data-dp>0) then
752 res = res .."\nRemaining segment payload:\n"
753 for i2=dp, #tag.SEG[i].data-2 do
754 res = res..tag.SEG[i].data[dp].." "
757 if (tag.SEG[i].kgh) then
758 res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and "valid" or "error")..")"
759 else res = res..tag.SEG[i].data[dp] end
764 return print("Segment not found")
769 -- edit segment helper
770 function editSegment(tag, index)
772 local edit_possible="valid len RD WRP WRC Stamp Payload"
773 if (istable(tag.SEG[index])) then
774 for k,v in pairs(tag.SEG[index]) do
775 if(string.find(edit_possible,k)) then
776 tag.SEG[index][k]=tonumber(input(k..": ", v),10)
779 else print("Segment with Index: "..("%02d"):format(index).." not found in Tag")
782 regenSegmentHeader(tag.SEG[index])
783 print("\n"..dumpSegment(tag, index).."\n")
789 function editSegmentData(data)
790 if (istable(data)) then
792 data[i]=input("Data"..i..": ", data[i])
796 print("no Segment-Data found")
801 -- list available segmets in virtual tag
802 function segmentList(tag)
805 if (istable(tag.SEG[0])) then
807 res = res .. tag.SEG[i].index .. " "
810 else print("no Segments found in Tag")
816 -- helper to selecting a segment
817 function selectSegment(tag)
819 if (istable(tag.SEG[0])) then
820 print("availabe Segments:\n"..segmentList(tag))
821 sel=input("select Segment: ", '00')
823 if (sel) then return sel
826 print("\nno Segments found")
833 function addSegment(tag)
841 ['raw'] = {'0d', '40', '04', '00'},
849 if (istable(tag.SEG[0])) then
850 tag.SEG[#tag.SEG].last=0
851 table.insert(tag.SEG, segment)
853 tag.SEG[#tag.SEG].data[i]=("%02x"):format(i)
855 tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG)
858 print("no Segment-Table found")
863 -- delete segment (except segment 00)
864 function delSegment(tag, index)
865 if (istable(tag.SEG[0])) then
867 if (type(index)=="string") then index=tonumber(index,10) end
869 table.remove(tag.SEG, index)
871 tag.SEG[i].index=("%02d"):format(i)
874 if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end
880 -- calculate Master-Token crc
881 function calcMtCrc(bytes)
883 local cmd=bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[7]..bytes[6]..bytes[8]
884 local len=(tonumber(0xfc ,10)-("%d"):format('0x'..bytes[7]))
888 local res=("%02x"):format(utils.Crc8Legic(cmd))
893 -- check all segmnet-crc
894 function checkAllSegCrc(tag)
895 if (istable(tag.SEG[0])) then
897 crc=calcSegmentCrc(tag, i)
900 else return print("Matser-Token / unsegmented Tag") end
904 -- check all segmnet-crc
905 function checkAllKghCrc(tag)
906 if (istable(tag.SEG[0])) then
908 crc=calcKghCrc(tag, i)
909 if (tag.SEG[i].kgh) then
910 tag.SEG[i].data[#tag.SEG[i].data-1]=crc
917 -- build kghCrc credentials
918 function kghCrcCredentials(tag, segid)
919 if (istable(tag) and istable(tag.SEG[0])) then
921 if (type(segid)=="string") then segid=tonumber(segid,10) end
922 if (segid>0) then x='93' end
923 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP)
924 cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x
925 for i=0, #tag.SEG[segid].data-2 do
926 cred = cred..tag.SEG[segid].data[i]
933 -- validate kghCRC to segment in tag-table
934 function checkKghCrc(tag, segid)
935 if (type(tag.SEG[segid])=='table') then
936 if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then
937 local data=kghCrcCredentials(tag, segid)
938 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end
939 else return false; end
940 else oops("'Kaba Group header' detected but no Segment-Data found") end
944 -- calcuate kghCRC for a given segment
945 function calcKghCrc(tag, segid)
946 if (istable(tag.SEG[0])) then
947 -- check if a 'Kaber Group Header' exists
949 local data=kghCrcCredentials(tag, segid)
950 return ("%02x"):format(utils.Crc8Legic(data))
955 -- build segmentCrc credentials
956 function segmentCrcCredentials(tag, segid)
957 if (istable(tag.SEG[0])) then
958 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
959 cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4]
961 else return print("Master-Token / unsegmented Tag!") end
965 -- validate segmentCRC for a given segment
966 function checkSegmentCrc(tag, segid)
967 local data=segmentCrcCredentials(tag, segid)
968 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then
975 -- calculate segmentCRC for a given segment
976 function calcSegmentCrc(tag, segid)
977 if (istable(tag.SEG[0])) then
978 -- check if a 'Kaber Group Header' exists
979 local data=segmentCrcCredentials(tag, segid)
980 return ("%02x"):format(utils.Crc8Legic(data))
985 -- helptext for modify-mode
986 function modifyHelp()
989 Data I/O Segment Manipulation File I/O
990 ------------------ -------------------- ------------------
991 rt => read Tag ds => dump Segments lf => load File
992 wt => write Tag as => add Segment sf => save File
993 es => edit Segment xf => xor to File
994 ct => copy io Tag ed => edit Data
995 tc => copy oi Tag rs => remove Segment
996 cc => check Segment-CRC
997 di => dump inTag ck => check KGH
998 do => dump outTag tk => toggle KGH-Flag
1000 q => quit et => edit Token h => this Help
1006 -- modify Tag (interactive)
1007 function modifyMode()
1008 local i, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes
1011 print(modifyHelp().."\n".."tags im Memory:"..(istable(inTAG) and " inTAG" or "")..(istable(outTAG) and " outTAG" or ""))
1013 ["rt"] = function(x) inTAG=readFromPM3(); actions.di() end,
1014 ["wt"] = function(x)
1015 if(istable(inTAG.SEG)) then
1017 if (istable(inTAG.Bck)) then
1018 for i=0, #inTAG.SEG do
1019 taglen=taglen+inTAG.SEG[i].len+5
1022 -- read new tag (output tag)
1023 outTAG=readFromPM3()
1024 outbytes=tagToBytes(outTAG)
1025 -- copy 'inputbuffer' to 'outputbuffer'
1026 inTAG.MCD = outbytes[1]
1027 inTAG.MSN0 = outbytes[2]
1028 inTAG.MSN1 = outbytes[3]
1029 inTAG.MSN2 = outbytes[4]
1030 inTAG.MCC = outbytes[5]
1031 -- recheck all segments-crc/kghcrc (only on a credential)
1032 if(istable(inTAG.Bck)) then
1033 checkAllSegCrc(inTAG)
1034 checkAllKghCrc(inTAG)
1036 --get bytes from ready outTAG
1037 bytes=tagToBytes(inTAG)
1039 if (inTAG.Type~="SAM") then bytes[22]=calcMtCrc(bytes) end
1041 writeFile(bytes, 'MylegicClone.hex')
1042 writeToTag(bytes, taglen, 'MylegicClone.hex')
1047 ["ct"] = function(x)
1048 print("copy virtual input-TAG to output-TAG")
1051 ["tc"] = function(x)
1052 print("copy virtual output-TAG to input-TAG")
1055 ["lf"] = function(x)
1056 if (file_check(x)) then filename=x
1057 else filename=input("enter filename: ", "legic.temp") end
1058 inTAG=readFile(filename)
1060 ["sf"] = function(x)
1061 if(istable(inTAG)) then
1062 outfile=input("enter filename:", "legic.temp")
1063 bytes=tagToBytes(inTAG)
1064 --bytes=xorBytes(bytes, inTAG.MCC)
1066 writeFile(bytes, outfile)
1070 ["xf"] = function(x)
1071 if(istable(inTAG)) then
1072 outfile=input("enter filename:", "legic.temp")
1073 crc=input("enter new crc: ('00' for a plain dump)", inTAG.MCC)
1074 print("obfuscate witth: "..crc)
1075 bytes=tagToBytes(inTAG)
1078 writeFile(bytes, outfile)
1082 ["di"] = function(x) if (istable(inTAG)) then print("\n"..dumpTag(inTAG).."\n") end end,
1083 ["do"] = function(x) if (istable(outTAG)) then print("\n"..dumpTag(outTAG).."\n") end end,
1084 ["ds"] = function(x)
1085 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1086 else sel=selectSegment(inTAG) end
1087 if (sel) then print("\n"..(dumpSegment(inTAG, sel) or "no Segments available").."\n") end
1089 ["es"] = function(x)
1090 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1091 else sel=selectSegment(inTAG) end
1093 if(istable(inTAG.SEG[0])) then
1094 inTAG=editSegment(inTAG, sel)
1095 inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel])
1096 else print("no Segments in Tag") end
1099 ["as"] = function(x)
1100 if (istable(inTAG.SEG[0])) then
1101 inTAG=addSegment(inTAG)
1102 inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1])
1103 inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG])
1104 else print("Master-Token / unsegmented Tag!")
1107 ["rs"] = function(x)
1108 if (istable(inTAG.SEG[0])) then
1109 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1110 else sel=selectSegment(inTAG) end
1111 inTAG=delSegment(inTAG, sel)
1112 for i=0, #inTAG.SEG do
1113 inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i])
1117 ["ed"] = function(x)
1118 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1119 else sel=selectSegment(inTAG) end
1121 inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data)
1124 ["et"] = function(x)
1125 if (istable(inTAG)) then
1129 ["mt"] = function(x) inTAG=makeToken(); actions.di() end,
1130 ["ts"] = function(x)
1131 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1132 else sel=selectSegment(inTAG) end
1133 regenSegmentHeader(inTAG.SEG[sel])
1135 ["tk"] = function(x)
1136 if (istable(inTAG) and istable(inTAG.SEG[0])) then
1137 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1138 else sel=selectSegment(inTAG) end
1139 if(inTAG.SEG[sel].kgh) then inTAG.SEG[sel].kgh=false
1140 else inTAG.SEG[sel].kgh=true end
1144 if (type(x)=="string" and string.len(x)>0) then
1145 print(("%02x"):format(utils.Crc8Legic(x)))
1148 ["xc"] = function(x)
1149 if (istable(inTAG) and istable(inTAG.SEG[0])) then
1150 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1151 else sel=selectSegment(inTAG) end
1152 print("k "..kghCrcCredentials(inTAG, sel))
1155 ["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end,
1156 ["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end,
1158 print("modify-modus! enter 'h' for help or 'q' to quit")
1160 ic=input("Legic command? ('h' for help - 'q' for quit)", "h")
1162 if (type(actions[string.lower(string.sub(ic,0,1))])=='function') then
1163 actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3))
1164 elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then
1165 actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4))
1166 else actions.h('') end
1167 until (string.sub(ic,0,1)=="q")
1172 if (#args == 0 ) then modifyMode() end
1174 local inTAG, outTAG, outfile, interactive, crc, ofs, cfs, dfs
1175 -- just a spacer for better readability
1178 for o, a in getopt.getopt(args, 'hrmi:do:c:') do
1180 if o == "h" then return help(); end
1181 -- read tag from PM3
1182 if o == "r" then inTAG=readFromPM3() end
1184 if o == "i" then inTAG=readFile(a) end
1186 if o == "d" then dfs=true end
1187 -- interacive modifying
1188 if o == "m" then interactive=true; modifyMode() end
1189 -- xor (e.g. for clone or plain file)
1190 if o == "c" then cfs=true; crc=a end
1192 if o == "o" then outfile=a; ofs=true end
1195 -- file conversion (output to file)
1197 -- dump infile / tag-read
1199 print("-----------------------------------------")
1200 print(dumpTag(inTAG))
1202 bytes=tagToBytes(inTAG)
1204 -- xor willl be done in function writeFile
1205 -- with the value of byte[5]
1210 writeFile(bytes, outfile)
1211 --- read real tag into virtual tag
1212 -- inTAG=readFromPM3() end
1213 --- or simply use the bytes that where wriiten
1214 inTAG=bytesToTag(bytes, inTAG)
1217 print("-----------------------------------------")
1218 print(dumpTag(inTAG))