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 +----+----+----+----+----+----+----+----+
15 MSN = Manufacturer SerialNumber
16 60 ea = DCF Low + DCF high
17 9f = raw byte which holds the bits for OLE,WRP,WRC,RD
18 ff = unknown but important
20 11 = unknown but important
21 Bck = header-backup-area
22 00 00 = Year (00 = 2000) & Week (not important)
24 SegC = Crc8 over the Segment Header
25 Stp = Stamp (could be more as 4 - up to 7)
26 UID = dec User-ID for online-Mapping
27 kghC = crc8 over MCD + MSN0..MSN2 + UID
30 (example) Legic-Cash on MIM256/1024 tag' (37 bytes)
31 +----+----+----+----+----+----+----+----+
32 0x00|Seg0|Seg1|Seg2|Seg3|SegC|STP0|STP1|STP2|
33 +----+----+----+----+----+----+----+----+
34 0x08|STP3|STP4|STP5|STP6| 01 |CURh|CURl|LIMh|
35 +----+----+----+----+----+----+----+----+
36 0x10|LIMm|LIMl|CHKh|CHKl|BALh|BALm|BALl|LRBh|
37 +----+----+----+----+----+----+----+----+
38 0x18|LRBm|LRBl|CHKh|CHKl|SHDh|SHDm|SHDl|LRSh|
39 +----+----+----+----+----+----+----+----+
40 0x20|LRSm|LRSl| CV |CHKh|CHKl|
41 +----+----+----+----+----+
42 STP = Stamp (seems to be always 7 bytes)
43 01 = unknown but important
44 CUR = currency in HEX (ISO 4217)
46 CHK = crc16 over byte-addr 0x05..0x12
48 LRB = ID of the reader that changed the balance
49 CHK = crc16 over BAL + LRB
51 LRS = ID of the reader that changed the shadow balance (?? should be always the same as LRB)
52 CV = Counter value for transactions
53 CHK = crc16 over SHD + LRS + CV
56 example = "script run legic"
62 This script helps you to read, create and modify Legic Prime Tags (MIM22, MIM256, MIM1024)
63 it's kinda interactive with following commands in three categories:
65 Data I/O Segment Manipulation Token-Data
66 ----------------- -------------------- -----------------
67 rt => read Tag as => add Segment mt => make Token
68 wt => write Tag es => edit Segment Header et => edit Token data
69 ct => copy io Tag ed => edit Segment Data tk => toggle KGH-Flag
70 tc => copy oi Tag rs => remove Segment
71 cc => check Segment-CRC File I/O
72 di => dump inTag ck => check KGH -----------------
73 do => dump outTag lf => load File
74 ds => dump Segments sf => save File
75 lc => dump Legic-Cash xf => xor to File
76 d3p => dump 3rd Party Cash
77 r3p => raw 3rd Party Cash
80 rt: 'read tag' - reads a tag placed near to the PM3
81 wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3
82 without the need of changing anything - MCD,MSN,MCC will be read from the tag
83 before and applied to the output.
84 ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not usefull yet, but inernally needed
85 tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not usefull yet, but inernally needed
86 di: 'dump inTag' - shows the current content of the 'virtual Tag'
87 do: 'dump outTag' - shows the current content of the 'virtual outTag'
88 ds: 'dump Segments' - will show the content of a selected Segment
89 as: 'add Segment' - will add a 'empty' Segment to the inTag
90 es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid)
91 all other Segment-Header-Values are either calculated or not needed to edit (yet)
92 ed: 'edit data' - edit the Data of a Segment (ADF-Aera / Stamp & Payload specific Data)
93 et: 'edit Token' - edit Data of a Token (CDF-Area / SAM, SAM64, SAM63, IAM, GAM specific Data)
94 mt: 'make Token' - create a Token 'from scratch' (guided)
95 rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token)
96 cc: 'check Segment-CRC'- checks & calculates (if check failed) the Segment-CRC of all Segments
97 ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected
98 'Kaba Group Header CRC calculation'
99 tk: 'toggle KGH' - toglle the (script-internal) flag for kgh-calculation for a segment
100 xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment
101 dlc: 'dump Legic-Cash' - show balance and checksums of a legic-Cash Segment
102 d3p: 'dump 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd Party Cash-Segment
103 r3p: 'raw 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd Party Cash-Segment
104 e3p: 'edit 3rd Party' - edit Data in 3rd Party Cash Segment
105 lf: 'load file' - load a (xored) file from the local Filesystem into the 'virtual inTag'
106 sf: 'save file' - saves the 'virtual inTag' to the local Filesystem (xored with Tag-MCC)
107 xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with choosen MCC - use '00' for plain values)
113 local utils = require('utils')
114 local getopt = require('getopt')
117 -- global variables / defines
118 local bxor = bit32.bxor
119 local bbit = bit32.extract
120 local input = utils.input
121 local confirm = utils.confirm
124 -- curency-codes for Legic-Cash-Segments (ISO 4217)
133 -- This is only meant to be used when errors occur
144 print("Example usage")
149 -- table check helper
151 return type(t) == 'table'
156 function xorme(hex, xor, index)
157 if ( index >= 23 ) then
158 return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
165 -- (de)obfuscate bytes
166 function xorBytes(inBytes, crc)
168 for index = 1, #inBytes do
169 bytes[index] = xorme(inBytes[index], crc, index)
171 if (#inBytes == #bytes) then
173 bytes[5] = string.sub(crc,-2)
176 print("error: byte-count missmatch")
182 -- check availability of file
183 function file_check(file_name)
184 local file_found=io.open(file_name, "r")
185 if file_found==nil then
193 -- read file into virtual-tag
194 function readFile(filename)
197 if (file_check(filename)==false) then
198 return oops("input file: "..filename.." not found")
200 bytes = getInputBytes(filename)
201 if (bytes == false) then return oops('couldnt get input bytes')
204 bytes = xorBytes(bytes,bytes[5])
205 -- create Tag for plain bytes
207 -- load plain bytes to tag-table
208 tag=bytesToTag(bytes, tag)
215 -- write bytes to file
216 function writeFile(bytes, filename)
217 if (filename~='MylegicClone.hex') then
218 if (file_check(filename)) then
219 local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
220 if (answer==false) then return print("user abort") end
225 local fho,err = io.open(filename, "w")
226 if err then oops("OOps ... faild to open output-file ".. filename) end
227 bytes=xorBytes(bytes, bytes[5])
231 elseif (bcnt <= 7) then
232 line=line.." "..bytes[i]
235 -- write line to new file
236 fho:write(line.."\n")
237 -- reset counter & line
244 print("\nwrote ".. #bytes .." bytes to " .. filename)
249 -- put certain bytes into a new table
250 function bytesToTable(bytes, bstart, bend)
252 for i=0, (bend-bstart) do
259 -- read from pm3 into virtual-tag
260 function readFromPM3()
261 local tag, bytes, infile
262 --if (confirm("is the Tag placed onto the Proxmak3 and ready for reading?")) then
263 --print("reading Tag ...")
264 --infile=input("input a name for the temp-file:", "legic.temp")
265 --if (file_check(infile)) then
266 -- local answer = confirm("\nthe output-file "..infile.." alredy exists!\nthis will delete the previous content!\ncontinue?")
267 -- if (answer==false) then return print("user abort") end
270 core.console("hf legic reader")
271 core.console("hf legic save "..infile)
272 --print("read temp-file into virtual Tag ...")
275 --else return print("user abort"); end
279 -- write virtual Tag to real Tag
280 function writeToTag(plainBytes, taglen, filename)
282 if(utils.confirm("\nplace your empty tag onto the PM3 to read & write\n") == false) then
286 -- write data to file
288 WriteBytes = utils.input("enter number of bytes to write?", taglen)
290 -- load file into pm3-buffer
291 if (type(filename)~="string") then filename=input("filename to load to pm3-buffer?","legic.temp") end
292 cmd = 'hf legic load '..filename
295 -- write pm3-buffer to Tag
296 for i=0, WriteBytes do
297 if ( i<5 or i>6) then
298 cmd = ('hf legic write 0x%02x 0x01'):format(i)
302 -- write DCF in reverse order (requires 'mosci-patch')
303 cmd = 'hf legic write 0x05 0x02'
307 print("skipping byte 0x05 - will be written next step")
315 -- read file into table
316 function getInputBytes(infile)
319 local fhi,err = io.open(infile)
320 if err then oops("faild to read from file ".. infile); return false; end
323 if line == nil then break end
324 for byte in line:gmatch("%w+") do
325 table.insert(bytes, byte)
329 print(#bytes .. " bytes from "..infile.." loaded")
334 -- create tag-table helper
335 function createTagTable()
361 -- put bytes into tag-table
362 function bytesToTag(bytes, tag)
363 if(istable(tag)) then
373 tag.Type=getTokenType(tag.DCFl);
374 tag.OLE=bbit("0x"..tag.DCFl,7,1)
375 tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4))
376 tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3))
377 tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1))
378 tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10))
379 tag.data=bytesToTable(bytes, 10, 13)
380 tag.Bck=bytesToTable(bytes, 14, 20)
381 tag.MTC=bytesToTable(bytes, 21, 22)
383 print("Tag-Type: ".. tag.Type)
384 if (tag.Type=="SAM" and #bytes>23) then
385 tag=segmentsToTag(bytes, tag)
386 print((#tag.SEG+1).." Segment(s) found")
387 -- unsegmented Master-Token
391 table.insert(tag.data, tag.Bck[i])
393 tag.data[#tag.data]=tag.MTC[0]
395 --tag.MTC[0]=tag.MTC[1]
398 print(#bytes.." bytes for Tag processed")
401 return oops("tag is no table in: bytesToTag ("..type(tag)..")")
405 -- read Tag-Table in bytes-table
406 function tagToBytes(tag)
407 if (istable(tag)) then
411 table.insert(bytes, tag.MCD)
412 table.insert(bytes, tag.MSN0)
413 table.insert(bytes, tag.MSN1)
414 table.insert(bytes, tag.MSN2)
415 table.insert(bytes, tag.MCC)
416 table.insert(bytes, tag.DCFl)
417 table.insert(bytes, tag.DCFh)
418 table.insert(bytes, tag.raw)
419 table.insert(bytes, tag.SSC)
421 for i=0, #tag.data do
422 table.insert(bytes, tag.data[i])
425 if(istable(tag.Bck)) then
427 table.insert(bytes, tag.Bck[i])
430 -- token-create-time / master-token crc
432 table.insert(bytes, tag.MTC[i])
435 if (type(tag.SEG[0])=='table') then
437 for i2=1, #tag.SEG[i].raw+1 do
438 table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2])
440 table.insert(bytes, #bytes+1, tag.SEG[i].crc)
441 for i2=0, #tag.SEG[i].data-1 do
442 table.insert(bytes, #bytes+1, tag.SEG[i].data[i2])
447 for i=#bytes+1, 1024 do
448 table.insert(bytes, i, '00')
450 print(#bytes.." bytes of Tag dumped")
453 return oops("tag is no table in tagToBytes ("..type(tag)..")")
460 ['Type'] = {"SAM", "SAM63", "SAM64", "IAM", "GAM"},
461 ['DCF'] = {"60ea", "31fa", "30fa", "80fa", "f0fa"},
462 ['WRP'] = {"15", "2", "2", "2", "2"},
463 ['WRC'] = {"01", "02", "02", "00", "00"},
464 ['RD'] = {"01", "00", "00", "00", "00"},
465 ['Stamp'] = {"00", "00", "00", "00", "00"},
466 ['Segment'] = {"0d", "c0", "04", "00", "be", "01", "02", "03", "04", "01", "02", "03", "04"}
469 for k, v in pairs(mt.Type) do
470 ttype=ttype..k..") "..v.." "
472 mtq=tonumber(input("select number for Token-Type\n"..ttype, '1'), 10)
473 if (type(mtq)~="number") then return print("selection invalid!")
474 elseif (mtq>#mt.Type) then return print("selection invalid!")
475 else print("Token-Type '"..mt.Type[mtq].."' selected") end
476 local raw=calcHeaderRaw(mt.WRP[mtq], mt.WRC[mtq], mt.RD[mtq])
479 bytes={"01", "02", "03", "04", "cb", string.sub(mt.DCF[mtq], 0, 2), string.sub(mt.DCF[mtq], 3), raw,
480 "00", "00", "00", "00", "00", "00", "00", "00",
481 "00", "00", "00", "00", "00", "00"}
483 for i=0, #mt.Segment do
484 table.insert(bytes, mt.Segment[i])
489 for i=#bytes, 1023 do table.insert(bytes, "00") end
490 -- if Master-Token -> calc Master-Token-CRC
491 if (mtq>1) then bytes[22]=calcMtCrc(bytes) end
492 local tempTag=createTagTable()
493 -- remove segment if MasterToken
494 if (mtq>1) then tempTag.SEG[0]=nil end
495 return bytesToTag(bytes, tempTag)
500 function editTag(tag)
501 -- for simulation it makes sense to edit everything
502 local edit_sim="MCD MSN0 MSN1 MSN2 MCC DCFl DCFh WRP WRC RD"
503 -- on real tags it makes only sense to edit DCF, WRP, WRC, RD
504 local edit_real="DCFl DCFh WRP WRC RD"
505 if (confirm("do you want to edit non-writeable values (e.g. for simulation)?")) then
507 else edit_tag=edit_real end
509 if(istable(tag)) then
510 for k,v in pairs(tag) do
511 if(type(v)~="table" and type(v)~="boolean" and string.find(edit_tag, k)) then
512 tag[k]=input("value for: "..k, v)
516 if (tag.Type=="SAM") then ttype="Header"; else ttype="Stamp"; end
517 if (confirm("do you want to edit "..ttype.." Data?")) then
518 -- master-token specific
519 if(istable(tag.Bck)==false) then
520 -- stamp-data length=(0xfc-DCFh)
521 -- on MT: SSC holds the Starting Stamp Character (Stamp0)
522 tag.SSC=input(ttype.."0: ", tag.SSC)
523 -- rest of stamp-bytes are in tag.data 0..n
524 for i=0, (tonumber(0xfc ,10)-("%d"):format('0x'..tag.DCFh))-2 do
525 tag.data[i]=input(ttype.. i+1 ..": ", tag.data[i])
528 --- on credentials byte7 should always be 9f and byte8 ff
529 -- on Master-Token not (even on SAM63/64 not)
530 -- tag.SSC=input(ttype.."0: ", tag.SSC)
531 for i=0, #tag.data do
532 tag.data[i]=input(ttype.. i ..": ", tag.data[i])
537 bytes=tagToBytes(tag)
539 --- check data-consistency (calculate tag.raw)
540 bytes[8]=calcHeaderRaw(tag.WRP, tag.WRC, tag.RD)
542 --- Master-Token specific
543 -- should be triggered if a SAM was converted to a non-SAM (user-Token to Master-Token)
544 -- or a Master-Token has being edited (also SAM64 & SAM63 - which are in fact Master-Token)
545 if(tag.Type~="SAM" or bytes[6]..bytes[7]~="60ea") then
546 -- calc new Master-Token crc
547 bytes[22]=calcMtCrc(bytes)
549 -- ensure tag.SSC set to 'ff' on credential-token (SAM)
551 -- if a Master-Token was converted to a Credential-Token
552 -- lets unset the Time-Area to 00 00 (will contain Stamp-Data on MT)
557 tag=bytesToTag(bytes, tag)
562 -- calculates header-byte (addr 0x07)
563 function calcHeaderRaw(wrp, wrc, rd)
565 wrp=("%02x"):format(tonumber(wrp, 10))
567 res=("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0))
572 -- dump virtual Tag-Data
573 function dumpTag(tag)
579 res ="\nCDF: System Area"
580 res= res.."\n"..dumpCDF(tag)
581 -- segments (user-token area)
582 if(tag.Type=="SAM") then
583 res = res.."\n\nADF: User Area"
585 res=res.."\n"..dumpSegment(tag, i).."\n"
592 -- get segmemnt-data from byte-table
593 function getSegmentData(bytes, start, index)
600 ['raw'] = {'00', '00', '00', '00'},
608 if (bytes[start]) then
611 segment.index = index
612 -- flag = high nibble of byte 1
613 segment.flag = string.sub(bytes[start+1],0,1)
614 -- valid = bit 6 of byte 1
615 segment.valid = bbit("0x"..bytes[start+1],6,1)
616 -- last = bit 7 of byte 1
617 segment.last = bbit("0x"..bytes[start+1],7,1)
618 -- len = (byte 0)+(bit0-3 of byte 1)
619 segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16)
620 -- raw segment header
621 segment.raw = {bytes[start], bytes[start+1], bytes[start+2], bytes[start+3]}
622 -- wrp (write proteted) = byte 2
623 segment.WRP = tonumber(bytes[start+2],16)
624 -- wrc (write control) - bit 4-6 of byte 3
625 segment.WRC = tonumber(bbit("0x"..bytes[start+3],4,3),16)
626 -- rd (read disabled) - bit 7 of byte 3
627 segment.RD = tonumber(bbit("0x"..bytes[start+3],7,1),16)
629 segment.crc = bytes[start+4]
630 -- segment-data starts at segment.len - segment.header - segment.crc
631 for i=0, (segment.len-5) do
632 segment.data[i]=bytes[start+5+i]
640 -- put segments from byte-table to tag-table
641 function segmentsToTag(bytes, tag)
645 if (istable(tag)) then
648 tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i))
649 if (tag.Type=="SAM") then
650 if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end
652 start=start+tag.SEG[i].len
653 until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126)
655 else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end
656 else print("no Segments: must be a MIM22") end
660 -- regenerate segment-header (after edit)
661 function regenSegmentHeader(segment)
663 local raw = segment.raw
665 -- len bit0..7 | len=12bit=low nibble of byte1..byte0
666 raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8))
667 -- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh?
668 raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),4.4)..bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8))
670 raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8))
672 raw[4]=("%02x"):format(bbit("0x"..("%03x"):format(seg.WRC),4,3)..bbit("0x"..("%02x"):format(seg.RD*128),0,8))
674 seg.flag=string.sub(raw[2],0,1)
675 --print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4])
676 if(#seg.data>(seg.len-5)) then
677 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
678 print("Data-Length has being reduced: removing ".. #seg.data-(seg.len-5) .." bytes from Payload");
679 for i=(seg.len-5), #seg.data-1 do
680 table.remove(seg.data)
682 elseif (#seg.data<(seg.len-5)) then
683 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
684 print("Data-Length has being extended: adding "..(seg.len-5)-#seg.data.." bytes to Payload");
685 for i=#seg.data, (seg.len-5)-1 do
686 table.insert(seg.data, '00')
693 -- determine TagType (bits 0..6 of DCFlow)
694 function getTokenType(DCFl)
700 local tt = tonumber(bbit("0x"..DCFl,0,7),10)
701 if (tt >= 0 and tt <= 47) then tt = "IAM"
702 elseif (tt == 49) then tt = "SAM63"
703 elseif (tt == 48) then tt = "SAM64"
704 elseif (tt >= 50 and tt <= 111) then tt = "SAM"
705 elseif (tt >= 112 and tt <= 127) then tt = "GAM"
711 -- dump tag-system area
712 function dumpCDF(tag)
717 if (istable(tag)) then
718 res = res.."MCD: "..tag.MCD..", MSN: "..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..", MCC: "..tag.MCC.."\n"
719 res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n"
720 res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..((tag.raw=='9f') and (", SSC="..tag.SSC.."\n") or "\n")
722 -- credential (end-user tag)
723 if (tag.Type=="SAM") then
724 res = res.."Remaining Header Area\n"
725 for i=0, (#tag.data) do
726 res = res..tag.data[i].." "
728 res = res.."\nBackup Area\n"
729 for i=0, (#tag.Bck) do
730 res = res..tag.Bck[i].." "
732 res = res.."\nTime Area\n"
733 for i=0, (#tag.MTC) do
734 res = res..tag.MTC[i].." "
737 -- Master Token specific
739 res = res .."Master-Token Area\nStamp: "
740 res= res..tag.SSC.." "
741 for i=0, tag.Stamp_len-2 do
742 res = res..tag.data[i].." "
744 res=res.."\nunused payload\n"
745 for i=0, (#tag.data-tag.Stamp_len-1) do
746 res = res..tag.data[i].." "
748 bytes=tagToBytes(tag)
749 local mtcrc=calcMtCrc(bytes)
750 res=res.."\nMaster-Token CRC: "
751 res = res ..tag.MTC[1].." ("..((tag.MTC[1]==mtcrc) and "valid" or "error")..")"
754 else print("no valid Tag in dumpCDF") end
758 -- dump single segment
759 function dumpSegment(tag, index)
762 local dp=0 --data-position in table
763 local res="" --result
764 local raw="" --raw-header
766 if ( (istable(tag.SEG[i])) and tag.Type=="SAM") then
767 if (istable(tag.SEG[i].raw)) then
768 for k,v in pairs(tag.SEG[i].raw) do
774 res = res.."Segment "..("%02d"):format(tag.SEG[i].index)..": "
775 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).."), "
776 res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", "
777 res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." "
778 res = res .."("..(checkSegmentCrc(tag, i) and "valid" or "error")..")"
782 if (tag.SEG[i].WRC>0) then
783 res = res .."\nWRC protected area:\n"
784 for i2=dp, tag.SEG[i].WRC-1 do
785 res = res..tag.SEG[i].data[dp].." "
791 if ((tag.SEG[i].WRP-tag.SEG[i].WRC)>0) then
792 res = res .."\nRemaining write protected area:\n"
793 for i2=dp, (tag.SEG[i].WRP-tag.SEG[i].WRC)-1 do
794 res = res..tag.SEG[i].data[dp].." "
800 if (#tag.SEG[i].data-dp>0) then
801 res = res .."\nRemaining segment payload:\n"
802 for i2=dp, #tag.SEG[i].data-2 do
803 res = res..tag.SEG[i].data[dp].." "
806 if (tag.SEG[i].kgh) then
807 res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and "valid" or "error")..")"
808 else res = res..tag.SEG[i].data[dp] end
813 return print("Segment not found")
818 -- edit segment helper
819 function editSegment(tag, index)
821 local edit_possible="valid len RD WRP WRC Stamp Payload"
822 if (istable(tag.SEG[index])) then
823 for k,v in pairs(tag.SEG[index]) do
824 if(string.find(edit_possible,k)) then
825 tag.SEG[index][k]=tonumber(input(k..": ", v),10)
828 else print("Segment with Index: "..("%02d"):format(index).." not found in Tag")
831 regenSegmentHeader(tag.SEG[index])
832 print("\n"..dumpSegment(tag, index).."\n")
838 function editSegmentData(data)
839 local lc=check4LegicCash(data)
841 if (istable(data)) then
843 data[i]=input("Data"..i..": ", data[i])
845 if (lc) then data=fixLegicCash(data) end
848 print("no Segment-Data found")
853 -- list available segmets in virtual tag
854 function segmentList(tag)
857 if (istable(tag.SEG[0])) then
859 res = res .. tag.SEG[i].index .. " "
862 else print("no Segments found in Tag")
868 -- helper to selecting a segment
869 function selectSegment(tag)
871 if (istable(tag.SEG[0])) then
872 print("availabe Segments:\n"..segmentList(tag))
873 sel=input("select Segment: ", '00')
875 if (sel) then return sel
878 print("\nno Segments found")
885 function addSegment(tag)
893 ['raw'] = {'0d', '40', '04', '00'},
901 if (istable(tag.SEG[0])) then
902 tag.SEG[#tag.SEG].last=0
903 table.insert(tag.SEG, segment)
905 tag.SEG[#tag.SEG].data[i]=("%02x"):format(i)
907 tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG)
910 print("no Segment-Table found")
915 -- delete segment (except segment 00)
916 function delSegment(tag, index)
917 if (istable(tag.SEG[0])) then
919 if (type(index)=="string") then index=tonumber(index,10) end
921 table.remove(tag.SEG, index)
923 tag.SEG[i].index=("%02d"):format(i)
926 if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end
932 -- return bytes 'sstrat' to 'send' from a table
933 function dumpTable(tab, header, tstart, tend)
935 for i=tstart, tend do
938 if (string.len(header)==0) then return res
939 else return (header.." #"..(tend-tstart+1).."\n"..res) end
942 function dump3rdPartyCash1(tag , seg)
943 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
944 local stamp=tag.SEG[seg].data[0].." "..tag.SEG[seg].data[1].." "..tag.SEG[seg].data[2]
945 local datastamp=tag.SEG[seg].data[20].." "..tag.SEG[seg].data[21].." "..tag.SEG[seg].data[22]
946 local balance=tonumber(tag.SEG[seg].data[32]..tag.SEG[seg].data[33] ,16)
947 local balancecrc=utils.Crc8Legic(uid..tag.SEG[seg].data[32]..tag.SEG[seg].data[33])
948 local mirror=tonumber(tag.SEG[seg].data[35]..tag.SEG[seg].data[36] ,16)
949 local mirrorcrc=utils.Crc8Legic(uid..tag.SEG[seg].data[35]..tag.SEG[seg].data[36])
950 local lastbal0=tonumber(tag.SEG[seg].data[39]..tag.SEG[seg].data[40] ,16)
951 local lastbal1=tonumber(tag.SEG[seg].data[41]..tag.SEG[seg].data[42] ,16)
952 local lastbal2=tonumber(tag.SEG[seg].data[43]..tag.SEG[seg].data[44] ,16)
955 -- display decoded/known stuff
956 print("\n------------------------------")
957 print("Tag-ID:\t\t "..uid)
958 print("Stamp:\t\t "..stamp)
959 print("UID-Mapping: \t\t"..("%06d"):format(tonumber(tag.SEG[seg].data[46]..tag.SEG[seg].data[47]..tag.SEG[seg].data[48], 16)))
960 print("------------------------------")
961 print("checksum 1:\t\t "..tag.SEG[seg].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 19, 30)), tag.SEG[seg].data[31])..")")
962 print("checksum 2:\t\t "..tag.SEG[seg].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 32, 33)), tag.SEG[seg].data[34])..")")
963 print("checksum 3:\t\t "..tag.SEG[seg].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 35, 36)), tag.SEG[seg].data[37])..")")
965 print("checksum 4:\t\t "..tag.SEG[seg].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 46, 54)), tag.SEG[seg].data[55])..")")
966 print("checksum 5:\t\t "..tag.SEG[seg].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 56, 61)), tag.SEG[seg].data[62])..")")
967 print("checksum 6:\t\t "..tag.SEG[seg].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 63, 72)), tag.SEG[seg].data[73])..")")
968 print("checksum 7:\t\t "..tag.SEG[seg].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 74, 88)), tag.SEG[seg].data[89])..")")
969 print("------------------------------")
970 print(string.format("Balance:\t\t %3.2f", balance/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[34])..")")
971 print(string.format("Shadow:\t\t\t %3.2f", mirror/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[37])..")")
972 print("------------------------------")
973 print(string.format("History 1:\t\t %3.2f", lastbal0/100))
974 print(string.format("History 2:\t\t %3.2f", lastbal1/100))
975 print(string.format("History 3:\t\t %3.2f", lastbal2/100))
976 print("------------------------------\n")
980 function compareCrc(calc, guess)
981 calc=("%02x"):format(calc)
982 if (calc==guess) then return "valid"
983 else return "error "..calc.."!="..guess end
988 function compareCrc16(calc, guess)
989 calc=("%04x"):format(calc)
990 if (calc==guess) then return "valid"
991 else return "error "..calc.."!="..guess end
995 -- repair / fix (yet known) crc's of a '3rd Party Cash-Segment'
996 -- not all bytes know until yet !!
997 function fix3rdPartyCash1(uid, data)
1000 data[31]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)))
1002 data[34]=("%02x"):format(utils.Crc8Legic(uid..data[32]..data[33]))
1004 data[37]=("%02x"):format(utils.Crc8Legic(uid..data[35]..data[36]))
1006 data[55]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 46, 54)))
1008 data[62]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)))
1010 data[73]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 63, 72)))
1012 data[89]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)))
1018 -- edit uid 3rd party cash
1019 function edit3rdUid(mapid, uid, data)
1020 mapid=("%06x"):format(tonumber(mapid, 10))
1021 data[46]=string.sub(mapid, 0 ,2)
1022 data[47]=string.sub(mapid, 3 ,4)
1023 data[48]=string.sub(mapid, 5 ,6)
1024 return fix3rdPartyCash1(uid, data)
1028 -- edit balance 3rd party cash
1029 function edit3rdCash(new_cash, uid, data)
1030 new_cash=("%04x"):format(new_cash)
1031 data[32]=string.sub(new_cash, 0,2)
1032 data[33]=string.sub(new_cash, 3,4)
1033 data[34]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
1034 data[35]=string.sub(new_cash, 0,2)
1035 data[36]=string.sub(new_cash, 3,4)
1036 data[37]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
1037 data[39]=string.sub(new_cash, 0,2)
1038 data[40]=string.sub(new_cash, 3,4)
1043 return fix3rdPartyCash1(uid, data)
1047 -- repair / fix crc's of a 'Legic-Cash-Segment'
1048 function fixLegicCash(data)
1049 if(#data==32 and data[7]=="01") then
1050 local crc1, crc2, crc3
1051 crc1=("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12)))
1052 crc2=("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20)))
1053 crc3=("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29)))
1054 data[13]=string.sub(crc1, 0, 2)
1055 data[14]=string.sub(crc1, 3, 4)
1056 data[21]=string.sub(crc2, 0, 2)
1057 data[22]=string.sub(crc2, 3, 4)
1058 data[30]=string.sub(crc3, 0, 2)
1059 data[31]=string.sub(crc3, 3, 4)
1065 -- chack for signature of a '3rd Party Cash-Segment'
1066 -- not all bytes know until yet !!
1067 function check43rdPartyCash1(uid, data)
1069 -- too explicit checking will avoid fixing ;-)
1070 if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)), data[31])=="valid") then
1071 --if (compareCrc(utils.Crc8Legic(uid..data[32]..data[33]), data[34])=="valid") then
1072 --if (compareCrc(utils.Crc8Legic(uid..data[35]..data[36]), data[37])=="valid") then
1073 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)), data[62])=="valid") then
1074 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)), data[89])=="valid") then
1075 io.write("3rd Party Cash-Segment detected ")
1087 -- chack for signature of a 'Legic-Cash-Segment'
1088 function check4LegicCash(data)
1090 local stamp_len=(#data-25)
1092 for i=0, stamp_len-1 do
1093 stamp=stamp..data[i].." "
1095 if (data[7]=="01") then
1096 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12))) == data[13]..data[14]) then
1097 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20))) == data[21]..data[22]) then
1098 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29))) == data[30]..data[31]) then
1099 io.write("Legic-Cash Segment detected ")
1109 -- calculate Master-Token crc
1110 function calcMtCrc(bytes)
1112 local cmd=bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[7]..bytes[6]..bytes[8]
1113 local len=(tonumber(0xfc ,10)-("%d"):format('0x'..bytes[7]))
1117 local res=("%02x"):format(utils.Crc8Legic(cmd))
1122 -- check all segmnet-crc
1123 function checkAllSegCrc(tag)
1124 if (istable(tag.SEG[0])) then
1125 for i=0, #tag.SEG do
1126 crc=calcSegmentCrc(tag, i)
1129 else return print("Matser-Token / unsegmented Tag") end
1133 -- check all segmnet-crc
1134 function checkAllKghCrc(tag)
1135 if (istable(tag.SEG[0])) then
1136 for i=0, #tag.SEG do
1137 crc=calcKghCrc(tag, i)
1138 if (tag.SEG[i].kgh) then
1139 tag.SEG[i].data[#tag.SEG[i].data-1]=crc
1146 -- build kghCrc credentials
1147 function kghCrcCredentials(tag, segid)
1148 if (istable(tag) and istable(tag.SEG[0])) then
1150 if (type(segid)=="string") then segid=tonumber(segid,10) end
1151 if (segid>0) then x='93' end
1152 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP)
1153 cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x
1154 for i=0, #tag.SEG[segid].data-2 do
1155 cred = cred..tag.SEG[segid].data[i]
1162 -- validate kghCRC to segment in tag-table
1163 function checkKghCrc(tag, segid)
1164 if (type(tag.SEG[segid])=='table') then
1165 if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then
1166 local data=kghCrcCredentials(tag, segid)
1167 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end
1168 else return false; end
1169 else oops("'Kaba Group header' detected but no Segment-Data found") end
1173 -- calcuate kghCRC for a given segment
1174 function calcKghCrc(tag, segid)
1175 if (istable(tag.SEG[0])) then
1176 -- check if a 'Kaber Group Header' exists
1178 local data=kghCrcCredentials(tag, segid)
1179 return ("%02x"):format(utils.Crc8Legic(data))
1184 -- build segmentCrc credentials
1185 function segmentCrcCredentials(tag, segid)
1186 if (istable(tag.SEG[0])) then
1187 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1188 cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4]
1190 else return print("Master-Token / unsegmented Tag!") end
1194 -- validate segmentCRC for a given segment
1195 function checkSegmentCrc(tag, segid)
1196 local data=segmentCrcCredentials(tag, segid)
1197 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then
1204 -- calculate segmentCRC for a given segment
1205 function calcSegmentCrc(tag, segid)
1206 if (istable(tag.SEG[0])) then
1207 -- check if a 'Kaber Group Header' exists
1208 local data=segmentCrcCredentials(tag, segid)
1209 return ("%02x"):format(utils.Crc8Legic(data))
1214 -- helptext for modify-mode
1215 function modifyHelp()
1218 Data I/O Segment Manipulation Token-Data
1219 ----------------- -------------------- -----------------
1220 rt => read Tag as => add Segment mt => make Token
1221 wt => write Tag es => edit Segment Header et => edit Token data
1222 ct => copy io Tag ed => edit Segment Data tk => toggle KGH-Flag
1223 tc => copy oi Tag rs => remove Segment
1224 tt => toggle Tag cc => check Segment-CRC File I/O
1225 di => dump inTag ck => check KGH -----------------
1226 do => dump outTag e3p => edit 3rd Party Cash lf => load File
1227 ds => dump Segment sf => save File
1228 dlc => dump Legic-Cash xf => xor to File
1229 d3p => dump 3rd Party Cash
1230 r3p => raw 3rd Party Cash
1240 -- modify Tag (interactive)
1241 function modifyMode()
1242 local i, backupTAG, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes
1246 print(" Version: "..version);
1247 print(modifyHelp().."\n".."tags im Memory: "..(istable(inTAG) and ((currentTag=='inTAG') and "*mainTAG" or "mainTAG") or "").." "..(istable(backupTAG) and ((currentTag=='backupTAG') and "*backupTAG" or "backupTAG") or ""))
1249 ["rt"] = function(x)
1250 inTAG=readFromPM3();
1253 ["wt"] = function(x)
1254 if(istable(inTAG.SEG)) then
1256 if (istable(inTAG.Bck)) then
1257 for i=0, #inTAG.SEG do
1258 taglen=taglen+inTAG.SEG[i].len+5
1262 local uid_old=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1263 -- read new tag (output tag)
1264 outTAG=readFromPM3()
1265 outbytes=tagToBytes(outTAG)
1266 -- copy 'inputbuffer' to 'outputbuffer'
1267 inTAG.MCD = outbytes[1]
1268 inTAG.MSN0 = outbytes[2]
1269 inTAG.MSN1 = outbytes[3]
1270 inTAG.MSN2 = outbytes[4]
1271 inTAG.MCC = outbytes[5]
1272 -- recheck all segments-crc/kghcrc (only on a credential)
1273 if(istable(inTAG.Bck)) then
1274 checkAllSegCrc(inTAG)
1275 checkAllKghCrc(inTAG)
1276 local uid_new=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1277 for i=0, #inTAG.SEG do
1278 if (check43rdPartyCash1(uid_old, inTAG.SEG[i].data)) then
1279 io.write(" - fixing known checksums ... ")
1280 inTAG.SEG[i].data=fix3rdPartyCash1(uid_new, inTAG.SEG[i].data)
1285 --get bytes from ready outTAG
1286 bytes=tagToBytes(inTAG)
1288 if (inTAG.Type~="SAM") then bytes[22]=calcMtCrc(bytes) end
1290 writeFile(bytes, 'MylegicClone.hex')
1291 writeToTag(bytes, taglen, 'MylegicClone.hex')
1297 -- switich and copy virtual tags
1299 ["ct"] = function(x)
1300 print("copy mainTAG to backupTAG")
1301 outTAG=deepCopy(inTAG)
1302 backupTAG=deepCopy(inTAG)
1304 ["tc"] = function(x)
1305 print("copy backupTAG to mainTAG")
1306 inTAG=deepCopy(backupTAG)
1308 ["tt"] = function(x)
1309 print("toggle to "..((currentTag=='inTAG') and "backupTAG" or "mainTAG"))
1310 if(currentTag=="inTAG") then
1311 outTAG=deepCopy(inTAG)
1312 inTAG=deepCopy(backupTAG)
1313 currentTag='backupTAG'
1315 inTAG=deepCopy(outTAG)
1319 ["lf"] = function(x)
1320 if (file_check(x)) then filename=x
1321 else filename=input("enter filename: ", "legic.temp") end
1322 inTAG=readFile(filename)
1324 ["sf"] = function(x)
1325 if(istable(inTAG)) then
1326 outfile=input("enter filename:", "legic.temp")
1327 bytes=tagToBytes(inTAG)
1328 --bytes=xorBytes(bytes, inTAG.MCC)
1330 writeFile(bytes, outfile)
1334 ["xf"] = function(x)
1335 if(istable(inTAG)) then
1336 outfile=input("enter filename:", "legic.temp")
1337 crc=input("enter new crc: ('00' for a plain dump)", inTAG.MCC)
1338 print("obfuscate witth: "..crc)
1339 bytes=tagToBytes(inTAG)
1342 writeFile(bytes, outfile)
1346 ["di"] = function(x)
1347 if (istable(inTAG)) then
1348 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1349 if(istable(inTAG.SEG[0])) then
1350 for i=0, #inTAG.SEG do
1351 if(check43rdPartyCash1(uid, inTAG.SEG[i].data)) then
1352 io.write("in Segment index: "..inTAG.SEG[i].index.."\n")
1353 elseif(check4LegicCash(inTAG.SEG[i].data)) then
1354 io.write("in Segment index: "..inTAG.SEG[i].index.."\n")
1356 lci=inTAG.SEG[i].index;
1360 print("\n"..dumpTag(inTAG).."\n")
1361 if (lc) then actions["dlc"](lci) end
1364 ["do"] = function(x) if (istable(backupTAG)) then print("\n"..dumpTag(backupTAG).."\n") end end,
1365 ["ds"] = function(x)
1366 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1367 else sel=selectSegment(inTAG) end
1368 if (sel) then print("\n"..(dumpSegment(inTAG, sel) or "no Segments available").."\n") end
1370 ["es"] = function(x)
1371 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1372 else sel=selectSegment(inTAG) end
1374 if(istable(inTAG.SEG[0])) then
1375 inTAG=editSegment(inTAG, sel)
1376 inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel])
1377 else print("no Segments in Tag") end
1380 ["as"] = function(x)
1381 if (istable(inTAG.SEG[0])) then
1382 inTAG=addSegment(inTAG)
1383 inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1])
1384 inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG])
1385 else print("Master-Token / unsegmented Tag!")
1388 ["rs"] = function(x)
1389 if (istable(inTAG.SEG[0])) then
1390 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1391 else sel=selectSegment(inTAG) end
1392 inTAG=delSegment(inTAG, sel)
1393 for i=0, #inTAG.SEG do
1394 inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i])
1398 ["ed"] = function(x)
1399 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1400 else sel=selectSegment(inTAG) end
1402 inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data)
1405 ["et"] = function(x)
1406 if (istable(inTAG)) then
1410 ["mt"] = function(x) inTAG=makeToken(); actions.di() end,
1411 ["ts"] = function(x)
1412 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1413 else sel=selectSegment(inTAG) end
1414 regenSegmentHeader(inTAG.SEG[sel])
1416 ["tk"] = function(x)
1417 if (istable(inTAG) and istable(inTAG.SEG[0])) then
1418 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1419 else sel=selectSegment(inTAG) end
1420 if(inTAG.SEG[sel].kgh) then inTAG.SEG[sel].kgh=false
1421 else inTAG.SEG[sel].kgh=true end
1425 if (type(x)=="string" and string.len(x)>0) then
1426 print(("%02x"):format(utils.Crc8Legic(x)))
1429 ["xb"] = function(x)
1431 ["xc"] = function(x)
1432 if (istable(inTAG) and istable(inTAG.SEG[0])) then
1433 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
1434 else sel=selectSegment(inTAG) end
1435 print("k "..kghCrcCredentials(inTAG, sel))
1438 ["dlc"] = function(x)
1439 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1440 -- if segment index was user defined
1441 if (type(x)=="string" and string.len(x)>0) then
1443 print(string.format("User-Selected Index %02d", x))
1444 -- or try to find match
1445 else x=autoSelectSegment(inTAG, "legiccash") end
1446 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1447 if (istable(inTAG.SEG[x])) then
1448 io.write("in Segment "..inTAG.SEG[x].index.." :\n")
1449 print("--------------------------------\n\tLegic-Cash Values\n--------------------------------")
1450 local limit, curr, balance, rid, tcv
1451 -- currency of balance & limit
1452 curr=currency[inTAG.SEG[x].data[8]..inTAG.SEG[x].data[9]]
1454 limit=string.format("%4.2f", tonumber(inTAG.SEG[x].data[10]..inTAG.SEG[x].data[11]..inTAG.SEG[x].data[12], 16)/100)
1456 balance=string.format("%4.2f", tonumber(inTAG.SEG[x].data[15]..inTAG.SEG[x].data[16]..inTAG.SEG[x].data[17], 16)/100)
1457 -- reader-id who wrote last transaction
1458 rid=tonumber(inTAG.SEG[x].data[18]..inTAG.SEG[x].data[19]..inTAG.SEG[x].data[20], 16)
1459 -- transaction counter value
1460 tcv=tonumber(inTAG.SEG[x].data[29], 16)
1461 print("Currency:\t\t "..curr)
1462 print("Limit:\t\t\t "..limit)
1463 print("Balance:\t\t "..balance)
1464 print("Transaction Counter:\t "..tcv)
1465 print("Reader-ID:\t\t "..rid.."\n--------------------------------\n")
1469 ["df"] = function(x)
1472 for i=0, #inTAG.SEG[1].data do
1473 res=res..inTAG.SEG[1].data[i]
1477 ["d3p"] = function(x)
1478 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1479 -- if segment index was user defined
1480 if (type(x)=="string" and string.len(x)>0) then
1482 print(string.format("User-Selected Index %02d", x))
1483 -- or try to find match
1484 else x=autoSelectSegment(inTAG, "3rdparty") end
1486 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
1487 uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1488 if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then
1489 dump3rdPartyCash1(inTAG, x)
1493 ["r3p"] = function(x)
1494 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1495 -- if segment index was user defined
1496 if (type(x)=="string" and string.len(x)>0) then
1498 print(string.format("User-Selected Index %02d", x))
1499 -- or try to find match
1500 else x=autoSelectSegment(inTAG, "3rdparty") end
1501 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1502 if (istable(inTAG.SEG[x])) then
1503 print("\n\t\tStamp : "..dumpTable(inTAG.SEG[x].data, "", 0 , 2))
1504 print("\t\tBlock 0: "..dumpTable(inTAG.SEG[x].data, "", 3 , 18))
1506 print("\t\tBlock 1: "..dumpTable(inTAG.SEG[x].data, "", 19, 30))
1507 print("checksum 1: Tag-ID .. Block 1 => LegicCrc8 = "..inTAG.SEG[x].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 19, 30)), inTAG.SEG[x].data[31])..")")
1509 print("\t\tBlock 2: "..dumpTable(inTAG.SEG[x].data, "", 32, 33))
1510 print("checksum 2: Block 2 => LegicCrc8 = "..inTAG.SEG[x].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 32, 33)), inTAG.SEG[x].data[34])..")")
1512 print("\t\tBlock 3: "..dumpTable(inTAG.SEG[x].data, "", 35, 36))
1513 print("checksum 3: Block 3 => LegicCrc8 = "..inTAG.SEG[x].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 35, 36)), inTAG.SEG[x].data[37])..")")
1515 print("\t\tyet unknown: "..inTAG.SEG[x].data[38])
1517 print("\t\tHisatory 1: "..dumpTable(inTAG.SEG[x].data, "", 39, 40))
1518 print("\t\tHisatory 2: "..dumpTable(inTAG.SEG[x].data, "", 41, 42))
1519 print("\t\tHisatory 3: "..dumpTable(inTAG.SEG[x].data, "", 43, 44))
1521 print("\t\tyet unknown: "..inTAG.SEG[x].data[45])
1523 print("\t\tKGH-UID HEX: "..dumpTable(inTAG.SEG[x].data, "", 46, 48))
1524 print("\t\tBlock 4: "..dumpTable(inTAG.SEG[x].data, "", 49, 54))
1525 print("checksum 4: Tag-ID .. KGH-UID .. Block 4 => LegicCrc8 = "..inTAG.SEG[x].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 46, 54)), inTAG.SEG[x].data[55])..")")
1527 print("\t\tBlock 5: "..dumpTable(inTAG.SEG[x].data, "", 56, 61))
1528 print("checksum 5: Tag-ID .. Block 5 => LegicCrc8 = "..inTAG.SEG[x].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 56, 61)), inTAG.SEG[x].data[62])..")")
1530 print("\t\tBlock 6: "..dumpTable(inTAG.SEG[x].data, "", 63, 72))
1531 print("checksum 6: Tag-ID .. Block 6 => LegicCrc8 = "..inTAG.SEG[x].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 63, 72)), inTAG.SEG[x].data[73])..")")
1533 print("\t\tBlock 7: "..dumpTable(inTAG.SEG[x].data, "", 74, 88))
1534 print("checksum 7: Tag-ID .. Block 7 => LegicCrc8 = "..inTAG.SEG[x].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 74, 88)), inTAG.SEG[x].data[89])..")")
1536 print("\t\tBlock 8: "..dumpTable(inTAG.SEG[x].data, "", 90, 94))
1539 ["e3p"] = function(x)
1540 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1541 -- if segment index was user defined
1542 if (type(x)=="string" and string.len(x)>0) then
1544 print(string.format("User-Selected Index %02d", x))
1545 -- or try to find match
1546 else x=autoSelectSegment(inTAG, "3rdparty") end
1548 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
1549 --if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then
1551 if (confirm("\nedit Balance?")) then
1552 local new_cash=input("enter new Balance without comma or currency", "100")
1553 inTAG.SEG[x].data=edit3rdCash(new_cash, uid, inTAG.SEG[x].data)
1555 -- change User-ID (used for online-account-mapping)
1556 if (confirm("\nedit UserID-Mapping?")) then
1557 local new_mapid=input("enter new UserID (6-digit value)", "012345")
1558 inTAG.SEG[x].data=edit3rdUid(new_mapid, uid, inTAG.SEG[x].data)
1560 if (confirm("\nedit Stamp?")) then
1561 local new_stamp=input("enter new Stamp", getSegmentStamp(inTAG.SEG[x]))
1562 inTAG.SEG[x].data=editStamp(new_stamp, uid, inTAG.SEG[x].data)
1563 new_stamp=getSegmentStamp(inTAG.SEG[x], 'true')
1564 print("stamp_bytes: "..#new_stamp)
1565 -- replace stamp in 'block 1' also
1566 io.write("editing stamp in Block 1 also ")
1567 for i=20, (20+#new_stamp-1) do
1568 inTAG.SEG[x].data[i]=new_stamp[i-19]
1572 -- fix known checksums
1573 inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data)
1576 -- print out new settings
1577 dump3rdPartyCash1(inTAG, x)
1581 ["gs"] = function(x)
1582 if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10)
1583 else x=selectSegment(inTAG) end
1584 local stamp=getSegmentStamp(inTAG.SEG[x])
1585 print("Stamp : "..stamp)
1586 stamp=str2bytes(stamp)
1587 print("lenght: "..#stamp)
1589 ["c6"] = function(x) local crc16=string.format("%4.04x", utils.Crc16(x))
1590 print(string.sub(crc16, 0,2).." "..string.sub(crc16, 3,4))
1592 ["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end,
1593 ["cb"] = function(x)
1594 if (istable(inTAG)) then
1595 print("purge BackupArea")
1596 inTAG=clearBackupArea(inTAG)
1599 ["f3p"] = function(x)
1600 if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10)
1601 else x=selectSegment(inTAG) end
1602 if (istable(inTAG.SEG[x])) then
1603 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
1604 inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data)
1607 ["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end,
1609 print("modify-modus! enter 'h' for help or 'q' to quit")
1611 ic=input("Legic command? ('h' for help - 'q' for quit)", "h")
1613 if (type(actions[string.lower(string.sub(ic,0,3))])=='function') then
1614 actions[string.lower(string.sub(ic,0,3))](string.sub(ic,5))
1615 elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then
1616 actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4))
1617 elseif (type(actions[string.lower(string.sub(ic,0,1))])=='function') then
1618 actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3))
1619 else actions.h('') end
1620 until (string.sub(ic,0,1)=="q")
1623 function clearBackupArea(tag)
1624 for i=1, #tag.Bck do
1630 function getSegmentStamp(seg, bytes)
1633 --- the 'real' stamp on MIM is not really easy to tell for me since the 'data-block' covers stamp0..stampn+data0..datan
1634 -- there a no stamps longer than 7 bytes & they are write-protected by default , and I have not seen user-credntials
1635 -- with stamps smaller 3 bytes (except: Master-Token)
1636 -- WRP -> Read/Write Protection
1637 -- WRC -> Read/Write Condition
1638 -- RD depends on WRC - if WRC > 0 and RD=1: only reader with matching #WRC of Stamp-bytes in thier Database have Read-Access to the Tag
1639 if (seg.WRP<7) then stamp_len=(seg.WRP) end
1640 for i=1, (stamp_len) do
1641 stamp=stamp..seg.data[i-1]
1644 stamp=str2bytes(stamp)
1646 else return stamp end
1649 function str2bytes(s)
1651 if (string.len(s)%2~=0) then return print("stamp should be a even hexstring e.g.: deadbeef or 0badc0de") end
1652 for i=1, string.len(s), 2 do
1653 table.insert(res, string.sub(s,i,(i+1)))
1658 function editStamp(new_stamp, uid, data)
1659 local stamp=str2bytes(new_stamp)
1660 for i=0, #stamp-1 do
1663 -- now fill in stamp
1664 for i=0, (string.len(new_stamp)/2)-1 do
1667 return fix3rdPartyCash1(uid, data)
1670 function autoSelectSegment(tag, s)
1671 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1674 io.write("autoSelect ")
1675 --- search for desired segment-type
1676 -- 3rd Party Segment
1677 if (s=="3rdparty") then
1681 res=check43rdPartyCash1(uid, tag.SEG[x].data)
1682 until ( res or x==0 )
1684 -- Legic-Cash Segment
1685 if (s=="legiccash") then
1689 res=check4LegicCash(tag.SEG[x].data)
1690 until ( res or x==0 )
1695 io.write("\nautoselected Index: "..string.format("%02d", x).."\n")
1700 io.write("no Segment found\n")
1706 if (#args == 0 ) then modifyMode() end
1708 local inTAG, backupTAG, outTAG, outfile, interactive, crc, ofs, cfs, dfs
1709 -- just a spacer for better readability
1712 for o, a in getopt.getopt(args, 'hrmi:do:c:') do
1714 if o == "h" then return help(); end
1715 -- read tag from PM3
1716 if o == "r" then inTAG=readFromPM3() end
1718 if o == "i" then inTAG=readFile(a) end
1720 if o == "d" then dfs=true end
1721 -- interacive modifying
1722 if o == "m" then interactive=true; modifyMode() end
1723 -- xor (e.g. for clone or plain file)
1724 if o == "c" then cfs=true; crc=a end
1726 if o == "o" then outfile=a; ofs=true end
1729 -- file conversion (output to file)
1731 -- dump infile / tag-read
1733 print("-----------------------------------------")
1734 print(dumpTag(inTAG))
1736 bytes=tagToBytes(inTAG)
1738 -- xor willl be done in function writeFile
1739 -- with the value of byte[5]
1744 writeFile(bytes, outfile)
1745 --- read real tag into virtual tag
1746 -- inTAG=readFromPM3() end
1747 --- or simply use the bytes that where wriiten
1748 inTAG=bytesToTag(bytes, inTAG)
1751 print("-----------------------------------------")
1752 print(dumpTag(inTAG))
1759 -- Creates a complete/deep copy of the data
1760 function deepCopy(object)
1761 local lookup_table = {}
1762 local function _copy(object)
1763 if type(object) ~= "table" then
1765 elseif lookup_table[object] then
1766 return lookup_table[object]
1768 local new_table = {}
1769 lookup_table[object] = new_table
1770 for index, value in pairs(object) do
1771 new_table[_copy(index)] = _copy(value)
1773 return setmetatable(new_table, getmetatable(object))
1775 return _copy(object)