2 if it don't works with you tag-layout - be so kind and let me know ;-)
4 Tested on Tags with those Layouts:
6 (example) Legic-Prime Layout with 'Kaba Group Header'
7 +----+----+----+----+----+----+----+----+
8 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
9 +----+----+----+----+----+----+----+----+
10 0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
11 +----+----+----+----+----+----+----+----+
12 0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
13 +----+----+----+----+----+----+----+----+
14 0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
15 +----+----+----+----+----+----+----+----+
19 MSN = Manufacturer SerialNumber
20 60 ea = DCF Low + DCF high
21 9f = raw byte which holds the bits for OLE,WRP,WRC,RD
22 ff = unknown but important
24 11 = unknown but important
25 Bck = header-backup-area
26 00 00 = Year (00 = 2000) & Week (not important)
28 SegC = Crc8 over the Segment Header
29 Stp = Stamp (could be more as 4 - up to 7)
30 UID = dec User-ID for online-Mapping
31 kghC = crc8 over MCD + MSN0..MSN2 + UID
34 (example) Legic-Cash on MIM256/1024 tag' (37 bytes)
35 +----+----+----+----+----+----+----+----+
36 0x00|Seg0|Seg1|Seg2|Seg3|SegC|STP0|STP1|STP2|
37 +----+----+----+----+----+----+----+----+
38 0x08|STP3|STP4|STP5|STP6| 01 |CURh|CURl|LIMh|
39 +----+----+----+----+----+----+----+----+
40 0x10|LIMm|LIMl|CHKh|CHKl|BALh|BALm|BALl|LRBh|
41 +----+----+----+----+----+----+----+----+
42 0x18|LRBm|LRBl|CHKh|CHKl|SHDh|SHDm|SHDl|LRSh|
43 +----+----+----+----+----+----+----+----+
44 0x20|LRSm|LRSl| CV |CHKh|CHKl|
45 +----+----+----+----+----+
46 STP = Stamp (seems to be always 7 bytes)
47 01 = unknown but important
48 CUR = currency in HEX (ISO 4217)
50 CHK = crc16 over byte-addr 0x05..0x12
52 LRB = ID of the reader that changed the balance
53 CHK = crc16 over BAL + LRB
55 LRS = ID of the reader that changed the shadow balance (?? should be always the same as LRB)
56 CV = Counter value for transactions
57 CHK = crc16 over SHD + LRS + CV
59 (example) Legic-Prime Layout 'gantner unsegmented user-credential'
60 +----+----+----+----+----+----+----+----+
61 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 08 |
62 +----+----+----+----+----+----+----+----+
63 0x08|Stp0|Stp1|Stp2|Stp3|Stp4|Dat0|Dat1|uCRC| <- addr 0x08..0x0f is WRP
64 +----+----+----+----+----+----+----+----+
65 0x10|emb0| <- this is only within wrp if addr 0x07==09
68 MSN = Manufacturer SerialNumber
69 60 ea = DCF Low + DCF high
70 08 = raw byte which holds the bits for OLE,WRP,WRC,RD
71 Stp = Stamp (could be more as 4 - up to 7)
72 Dat = Online-Mapping Data
73 uCRC = crc8 over addr 0x00..0x03+0x07..0x0E
76 (example) Legic-Prime Layout 'gantner unsegmented Master-Token (IAM) with a stamp_len of 4'
77 +----+----+----+----+----+----+----+----+
78 0x00|MCD |MSN0|MSN1|MSN2|MCC | 20 | f8 | 08 |
79 +----+----+----+----+----+----+----+----+
80 0x08|Stp0|Stp1|Stp2|Stp3| 00 | 00 | 00 |CRC1|
81 +----+----+----+----+----+----+----+----+
82 0x10| 00 | 00 | 00 | 00 | 00 |CRC2|
83 +----+----+----+----+----+----+
85 MSN = Manufacturer SerialNumber
86 60 ea = DCF Low + DCF high
87 08 = raw byte which holds the bits for OLE,WRP,WRC,RD
88 Stp = Stamp (could be more as 4 - up to 7)
89 Dat = Online-Mapping Data
90 CRC1 = crc8 over addr 0x00..0x03+0x07..0x0E (special 'gantner crc8')
91 CRC2 = MCD + MSB0..2+ addr 0x06 + addr 0x05 + addr 0x07 + Stamp (regular Master-Token-CRC)
94 example = "script run legic"
101 This script helps you to read, create and modify Legic Prime Tags (MIM22, MIM256, MIM1024)
102 it's kinda interactive with following commands in three categories:
104 Data I/O Segment Manipulation Token-Data
105 ----------------- -------------------- -----------------
106 rt => read Tag as => add Segment mt => make Token
107 wt => write Tag es => edit Segment Header et => edit Token data
108 ed => edit Segment Data tk => toggle KGH-Flag
109 File I/O rs => remove Segment
110 ----------------- cc => check Segment-CRC
111 lf => load File ck => check KGH
112 sf => save File ds => dump Segments
116 (partially) known Segments Virtual Tags Script Output
117 --------------------------- ------------------------------- ------------------------
118 dlc => dump Legic-Cash ct => copy mainTag to backupTag tac => toggle ansicolors
119 elc => edit Legic-Cash tc => copy backupTag to mainTag
120 d3p => dump 3rd-Party-Cash tt => switch mainTag & backupTag
121 e3p => edit 3rd-Party-Cash di => dump mainTag
126 rt: 'read tag' - reads a tag placed near to the PM3
127 wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3
128 without the need of changing anything - MCD,MSN,MCC will be read from the tag
129 before and applied to the output.
131 lf: 'load file' - load a (xored) file from the local Filesystem into the 'virtual inTag'
132 sf: 'save file' - saves the 'virtual inTag' to the local Filesystem (xored with Tag-MCC)
133 xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with choosen MCC - use '00' for plain values)
135 ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not usefull yet, but inernally needed
136 tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not usefull yet, but inernally needed
137 tt: 'toggle tag' - copy mainTag to BackupTag and backupTag to mainTag
139 di: 'dump mainTag' - shows the current content of the 'virtual Tag'
140 do: 'dump backupTag' - shows the current content of the 'virtual outTag'
141 ds: 'dump Segments' - will show the content of a selected Segment
142 as: 'add Segment' - will add a 'empty' Segment to the inTag
143 es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid)
144 all other Segment-Header-Values are either calculated or not needed to edit (yet)
145 ed: 'edit data' - edit the Data of a Segment (ADF-Aera / Stamp & Payload specific Data)
146 et: 'edit Token' - edit Data of a Token (CDF-Area / SAM, SAM64, SAM63, IAM, GAM specific Data)
147 mt: 'make Token' - create a Token 'from scratch' (guided)
148 rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token)
149 cc: 'check Segment-CRC'- checks & calculates (if check failed) the Segment-CRC of all Segments
150 ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected
151 'Kaba Group Header CRC calculation'
152 tk: 'toggle KGH' - toglle the (script-internal) flag for kgh-calculation for a segment
153 xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment
155 dlc: 'dump Legic-Cash' - show balance and checksums of a Legic-Cash Segment
156 elc: 'edit Legic-Cash' - edit values of a Legic-Cash Segment
158 d3p: 'dump 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd-Party Cash Segment
159 e3p: 'edit 3rd Party' - edit Data in 3rd-Party Cash Segment
161 tac: 'toggle ansicolors'- switch on and off the colored text-output of this script
162 default can be changed by setting the variable 'colored_output' to false
168 local utils = require('utils')
169 local getopt = require('getopt')
170 local ansicolors = require('ansicolors')
173 -- global variables / defines
174 local bxor = bit32.bxor
175 local bbit = bit32.extract
176 local input = utils.input
177 local confirm = utils.confirm
180 -- init ansicolor-values & ansicolors switch
181 local colored_output = true
192 -- default colors (change to whatever you want)
193 function load_colors(onoff)
196 acgreen = ansicolors.green
197 accyan = ansicolors.cyan
198 acred = ansicolors.red
199 acyellow= ansicolors.yellow
200 acblue = ansicolors.blue
201 acmagenta= ansicolors.magenta
202 acoff = ansicolors.reset
216 -- curency-codes for Legic-Cash-Segments (ISO 4217)
225 -- This is only meant to be used when errors occur
227 print(acred.."ERROR: "..acoff ,err)
235 print("Version: "..version)
236 print("Example usage: "..example)
240 -- table check helper
242 return type(t) == 'table'
246 -- creates a 'deep copy' of a table (a=b only references)
247 function deepCopy(object)
248 local lookup_table = {}
249 local function _copy(object)
250 if type(object) ~= "table" then
252 elseif lookup_table[object] then
253 return lookup_table[object]
256 lookup_table[object] = new_table
257 for index, value in pairs(object) do
258 new_table[_copy(index)] = _copy(value)
260 return setmetatable(new_table, getmetatable(object))
267 function xorme(hex, xor, index)
268 if ( index >= 23 ) then
269 return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
276 -- (de)obfuscate bytes
277 function xorBytes(inBytes, crc)
279 for index = 1, #inBytes do
280 bytes[index] = xorme(inBytes[index], crc, index)
282 if (#inBytes == #bytes) then
284 bytes[5] = string.sub(crc,-2)
287 print("error: byte-count missmatch")
293 -- check availability of file
294 function file_check(file_name)
295 local file_found=io.open(file_name, "r")
296 if file_found==nil then
304 -- split csv-string into table
305 local function split(str, sep)
306 local sep = sep or ','
308 local matchfunc = string.gmatch(str, "([^"..sep.."]+)")
309 if not matchfunc then return {str} end
310 for str in matchfunc do
311 table.insert(fields, str)
317 -- put a string into a bytes-table
318 function str2bytes(s)
320 if (string.len(s)%2~=0) then return print("stamp should be a even hexstring e.g.: deadbeef or 0badc0de") end
321 for i=1, string.len(s), 2 do
322 table.insert(res, string.sub(s,i,(i+1)))
328 -- put certain bytes into a new table
329 function bytesToTable(bytes, bstart, bend)
331 for i=0, (bend-bstart) do
338 -- read file into table
339 function getInputBytes(infile)
342 local fhi,err = io.open(infile)
343 if err then oops("faild to read from file ".. infile); return false; end
346 if line == nil then break end
347 for byte in line:gmatch("%w+") do
348 table.insert(bytes, byte)
352 if (bytes[7]=='00') then return false end
353 print(#bytes .. " bytes from "..infile.." loaded")
358 -- create tag-table helper
359 function createTagTable()
385 -- put bytes into tag-table
386 function bytesToTag(bytes, tag)
387 if(istable(tag)) then
397 tag.Type=getTokenType(tag.DCFl);
398 tag.OLE=bbit("0x"..tag.DCFl,7,1)
399 tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4))
400 tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3))
401 tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1))
402 if (tag.Type=="SAM" and tag.raw=='9f') then
403 tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10))
404 elseif (tag.Type=="SAM" and (tag.raw=='08' or tag.raw=='09')) then
405 tag.Stamp_len = tonumber(tag.raw,10)
407 tag.data=bytesToTable(bytes, 10, 13)
408 tag.Bck=bytesToTable(bytes, 14, 20)
409 tag.MTC=bytesToTable(bytes, 21, 22)
411 print(acgreen.."Tag-Type: ".. tag.Type..acoff)
412 if (tag.Type=="SAM" and #bytes>23) then
413 tag=segmentsToTag(bytes, tag)
414 print(acgreen..(#tag.SEG+1).." Segment(s) found"..acoff)
415 -- unsegmented Master-Token
419 table.insert(tag.data, tag.Bck[i])
421 tag.data[#tag.data]=tag.MTC[0]
423 --tag.MTC[0]=tag.MTC[1]
426 print(accyan..#bytes.." bytes for Tag processed"..acoff)
429 return oops("tag is no table in: bytesToTag ("..type(tag)..")")
433 -- put segments from byte-table to tag-table
434 function segmentsToTag(bytes, tag)
438 if (istable(tag)) then
441 tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i))
442 if (tag.Type=="SAM") then
443 if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end
445 start=start+tag.SEG[i].len
446 until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126)
448 else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end
449 else print("no Segments: must be a MIM22") end
453 -- read Tag-Table in bytes-table
454 function tagToBytes(tag)
455 if (istable(tag)) then
459 table.insert(bytes, tag.MCD)
460 table.insert(bytes, tag.MSN0)
461 table.insert(bytes, tag.MSN1)
462 table.insert(bytes, tag.MSN2)
463 table.insert(bytes, tag.MCC)
464 table.insert(bytes, tag.DCFl)
465 table.insert(bytes, tag.DCFh)
466 table.insert(bytes, tag.raw)
467 table.insert(bytes, tag.SSC)
469 for i=0, #tag.data do
470 table.insert(bytes, tag.data[i])
473 if(istable(tag.Bck)) then
475 table.insert(bytes, tag.Bck[i])
478 -- token-create-time / master-token crc
480 table.insert(bytes, tag.MTC[i])
483 if (type(tag.SEG[0])=='table') then
485 for i2=1, #tag.SEG[i].raw+1 do
486 table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2])
488 table.insert(bytes, #bytes+1, tag.SEG[i].crc)
489 for i2=0, #tag.SEG[i].data-1 do
490 table.insert(bytes, #bytes+1, tag.SEG[i].data[i2])
495 for i=#bytes+1, 1024 do
496 table.insert(bytes, i, '00')
500 return oops("tag is no table in tagToBytes ("..type(tag)..")")
505 -- read from pm3 into virtual-tag
506 function readFromPM3()
507 local tag, bytes, infile
509 core.console("hf legic reader")
510 core.console("hf legic save "..infile)
516 -- write virtual Tag to real Tag
517 function writeToTag(tag)
519 local filename='MylegicClone.hex'
521 if(utils.confirm(acred.."\nplace the (empty) Tag onto the PM3\nand confirm writing to this Tag: "..acoff) == false) then
524 -- get used bytes / tag-len
525 if(istable(tag.SEG)) then
526 if (istable(tag.Bck)) then
528 taglen=taglen+tag.SEG[i].len+5
531 local uid_old=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
532 -- read new tag into memory so we can xor the new data with the new MCC
534 outbytes=tagToBytes(outTAG)
535 -- copy 'inputbuffer' to 'outputbuffer'
536 tag.MCD = outbytes[1]
537 tag.MSN0 = outbytes[2]
538 tag.MSN1 = outbytes[3]
539 tag.MSN2 = outbytes[4]
540 tag.MCC = outbytes[5]
541 -- recheck all segments-crc/kghcrc (only on a credential)
542 if(istable(tag.Bck)) then
545 local uid_new=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
547 if (check43rdPartyCash1(uid_old, tag.SEG[i].data)) then
548 io.write(accyan.."\nfixing known checksums"..acoff.." ... ")
549 if (fix3rdPartyCash1(uid_new, tag.SEG[i].data)) then
550 io.write(acgreen.." done\n"..acoff)
551 else oops("\nsomething went wrong at the repair of the 3rd-party-cash-segment") end
555 bytes=tagToBytes(tag)
557 if (tag.Type~="SAM") then bytes[22]=calcMtCrc(bytes) end
559 print("write temp-file '"..filename.."'")
561 writeFile(bytes, filename)
562 --writeToTag(bytes, taglen, 'MylegicClone.hex')
566 -- write data to file
568 WriteBytes = utils.input(acyellow.."enter number of bytes to write?"..acoff, taglen)
569 -- load file into pm3-buffer
570 if (type(filename)~="string") then filename=input(acyellow.."filename to load to pm3-buffer?"..acoff,"legic.temp") end
571 cmd = 'hf legic load '..filename
573 -- write pm3-buffer to Tag
574 for i=0, WriteBytes do
575 if ( i<5 or i>6) then
576 cmd = ('hf legic write 0x%02x 0x01'):format(i)
580 -- write DCF in reverse order (requires 'mosci-patch')
581 cmd = 'hf legic write 0x05 0x02'
582 print(acgreen..cmd..acoff)
586 print(acgreen.."skip byte 0x05 - will be written next step"..acoff)
595 -- read file into virtual-tag
596 function readFile(filename)
600 if (file_check(filename)==false) then
601 return oops("input file: "..filename.." not found")
603 bytes = getInputBytes(filename)
604 if (bytes == false) then return oops('couldnt get input bytes')
607 bytes = xorBytes(bytes,bytes[5])
608 print("create virtual tag from ".. #bytes .. " bytes")
609 -- create Tag for plain bytes
611 -- load plain bytes to tag-table
613 tag=bytesToTag(bytes, tag)
620 -- write bytes to file
621 function writeFile(bytes, filename)
622 if (filename~='MylegicClone.hex') then
623 if (file_check(filename)) then
624 local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
625 if (answer==false) then return print("user abort") end
630 local fho,err = io.open(filename, "w")
631 if err then oops("OOps ... faild to open output-file ".. filename) end
632 bytes=xorBytes(bytes, bytes[5])
636 elseif (bcnt <= 7) then
637 line=line.." "..bytes[i]
640 -- write line to new file
641 fho:write(line.."\n")
642 -- reset counter & line
649 print("\nwrote ".. #bytes .." bytes to " .. filename)
656 function makeTagMap()
659 tagMap['name']=input(accyan.."enter Name for this Map: "..acoff , "newTagMap")
660 tagMap['mappings']={}
662 -- insert fixed Tag-CRC
663 table.insert(tagMap.crc8, {name='TAG-CRC', pos=5, seq={1, 4}})
666 print(accyan.."new tagMap created"..acoff)
671 -- save mapping to file
672 function saveTagMap(map, filename)
673 if (string.len(filename)>0) then
674 if (file_check(filename)) then
675 local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
676 if (answer==false) then return print("user abort") end
681 local fho,err = io.open(filename, "w")
682 if err then oops("OOps ... faild to open output-file ".. filename) end
684 -- write line to new file
685 for k, v in pairs(map) do
687 for k2, v2 in pairs(v) do
688 if (k=='mappings') then
689 fho:write(k..","..k2..","..v2['name']..","..v2['start']..","..v2['end']..","..((v2['highlight']) and "1" or "0").."\n")
690 elseif (k=="crc8") then
692 tmp=k..","..k2..","..v2['name']..","..v2['pos']..","
693 tmp=tmp..tbl2seqstr(v2['seq'])
698 fho:write(k..","..v.."\n")
707 function toggleHighlight(tbl)
708 if (tbl['highlight']) then tbl['highlight']=false
709 else tbl['highlight']=true end
714 -- return table od seqence-string
715 function seqstr2tbl(seqstr)
716 local s=split(seqstr)
719 for sk, sv in pairs(s) do
722 table.insert(res, s2[1])
723 table.insert(res, s2[2])
731 -- return sequence-string from table
732 function tbl2seqstr(seqtbl)
734 if (istable(seqtbl)) then
735 for sk, sv in pairs(seqtbl) do
736 res=res..sv..((sk%2==0) and "," or "-")
738 if (string.sub(res, string.len(res))==",") then
739 res=string.sub(res, 1, string.len(res)-1)
746 -- read map-file into map
747 function loadTagMap(filename)
748 local map={mappings={}, crc8={}, crc16={}}
754 if (file_check(filename)==false) then
755 return oops("input file: "..filename.." not found")
757 local fhi,err = io.open(filename)
766 if (fields[1]=='offset') then
767 offset=tonumber(fields[2],10)
770 map[fields[1]]=fields[2]
771 elseif (fields[1]=='mappings') then
775 temp['name']=fields[3]
776 temp['start']=tonumber(fields[4], 10)
777 temp['end']=tonumber(fields[5], 10)
778 if(temp['start']>22) then
779 temp['start']=temp['start']+offset
780 temp['end']=temp['end']+offset
782 if (tonumber(fields[6], 10)==1) then temp['highlight']= true
783 else temp['highlight']= false end
784 table.insert(map['mappings'], m, temp)
785 elseif (fields[1]=='crc8') then
789 temp['name']=fields[3]
790 temp['pos']=tonumber(fields[4], 10)+offset
791 local s=string.sub(line, string.len(fields[1]..","..fields[2]..","..fields[3]..",")+1, string.len(line))
792 temp['seq']=seqstr2tbl(s)
793 for k, v in pairs(temp['seq']) do
794 if(tonumber(v, 10)>22) then v=tonumber(v, 10)+offset end
795 temp['seq'][k]=tonumber(v, 10)
797 table.insert(map.crc8, temp)
806 -- dump tagMap (mappings only)
807 function dumpTagMap(tag, tagMap)
808 if(#tagMap.mappings>0) then
809 bytes=tagToBytes(tag)
812 -- start display mappings
813 for k, v in pairs(tagMap.mappings) do
814 if ((lastend+1)<v['start']) then
817 if (isPosCrc8(tagMap, v['start'])>0) then
818 if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, v['start']) ) ) then
819 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acgreen..v['name']..acoff..":")
821 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acred..v['name']..acoff..":")
824 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..((v['highlight']) and acmagenta or acyellow)..v['name']..acoff..":")
827 for i=((string.len(v['name']))/10), 2 do
830 for i=v['start'], v['end'] do
831 temp=temp..bytes[i].." "
841 function isPosCrc8(tagMap, pos)
843 if (#tagMap.crc8>0) then
844 for k, v in pairs(tagMap.crc8) do
845 if(v['pos']==pos) then res=k end
853 function checkMapCrc8(tagMap, bytes, n)
855 if (#tagMap.crc8>0) then
856 if(istable(tagMap.crc8[n])) then
858 for k2, v2 in pairs(tagMap.crc8[n]) do
859 if (istable(v2)) then
860 temp=temp..tbl2seqstr(v2)
864 local tempres=getSequences(bytes, temp)
865 tempres=("%02x"):format(utils.Crc8Legic(tempres))
866 if (bytes[tagMap.crc8[n]['pos']]==tempres) then
876 function editTagMap(tag, tagMap)
878 Data: dm = show dr = dump raw
879 Mappings: im = insert am = add rm = remove
880 CRC8: ac8 = add sc8 = show rc8 = remove
883 --if(#tagMap.mappings==0) then oops("no mappings in tagMap"); return tagMap end
884 print("tagMap edit-mode submenu")
886 x=input('tagMap submenu:', 'h')
887 if (x=='h') then print(t)
888 elseif (x=='dm') then tagMmap=dumpTagMap(tag, tagMap)
889 elseif (x=='dr') then tagMmap=dumpMap(tag, tagMap)
890 elseif (x=='rc8') then
891 if (istable(tagMap.crc8)) then
892 local x1 = selectTableEntry(tagMap.crc8, "select number of CRC8 to remove:")
893 if (istable(tagMap.crc8[x1])) then
894 table.remove(tagMap.crc8, x1)
897 elseif (x=='ac8') then
898 local p=tonumber(input("enter byte-addr of crc8", '0'),10)
900 local i1=input("enter comma-seperated byte-sequences (e.g.: '1-4,23-26')", '1-4,23-26')
901 local s1=split(i1, ',')
904 for k, v in pairs(s1) do
907 table.insert(temp.seq, v1[1])
908 table.insert(temp.seq, v1[2])
912 temp['name']=input("enter a name for the CRC8", "CRC "..(#tagMap.crc8+1))
913 table.insert(tagMap.crc8, temp)
916 elseif (string.sub(x, 1, 3)=='sc8') then
917 local bytes=tagToBytes(tag)
919 -- trigger manually by sc8 <'4digit' checkadd> <'seqeuence-string'>
920 -- e.g.: sc8 0027 1-4,23-36
921 if (string.len(x)>=9) then
922 pos=tonumber(string.sub(x, 5, 8), 10)
923 x=string.sub(x, 9, string.len(x))
924 print("x: "..x.." - pos:"..pos)
926 x=selectTableEntry(tagMap.crc8, "select CRC:")
927 if(istable(tagMap.crc8[x])) then
928 pos=tagMap.crc8[x]['pos']
929 x=tbl2seqstr(tagMap.crc8[x]['seq'])
932 if (type(x)=='string') then
933 res=("%02x"):format(utils.Crc8Legic(getSequences(bytes, x)))
934 print(accyan.."Sequence:\t"..acoff..x)
935 print(accyan.."Bytes:\t\t"..acoff..getSequences(bytes, x))
936 print(accyan.."calculated: "..acoff..res..accyan.." bytes["..pos.."]: "..acoff..bytes[pos].." ("..compareCrc(utils.Crc8Legic(getSequences(bytes, x)), bytes[pos])..")")
938 elseif (x=="tm") then
939 x=selectTableEntry(tagMap.mappings, "select number of Mapping:")
940 tagMap.mappings[x]=toggleHighlight(tagMap.mappings[x])
941 elseif (x=='am') then tagMap=addMapping(tag, tagMap)
942 elseif (x=='im') then tagMap=addMapping(tag, tagMap, selectTableEntry(tagMap.mappings, "select List-Position for insert:"))
943 elseif (x=='rm') then tagMap=deleteMapping(tag, tagMap)
944 elseif (x=='mas') then tagMap=mapTag(tagMap); tagMap=mapAllSegments(tag, tagMap)
945 elseif (type(actions[string.sub(x, 3)])=='function') then actions[string.sub(x, 3)]()
948 print("exit sub-Menu")
953 -- dump raw mapped and unmapped
954 function dumpMap(tag, tagMap)
957 local bytes = tagToBytes(tag)
958 local stats = getSegmentStats(bytes)
959 dend=stats[#stats]['end']
960 print(accyan.."Tag uses "..dend.." bytes:"..acoff)
961 for i=dstart, dend do
962 if (check4MappedByte(i, tagMap) and not check4MapCrc8(i, tagMap) and not check4Highlight(i, tagMap)) then io.write(""..acyellow)
963 elseif (check4MapCrc8(i, tagMap)) then
964 if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, i) ) ) then
965 io.write(""..acgreen)
972 -- highlighted mapping
973 if (check4Highlight(i, tagMap)) then io.write(""..acmagenta) end
976 if (i%8==0) then io.write("\n")
977 else io.write(" ") end
980 io.write("\n"..acoff)
984 -- show bytes used for crc-calculation
985 function getSequences(bytes, seqstr)
986 if (type(seqstr)~="string") then seqstr=input("enter comma-seperated sequences (e.g.: '1-4,23-26')", '1-4,23-26') end
987 local seqs=split(seqstr, ',')
990 for k, v in pairs(seqs) do
991 local seq = split(v,'-')
993 for i=seq[1], seq[2] do
994 res=res..bytes[i].." "
997 if(string.len(res)>0) then res=res.." " end
1000 oops("no sequence found in '"..seqstr.."'")
1006 -- check if byte-addr is a know crc
1007 function check4MapCrc8(addr, tagMap)
1009 for i=1, #tagMap.crc8 do
1010 if (addr == tagMap.crc8[i]['pos']) then
1018 -- check if byte-addr is a know crc
1019 function check4MapCrc16(addr, tagMap)
1021 for i=1, #tagMap.crc16 do
1022 if (addr == tagMap.crc16[i]['pos']) then
1030 -- check if byte is mapped or not
1031 function check4MappedByte(addr, tagMap)
1033 for _, v in pairs(tagMap.mappings) do
1034 if (addr >= v['start'] and addr <= v['end'] ) then
1042 -- check if byte is highlighted or not
1043 function check4Highlight(addr, tagMap)
1045 for _, v in pairs(tagMap.mappings) do
1046 if (addr >= v['start'] and addr <= v['end'] ) then
1054 -- add interactive mapping
1055 function addMapping(tag, tagMap, x)
1056 if (type(x)~="number") then x=#tagMap.mappings+1 end
1057 local bytes=tagToBytes(tag)
1059 myMapping['name'] =input(accyan.."enter Maping-Name:"..acoff, string.format("mapping %d", #tagMap.mappings+1))
1060 myMapping['start']=tonumber(input(accyan.."enter start-addr:"..acoff, '1'), 10)
1061 myMapping['end'] =tonumber(input(accyan.."enter end-addr:"..acoff, #bytes), 10)
1062 myMapping['highlight']=confirm("set highlighted")
1063 table.insert(tagMap.mappings, x, myMapping)
1069 function deleteMapping(tag, tagMap)
1070 if(#tagMap.mappings>0) then
1071 local d = selectTableEntry(tagMap.mappings, "select number of Mapping to remove:")
1072 if (type(d)=='number') then
1073 table.remove(tagMap.mappings, d)
1074 else oops("deleteMapping: got type = "..type(d).." - expected type = 'number'")
1081 -- select a mapping from a tagmap
1082 function selectTableEntry(table, action)
1083 if (type(action)~="string") then action="select number of item:" end
1084 for k, v in pairs(table) do
1085 print(accyan..k..acoff.."\t-> "..accyan..v['name']..acoff)
1087 local res = tonumber(input(action , 0), 10)
1088 if (istable(table[res])) then
1097 function mapAllSegments(tag, tagMap)
1098 local bytes=tagToBytes(tag)
1100 segs=getSegmentStats(bytes)
1101 if (istable(segs)) then
1102 for k, v in pairs(segs) do
1103 -- wrp (write proteted) = byte 2
1104 WRP = tonumber(bytes[v['start']+2],16)
1105 -- wrc (write control) - bit 4-6 of byte 3
1106 WRC = tonumber(bbit("0x"..bytes[v['start']+3],4,3),16)
1107 --tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." HDR", v['start'], v['start']+3)
1108 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." CRC", v['start']+4, v['start']+4, true)
1109 table.insert(tagMap.crc8, {name = 'Segment '..("%02d"):format(v['index']).." CRC", pos=v['start']+4, seq={1,4,v['start'],v['start']+3}} )
1112 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
1113 elseif (WRP>WRC and WRC>0) then
1115 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
1116 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+WRC+5, v['start']+5+WRP-1, true)
1119 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+5, v['start']+5+WRP-1, true)
1121 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." data", v['start']+5+WRPC, v['end'], false)
1124 print(#segs.." Segments mapped")
1126 oops("autoMapSegments failed: no Segments found")
1132 -- map all token data
1133 function mapTokenData(tagMap, mname, mstart, mend, mhigh)
1134 --if ( not mhigh ) then mhigh=false end
1136 myMapping['name'] =mname
1137 myMapping['start']=mstart
1138 myMapping['end'] =mend
1139 myMapping['highlight']=mhigh
1140 table.insert(tagMap.mappings, myMapping)
1146 function mapTag(tagMap)
1148 tagMap=mapTokenData(tagMap, 'Tag-ID', 1, 4, true)
1149 tagMap=mapTokenData(tagMap, 'Tag-CRC', 5, 5, false)
1150 tagMap=mapTokenData(tagMap, 'DCF', 6, 7, true)
1151 tagMap=mapTokenData(tagMap, 'THDR-Raw/Stamp-Len', 8, 8, true)
1152 tagMap=mapTokenData(tagMap, 'SSC', 9, 9, true)
1153 tagMap=mapTokenData(tagMap, 'Header', 10, 13, false)
1154 tagMap=mapTokenData(tagMap, 'Backup', 14, 19, true)
1155 tagMap=mapTokenData(tagMap, 'Bck-CRC', 20, 20, false)
1156 tagMap=mapTokenData(tagMap, 'TokenTime', 21, 22, false)
1162 -- dump virtual Tag-Data
1163 function dumpTag(tag)
1169 res =acyellow.."\nCDF: System Area"..acoff
1170 res= res.."\n"..dumpCDF(tag)
1171 -- segments (user-token area)
1172 if(tag.Type=="SAM" and tag.raw=='9f') then
1173 res = res..acyellow.."\n\nADF: User Area"..acoff
1174 for i=0, #tag.SEG do
1175 res=res.."\n"..dumpSegment(tag, i).."\n"
1182 -- dump tag-system area
1183 function dumpCDF(tag)
1188 if (istable(tag)) then
1189 res = res..accyan.."MCD: "..acoff..tag.MCD..accyan.." MSN: "..acoff..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..accyan.." MCC: "..acoff..tag.MCC.."\n"
1190 res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n"
1191 res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..((tag.raw=='9f') and (", SSC="..tag.SSC.."\n") or "\n")
1193 -- credential (end-user tag)
1194 if (tag.Type=="SAM" and tag.raw=='9f') then
1195 res = res.."Remaining Header Area\n"
1196 for i=0, (#tag.data) do
1197 res = res..tag.data[i].." "
1199 res = res.."\nBackup Area\n"
1200 for i=0, (#tag.Bck) do
1201 res = res..tag.Bck[i].." "
1203 res = res.."\nTime Area\n"
1204 for i=0, (#tag.MTC) do
1205 res = res..tag.MTC[i].." "
1209 -- Master Token specific
1210 elseif (tag.Type~="SAM") then
1211 res = res .."Master-Token Area\nStamp: "
1212 res= res..tag.SSC.." "
1213 for i=0, tag.Stamp_len-2 do
1214 res = res..tag.data[i].." "
1216 res=res.."\nunused payload\n"
1217 for i=0, (#tag.data-tag.Stamp_len-1) do
1218 res = res..tag.data[i].." "
1220 bytes=tagToBytes(tag)
1221 local mtcrc=calcMtCrc(bytes)
1222 res=res.."\nMaster-Token CRC: "
1223 res = res ..tag.MTC[1].." ("..((tag.MTC[1]==mtcrc) and "valid" or "error")..")"
1226 -- 'Gantner User-Credential' specific
1227 elseif (tag.Type=="SAM" and (tag.raw=='08' or tag.raw=='09')) then
1228 print(acgreen.."Gantner Detected"..acoff)
1232 else print("no valid Tag in dumpCDF") end
1236 -- dump single segment
1237 function dumpSegment(tag, index)
1240 local dp=0 --data-position in table
1241 local res="" --result
1242 local raw="" --raw-header
1244 if ( (istable(tag.SEG[i])) and tag.Type=="SAM" and tag.raw=="9f") then
1245 if (istable(tag.SEG[i].raw)) then
1246 for k,v in pairs(tag.SEG[i].raw) do
1252 res = res..accyan.."Segment "..("%02d"):format(tag.SEG[i].index)..acoff..": "
1253 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).."), "
1254 res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", "
1255 res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." "
1256 res = res .."("..(checkSegmentCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
1261 if ((tag.SEG[i].WRC>0)) then
1262 res = res .."\nWRC protected area:\n"
1263 for i2=dp, dp+tag.SEG[i].WRC-1 do
1264 res = res..tag.SEG[i].data[i2].." "
1270 if (tag.SEG[i].WRP>tag.SEG[i].WRC) then
1271 res = res .."\nRemaining write protected area:\n"
1272 for i2=dp, dp+(tag.SEG[i].WRP-tag.SEG[i].WRC)-1 do
1273 res = res..tag.SEG[i].data[i2].." "
1279 if (#tag.SEG[i].data-dp>0) then
1280 res = res .."\nRemaining segment payload:\n"
1281 for i2=dp, #tag.SEG[i].data-2 do
1282 res = res..tag.SEG[i].data[dp].." "
1285 if (tag.SEG[i].kgh) then
1286 res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
1287 else res = res..tag.SEG[i].data[dp] end
1292 return print("Segment not found")
1297 -- return bytes 'sstrat' to 'send' from a table
1298 function dumpTable(tab, header, tstart, tend)
1300 for i=tstart, tend do
1301 res=res..tab[i].." "
1303 if (string.len(header)==0) then return res
1304 else return (header.." #"..(tend-tstart+1).."\n"..res) end
1308 -- dump 3rd Party Cash
1309 function dump3rdPartyCash1(tag , seg)
1310 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1311 local stamp=tag.SEG[seg].data[0].." "..tag.SEG[seg].data[1].." "..tag.SEG[seg].data[2]
1312 local datastamp=tag.SEG[seg].data[20].." "..tag.SEG[seg].data[21].." "..tag.SEG[seg].data[22]
1313 local balance=tonumber(tag.SEG[seg].data[32]..tag.SEG[seg].data[33] ,16)
1314 local balancecrc=utils.Crc8Legic(uid..tag.SEG[seg].data[32]..tag.SEG[seg].data[33])
1315 local mirror=tonumber(tag.SEG[seg].data[35]..tag.SEG[seg].data[36] ,16)
1316 local mirrorcrc=utils.Crc8Legic(uid..tag.SEG[seg].data[35]..tag.SEG[seg].data[36])
1317 local lastbal0=tonumber(tag.SEG[seg].data[39]..tag.SEG[seg].data[40] ,16)
1318 local lastbal1=tonumber(tag.SEG[seg].data[41]..tag.SEG[seg].data[42] ,16)
1319 local lastbal2=tonumber(tag.SEG[seg].data[43]..tag.SEG[seg].data[44] ,16)
1322 -- display decoded/known stuff
1323 print("\n------------------------------")
1324 print("Tag-ID:\t\t "..uid)
1325 print("Stamp:\t\t "..stamp)
1326 print("UID-Mapping: \t\t"..("%06d"):format(tonumber(tag.SEG[seg].data[46]..tag.SEG[seg].data[47]..tag.SEG[seg].data[48], 16)))
1327 print("------------------------------")
1328 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])..")")
1329 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])..")")
1330 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])..")")
1332 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])..")")
1333 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])..")")
1334 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])..")")
1335 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])..")")
1336 print("------------------------------")
1337 print(string.format("Balance:\t\t %3.2f", balance/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[34])..")")
1338 print(string.format("Shadow:\t\t\t %3.2f", mirror/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[37])..")")
1339 print("------------------------------")
1340 print(string.format("History 1:\t\t %3.2f", lastbal0/100))
1341 print(string.format("History 2:\t\t %3.2f", lastbal1/100))
1342 print(string.format("History 3:\t\t %3.2f", lastbal2/100))
1343 print("------------------------------\n")
1347 -- dump Legic-Cash data
1348 function dumpLegicCash(tag, x)
1349 if (istable(tag.SEG[x])) then
1350 io.write("in Segment "..tag.SEG[x].index.." :\n")
1351 print("--------------------------------\n\tLegic-Cash Values\n--------------------------------")
1352 local limit, curr, balance, rid, tcv
1353 -- currency of balance & limit
1354 curr=currency[tag.SEG[x].data[8]..tag.SEG[x].data[9]]
1356 limit=string.format("%4.2f", tonumber(tag.SEG[x].data[10]..tag.SEG[x].data[11]..tag.SEG[x].data[12], 16)/100)
1358 balance=string.format("%4.2f", tonumber(tag.SEG[x].data[15]..tag.SEG[x].data[16]..tag.SEG[x].data[17], 16)/100)
1359 -- reader-id who wrote last transaction
1360 rid=tonumber(tag.SEG[x].data[18]..tag.SEG[x].data[19]..tag.SEG[x].data[20], 16)
1361 -- transaction counter value
1362 tcv=tonumber(tag.SEG[x].data[29], 16)
1363 print("Currency:\t\t "..curr)
1364 print("Limit:\t\t\t "..limit)
1365 print("Balance:\t\t "..balance)
1366 print("Transaction Counter:\t "..tcv)
1367 print("Reader-ID:\t\t "..rid.."\n--------------------------------\n")
1373 function print3rdPartyCash1(tag, x)
1374 if (istable(tag.SEG[x])) then
1375 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1376 print("\n\t\tStamp : "..dumpTable(tag.SEG[x].data, "", 0 , 2))
1377 print("\t\tBlock 0: "..dumpTable(tag.SEG[x].data, "", 3 , 18))
1379 print("\t\tBlock 1: "..dumpTable(tag.SEG[x].data, "", 19, 30))
1380 print("checksum 1: Tag-ID .. Block 1 => LegicCrc8 = "..tag.SEG[x].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 19, 30)), tag.SEG[x].data[31])..")")
1382 print("\t\tBlock 2: "..dumpTable(tag.SEG[x].data, "", 32, 33))
1383 print("checksum 2: Block 2 => LegicCrc8 = "..tag.SEG[x].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 32, 33)), tag.SEG[x].data[34])..")")
1385 print("\t\tBlock 3: "..dumpTable(tag.SEG[x].data, "", 35, 36))
1386 print("checksum 3: Block 3 => LegicCrc8 = "..tag.SEG[x].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 35, 36)), tag.SEG[x].data[37])..")")
1388 print("\t\tyet unknown: "..tag.SEG[x].data[38])
1390 print("\t\tHisatory 1: "..dumpTable(tag.SEG[x].data, "", 39, 40))
1391 print("\t\tHisatory 2: "..dumpTable(tag.SEG[x].data, "", 41, 42))
1392 print("\t\tHisatory 3: "..dumpTable(tag.SEG[x].data, "", 43, 44))
1394 print("\t\tyet unknown: "..tag.SEG[x].data[45])
1396 print("\t\tKGH-UID HEX: "..dumpTable(tag.SEG[x].data, "", 46, 48))
1397 print("\t\tBlock 4: "..dumpTable(tag.SEG[x].data, "", 49, 54))
1398 print("checksum 4: Tag-ID .. KGH-UID .. Block 4 => LegicCrc8 = "..tag.SEG[x].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 46, 54)), tag.SEG[x].data[55])..")")
1400 print("\t\tBlock 5: "..dumpTable(tag.SEG[x].data, "", 56, 61))
1401 print("checksum 5: Tag-ID .. Block 5 => LegicCrc8 = "..tag.SEG[x].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 56, 61)), tag.SEG[x].data[62])..")")
1403 print("\t\tBlock 6: "..dumpTable(tag.SEG[x].data, "", 63, 72))
1404 print("checksum 6: Tag-ID .. Block 6 => LegicCrc8 = "..tag.SEG[x].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 63, 72)), tag.SEG[x].data[73])..")")
1406 print("\t\tBlock 7: "..dumpTable(tag.SEG[x].data, "", 74, 88))
1407 print("checksum 7: Tag-ID .. Block 7 => LegicCrc8 = "..tag.SEG[x].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 74, 88)), tag.SEG[x].data[89])..")")
1409 print("\t\tBlock 8: "..dumpTable(tag.SEG[x].data, "", 90, 94))
1413 --- Token related --
1416 function makeToken()
1418 ['Type'] = {"SAM", "SAM63", "SAM64", "IAM", "GAM"},
1419 ['DCF'] = {"60ea", "31fa", "30fa", "80fa", "f0fa"},
1420 ['WRP'] = {"15", "2", "2", "2", "2"},
1421 ['WRC'] = {"01", "02", "02", "00", "00"},
1422 ['RD'] = {"01", "00", "00", "00", "00"},
1423 ['Stamp'] = {"00", "00", "00", "00", "00"},
1424 ['Segment'] = {"0d", "c0", "04", "00", "be", "01", "02", "03", "04", "01", "02", "03", "04"}
1427 for k, v in pairs(mt.Type) do
1428 ttype=ttype..k..") "..v.." "
1430 mtq=tonumber(input("select number for Token-Type\n"..ttype, '1'), 10)
1431 if (type(mtq)~="number") then return print("selection invalid!")
1432 elseif (mtq>#mt.Type) then return print("selection invalid!")
1433 else print("Token-Type '"..mt.Type[mtq].."' selected") end
1434 local raw=calcHeaderRaw(mt.WRP[mtq], mt.WRC[mtq], mt.RD[mtq])
1437 bytes={"01", "02", "03", "04", "cb", string.sub(mt.DCF[mtq], 0, 2), string.sub(mt.DCF[mtq], 3), raw,
1438 "00", "00", "00", "00", "00", "00", "00", "00",
1439 "00", "00", "00", "00", "00", "00"}
1441 for i=0, #mt.Segment do
1442 table.insert(bytes, mt.Segment[i])
1447 for i=#bytes, 1023 do table.insert(bytes, "00") end
1448 -- if Master-Token -> calc Master-Token-CRC
1449 if (mtq>1) then bytes[22]=calcMtCrc(bytes) end
1450 local tempTag=createTagTable()
1451 -- remove segment if MasterToken
1452 if (mtq>1) then tempTag.SEG[0]=nil end
1453 return bytesToTag(bytes, tempTag)
1458 function editTag(tag)
1459 -- for simulation it makes sense to edit everything
1460 local edit_sim="MCD MSN0 MSN1 MSN2 MCC DCFl DCFh WRP WRC RD"
1461 -- on real tags it makes only sense to edit DCF, WRP, WRC, RD
1462 local edit_real="DCFl DCFh WRP WRC RD"
1463 if (confirm(acyellow.."do you want to edit non-writeable values (e.g. for simulation)?"..acoff)) then
1465 else edit_tag=edit_real end
1467 if(istable(tag)) then
1468 for k,v in pairs(tag) do
1469 if(type(v)~="table" and type(v)~="boolean" and string.find(edit_tag, k)) then
1470 tag[k]=input("value for: "..accyan..k..acoff, v)
1474 if (tag.Type=="SAM") then ttype="Header"; else ttype="Stamp"; end
1475 if (confirm(acyellow.."do you want to edit "..ttype.." Data?"..acoff)) then
1476 -- master-token specific
1477 if(istable(tag.Bck)==false) then
1478 -- stamp-data length=(0xfc-DCFh)
1479 -- on MT: SSC holds the Starting Stamp Character (Stamp0)
1480 tag.SSC=input(ttype.."0: ", tag.SSC)
1481 -- rest of stamp-bytes are in tag.data 0..n
1482 for i=0, (tonumber(0xfc ,10)-("%d"):format('0x'..tag.DCFh))-2 do
1483 tag.data[i]=input(ttype.. i+1 ..": ", tag.data[i])
1486 --- on credentials byte7 should always be 9f and byte8 ff
1487 -- on Master-Token not (even on SAM63/64 not)
1488 -- tag.SSC=input(ttype.."0: ", tag.SSC)
1489 for i=0, #tag.data do
1490 tag.data[i]=input(ttype.. i ..": ", tag.data[i])
1495 bytes=tagToBytes(tag)
1497 --- check data-consistency (calculate tag.raw)
1498 bytes[8]=calcHeaderRaw(tag.WRP, tag.WRC, tag.RD)
1500 --- Master-Token specific
1501 -- should be triggered if a SAM was converted to a non-SAM (user-Token to Master-Token)
1502 -- or a Master-Token has being edited (also SAM64 & SAM63 - which are in fact Master-Token)
1503 if(tag.Type~="SAM" or bytes[6]..bytes[7]~="60ea") then
1504 -- calc new Master-Token crc
1505 bytes[22]=calcMtCrc(bytes)
1507 -- ensure tag.SSC set to 'ff' on credential-token (SAM)
1509 -- if a Master-Token was converted to a Credential-Token
1510 -- lets unset the Time-Area to 00 00 (will contain Stamp-Data on MT)
1515 tag=bytesToTag(bytes, tag)
1520 -- calculates header-byte (addr 0x07)
1521 function calcHeaderRaw(wrp, wrc, rd)
1523 wrp=("%02x"):format(tonumber(wrp, 10))
1525 res=("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0))
1530 -- determine TagType (bits 0..6 of DCFlow)
1531 function getTokenType(DCFl)
1537 local tt = tonumber(bbit("0x"..DCFl,0,7),10)
1538 if (tt >= 0 and tt <= 47) then tt = "IAM"
1539 elseif (tt == 49) then tt = "SAM63"
1540 elseif (tt == 48) then tt = "SAM64"
1541 elseif (tt >= 50 and tt <= 111) then tt = "SAM"
1542 elseif (tt >= 112 and tt <= 127) then tt = "GAM"
1548 -- clear beackup-area of a virtual tag
1549 function clearBackupArea(tag)
1550 for i=1, #tag.Bck do
1556 --- Segment related --
1558 -- get segmemnt-data from byte-table
1559 function getSegmentData(bytes, start, index)
1566 ['raw'] = {'00', '00', '00', '00'},
1574 if (bytes[start]) then
1577 segment.index = index
1578 -- flag = high nibble of byte 1
1579 segment.flag = string.sub(bytes[start+1],0,1)
1580 -- valid = bit 6 of byte 1
1581 segment.valid = bbit("0x"..bytes[start+1],6,1)
1582 -- last = bit 7 of byte 1
1583 segment.last = bbit("0x"..bytes[start+1],7,1)
1584 -- len = (byte 0)+(bit0-3 of byte 1)
1585 segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16)
1586 -- raw segment header
1587 segment.raw = {bytes[start], bytes[start+1], bytes[start+2], bytes[start+3]}
1588 -- wrp (write proteted) = byte 2
1589 segment.WRP = tonumber(bytes[start+2],16)
1590 -- wrc (write control) - bit 4-6 of byte 3
1591 segment.WRC = tonumber(bbit("0x"..bytes[start+3],4,3),16)
1592 -- rd (read disabled) - bit 7 of byte 3
1593 segment.RD = tonumber(bbit("0x"..bytes[start+3],7,1),16)
1595 segment.crc = bytes[start+4]
1596 -- segment-data starts at segment.len - segment.header - segment.crc
1597 for i=0, (segment.len-5) do
1598 segment.data[i]=bytes[start+5+i]
1606 -- get index, start-aadr, length and content
1607 function getSegmentStats(bytes)
1609 local sValid, sLast, sLen, sStart, x
1614 -- valid = bit 6 of byte 1
1615 sValid = bbit("0x"..bytes[sStart+1],6,1)
1616 -- last = bit 7 of byte 1
1617 sLast = bbit("0x"..bytes[sStart+1],7,1)
1618 -- len = (byte 0)+(bit0-3 of byte 1)
1619 sLen = tonumber(bbit("0x"..bytes[sStart+1],0,4)..bytes[sStart],16)
1620 --print("index: "..("%02d"):format(x).." Len: "..sLen.." start:"..sStart.." end: "..(sStart+sLen-1))
1623 s['end']=sStart+sLen-1
1625 if ( (sStart+sLen-1)>sStart ) then
1626 table.insert(sStats, s)
1630 until (sLast==1 or sValid==0 or x==126)
1631 if (#sStats>0 ) then return sStats
1632 else return false; end
1636 -- regenerate segment-header (after edit)
1637 function regenSegmentHeader(segment)
1639 local raw = segment.raw
1641 -- len bit0..7 | len=12bit=low nibble of byte1..byte0
1642 raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8))
1643 -- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh?
1644 raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),8,4)+bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8))
1646 raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8))
1648 raw[4]=("%02x"):format(tonumber((seg.WRC*16)+(seg.RD*128),10))
1650 seg.flag=string.sub(raw[2],0,1)
1651 --print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4])
1652 if(#seg.data>(seg.len-5)) then
1653 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
1654 print(acyellow.."Data-Length has being reduced:"..acgreen.." auto-removing "..acyellow.. #seg.data-(seg.len-5) ..acgreen .." bytes from Payload!"..acoff);
1655 for i=(seg.len-5), #seg.data-1 do
1656 table.remove(seg.data)
1658 elseif (#seg.data<(seg.len-5)) then
1659 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
1660 print(acyellow.."Data-Length has being extended:"..acgreen.." auto-adding "..acyellow..(seg.len-5)-#seg.data ..acgreen .." bytes to Payload!"..acoff);
1661 for i=#seg.data, (seg.len-5-1) do
1662 table.insert(seg.data, '00')
1669 -- edit segment helper
1670 function editSegment(tag, index)
1672 local edit_possible="valid len RD WRP WRC Stamp Payload"
1673 if (istable(tag.SEG[index])) then
1674 for k,v in pairs(tag.SEG[index]) do
1675 if(string.find(edit_possible,k)) then
1676 tag.SEG[index][k]=tonumber(input(accyan..k..acoff..": ", v),10)
1679 else print("Segment with Index: "..("%02d"):format(index).." not found in Tag")
1682 regenSegmentHeader(tag.SEG[index])
1683 print("\n"..dumpSegment(tag, index).."\n")
1688 -- edit Segment Data
1689 function editSegmentData(data)
1690 local lc=check4LegicCash(data)
1692 if (istable(data)) then
1694 data[i]=input(accyan.."Data"..i..acoff..": ", data[i])
1696 if (lc) then data=fixLegicCash(data) end
1699 print("no Segment-Data found")
1704 -- list available segmets in virtual tag
1705 function segmentList(tag)
1708 if (istable(tag.SEG[0])) then
1709 for i=0, #tag.SEG do
1710 res = res .. tag.SEG[i].index .. " "
1713 else print("no Segments found in Tag")
1719 -- helper to selecting a segment
1720 function selectSegment(tag)
1722 if (istable(tag.SEG[0])) then
1723 print("availabe Segments:\n"..segmentList(tag))
1724 sel=input("select Segment: ", '00')
1725 sel=tonumber(sel,10)
1726 if (sel) then return sel
1729 print("\nno Segments found")
1736 function addSegment(tag)
1744 ['raw'] = {'0d', '40', '04', '00'},
1752 if (istable(tag.SEG[0])) then
1753 tag.SEG[#tag.SEG].last=0
1754 table.insert(tag.SEG, segment)
1756 tag.SEG[#tag.SEG].data[i]=("%02x"):format(i)
1758 tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG)
1761 print("no Segment-Table found")
1766 -- get only the stamp-bytes of a segment
1767 function getSegmentStamp(seg, bytes)
1770 --- the 'real' stamp on MIM is not really easy to tell for me since the 'data-block' covers stamp0..stampn+data0..datan
1771 -- there a no stamps longer than 7 bytes & they are write-protected by default , and I have not seen user-credntials
1772 -- with stamps smaller 3 bytes (except: Master-Token)
1773 -- WRP -> Read/Write Protection
1774 -- WRC -> Read/Write Condition
1775 -- 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
1776 if (seg.WRP<7) then stamp_len=(seg.WRP) end
1777 for i=1, (stamp_len) do
1778 stamp=stamp..seg.data[i-1]
1781 stamp=str2bytes(stamp)
1783 else return stamp end
1787 -- edit stamp of a segment
1788 function editStamp(new_stamp, uid, data)
1789 local stamp=str2bytes(new_stamp)
1790 for i=0, #stamp-1 do
1793 -- now fill in stamp
1794 for i=0, (string.len(new_stamp)/2)-1 do
1797 return fix3rdPartyCash1(uid, data)
1801 -- autoselect special/known segments
1802 function autoSelectSegment(tag, s)
1803 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1806 io.write("autoSelect ")
1807 --- search for desired segment-type
1808 -- 3rd Party Segment
1809 if (s=="3rdparty") then
1813 res=check43rdPartyCash1(uid, tag.SEG[x].data)
1814 until ( res or x==0 )
1816 -- Legic-Cash Segment
1817 if (s=="legiccash") then
1821 res=check4LegicCash(tag.SEG[x].data)
1822 until ( res or x==0 )
1827 io.write("\nautoselected Index: "..string.format("%02d", x).."\n")
1832 io.write(acyellow.."no Segment found\n"..acoff)
1837 -- delete segment (except segment 00)
1838 function delSegment(tag, index)
1839 if (istable(tag.SEG[0])) then
1841 if (type(index)=="string") then index=tonumber(index,10) end
1843 table.remove(tag.SEG, index)
1844 for i=0, #tag.SEG do
1845 tag.SEG[i].index=("%02d"):format(i)
1848 if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end
1854 -- edit uid 3rd party cash
1855 function edit3rdUid(mapid, uid, data)
1856 mapid=("%06x"):format(tonumber(mapid, 10))
1857 data[46]=string.sub(mapid, 1 ,2)
1858 data[47]=string.sub(mapid, 3 ,4)
1859 data[48]=string.sub(mapid, 5 ,6)
1860 return fix3rdPartyCash1(uid, data)
1864 -- edit balance 3rd party cash
1865 function edit3rdCash(new_cash, uid, data)
1866 new_cash=("%04x"):format(new_cash)
1867 data[32]=string.sub(new_cash, 0,2)
1868 data[33]=string.sub(new_cash, 3,4)
1869 data[34]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
1870 data[35]=string.sub(new_cash, 0,2)
1871 data[36]=string.sub(new_cash, 3,4)
1872 data[37]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
1873 data[39]=string.sub(new_cash, 0,2)
1874 data[40]=string.sub(new_cash, 3,4)
1879 return fix3rdPartyCash1(uid, data)
1883 -- edit 3rd-party cash
1884 function edit3rdPartyCash1(tag, x)
1885 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1886 if (confirm("\nedit Balance?")) then
1887 local new_cash=input("enter new Balance≤\nwithout comma and without currency-sign! (0-65535)", "100")
1888 tag.SEG[x].data=edit3rdCash(new_cash, uid, tag.SEG[x].data)
1890 -- change User-ID (used for online-account-mapping)
1891 if (confirm("\nedit UserID-Mapping?")) then
1892 local new_mapid=input("enter new UserID (6-digit value)", "012345")
1893 tag.SEG[x].data=edit3rdUid(new_mapid, uid, tag.SEG[x].data)
1895 if (confirm("\nedit Stamp?")) then
1896 local new_stamp=input("enter new Stamp", getSegmentStamp(tag.SEG[x]))
1897 tag.SEG[x].data=editStamp(new_stamp, uid, tag.SEG[x].data)
1898 new_stamp=getSegmentStamp(tag.SEG[x], 'true')
1899 print("stamp_bytes: "..#new_stamp)
1900 -- replace stamp in 'block 1' also
1901 io.write("editing stamp in Block 1 also ")
1902 for i=20, (20+#new_stamp-1) do
1903 tag.SEG[x].data[i]=new_stamp[i-19]
1907 -- fix known checksums
1908 tag.SEG[x].data=fix3rdPartyCash1(uid, tag.SEG[x].data)
1915 function editLegicCash(data)
1916 local limit, curr, balance, rid, tcv
1917 -- currency of balance & limit
1918 curr=currency[data[8]..data[9]]
1920 limit=string.format("%4.2f", tonumber(data[10]..data[11]..data[12], 16)/100)
1922 balance=string.format("%4.2f", tonumber(data[15]..data[16]..data[17], 16)/100)
1923 -- reader-id who wrote last transaction
1924 rid=tonumber(data[18]..data[19]..data[20], 16)
1925 -- transaction counter value
1926 tcv=tonumber(data[29], 16)
1929 if (confirm(accyan.."change Currency?"..acoff)) then
1930 for k, v in pairs(currency) do io.write(k .. " = " .. v .. "\n") end
1931 curr=input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[8]..data[9])
1932 data[8]=string.sub(curr, 1, 2)
1933 data[9]=string.sub(curr, 3, 4)
1937 if (confirm(accyan.."change Limit?"..acoff)) then
1938 limit=string.format("%06x", input(accyan.."enter the Decimal for the new Limit:"..acoff, limit))
1939 data[10]=string.sub(limit, 1, 2)
1940 data[11]=string.sub(limit, 3, 4)
1941 data[12]=string.sub(limit, 5, 6)
1945 if (confirm(accyan.."change Balance?"..acoff)) then
1946 balance=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, balance))
1947 print("Balance: "..balance)
1948 data[15]=string.sub(balance, 1, 2)
1949 data[16]=string.sub(balance, 3, 4)
1950 data[17]=string.sub(balance, 5, 6)
1953 -- edit transaction-counter
1954 if (confirm(accyan.."change Transaction-Counter?"..acoff)) then
1955 tcv=string.format("%02x", input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[29]))
1960 if (confirm(accyan.."change Last-Reader-ID?"..acoff)) then
1961 rid=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, rid))
1962 print("Balance: "..balance)
1963 data[18]=string.sub(rid, 1, 2)
1964 data[19]=string.sub(rid, 3, 4)
1965 data[20]=string.sub(rid, 5, 6)
1968 return fixLegicCash(data)
1972 -- chack for signature of a 'Legic-Cash-Segment'
1973 function check4LegicCash(data)
1975 local stamp_len=(#data-25)
1977 for i=0, stamp_len-1 do
1978 stamp=stamp..data[i].." "
1980 if (data[7]=="01") then
1981 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12))) == data[13]..data[14]) then
1982 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20))) == data[21]..data[22]) then
1983 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29))) == data[30]..data[31]) then
1984 io.write(accyan.."Legic-Cash Segment detected "..acoff)
1995 -- chack for signature of a '3rd Party Cash-Segment' - not all bytes know until yet !!
1996 function check43rdPartyCash1(uid, data)
1998 -- too explicit checking will avoid fixing ;-)
1999 if (string.find(compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)), data[31]),"valid")) then
2000 --if (compareCrc(utils.Crc8Legic(uid..data[32]..data[33]), data[34])=="valid") then
2001 --if (compareCrc(utils.Crc8Legic(uid..data[35]..data[36]), data[37])=="valid") then
2002 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)), data[62])=="valid") then
2003 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)), data[89])=="valid") then
2004 io.write(accyan.."3rd Party Cash-Segment detected "..acoff)
2017 -- build segmentCrc credentials
2018 function segmentCrcCredentials(tag, segid)
2019 if (istable(tag.SEG[0])) then
2020 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
2021 cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4]
2023 else return print(acyellow.."Master-Token / unsegmented Tag!"..acoff) end
2027 -- build kghCrc credentials
2028 function kghCrcCredentials(tag, segid)
2029 if (istable(tag) and istable(tag.SEG[0])) then
2031 if (type(segid)=="string") then segid=tonumber(segid,10) end
2032 if (segid>0) then x='93' end
2033 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP)
2034 cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x
2035 for i=0, #tag.SEG[segid].data-2 do
2036 cred = cred..tag.SEG[segid].data[i]
2043 -- compare two bytes
2044 function compareCrc(calc, guess)
2045 calc=("%02x"):format(calc)
2046 if (calc==guess) then return acgreen.."valid"..acoff
2047 else return acred.."error "..acoff..calc.."!="..guess end
2052 function compareCrc16(calc, guess)
2053 calc=("%04x"):format(calc)
2054 if (calc==guess) then return acgreen.."valid"..acoff
2055 else return acred.."error "..acoff..calc.."!="..guess end
2059 -- repair / fix crc's of a 'Legic-Cash-Segment'
2060 function fixLegicCash(data)
2061 if(#data==32 and data[7]=="01") then
2062 local crc1, crc2, crc3
2063 -- set shadow-balance equal to balance
2067 -- set shadow-last-reader to last-reader
2071 -- calculate all crc's
2072 crc1=("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12)))
2073 crc2=("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20)))
2074 crc3=("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29)))
2076 data[13]=string.sub(crc1, 1, 2)
2077 data[14]=string.sub(crc1, 3, 4)
2078 data[21]=string.sub(crc2, 1, 2)
2079 data[22]=string.sub(crc2, 3, 4)
2080 data[30]=string.sub(crc3, 1, 2)
2081 data[31]=string.sub(crc3, 3, 4)
2087 -- repair / fix (yet known) crc's of a '3rd Party Cash-Segment' - not all bytes know until yet !!
2088 function fix3rdPartyCash1(uid, data)
2091 data[31]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)))
2093 data[34]=("%02x"):format(utils.Crc8Legic(uid..data[32]..data[33]))
2095 data[37]=("%02x"):format(utils.Crc8Legic(uid..data[35]..data[36]))
2097 data[55]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 46, 54)))
2099 data[62]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)))
2101 data[73]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 63, 72)))
2103 data[89]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)))
2109 -- calculate Master-Token crc
2110 function calcMtCrc(bytes)
2112 local cmd=bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[7]..bytes[6]..bytes[8]
2113 local len=(tonumber(0xfc ,10)-("%d"):format('0x'..bytes[7]))
2117 local res=("%02x"):format(utils.Crc8Legic(cmd))
2122 -- calculate segmentCRC for a given segment
2123 function calcSegmentCrc(tag, segid)
2124 if (istable(tag.SEG[0])) then
2125 -- check if a 'Kaber Group Header' exists
2126 local data=segmentCrcCredentials(tag, segid)
2127 return ("%02x"):format(utils.Crc8Legic(data))
2132 -- calcuate kghCRC for a given segment
2133 function calcKghCrc(tag, segid)
2134 if (istable(tag.SEG[0])) then
2135 -- check if a 'Kaber Group Header' exists
2137 local data=kghCrcCredentials(tag, segid)
2138 return ("%02x"):format(utils.Crc8Legic(data))
2143 -- check all segmnet-crc
2144 function checkAllSegCrc(tag)
2145 if (istable(tag.SEG[0])) then
2146 for i=0, #tag.SEG do
2147 crc=calcSegmentCrc(tag, i)
2150 else return print(acyellow.."Master-Token / unsegmented Tag"..acoff) end
2154 -- check all segmnet-crc
2155 function checkAllKghCrc(tag)
2156 if (istable(tag.SEG[0])) then
2157 for i=0, #tag.SEG do
2158 crc=calcKghCrc(tag, i)
2159 if (tag.SEG[i].kgh) then
2160 tag.SEG[i].data[#tag.SEG[i].data-1]=crc
2167 -- validate segmentCRC for a given segment
2168 function checkSegmentCrc(tag, segid)
2169 local data=segmentCrcCredentials(tag, segid)
2170 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then
2177 -- validate kghCRC to segment in tag-table
2178 function checkKghCrc(tag, segid)
2179 if (type(tag.SEG[segid])=='table') then
2180 if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then
2181 local data=kghCrcCredentials(tag, segid)
2182 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end
2183 else return false; end
2184 else oops(acred.."'Kaba Group header' detected but no Segment-Data found"..ansocolors.reset) end
2188 -- helptext for modify-mode
2189 function modifyHelp()
2192 Data I/O Segment Manipulation Token-Data
2193 ----------------- -------------------- ---------------------
2194 rt => read Tag as => add Segment mt => make Token
2195 wt => write Tag es => edit Segment Header et => edit Token data
2196 ed => edit Segment Data tk => toggle KGH-Flag
2197 File I/O rs => remove Segment
2198 ----------------- cc => check Segment-CRC
2199 lf => load File ck => check KGH
2200 sf => save File ds => dump Segments
2204 Virtual Tags tagMap (partial) known Segments
2205 -------------------------------- --------------------- ---------------------------
2206 ct => copy mainTag to backupTag mm => make (new) Map dlc => dump Legic-Cash
2207 tc => copy backupTag to mainTag em => edit Map submenu elc => edit Legic-Cash
2208 tt => switch mainTag & backupTag lm => load map from file d3p => dump 3rd-Party-Cash
2209 di => dump mainTag sm => save map to file e3p => edit 3rd-Party-Cash
2210 do => dump backupTag
2212 h => this help q => quit
2218 -- modify Tag (interactive)
2219 function modifyMode()
2220 local i, backupTAG, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes, tagMap
2226 print(" Version: "..version);
2227 print(modifyHelp().."\n".."tags im Memory: "..(istable(inTAG) and ((currentTag=='inTAG') and acgreen.."*mainTAG"..acoff or "mainTAG") or "").." "..(istable(backupTAG) and ((currentTag=='backupTAG') and acgreen.."*backupTAG"..acoff or "backupTAG") or ""))
2230 -- read real Tag with PM3 into virtual 'mainTAG'
2231 ["rt"] = function(x)
2232 inTAG=readFromPM3();
2236 -- write content of virtual 'mainTAG' to real Tag with PM3
2237 ["wt"] = function(x)
2241 -- copy mainTAG to backupTAG
2242 ["ct"] = function(x)
2243 print(accyan.."copy mainTAG to backupTAG"..acoff)
2244 backupTAG=deepCopy(inTAG)
2247 -- copy backupTAG to mainTAG
2248 ["tc"] = function(x)
2249 print(accyan.."copy backupTAG to mainTAG"..acoff)
2250 inTAG=deepCopy(backupTAG)
2253 -- toggle between mainTAG and backupTAG
2254 ["tt"] = function(x)
2255 -- copy main to temp
2256 outTAG=deepCopy(inTAG)
2257 -- copy backup to main
2258 inTAG=deepCopy(backupTAG)
2259 print(accyan.."toggle to "..accyan..((currentTag=='inTAG') and "backupTAG" or "mainTAG")..acoff)
2260 if(currentTag=="inTAG") then currentTag='backupTAG'
2261 else currentTag='inTAG' end
2262 -- copy temp (main) to backup
2263 backupTAG=deepCopy(outTAG)
2266 -- load file into mainTAG
2267 ["lf"] = function(x)
2269 if (type(x)=='string' and file_check(x)) then filename=x
2270 else filename=input("enter filename: ", "legic.temp") end
2271 inTAG=readFile(filename)
2272 -- check for existing tagMap
2273 if (file_check(filename..".map")) then
2274 if(confirm(accyan.."Mapping-File for "..acoff..filename..accyan.." found - load it also?"..acoff)) then
2275 tagMap=loadTagMap(filename..".map")
2280 -- save values of mainTAG to a file (xored with MCC of mainTAG)
2281 ["sf"] = function(x)
2282 if(istable(inTAG)) then
2283 outfile=input("enter filename:", "legic.temp")
2284 bytes=tagToBytes(inTAG)
2285 --bytes=xorBytes(bytes, inTAG.MCC)
2287 writeFile(bytes, outfile)
2292 -- save values of mainTAG to a file (xored with 'specific' MCC)
2293 ["xf"] = function(x)
2294 if(istable(inTAG)) then
2295 outfile=input("enter filename:", "legic.temp")
2296 crc=input("enter new crc: ('00' for a plain dump)", inTAG.MCC)
2297 print("obfuscate witth: "..crc)
2298 bytes=tagToBytes(inTAG)
2301 writeFile(bytes, outfile)
2306 -- dump mainTAG (and all Segments)
2307 ["di"] = function(x)
2308 if (istable(inTAG)) then
2309 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2310 if(istable(inTAG.SEG[0])) then
2311 for i=0, #inTAG.SEG do
2312 if(check43rdPartyCash1(uid, inTAG.SEG[i].data)) then
2313 io.write(accyan.."in Segment index: "..inTAG.SEG[i].index ..acoff.. "\n")
2314 elseif(check4LegicCash(inTAG.SEG[i].data)) then
2315 io.write(accyan.."in Segment index: "..inTAG.SEG[i].index..acoff.."\n")
2317 lci=inTAG.SEG[i].index;
2321 print("\n"..dumpTag(inTAG).."\n")
2322 if (lc) then actions["dlc"](lci) end
2327 -- dump backupTAG (and all Segments)
2328 ["do"] = function(x) if (istable(backupTAG)) then print("\n"..dumpTag(backupTAG).."\n") end end,
2330 -- create a empty tagMap
2331 ["mm"] = function(x)
2332 -- clear existing tagMap and init
2333 if (istable(inTAG)) then
2339 ["em"] = function(x)
2340 if (istable(inTAG)==false) then
2341 if (confirm("no mainTAG in memory!\nread from PM3?")) then
2343 elseif (confirm("load from File?")) then
2348 if (istable(tagMap)==false) then actions['mm']() end
2350 tagMap=editTagMap(inTAG, tagMap)
2354 ["sm"] = function(x)
2355 if (istable(tagMap)) then
2356 if (istable(tagMap) and #tagMap.mappings>0) then
2357 print(accyan.."Map contains "..acoff..#tagMap..accyan.." mappings"..acoff)
2358 saveTagMap(tagMap, input(accyan.."enter filename:"..acoff, "Legic.map"))
2360 print(acyellow.."no mappings in tagMap!"..acoff)
2366 ["lm"] = function(x)
2367 tagMap=loadTagMap(input(accyan.."enter filename:"..acoff, "Legic.map"))
2370 -- dump single segment
2371 ["ds"] = function(x)
2372 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2373 else sel=selectSegment(inTAG) end
2374 if (sel) then print("\n"..(dumpSegment(inTAG, sel) or acred.."no Segments available") ..acoff.."\n") end
2377 -- edit segment header
2378 ["es"] = function(x)
2379 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2380 else sel=selectSegment(inTAG) end
2382 if(istable(inTAG.SEG[0])) then
2383 inTAG=editSegment(inTAG, sel)
2384 inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel])
2385 else print(acyellow.."no Segments in Tag"..acoff) end
2390 ["as"] = function(x)
2391 if (istable(inTAG.SEG[0])) then
2392 inTAG=addSegment(inTAG)
2393 inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1])
2394 inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG])
2395 else print(accyan.."Master-Token / unsegmented Tag!"..acoff)
2400 ["rs"] = function(x)
2401 if (istable(inTAG.SEG[0])) then
2402 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2403 else sel=selectSegment(inTAG) end
2404 inTAG=delSegment(inTAG, sel)
2405 for i=0, #inTAG.SEG do
2406 inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i])
2411 -- edit data-portion of single segment
2412 ["ed"] = function(x)
2413 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2414 else sel=selectSegment(inTAG) end
2415 if (istable(inTAG.SEG[sel])) then
2416 inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data)
2420 -- edit Tag (MCD, MSN, MCC etc.)
2421 ["et"] = function(x)
2422 if (istable(inTAG)) then
2427 -- make (dummy) Token
2428 ["mt"] = function(x) inTAG=makeToken(); actions.di() end,
2430 -- fix segment-crc on single segment
2431 ["ts"] = function(x)
2432 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2433 else sel=selectSegment(inTAG) end
2434 regenSegmentHeader(inTAG.SEG[sel])
2437 -- toggle kgh-crc-flag on a single segment
2438 ["tk"] = function(x)
2439 if (istable(inTAG) and istable(inTAG.SEG[0])) then
2440 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2441 else sel=selectSegment(inTAG) end
2442 if(inTAG.SEG[sel].kgh) then inTAG.SEG[sel].kgh=false
2443 else inTAG.SEG[sel].kgh=true end
2447 -- calculate LegicCrc8
2449 if (type(x)=="string" and string.len(x)>0) then
2450 print(("%02x"):format(utils.Crc8Legic(x)))
2455 ["xb"] = function(x)
2458 -- print string for LegicCrc8-calculation about single segment
2459 ["xc"] = function(x)
2460 if (istable(inTAG) and istable(inTAG.SEG[0])) then
2461 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2462 else sel=selectSegment(inTAG) end
2463 print("k "..kghCrcCredentials(inTAG, sel))
2467 -- fix legic-cash checksums
2468 ["flc"] = function(x)
2469 if (type(x)=="string" and string.len(x)>0) then x=tonumber(x,10)
2470 else x=selectSegment(inTAG) end
2471 inTAG.SEG[x].data=fixLegicCash(inTAG.SEG[x].data)
2474 -- edit legic-cash values fixLegicCash(data)
2475 ["elc"] = function(x)
2476 x=autoSelectSegment(inTAG, "legiccash")
2477 inTAG.SEG[x].data=editLegicCash(inTAG.SEG[x].data)
2480 -- dump legic-cash human-readable
2481 ["dlc"] = function(x)
2482 -- if segment index was user defined
2483 if (type(x)=="string" and string.len(x)>0) then
2485 print(string.format("User-Selected Index %02d", x))
2486 -- or try to find match
2487 else x=autoSelectSegment(inTAG, "legiccash") end
2489 dumpLegicCash(inTAG, x)
2492 -- dump 3rd-party-cash-segment
2493 ["d3p"] = function(x)
2494 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2495 -- if segment index was user defined
2496 if (type(x)=="string" and string.len(x)>0) then
2498 print(string.format("User-Selected Index %02d", x))
2499 -- or try to find match
2500 else x=autoSelectSegment(inTAG, "3rdparty") end
2501 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
2502 uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2503 if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then
2504 dump3rdPartyCash1(inTAG, x)
2509 -- dump 3rd-party-cash-segment (raw blocks and checksums over 'known areas')
2510 ["r3p"] = function(x)
2511 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2512 -- if segment index was user defined
2513 if (type(x)=="string" and string.len(x)>0) then
2515 print(string.format("User-Selected Index %02d", x))
2516 -- or try to find match
2517 else x=autoSelectSegment(inTAG, "3rdparty") end
2518 print3rdPartyCash1(inTAG, x)
2521 -- edit 3rd-party-cash-segment values (Balance, Mapping-UID, Stamp)
2522 ["e3p"] = function(x)
2523 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2524 -- if segment index was user defined
2525 if (type(x)=="string" and string.len(x)>0) then
2527 print(string.format("User-Selected Index %02d", x))
2528 -- or try to find match
2529 else x=autoSelectSegment(inTAG, "3rdparty") end
2530 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
2531 inTAG=edit3rdPartyCash1(inTAG, x)
2532 dump3rdPartyCash1(inTAG, x)
2536 -- force fixing 3rd-party-checksums
2537 ["f3p"] = function(x)
2538 if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10)
2539 else x=selectSegment(inTAG) end
2540 if (istable(inTAG.SEG[x])) then
2541 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2542 inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data)
2546 -- get stamp from single segment
2547 ["gs"] = function(x)
2548 if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10)
2549 else x=selectSegment(inTAG) end
2550 local stamp=getSegmentStamp(inTAG.SEG[x])
2551 print("Stamp : "..stamp)
2552 stamp=str2bytes(stamp)
2553 print("lenght: "..#stamp)
2557 ["c6"] = function(x) local crc16=string.format("%4.04x", utils.Crc16(x))
2558 print(string.sub(crc16, 0,2).." "..string.sub(crc16, 3,4))
2561 -- check & fix segments-crc of all segments
2562 ["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end,
2564 -- set backup-area-bytes to '00'
2565 ["cb"] = function(x)
2566 if (istable(inTAG)) then
2567 print(accyan.."purge BackupArea"..acoff)
2568 inTAG=clearBackupArea(inTAG)
2572 -- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
2573 ["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end,
2575 -- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
2576 ["tac"] = function(x)
2577 if (colored_output) then
2578 colored_output=false
2582 load_colors(colored_output)
2586 -- defualt message / prompt
2587 ic=input("Legic command? ('h' for help - 'q' for quit)", "h")
2588 -- command actions decisions (first match, longer commands before shorter)
2589 if (type(actions[string.lower(string.sub(ic,0,3))])=='function') then
2590 actions[string.lower(string.sub(ic,0,3))](string.sub(ic,5))
2591 elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then
2592 actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4))
2593 elseif (type(actions[string.lower(string.sub(ic,0,1))])=='function') then
2594 actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3))
2595 else actions.h('') end
2596 until (string.sub(ic,0,1)=="q")
2602 -- set init colors/switch (can be toggled with 'tac' => 'toggle ansicolors')
2603 load_colors(colored_output)
2604 if (#args == 0 ) then modifyMode() end
2606 local inTAG, backupTAG, outTAG, outfile, interactive, crc, ofs, cfs, dfs
2607 -- just a spacer for better readability
2610 for o, a in getopt.getopt(args, 'hrmi:do:c:') do
2612 if o == "h" then return help(); end
2613 -- read tag from PM3
2614 if o == "r" then inTAG=readFromPM3() end
2616 if o == "i" then inTAG=readFile(a) end
2618 if o == "d" then dfs=true end
2619 -- interacive modifying
2620 if o == "m" then interactive=true; modifyMode() end
2621 -- xor (e.g. for clone or plain file)
2622 if o == "c" then cfs=true; crc=a end
2624 if o == "o" then outfile=a; ofs=true end
2627 -- file conversion (output to file)
2629 -- dump infile / tag-read
2631 print("-----------------------------------------")
2632 print(dumpTag(inTAG))
2634 bytes=tagToBytes(inTAG)
2636 -- xor willl be done in function writeFile
2637 -- with the value of byte[5]
2642 writeFile(bytes, outfile)
2643 --- read real tag into virtual tag
2644 -- inTAG=readFromPM3() end
2645 --- or simply use the bytes that where wriiten
2646 inTAG=bytesToTag(bytes, inTAG)
2649 print("-----------------------------------------")
2650 print(dumpTag(inTAG))