]> git.zerfleddert.de Git - proxmark3-svn/blob - client/scripts/legic.lua
ADD: @icsom changes and additions to lua scripts for LEGIC
[proxmark3-svn] / client / scripts / legic.lua
1 --[[
2
3 (example) Legic-Prime Layout with 'Kaba Group Header'
4 +----+----+----+----+----+----+----+----+
5 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
6 +----+----+----+----+----+----+----+----+
7 0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
8 +----+----+----+----+----+----+----+----+
9 0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
10 +----+----+----+----+----+----+----+----+
11 0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
12 +----+----+----+----+----+----+----+----+
13 0x20|UID1|UID2|kghC|
14 +----+----+----+
15 MCD = Manufacturer ID
16 MSN = Manufacturer SerialNumber
17 60 ea = DCF Low + DCF high
18 9f = raw byte which holds the bits for OLE,WRP,WRC,RD
19 ff = unknown but important
20 00 = unimportant
21 11 = unknown but important
22 Bck = header-backup-area
23 00 00 = Year (00 = 2000) & Week (not important)
24 Seg = Segment Header
25 SegC = Crc8 over the Segment Header
26 Stp = Stamp (could be more as 4 - up to 7)
27 UID = dec User-ID for online-Mapping
28 kghC = crc8 over MCD + MSN0..MSN2 + UID
29
30
31 (example) Legic-Cash on MIM256/1024 tag' (37 bytes)
32 +----+----+----+----+----+----+----+----+
33 0x00|Seg0|Seg1|Seg2|Seg3|SegC|STP0|STP1|STP2|
34 +----+----+----+----+----+----+----+----+
35 0x08|STP3|STP4|STP5|STP6| 01 |CURh|CURl|LIMh|
36 +----+----+----+----+----+----+----+----+
37 0x10|LIMm|LIMl|CHKh|CHKl|BALh|BALm|BALl|LRBh|
38 +----+----+----+----+----+----+----+----+
39 0x18|LRBm|LRBl|CHKh|CHKl|SHDh|SHDm|SHDl|LRSh|
40 +----+----+----+----+----+----+----+----+
41 0x20|LRSm|LRSl| CV |CHKh|CHKl|
42 +----+----+----+----+----+
43 STP = Stamp (seems to be always 7 bytes)
44 01 = unknown but important
45 CUR = currency in HEX (ISO 4217)
46 LIM = Cash-Limit
47 CHK = crc16 over byte-addr 0x05..0x12
48 BAL = Balance
49 LRB = ID of the reader that changed the balance
50 CHK = crc16 over BAL + LRB
51 SHD = shadow Balance
52 LRS = ID of the reader that changed the shadow balance (?? should be always the same as LRB)
53 CV = Counter value for transactions
54 CHK = crc16 over SHD + LRS + CV
55 --]]
56
57 example = "script run legic"
58 author = "Mosci"
59 version = "1.0.3"
60
61 desc =
62 [[
63
64 This script helps you to read, create and modify Legic Prime Tags (MIM22, MIM256, MIM1024)
65 it's kinda interactive with following commands in three categories:
66
67 Data I/O Segment Manipulation Token-Data
68 ----------------- -------------------- -----------------
69 rt => read Tag as => add Segment mt => make Token
70 wt => write Tag es => edit Segment Header et => edit Token data
71 ed => edit Segment Data tk => toggle KGH-Flag
72 File I/O rs => remove Segment
73 ----------------- cc => check Segment-CRC
74 lf => load File ck => check KGH
75 sf => save File ds => dump Segments
76 xf => xor to File
77
78
79 (partially) known Segments Virtual Tags Script Output
80 --------------------------- ------------------------------- ------------------------
81 dlc => dump Legic-Cash ct => copy mainTag to backupTag tac => toggle ansicolors
82 elc => edit Legic-Cash tc => copy backupTag to mainTag
83 d3p => dump 3rd-Party-Cash tt => switch mainTag & backupTag
84 e3p => edit 3rd-Party-Cash di => dump mainTag
85 do => dump backupTag
86
87
88
89 rt: 'read tag' - reads a tag placed near to the PM3
90 wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3
91 without the need of changing anything - MCD,MSN,MCC will be read from the tag
92 before and applied to the output.
93
94 lf: 'load file' - load a (xored) file from the local Filesystem into the 'virtual inTag'
95 sf: 'save file' - saves the 'virtual inTag' to the local Filesystem (xored with Tag-MCC)
96 xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with choosen MCC - use '00' for plain values)
97
98 ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not usefull yet, but inernally needed
99 tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not usefull yet, but inernally needed
100 tt: 'toggle tag' - copy mainTag to BackupTag and backupTag to mainTag
101
102 di: 'dump mainTag' - shows the current content of the 'virtual Tag'
103 do: 'dump backupTag' - shows the current content of the 'virtual outTag'
104 ds: 'dump Segments' - will show the content of a selected Segment
105 as: 'add Segment' - will add a 'empty' Segment to the inTag
106 es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid)
107 all other Segment-Header-Values are either calculated or not needed to edit (yet)
108 ed: 'edit data' - edit the Data of a Segment (ADF-Aera / Stamp & Payload specific Data)
109 et: 'edit Token' - edit Data of a Token (CDF-Area / SAM, SAM64, SAM63, IAM, GAM specific Data)
110 mt: 'make Token' - create a Token 'from scratch' (guided)
111 rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token)
112 cc: 'check Segment-CRC'- checks & calculates (if check failed) the Segment-CRC of all Segments
113 ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected
114 'Kaba Group Header CRC calculation'
115 tk: 'toggle KGH' - toglle the (script-internal) flag for kgh-calculation for a segment
116 xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment
117
118 dlc: 'dump Legic-Cash' - show balance and checksums of a Legic-Cash Segment
119 elc: 'edit Legic-Cash' - edit values of a Legic-Cash Segment
120
121 d3p: 'dump 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd-Party Cash Segment
122 e3p: 'edit 3rd Party' - edit Data in 3rd-Party Cash Segment
123
124 tac: 'toggle ansicolors'- switch on and off the colored text-output of this script
125 default can be changed by setting the variable 'colored_output' to false
126 ]]
127 currentTag="inTAG"
128
129 ---
130 -- requirements
131 local utils = require('utils')
132 local getopt = require('getopt')
133 local ansicolors = require('ansicolors')
134
135 ---
136 -- global variables / defines
137 local bxor = bit32.bxor
138 local bbit = bit32.extract
139 local input = utils.input
140 local confirm = utils.confirm
141
142 ---
143 -- init ansicolor-values & ansicolors switch
144 local colored_output = true
145 local acoff = ""
146 local acgreen= ""
147 local accyan = ""
148 local acred = ""
149 local acyellow = ""
150 local acblue = ""
151 local acmagenta = ""
152
153 --- Helper ---
154 ---
155 -- default colors (change to whatever you want)
156 function load_colors(onoff)
157 if (onoff) then
158 -- colors
159 acgreen = ansicolors.green
160 accyan = ansicolors.cyan
161 acred = ansicolors.red
162 acyellow= ansicolors.yellow
163 acblue = ansicolors.blue
164 acmagenta= ansicolors.magenta
165 acoff = ansicolors.reset
166 else
167 -- 'no color'
168 acgreen = ""
169 accyan = ""
170 acred = ""
171 acyellow= ""
172 acblue = ""
173 acmagenta= ""
174 acoff = ""
175 end
176 end
177
178 ---
179 -- curency-codes for Legic-Cash-Segments (ISO 4217)
180 local currency = {
181 ["03d2"]="EUR",
182 ["0348"]="USD",
183 ["033A"]="GBP",
184 ["02F4"]="CHF"
185 }
186
187 ---
188 -- This is only meant to be used when errors occur
189 function oops(err)
190 print(acred.."ERROR: "..acoff ,err)
191 return nil, err
192 end
193
194 ---
195 -- Usage help
196 function help()
197 print(desc)
198 print("Version: "..version)
199 print("Example usage: "..example)
200 end
201
202 ---
203 -- table check helper
204 function istable(t)
205 return type(t) == 'table'
206 end
207
208 ---
209 -- creates a 'deep copy' of a table (a=b only references)
210 function deepCopy(object)
211 local lookup_table = {}
212 local function _copy(object)
213 if type(object) ~= "table" then
214 return object
215 elseif lookup_table[object] then
216 return lookup_table[object]
217 end
218 local new_table = {}
219 lookup_table[object] = new_table
220 for index, value in pairs(object) do
221 new_table[_copy(index)] = _copy(value)
222 end
223 return setmetatable(new_table, getmetatable(object))
224 end
225 return _copy(object)
226 end
227
228 ---
229 -- xor single byte
230 function xorme(hex, xor, index)
231 if ( index >= 23 ) then
232 return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
233 else
234 return hex
235 end
236 end
237
238 ---
239 -- (de)obfuscate bytes
240 function xorBytes(inBytes, crc)
241 local bytes = {}
242 for index = 1, #inBytes do
243 bytes[index] = xorme(inBytes[index], crc, index)
244 end
245 if (#inBytes == #bytes) then
246 -- replace crc
247 bytes[5] = string.sub(crc,-2)
248 return bytes
249 else
250 print("error: byte-count missmatch")
251 return false
252 end
253 end
254
255 ---
256 -- check availability of file
257 function file_check(file_name)
258 local file_found=io.open(file_name, "r")
259 if file_found==nil then
260 return false
261 else
262 return true
263 end
264 end
265
266 ---
267 -- split csv-string into table
268 local function split(str, sep)
269 local sep = sep or ','
270 local fields={}
271 local matchfunc = string.gmatch(str, "([^"..sep.."]+)")
272 if not matchfunc then return {str} end
273 for str in matchfunc do
274 table.insert(fields, str)
275 end
276 return fields
277 end
278
279 ---
280 -- put a string into a bytes-table
281 function str2bytes(s)
282 local res={}
283 if (string.len(s)%2~=0) then return print("stamp should be a even hexstring e.g.: deadbeef or 0badc0de") end
284 for i=1, string.len(s), 2 do
285 table.insert(res, string.sub(s,i,(i+1)))
286 end
287 return res
288 end
289
290 ---
291 -- put certain bytes into a new table
292 function bytesToTable(bytes, bstart, bend)
293 local t={}
294 for i=0, (bend-bstart) do
295 t[i]=bytes[bstart+i]
296 end
297 return t
298 end
299
300 ---
301 -- read file into table
302 function getInputBytes(infile)
303 local line
304 local bytes = {}
305 local fhi,err = io.open(infile)
306 if err then oops("faild to read from file ".. infile); return false; end
307 while true do
308 line = fhi:read()
309 if line == nil then break end
310 for byte in line:gmatch("%w+") do
311 table.insert(bytes, byte)
312 end
313 end
314 fhi:close()
315 if (bytes[7]=='00') then return false end
316 print(#bytes .. " bytes from "..infile.." loaded")
317 return bytes
318 end
319
320 ---
321 -- create tag-table helper
322 function createTagTable()
323 local t={
324 ['MCD'] = '00',
325 ['MSN0']= '11',
326 ['MSN1']= '22',
327 ['MSN2']= '33',
328 ['MCC'] = 'cc',
329 ['DCFl']= 'ff',
330 ['DCFh']= 'ff',
331 ['Type']= 'GAM',
332 ['OLE'] = 0,
333 ['Stamp_len']= 18,
334 ['WRP'] = '00',
335 ['WRC'] = '00',
336 ['RD'] = '00',
337 ['raw'] = '9f',
338 ['SSC'] = 'ff',
339 ['data']= {},
340 ['bck'] = {},
341 ['MTC'] = {},
342 ['SEG'] = {}
343 }
344 return t
345 end
346
347 ---
348 -- put bytes into tag-table
349 function bytesToTag(bytes, tag)
350 if(istable(tag)) then
351 tag.MCD =bytes[1];
352 tag.MSN0=bytes[2];
353 tag.MSN1=bytes[3];
354 tag.MSN2=bytes[4];
355 tag.MCC =bytes[5];
356 tag.DCFl=bytes[6];
357 tag.DCFh=bytes[7];
358 tag.raw =bytes[8];
359 tag.SSC =bytes[9];
360 tag.Type=getTokenType(tag.DCFl);
361 tag.OLE=bbit("0x"..tag.DCFl,7,1)
362 tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4))
363 tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3))
364 tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1))
365 tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10))
366 tag.data=bytesToTable(bytes, 10, 13)
367 tag.Bck=bytesToTable(bytes, 14, 20)
368 tag.MTC=bytesToTable(bytes, 21, 22)
369
370 print(acgreen.."Tag-Type: ".. tag.Type..acoff)
371 if (tag.Type=="SAM" and #bytes>23) then
372 tag=segmentsToTag(bytes, tag)
373 print(acgreen..(#tag.SEG+1).." Segment(s) found"..acoff)
374 -- unsegmented Master-Token
375 -- only tag-data
376 else
377 for i=0, #tag.Bck do
378 table.insert(tag.data, tag.Bck[i])
379 end
380 tag.data[#tag.data]=tag.MTC[0]
381 tag.Bck=nil
382 --tag.MTC[0]=tag.MTC[1]
383 --tag.MTC[1]=nil
384 end
385 print(accyan..#bytes.." bytes for Tag processed"..acoff)
386 return tag
387 end
388 return oops("tag is no table in: bytesToTag ("..type(tag)..")")
389 end
390
391 ---
392 -- put segments from byte-table to tag-table
393 function segmentsToTag(bytes, tag)
394 if(#bytes>23) then
395 local start=23
396 local i=-1
397 if (istable(tag)) then
398 repeat
399 i=i+1
400 tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i))
401 if (tag.Type=="SAM") then
402 if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end
403 end
404 start=start+tag.SEG[i].len
405 until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126)
406 return tag
407 else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end
408 else print("no Segments: must be a MIM22") end
409 end
410
411 ---
412 -- read Tag-Table in bytes-table
413 function tagToBytes(tag)
414 if (istable(tag)) then
415 local bytes = {}
416 local i, i2
417 -- main token-data
418 table.insert(bytes, tag.MCD)
419 table.insert(bytes, tag.MSN0)
420 table.insert(bytes, tag.MSN1)
421 table.insert(bytes, tag.MSN2)
422 table.insert(bytes, tag.MCC)
423 table.insert(bytes, tag.DCFl)
424 table.insert(bytes, tag.DCFh)
425 table.insert(bytes, tag.raw)
426 table.insert(bytes, tag.SSC)
427 -- raw token data
428 for i=0, #tag.data do
429 table.insert(bytes, tag.data[i])
430 end
431 -- backup data
432 if(istable(tag.Bck)) then
433 for i=0, #tag.Bck do
434 table.insert(bytes, tag.Bck[i])
435 end
436 end
437 -- token-create-time / master-token crc
438 for i=0, #tag.MTC do
439 table.insert(bytes, tag.MTC[i])
440 end
441 -- process segments
442 if (type(tag.SEG[0])=='table') then
443 for i=0, #tag.SEG do
444 for i2=1, #tag.SEG[i].raw+1 do
445 table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2])
446 end
447 table.insert(bytes, #bytes+1, tag.SEG[i].crc)
448 for i2=0, #tag.SEG[i].data-1 do
449 table.insert(bytes, #bytes+1, tag.SEG[i].data[i2])
450 end
451 end
452 end
453 -- fill with zeros
454 for i=#bytes+1, 1024 do
455 table.insert(bytes, i, '00')
456 end
457 return bytes
458 end
459 return oops("tag is no table in tagToBytes ("..type(tag)..")")
460 end
461
462 --- PM3 I/O ---
463 ---
464 -- read from pm3 into virtual-tag
465 function readFromPM3()
466 local tag, bytes, infile
467 infile="legic.temp"
468 core.console("hf legic reader")
469 core.console("hf legic save "..infile)
470 tag=readFile(infile)
471 return tag
472 end
473
474 ---
475 -- write virtual Tag to real Tag
476 function writeToTag(tag)
477 local bytes
478 local filename='MylegicClone.hex'
479 local taglen=22
480 if(utils.confirm(acred.."\nplace the (empty) Tag onto the PM3\nand confirm writing to this Tag: "..acoff) == false) then
481 return
482 end
483 -- get used bytes / tag-len
484 if(istable(tag.SEG)) then
485 if (istable(tag.Bck)) then
486 for i=0, #tag.SEG do
487 taglen=taglen+tag.SEG[i].len+5
488 end
489 end
490 local uid_old=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
491 -- read new tag into memory so we can xor the new data with the new MCC
492 outTAG=readFromPM3()
493 outbytes=tagToBytes(outTAG)
494 -- copy 'inputbuffer' to 'outputbuffer'
495 tag.MCD = outbytes[1]
496 tag.MSN0 = outbytes[2]
497 tag.MSN1 = outbytes[3]
498 tag.MSN2 = outbytes[4]
499 tag.MCC = outbytes[5]
500 -- recheck all segments-crc/kghcrc (only on a credential)
501 if(istable(tag.Bck)) then
502 checkAllSegCrc(tag)
503 checkAllKghCrc(tag)
504 local uid_new=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
505 for i=0, #tag.SEG do
506 if (check43rdPartyCash1(uid_old, tag.SEG[i].data)) then
507 io.write(accyan.."\nfixing known checksums"..acoff.." ... ")
508 if (fix3rdPartyCash1(uid_new, tag.SEG[i].data)) then
509 io.write(acgreen.." done\n"..acoff)
510 else oops("\nsomething went wrong at the repair of the 3rd-party-cash-segment") end
511 end
512 end
513 end
514 bytes=tagToBytes(tag)
515 -- master-token-crc
516 if (tag.Type~="SAM") then bytes[22]=calcMtCrc(bytes) end
517 if (bytes) then
518 print("write temp-file '"..filename.."'")
519 print(accyan)
520 writeFile(bytes, filename)
521 --writeToTag(bytes, taglen, 'MylegicClone.hex')
522 print(acoff)
523 end
524 end
525 -- write data to file
526 if (taglen > 0) then
527 WriteBytes = utils.input(acyellow.."enter number of bytes to write?"..acoff, taglen)
528 -- load file into pm3-buffer
529 if (type(filename)~="string") then filename=input(acyellow.."filename to load to pm3-buffer?"..acoff,"legic.temp") end
530 cmd = 'hf legic load '..filename
531 core.console(cmd)
532 -- write pm3-buffer to Tag
533 for i=0, WriteBytes do
534 if ( i<5 or i>6) then
535 cmd = ('hf legic write 0x%02x 0x01'):format(i)
536 core.console(cmd)
537 --print(cmd)
538 elseif (i == 6) then
539 -- write DCF in reverse order (requires 'mosci-patch')
540 cmd = 'hf legic write 0x05 0x02'
541 print(acgreen..cmd..acoff)
542 core.console(cmd)
543 --print(cmd)
544 else
545 print(acgreen.."skip byte 0x05 - will be written next step"..acoff)
546 end
547 utils.Sleep(0.2)
548 end
549 end
550 end
551
552 --- File I/O ---
553 ---
554 -- read file into virtual-tag
555 function readFile(filename)
556 print(accyan)
557 local bytes = {}
558 local tag = {}
559 if (file_check(filename)==false) then
560 return oops("input file: "..filename.." not found")
561 else
562 bytes = getInputBytes(filename)
563 if (bytes == false) then return oops('couldnt get input bytes')
564 else
565 -- make plain bytes
566 bytes = xorBytes(bytes,bytes[5])
567 print("create virtual tag from ".. #bytes .. " bytes")
568 -- create Tag for plain bytes
569 tag=createTagTable()
570 -- load plain bytes to tag-table
571 print(acoff)
572 tag=bytesToTag(bytes, tag)
573 end
574 end
575 return tag
576 end
577
578 ---
579 -- write bytes to file
580 function writeFile(bytes, filename)
581 if (filename~='MylegicClone.hex') then
582 if (file_check(filename)) then
583 local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
584 if (answer==false) then return print("user abort") end
585 end
586 end
587 local line
588 local bcnt=0
589 local fho,err = io.open(filename, "w")
590 if err then oops("OOps ... faild to open output-file ".. filename) end
591 bytes=xorBytes(bytes, bytes[5])
592 for i = 1, #bytes do
593 if (bcnt == 0) then
594 line=bytes[i]
595 elseif (bcnt <= 7) then
596 line=line.." "..bytes[i]
597 end
598 if (bcnt == 7) then
599 -- write line to new file
600 fho:write(line.."\n")
601 -- reset counter & line
602 bcnt=-1
603 line=""
604 end
605 bcnt=bcnt+1
606 end
607 fho:close()
608 print("\nwrote ".. #bytes .." bytes to " .. filename)
609 return true
610 end
611
612 --- Map related ---
613 ---
614 -- make tagMap
615 function makeTagMap()
616 local tagMap={}
617 if (#tagMap==0) then
618 tagMap['name']=input(accyan.."enter Name for this Map: "..acoff , "newTagMap")
619 tagMap['mappings']={}
620 tagMap['crc8']={}
621 -- insert fixed Tag-CRC
622 table.insert(tagMap.crc8, {name='TAG-CRC', pos=5, seq={1, 4}})
623 tagMap['crc16']={}
624 end
625 print(accyan.."new tagMap created"..acoff)
626 return tagMap
627 end
628
629 ---
630 -- save mapping to file
631 function saveTagMap(map, filename)
632 if (string.len(filename)>0) then
633 if (file_check(filename)) then
634 local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
635 if (answer==false) then return print("user abort") end
636 end
637 end
638
639 local line
640 local fho,err = io.open(filename, "w")
641 if err then oops("OOps ... faild to open output-file ".. filename) end
642
643 -- write line to new file
644 for k, v in pairs(map) do
645 if (istable(v)) then
646 for k2, v2 in pairs(v) do
647 if (k=='mappings') then
648 fho:write(k..","..k2..","..v2['name']..","..v2['start']..","..v2['end']..","..((v2['highlight']) and "1" or "0").."\n")
649 elseif (k=="crc8") then
650 local tmp=""
651 tmp=k..","..k2..","..v2['name']..","..v2['pos']..","
652 tmp=tmp..tbl2seqstr(v2['seq'])
653 fho:write(tmp.."\n")
654 end
655 end
656 else
657 fho:write(k..","..v.."\n")
658 end
659 end
660 fho:close()
661 return true
662 end
663
664 ---
665 -- toggle higligh
666 function toggleHighlight(tbl)
667 if (tbl['highlight']) then tbl['highlight']=false
668 else tbl['highlight']=true end
669 return tbl
670 end
671
672 ---
673 -- return table od seqence-string
674 function seqstr2tbl(seqstr)
675 local s=split(seqstr)
676 local res={}
677 if (#s>=1) then
678 for sk, sv in pairs(s) do
679 s2=split(sv, '-')
680 if(#s2==2) then
681 table.insert(res, s2[1])
682 table.insert(res, s2[2])
683 end
684 end
685 end
686 return res
687 end
688
689 ---
690 -- return sequence-string from table
691 function tbl2seqstr(seqtbl)
692 local res=""
693 if (istable(seqtbl)) then
694 for sk, sv in pairs(seqtbl) do
695 res=res..sv..((sk%2==0) and "," or "-")
696 end
697 if (string.sub(res, string.len(res))==",") then
698 res=string.sub(res, 1, string.len(res)-1)
699 end
700 end
701 return res
702 end
703
704 ---
705 -- read map-file into map
706 function loadTagMap(filename)
707 local map={mappings={}, crc8={}, crc16={}}
708 local m=0
709 local c=0
710 local line, fields
711 local temp={}
712 local offset=0
713 if (file_check(filename)==false) then
714 return oops("input file: "..filename.." not found")
715 else
716 local fhi,err = io.open(filename)
717 while true do
718 line = fhi:read()
719 if line == nil then
720 break
721 else
722 fields=split(line)
723 end
724 if (#fields==2) then
725 if (fields[1]=='offset') then
726 offset=tonumber(fields[2],10)
727 end
728 -- map-name
729 map[fields[1]]=fields[2]
730 elseif (fields[1]=='mappings') then
731 m=m+1
732 temp={}
733 -- mapping
734 temp['name']=fields[3]
735 temp['start']=tonumber(fields[4], 10)
736 temp['end']=tonumber(fields[5], 10)
737 if(temp['start']>22) then
738 temp['start']=temp['start']+offset
739 temp['end']=temp['end']+offset
740 end
741 if (tonumber(fields[6], 10)==1) then temp['highlight']= true
742 else temp['highlight']= false end
743 table.insert(map['mappings'], m, temp)
744 elseif (fields[1]=='crc8') then
745 c=c+1
746 temp={}
747 -- crc8
748 temp['name']=fields[3]
749 temp['pos']=tonumber(fields[4], 10)+offset
750 local s=string.sub(line, string.len(fields[1]..","..fields[2]..","..fields[3]..",")+1, string.len(line))
751 temp['seq']=seqstr2tbl(s)
752 for k, v in pairs(temp['seq']) do
753 if(tonumber(v, 10)>22) then v=tonumber(v, 10)+offset end
754 temp['seq'][k]=tonumber(v, 10)
755 end
756 table.insert(map.crc8, temp)
757 end
758 end
759 fhi:close()
760 end
761 return map
762 end
763
764 ---
765 -- dump tagMap (mappings only)
766 function dumpTagMap(tag, tagMap)
767 if(#tagMap.mappings>0) then
768 bytes=tagToBytes(tag)
769 local temp
770 local lastend=0
771 -- start display mappings
772 for k, v in pairs(tagMap.mappings) do
773 if ((lastend+1)<v['start']) then
774 print("...")
775 end
776 if (isPosCrc8(tagMap, v['start'])>0) then
777 if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, v['start']) ) ) then
778 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acgreen..v['name']..acoff..":")
779 else
780 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acred..v['name']..acoff..":")
781 end
782 else
783 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..((v['highlight']) and acmagenta or acyellow)..v['name']..acoff..":")
784 end
785 temp=""
786 for i=((string.len(v['name']))/10), 2 do
787 temp=temp.."\t"
788 end
789 for i=v['start'], v['end'] do
790 temp=temp..bytes[i].." "
791 end
792 print(temp)
793 lastend=v['end']
794 end
795 end
796 end
797
798 ---
799 --
800 function isPosCrc8(tagMap, pos)
801 local res=0
802 if (#tagMap.crc8>0) then
803 for k, v in pairs(tagMap.crc8) do
804 if(v['pos']==pos) then res=k end
805 end
806 end
807 return res
808 end
809
810 ---
811 -- check mapped crc
812 function checkMapCrc8(tagMap, bytes, n)
813 local res=false
814 if (#tagMap.crc8>0) then
815 if(istable(tagMap.crc8[n])) then
816 temp=""
817 for k2, v2 in pairs(tagMap.crc8[n]) do
818 if (istable(v2)) then
819 temp=temp..tbl2seqstr(v2)
820 end
821 end
822 local tempres=""
823 local tempres=getSequences(bytes, temp)
824 tempres=("%02x"):format(utils.Crc8Legic(tempres))
825 if (bytes[tagMap.crc8[n]['pos']]==tempres) then
826 res=true
827 end
828 end
829 end
830 return res
831 end
832
833 ---
834 -- edit existing Map
835 function editTagMap(tag, tagMap)
836 local t = [[
837 Data: dm = show dr = dump raw
838 Mappings: im = insert am = add rm = remove
839 CRC8: ac8 = add sc8 = show rc8 = remove
840 : q = exit h = Help
841 ]]
842 --if(#tagMap.mappings==0) then oops("no mappings in tagMap"); return tagMap end
843 print("tagMap edit-mode submenu")
844 repeat
845 x=input('tagMap submenu:', 'h')
846 if (x=='h') then print(t)
847 elseif (x=='dm') then tagMmap=dumpTagMap(tag, tagMap)
848 elseif (x=='dr') then tagMmap=dumpMap(tag, tagMap)
849 elseif (x=='rc8') then
850 if (istable(tagMap.crc8)) then
851 local x1 = selectTableEntry(tagMap.crc8, "select number of CRC8 to remove:")
852 if (istable(tagMap.crc8[x1])) then
853 table.remove(tagMap.crc8, x1)
854 end
855 end
856 elseif (x=='ac8') then
857 local p=tonumber(input("enter byte-addr of crc8", '0'),10)
858 if (p>0) then
859 local i1=input("enter comma-seperated byte-sequences (e.g.: '1-4,23-26')", '1-4,23-26')
860 local s1=split(i1, ',')
861 if (#s1>0) then
862 local temp={seq={}}
863 for k, v in pairs(s1) do
864 v1=split(v, '-')
865 if(#v1==2) then
866 table.insert(temp.seq, v1[1])
867 table.insert(temp.seq, v1[2])
868 end
869 end
870 temp['pos']=p
871 temp['name']=input("enter a name for the CRC8", "CRC "..(#tagMap.crc8+1))
872 table.insert(tagMap.crc8, temp)
873 end
874 end
875 elseif (string.sub(x, 1, 3)=='sc8') then
876 local bytes=tagToBytes(tag)
877 local res, pos
878 -- trigger manually by sc8 <'4digit' checkadd> <'seqeuence-string'>
879 -- e.g.: sc8 0027 1-4,23-36
880 if (string.len(x)>=9) then
881 pos=tonumber(string.sub(x, 5, 8), 10)
882 x=string.sub(x, 9, string.len(x))
883 print("x: "..x.." - pos:"..pos)
884 else
885 x=selectTableEntry(tagMap.crc8, "select CRC:")
886 if(istable(tagMap.crc8[x])) then
887 pos=tagMap.crc8[x]['pos']
888 x=tbl2seqstr(tagMap.crc8[x]['seq'])
889 end
890 end
891 if (type(x)=='string') then
892 res=("%02x"):format(utils.Crc8Legic(getSequences(bytes, x)))
893 print(accyan.."Sequence:\t"..acoff..x)
894 print(accyan.."Bytes:\t\t"..acoff..getSequences(bytes, x))
895 print(accyan.."calculated: "..acoff..res..accyan.." bytes["..pos.."]: "..acoff..bytes[pos].." ("..compareCrc(utils.Crc8Legic(getSequences(bytes, x)), bytes[pos])..")")
896 end
897 elseif (x=="tm") then
898 x=selectTableEntry(tagMap.mappings, "select number of Mapping:")
899 tagMap.mappings[x]=toggleHighlight(tagMap.mappings[x])
900 elseif (x=='am') then tagMap=addMapping(tag, tagMap)
901 elseif (x=='im') then tagMap=addMapping(tag, tagMap, selectTableEntry(tagMap.mappings, "select List-Position for insert:"))
902 elseif (x=='rm') then tagMap=deleteMapping(tag, tagMap)
903 elseif (x=='mas') then tagMap=mapTag(tagMap); tagMap=mapAllSegments(tag, tagMap)
904 elseif (type(actions[string.sub(x, 3)])=='function') then actions[string.sub(x, 3)]()
905 end
906 until x=='q'
907 print("exit sub-Menu")
908 return tagMap
909 end
910
911 ---
912 -- dump raw mapped and unmapped
913 function dumpMap(tag, tagMap)
914 local dstart=1
915 local dend, cnt
916 local bytes = tagToBytes(tag)
917 local stats = getSegmentStats(bytes)
918 dend=stats[#stats]['end']
919 print(accyan.."Tag uses "..dend.." bytes:"..acoff)
920 for i=dstart, dend do
921 if (check4MappedByte(i, tagMap) and not check4MapCrc8(i, tagMap) and not check4Highlight(i, tagMap)) then io.write(""..acyellow)
922 elseif (check4MapCrc8(i, tagMap)) then
923 if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, i) ) ) then
924 io.write(""..acgreen)
925 else
926 io.write(""..acred)
927 end
928 else
929 io.write(""..acoff)
930 end
931 -- highlighted mapping
932 if (check4Highlight(i, tagMap)) then io.write(""..acmagenta) end
933
934 io.write(bytes[i])
935 if (i%8==0) then io.write("\n")
936 else io.write(" ") end
937 end
938
939 io.write("\n"..acoff)
940 end
941
942 ---
943 -- show bytes used for crc-calculation
944 function getSequences(bytes, seqstr)
945 if (type(seqstr)~="string") then seqstr=input("enter comma-seperated sequences (e.g.: '1-4,23-26')", '1-4,23-26') end
946 local seqs=split(seqstr, ',')
947 local res = ""
948 if(#seqs>0) then
949 for k, v in pairs(seqs) do
950 local seq = split(v,'-')
951 if (#seq>=2) then
952 for i=seq[1], seq[2] do
953 res=res..bytes[i].." "
954 end
955 end
956 if(string.len(res)>0) then res=res.." " end
957 end
958 else
959 oops("no sequence found in '"..seqstr.."'")
960 end
961 return res
962 end
963
964 ---
965 -- check if byte-addr is a know crc
966 function check4MapCrc8(addr, tagMap)
967 local res=false
968 for i=1, #tagMap.crc8 do
969 if (addr == tagMap.crc8[i]['pos']) then
970 res=true
971 end
972 end
973 return res
974 end
975
976 ---
977 -- check if byte-addr is a know crc
978 function check4MapCrc16(addr, tagMap)
979 local res=false
980 for i=1, #tagMap.crc16 do
981 if (addr == tagMap.crc16[i]['pos']) then
982 res=true
983 end
984 end
985 return res
986 end
987
988 ---
989 -- check if byte is mapped or not
990 function check4MappedByte(addr, tagMap)
991 local res=false
992 for _, v in pairs(tagMap.mappings) do
993 if (addr >= v['start'] and addr <= v['end'] ) then
994 res= true
995 end
996 end
997 return res
998 end
999
1000 ---
1001 -- check if byte is highlighted or not
1002 function check4Highlight(addr, tagMap)
1003 local res=false
1004 for _, v in pairs(tagMap.mappings) do
1005 if (addr >= v['start'] and addr <= v['end'] ) then
1006 res= v['highlight']
1007 end
1008 end
1009 return res
1010 end
1011
1012 ---
1013 -- add interactive mapping
1014 function addMapping(tag, tagMap, x)
1015 if (type(x)~="number") then x=#tagMap.mappings+1 end
1016 local bytes=tagToBytes(tag)
1017 local myMapping={}
1018 myMapping['name'] =input(accyan.."enter Maping-Name:"..acoff, string.format("mapping %d", #tagMap.mappings+1))
1019 myMapping['start']=tonumber(input(accyan.."enter start-addr:"..acoff, '1'), 10)
1020 myMapping['end'] =tonumber(input(accyan.."enter end-addr:"..acoff, #bytes), 10)
1021 myMapping['highlight']=confirm("set highlighted")
1022 table.insert(tagMap.mappings, x, myMapping)
1023 return tagMap
1024 end
1025
1026 ---
1027 -- delete mapping
1028 function deleteMapping(tag, tagMap)
1029 if(#tagMap.mappings>0) then
1030 local d = selectTableEntry(tagMap.mappings, "select number of Mapping to remove:")
1031 if (type(d)=='number') then
1032 table.remove(tagMap.mappings, d)
1033 else oops("deleteMapping: got type = "..type(d).." - expected type = 'number'")
1034 end
1035 end
1036 return tagMap
1037 end
1038
1039 ---
1040 -- select a mapping from a tagmap
1041 function selectTableEntry(table, action)
1042 if (type(action)~="string") then action="select number of item:" end
1043 for k, v in pairs(table) do
1044 print(accyan..k..acoff.."\t-> "..accyan..v['name']..acoff)
1045 end
1046 local res = tonumber(input(action , 0), 10)
1047 if (istable(table[res])) then
1048 return res
1049 else
1050 return false
1051 end
1052 end
1053
1054 ---
1055 -- map all segments
1056 function mapAllSegments(tag, tagMap)
1057 local bytes=tagToBytes(tag)
1058 local WRP,WRC,WRPC
1059 segs=getSegmentStats(bytes)
1060 if (istable(segs)) then
1061 for k, v in pairs(segs) do
1062 -- wrp (write proteted) = byte 2
1063 WRP = tonumber(bytes[v['start']+2],16)
1064 -- wrc (write control) - bit 4-6 of byte 3
1065 WRC = tonumber(bbit("0x"..bytes[v['start']+3],4,3),16)
1066 --tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." HDR", v['start'], v['start']+3)
1067 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." CRC", v['start']+4, v['start']+4, true)
1068 table.insert(tagMap.crc8, {name = 'Segment '..("%02d"):format(v['index']).." CRC", pos=v['start']+4, seq={1,4,v['start'],v['start']+3}} )
1069 if(WRC>WRP) then
1070 WRPC=WRC
1071 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
1072 elseif (WRP>WRC and WRC>0) then
1073 WRPC=WRP
1074 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
1075 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+WRC+5, v['start']+5+WRP-1, true)
1076 else
1077 WRPC=WRP
1078 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+5, v['start']+5+WRP-1, true)
1079 end
1080 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." data", v['start']+5+WRPC, v['end'], false)
1081
1082 end
1083 print(#segs.." Segments mapped")
1084 else
1085 oops("autoMapSegments failed: no Segments found")
1086 end
1087 return tagMap
1088 end
1089
1090 ---
1091 -- map all token data
1092 function mapTokenData(tagMap, mname, mstart, mend, mhigh)
1093 --if ( not mhigh ) then mhigh=false end
1094 local myMapping={}
1095 myMapping['name'] =mname
1096 myMapping['start']=mstart
1097 myMapping['end'] =mend
1098 myMapping['highlight']=mhigh
1099 table.insert(tagMap.mappings, myMapping)
1100 return tagMap
1101 end
1102
1103 ---
1104 -- map a map
1105 function mapTag(tagMap)
1106 tagMap=makeTagMap()
1107 tagMap=mapTokenData(tagMap, 'Tag-ID', 1, 4, true)
1108 tagMap=mapTokenData(tagMap, 'Tag-CRC', 5, 5, false)
1109 tagMap=mapTokenData(tagMap, 'DCF', 6, 7, true)
1110 tagMap=mapTokenData(tagMap, 'THDR-Raw/Stamp-Len', 8, 8, true)
1111 tagMap=mapTokenData(tagMap, 'SSC', 9, 9, true)
1112 tagMap=mapTokenData(tagMap, 'Header', 10, 13, false)
1113 tagMap=mapTokenData(tagMap, 'Backup', 14, 19, true)
1114 tagMap=mapTokenData(tagMap, 'Bck-CRC', 20, 20, false)
1115 tagMap=mapTokenData(tagMap, 'TokenTime', 21, 22, false)
1116 return tagMap
1117 end
1118
1119 --- Dump Data ---
1120 ---
1121 -- dump virtual Tag-Data
1122 function dumpTag(tag)
1123 local i, i2
1124 local res
1125 local dp=0
1126 local raw=""
1127 -- sytstem area
1128 res =acyellow.."\nCDF: System Area"..acoff
1129 res= res.."\n"..dumpCDF(tag)
1130 -- segments (user-token area)
1131 if(tag.Type=="SAM") then
1132 res = res..acyellow.."\n\nADF: User Area"..acoff
1133 for i=0, #tag.SEG do
1134 res=res.."\n"..dumpSegment(tag, i).."\n"
1135 end
1136 end
1137 return res
1138 end
1139
1140 ---
1141 -- dump tag-system area
1142 function dumpCDF(tag)
1143 local res=""
1144 local i=0
1145 local raw=""
1146 local bytes
1147 if (istable(tag)) then
1148 res = res..accyan.."MCD: "..acoff..tag.MCD..accyan.." MSN: "..acoff..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..accyan.." MCC: "..acoff..tag.MCC.."\n"
1149 res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n"
1150 res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..((tag.raw=='9f') and (", SSC="..tag.SSC.."\n") or "\n")
1151
1152 -- credential (end-user tag)
1153 if (tag.Type=="SAM") then
1154 res = res.."Remaining Header Area\n"
1155 for i=0, (#tag.data) do
1156 res = res..tag.data[i].." "
1157 end
1158 res = res.."\nBackup Area\n"
1159 for i=0, (#tag.Bck) do
1160 res = res..tag.Bck[i].." "
1161 end
1162 res = res.."\nTime Area\n"
1163 for i=0, (#tag.MTC) do
1164 res = res..tag.MTC[i].." "
1165 end
1166
1167 -- Master Token specific
1168 else
1169 res = res .."Master-Token Area\nStamp: "
1170 res= res..tag.SSC.." "
1171 for i=0, tag.Stamp_len-2 do
1172 res = res..tag.data[i].." "
1173 end
1174 res=res.."\nunused payload\n"
1175 for i=0, (#tag.data-tag.Stamp_len-1) do
1176 res = res..tag.data[i].." "
1177 end
1178 bytes=tagToBytes(tag)
1179 local mtcrc=calcMtCrc(bytes)
1180 res=res.."\nMaster-Token CRC: "
1181 res = res ..tag.MTC[1].." ("..((tag.MTC[1]==mtcrc) and "valid" or "error")..")"
1182 end
1183 return res
1184 else print("no valid Tag in dumpCDF") end
1185 end
1186
1187 ---
1188 -- dump single segment
1189 function dumpSegment(tag, index)
1190 local i=index
1191 local i2
1192 local dp=0 --data-position in table
1193 local res="" --result
1194 local raw="" --raw-header
1195 -- segment
1196 if ( (istable(tag.SEG[i])) and tag.Type=="SAM") then
1197 if (istable(tag.SEG[i].raw)) then
1198 for k,v in pairs(tag.SEG[i].raw) do
1199 raw=raw..v.." "
1200 end
1201 end
1202
1203 -- segment header
1204 res = res..accyan.."Segment "..("%02d"):format(tag.SEG[i].index)..acoff..": "
1205 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).."), "
1206 res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", "
1207 res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." "
1208 res = res .."("..(checkSegmentCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
1209 raw=""
1210
1211
1212 -- WRC protected
1213 if ((tag.SEG[i].WRC>0)) then
1214 res = res .."\nWRC protected area:\n"
1215 for i2=dp, dp+tag.SEG[i].WRC-1 do
1216 res = res..tag.SEG[i].data[i2].." "
1217 dp=dp+1
1218 end
1219 end
1220
1221 -- WRP mprotected
1222 if (tag.SEG[i].WRP>tag.SEG[i].WRC) then
1223 res = res .."\nRemaining write protected area:\n"
1224 for i2=dp, dp+(tag.SEG[i].WRP-tag.SEG[i].WRC)-1 do
1225 res = res..tag.SEG[i].data[i2].." "
1226 dp=dp+1
1227 end
1228 end
1229
1230 -- payload
1231 if (#tag.SEG[i].data-dp>0) then
1232 res = res .."\nRemaining segment payload:\n"
1233 for i2=dp, #tag.SEG[i].data-2 do
1234 res = res..tag.SEG[i].data[dp].." "
1235 dp=dp+1
1236 end
1237 if (tag.SEG[i].kgh) then
1238 res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
1239 else res = res..tag.SEG[i].data[dp] end
1240 end
1241 dp=0
1242 return res
1243 else
1244 return print("Segment not found")
1245 end
1246 end
1247
1248 ---
1249 -- return bytes 'sstrat' to 'send' from a table
1250 function dumpTable(tab, header, tstart, tend)
1251 res=""
1252 for i=tstart, tend do
1253 res=res..tab[i].." "
1254 end
1255 if (string.len(header)==0) then return res
1256 else return (header.." #"..(tend-tstart+1).."\n"..res) end
1257 end
1258
1259 ---
1260 -- dump 3rd Party Cash
1261 function dump3rdPartyCash1(tag , seg)
1262 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1263 local stamp=tag.SEG[seg].data[0].." "..tag.SEG[seg].data[1].." "..tag.SEG[seg].data[2]
1264 local datastamp=tag.SEG[seg].data[20].." "..tag.SEG[seg].data[21].." "..tag.SEG[seg].data[22]
1265 local balance=tonumber(tag.SEG[seg].data[32]..tag.SEG[seg].data[33] ,16)
1266 local balancecrc=utils.Crc8Legic(uid..tag.SEG[seg].data[32]..tag.SEG[seg].data[33])
1267 local mirror=tonumber(tag.SEG[seg].data[35]..tag.SEG[seg].data[36] ,16)
1268 local mirrorcrc=utils.Crc8Legic(uid..tag.SEG[seg].data[35]..tag.SEG[seg].data[36])
1269 local lastbal0=tonumber(tag.SEG[seg].data[39]..tag.SEG[seg].data[40] ,16)
1270 local lastbal1=tonumber(tag.SEG[seg].data[41]..tag.SEG[seg].data[42] ,16)
1271 local lastbal2=tonumber(tag.SEG[seg].data[43]..tag.SEG[seg].data[44] ,16)
1272
1273 test=""
1274 -- display decoded/known stuff
1275 print("\n------------------------------")
1276 print("Tag-ID:\t\t "..uid)
1277 print("Stamp:\t\t "..stamp)
1278 print("UID-Mapping: \t\t"..("%06d"):format(tonumber(tag.SEG[seg].data[46]..tag.SEG[seg].data[47]..tag.SEG[seg].data[48], 16)))
1279 print("------------------------------")
1280 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])..")")
1281 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])..")")
1282 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])..")")
1283
1284 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])..")")
1285 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])..")")
1286 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])..")")
1287 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])..")")
1288 print("------------------------------")
1289 print(string.format("Balance:\t\t %3.2f", balance/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[34])..")")
1290 print(string.format("Shadow:\t\t\t %3.2f", mirror/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[37])..")")
1291 print("------------------------------")
1292 print(string.format("History 1:\t\t %3.2f", lastbal0/100))
1293 print(string.format("History 2:\t\t %3.2f", lastbal1/100))
1294 print(string.format("History 3:\t\t %3.2f", lastbal2/100))
1295 print("------------------------------\n")
1296 end
1297
1298 ---
1299 -- dump Legic-Cash data
1300 function dumpLegicCash(tag, x)
1301 if (istable(tag.SEG[x])) then
1302 io.write("in Segment "..tag.SEG[x].index.." :\n")
1303 print("--------------------------------\n\tLegic-Cash Values\n--------------------------------")
1304 local limit, curr, balance, rid, tcv
1305 -- currency of balance & limit
1306 curr=currency[tag.SEG[x].data[8]..tag.SEG[x].data[9]]
1307 -- maximum balance
1308 limit=string.format("%4.2f", tonumber(tag.SEG[x].data[10]..tag.SEG[x].data[11]..tag.SEG[x].data[12], 16)/100)
1309 -- current balance
1310 balance=string.format("%4.2f", tonumber(tag.SEG[x].data[15]..tag.SEG[x].data[16]..tag.SEG[x].data[17], 16)/100)
1311 -- reader-id who wrote last transaction
1312 rid=tonumber(tag.SEG[x].data[18]..tag.SEG[x].data[19]..tag.SEG[x].data[20], 16)
1313 -- transaction counter value
1314 tcv=tonumber(tag.SEG[x].data[29], 16)
1315 print("Currency:\t\t "..curr)
1316 print("Limit:\t\t\t "..limit)
1317 print("Balance:\t\t "..balance)
1318 print("Transaction Counter:\t "..tcv)
1319 print("Reader-ID:\t\t "..rid.."\n--------------------------------\n")
1320 end
1321 end
1322
1323 ---
1324 -- raw 3rd-party
1325 function print3rdPartyCash1(tag, x)
1326 if (istable(tag.SEG[x])) then
1327 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1328 print("\n\t\tStamp : "..dumpTable(tag.SEG[x].data, "", 0 , 2))
1329 print("\t\tBlock 0: "..dumpTable(tag.SEG[x].data, "", 3 , 18))
1330 print()
1331 print("\t\tBlock 1: "..dumpTable(tag.SEG[x].data, "", 19, 30))
1332 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])..")")
1333 print()
1334 print("\t\tBlock 2: "..dumpTable(tag.SEG[x].data, "", 32, 33))
1335 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])..")")
1336 print()
1337 print("\t\tBlock 3: "..dumpTable(tag.SEG[x].data, "", 35, 36))
1338 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])..")")
1339 print()
1340 print("\t\tyet unknown: "..tag.SEG[x].data[38])
1341 print()
1342 print("\t\tHisatory 1: "..dumpTable(tag.SEG[x].data, "", 39, 40))
1343 print("\t\tHisatory 2: "..dumpTable(tag.SEG[x].data, "", 41, 42))
1344 print("\t\tHisatory 3: "..dumpTable(tag.SEG[x].data, "", 43, 44))
1345 print()
1346 print("\t\tyet unknown: "..tag.SEG[x].data[45])
1347 print()
1348 print("\t\tKGH-UID HEX: "..dumpTable(tag.SEG[x].data, "", 46, 48))
1349 print("\t\tBlock 4: "..dumpTable(tag.SEG[x].data, "", 49, 54))
1350 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])..")")
1351 print()
1352 print("\t\tBlock 5: "..dumpTable(tag.SEG[x].data, "", 56, 61))
1353 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])..")")
1354 print()
1355 print("\t\tBlock 6: "..dumpTable(tag.SEG[x].data, "", 63, 72))
1356 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])..")")
1357 print()
1358 print("\t\tBlock 7: "..dumpTable(tag.SEG[x].data, "", 74, 88))
1359 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])..")")
1360 print()
1361 print("\t\tBlock 8: "..dumpTable(tag.SEG[x].data, "", 90, 94))
1362 end
1363 end
1364
1365 --- Token related --
1366 ---
1367 -- make token
1368 function makeToken()
1369 local mt={
1370 ['Type'] = {"SAM", "SAM63", "SAM64", "IAM", "GAM"},
1371 ['DCF'] = {"60ea", "31fa", "30fa", "80fa", "f0fa"},
1372 ['WRP'] = {"15", "2", "2", "2", "2"},
1373 ['WRC'] = {"01", "02", "02", "00", "00"},
1374 ['RD'] = {"01", "00", "00", "00", "00"},
1375 ['Stamp'] = {"00", "00", "00", "00", "00"},
1376 ['Segment'] = {"0d", "c0", "04", "00", "be", "01", "02", "03", "04", "01", "02", "03", "04"}
1377 }
1378 ttype=""
1379 for k, v in pairs(mt.Type) do
1380 ttype=ttype..k..") "..v.." "
1381 end
1382 mtq=tonumber(input("select number for Token-Type\n"..ttype, '1'), 10)
1383 if (type(mtq)~="number") then return print("selection invalid!")
1384 elseif (mtq>#mt.Type) then return print("selection invalid!")
1385 else print("Token-Type '"..mt.Type[mtq].."' selected") end
1386 local raw=calcHeaderRaw(mt.WRP[mtq], mt.WRC[mtq], mt.RD[mtq])
1387 local mtCRC="00"
1388
1389 bytes={"01", "02", "03", "04", "cb", string.sub(mt.DCF[mtq], 0, 2), string.sub(mt.DCF[mtq], 3), raw,
1390 "00", "00", "00", "00", "00", "00", "00", "00",
1391 "00", "00", "00", "00", "00", "00"}
1392 if (mtq==1) then
1393 for i=0, #mt.Segment do
1394 table.insert(bytes, mt.Segment[i])
1395 end
1396 bytes[9]="ff"
1397 end
1398 -- fill bytes
1399 for i=#bytes, 1023 do table.insert(bytes, "00") end
1400 -- if Master-Token -> calc Master-Token-CRC
1401 if (mtq>1) then bytes[22]=calcMtCrc(bytes) end
1402 local tempTag=createTagTable()
1403 -- remove segment if MasterToken
1404 if (mtq>1) then tempTag.SEG[0]=nil end
1405 return bytesToTag(bytes, tempTag)
1406 end
1407
1408 ---
1409 -- edit token-data
1410 function editTag(tag)
1411 -- for simulation it makes sense to edit everything
1412 local edit_sim="MCD MSN0 MSN1 MSN2 MCC DCFl DCFh WRP WRC RD"
1413 -- on real tags it makes only sense to edit DCF, WRP, WRC, RD
1414 local edit_real="DCFl DCFh WRP WRC RD"
1415 if (confirm(acyellow.."do you want to edit non-writeable values (e.g. for simulation)?"..acoff)) then
1416 edit_tag=edit_sim
1417 else edit_tag=edit_real end
1418
1419 if(istable(tag)) then
1420 for k,v in pairs(tag) do
1421 if(type(v)~="table" and type(v)~="boolean" and string.find(edit_tag, k)) then
1422 tag[k]=input("value for: "..accyan..k..acoff, v)
1423 end
1424 end
1425
1426 if (tag.Type=="SAM") then ttype="Header"; else ttype="Stamp"; end
1427 if (confirm(acyellow.."do you want to edit "..ttype.." Data?"..acoff)) then
1428 -- master-token specific
1429 if(istable(tag.Bck)==false) then
1430 -- stamp-data length=(0xfc-DCFh)
1431 -- on MT: SSC holds the Starting Stamp Character (Stamp0)
1432 tag.SSC=input(ttype.."0: ", tag.SSC)
1433 -- rest of stamp-bytes are in tag.data 0..n
1434 for i=0, (tonumber(0xfc ,10)-("%d"):format('0x'..tag.DCFh))-2 do
1435 tag.data[i]=input(ttype.. i+1 ..": ", tag.data[i])
1436 end
1437 else
1438 --- on credentials byte7 should always be 9f and byte8 ff
1439 -- on Master-Token not (even on SAM63/64 not)
1440 -- tag.SSC=input(ttype.."0: ", tag.SSC)
1441 for i=0, #tag.data do
1442 tag.data[i]=input(ttype.. i ..": ", tag.data[i])
1443 end
1444 end
1445 end
1446
1447 bytes=tagToBytes(tag)
1448
1449 --- check data-consistency (calculate tag.raw)
1450 bytes[8]=calcHeaderRaw(tag.WRP, tag.WRC, tag.RD)
1451
1452 --- Master-Token specific
1453 -- should be triggered if a SAM was converted to a non-SAM (user-Token to Master-Token)
1454 -- or a Master-Token has being edited (also SAM64 & SAM63 - which are in fact Master-Token)
1455 if(tag.Type~="SAM" or bytes[6]..bytes[7]~="60ea") then
1456 -- calc new Master-Token crc
1457 bytes[22]=calcMtCrc(bytes)
1458 else
1459 -- ensure tag.SSC set to 'ff' on credential-token (SAM)
1460 bytes[9]='ff'
1461 -- if a Master-Token was converted to a Credential-Token
1462 -- lets unset the Time-Area to 00 00 (will contain Stamp-Data on MT)
1463 bytes[21]='00'
1464 bytes[22]='00'
1465 end
1466
1467 tag=bytesToTag(bytes, tag)
1468 end
1469 end
1470
1471 ---
1472 -- calculates header-byte (addr 0x07)
1473 function calcHeaderRaw(wrp, wrc, rd)
1474 local res
1475 wrp=("%02x"):format(tonumber(wrp, 10))
1476 rd=tonumber(rd, 16)
1477 res=("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0))
1478 return res
1479 end
1480
1481 ---
1482 -- determine TagType (bits 0..6 of DCFlow)
1483 function getTokenType(DCFl)
1484 --[[
1485 0x00–0x2f IAM
1486 0x30–0x6f SAM
1487 0x70–0x7f GAM
1488 ]]--
1489 local tt = tonumber(bbit("0x"..DCFl,0,7),10)
1490 if (tt >= 0 and tt <= 47) then tt = "IAM"
1491 elseif (tt == 49) then tt = "SAM63"
1492 elseif (tt == 48) then tt = "SAM64"
1493 elseif (tt >= 50 and tt <= 111) then tt = "SAM"
1494 elseif (tt >= 112 and tt <= 127) then tt = "GAM"
1495 else tt = "???" end
1496 return tt
1497 end
1498
1499 ---
1500 -- clear beackup-area of a virtual tag
1501 function clearBackupArea(tag)
1502 for i=1, #tag.Bck do
1503 tag.Bck[i]='00'
1504 end
1505 return tag
1506 end
1507
1508 --- Segment related --
1509 ---
1510 -- get segmemnt-data from byte-table
1511 function getSegmentData(bytes, start, index)
1512 local segment={
1513 ['index'] = '00',
1514 ['flag'] = 'c',
1515 ['valid'] = 0,
1516 ['last'] = 0,
1517 ['len'] = 13,
1518 ['raw'] = {'00', '00', '00', '00'},
1519 ['WRP'] = 4,
1520 ['WRC'] = 0,
1521 ['RD'] = 0,
1522 ['crc'] = '00',
1523 ['data'] = {},
1524 ['kgh'] = false
1525 }
1526 if (bytes[start]) then
1527 local i
1528 -- #index
1529 segment.index = index
1530 -- flag = high nibble of byte 1
1531 segment.flag = string.sub(bytes[start+1],0,1)
1532 -- valid = bit 6 of byte 1
1533 segment.valid = bbit("0x"..bytes[start+1],6,1)
1534 -- last = bit 7 of byte 1
1535 segment.last = bbit("0x"..bytes[start+1],7,1)
1536 -- len = (byte 0)+(bit0-3 of byte 1)
1537 segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16)
1538 -- raw segment header
1539 segment.raw = {bytes[start], bytes[start+1], bytes[start+2], bytes[start+3]}
1540 -- wrp (write proteted) = byte 2
1541 segment.WRP = tonumber(bytes[start+2],16)
1542 -- wrc (write control) - bit 4-6 of byte 3
1543 segment.WRC = tonumber(bbit("0x"..bytes[start+3],4,3),16)
1544 -- rd (read disabled) - bit 7 of byte 3
1545 segment.RD = tonumber(bbit("0x"..bytes[start+3],7,1),16)
1546 -- crc byte 4
1547 segment.crc = bytes[start+4]
1548 -- segment-data starts at segment.len - segment.header - segment.crc
1549 for i=0, (segment.len-5) do
1550 segment.data[i]=bytes[start+5+i]
1551 end
1552 return segment
1553 else return false;
1554 end
1555 end
1556
1557 ---
1558 -- get index, start-aadr, length and content
1559 function getSegmentStats(bytes)
1560 local sStats = {}
1561 local sValid, sLast, sLen, sStart, x
1562 sStart=23
1563 x=0
1564 repeat
1565 local s={}
1566 -- valid = bit 6 of byte 1
1567 sValid = bbit("0x"..bytes[sStart+1],6,1)
1568 -- last = bit 7 of byte 1
1569 sLast = bbit("0x"..bytes[sStart+1],7,1)
1570 -- len = (byte 0)+(bit0-3 of byte 1)
1571 sLen = tonumber(bbit("0x"..bytes[sStart+1],0,4)..bytes[sStart],16)
1572 --print("index: "..("%02d"):format(x).." Len: "..sLen.." start:"..sStart.." end: "..(sStart+sLen-1))
1573 s['index']=x
1574 s['start']=sStart
1575 s['end']=sStart+sLen-1
1576 s['len']=sLen
1577 if ( (sStart+sLen-1)>sStart ) then
1578 table.insert(sStats, s)
1579 end
1580 sStart=sStart+sLen
1581 x=x+1
1582 until (sLast==1 or sValid==0 or x==126)
1583 if (#sStats>0 ) then return sStats
1584 else return false; end
1585 end
1586
1587 ---
1588 -- regenerate segment-header (after edit)
1589 function regenSegmentHeader(segment)
1590 local seg=segment
1591 local raw = segment.raw
1592 local i
1593 -- len bit0..7 | len=12bit=low nibble of byte1..byte0
1594 raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8))
1595 -- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh?
1596 raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),8,4)+bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8))
1597 -- WRP
1598 raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8))
1599 -- WRC + RD
1600 raw[4]=("%02x"):format(tonumber((seg.WRC*16)+(seg.RD*128),10))
1601 -- flag
1602 seg.flag=string.sub(raw[2],0,1)
1603 --print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4])
1604 if(#seg.data>(seg.len-5)) then
1605 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
1606 print(acyellow.."Data-Length has being reduced:"..acgreen.." auto-removing "..acyellow.. #seg.data-(seg.len-5) ..acgreen .." bytes from Payload!"..acoff);
1607 for i=(seg.len-5), #seg.data-1 do
1608 table.remove(seg.data)
1609 end
1610 elseif (#seg.data<(seg.len-5)) then
1611 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
1612 print(acyellow.."Data-Length has being extended:"..acgreen.." auto-adding "..acyellow..(seg.len-5)-#seg.data ..acgreen .." bytes to Payload!"..acoff);
1613 for i=#seg.data, (seg.len-5-1) do
1614 table.insert(seg.data, '00')
1615 end
1616 end
1617 return seg
1618 end
1619
1620 ---
1621 -- edit segment helper
1622 function editSegment(tag, index)
1623 local k,v
1624 local edit_possible="valid len RD WRP WRC Stamp Payload"
1625 if (istable(tag.SEG[index])) then
1626 for k,v in pairs(tag.SEG[index]) do
1627 if(string.find(edit_possible,k)) then
1628 tag.SEG[index][k]=tonumber(input(accyan..k..acoff..": ", v),10)
1629 end
1630 end
1631 else print("Segment with Index: "..("%02d"):format(index).." not found in Tag")
1632 return false
1633 end
1634 regenSegmentHeader(tag.SEG[index])
1635 print("\n"..dumpSegment(tag, index).."\n")
1636 return tag
1637 end
1638
1639 ---
1640 -- edit Segment Data
1641 function editSegmentData(data)
1642 local lc=check4LegicCash(data)
1643 io.write("\n")
1644 if (istable(data)) then
1645 for i=0, #data-1 do
1646 data[i]=input(accyan.."Data"..i..acoff..": ", data[i])
1647 end
1648 if (lc) then data=fixLegicCash(data) end
1649 return data
1650 else
1651 print("no Segment-Data found")
1652 end
1653 end
1654
1655 ---
1656 -- list available segmets in virtual tag
1657 function segmentList(tag)
1658 local i
1659 local res = ""
1660 if (istable(tag.SEG[0])) then
1661 for i=0, #tag.SEG do
1662 res = res .. tag.SEG[i].index .. " "
1663 end
1664 return res
1665 else print("no Segments found in Tag")
1666 return false
1667 end
1668 end
1669
1670 ---
1671 -- helper to selecting a segment
1672 function selectSegment(tag)
1673 local sel
1674 if (istable(tag.SEG[0])) then
1675 print("availabe Segments:\n"..segmentList(tag))
1676 sel=input("select Segment: ", '00')
1677 sel=tonumber(sel,10)
1678 if (sel) then return sel
1679 else return '0' end
1680 else
1681 print("\nno Segments found")
1682 return false
1683 end
1684 end
1685
1686 ---
1687 -- add segment
1688 function addSegment(tag)
1689 local i
1690 local segment={
1691 ['index'] = '00',
1692 ['flag'] = 'c',
1693 ['valid'] = 1,
1694 ['last'] = 1,
1695 ['len'] = 13,
1696 ['raw'] = {'0d', '40', '04', '00'},
1697 ['WRP'] = 4,
1698 ['WRC'] = 0,
1699 ['RD'] = 0,
1700 ['crc'] = '00',
1701 ['data'] = {},
1702 ['kgh'] = false
1703 }
1704 if (istable(tag.SEG[0])) then
1705 tag.SEG[#tag.SEG].last=0
1706 table.insert(tag.SEG, segment)
1707 for i=0, 8 do
1708 tag.SEG[#tag.SEG].data[i]=("%02x"):format(i)
1709 end
1710 tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG)
1711 return tag
1712 else
1713 print("no Segment-Table found")
1714 end
1715 end
1716
1717 ---
1718 -- get only the stamp-bytes of a segment
1719 function getSegmentStamp(seg, bytes)
1720 local stamp=""
1721 local stamp_len=7
1722 --- the 'real' stamp on MIM is not really easy to tell for me since the 'data-block' covers stamp0..stampn+data0..datan
1723 -- there a no stamps longer than 7 bytes & they are write-protected by default , and I have not seen user-credntials
1724 -- with stamps smaller 3 bytes (except: Master-Token)
1725 -- WRP -> Read/Write Protection
1726 -- WRC -> Read/Write Condition
1727 -- 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
1728 if (seg.WRP<7) then stamp_len=(seg.WRP) end
1729 for i=1, (stamp_len) do
1730 stamp=stamp..seg.data[i-1]
1731 end
1732 if (bytes) then
1733 stamp=str2bytes(stamp)
1734 return stamp
1735 else return stamp end
1736 end
1737
1738 ---
1739 -- edit stamp of a segment
1740 function editStamp(new_stamp, uid, data)
1741 local stamp=str2bytes(new_stamp)
1742 for i=0, #stamp-1 do
1743 data[i]=stamp[i+1]
1744 end
1745 -- now fill in stamp
1746 for i=0, (string.len(new_stamp)/2)-1 do
1747 data[i]=stamp[i+1]
1748 end
1749 return fix3rdPartyCash1(uid, data)
1750 end
1751
1752 ---
1753 -- autoselect special/known segments
1754 function autoSelectSegment(tag, s)
1755 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1756 local x=#tag.SEG+1
1757 local res = false
1758 io.write("autoSelect ")
1759 --- search for desired segment-type
1760 -- 3rd Party Segment
1761 if (s=="3rdparty") then
1762 repeat
1763 io.write(". ")
1764 x=x-1
1765 res=check43rdPartyCash1(uid, tag.SEG[x].data)
1766 until ( res or x==0 )
1767 end
1768 -- Legic-Cash Segment
1769 if (s=="legiccash") then
1770 repeat
1771 io.write(". ")
1772 x=x-1
1773 res=check4LegicCash(tag.SEG[x].data)
1774 until ( res or x==0 )
1775 end
1776 ---
1777 -- segment found
1778 if (res) then
1779 io.write("\nautoselected Index: "..string.format("%02d", x).."\n")
1780 return x
1781 end
1782 ---
1783 -- nothing found
1784 io.write(acyellow.."no Segment found\n"..acoff)
1785 return -1
1786 end
1787
1788 ---
1789 -- delete segment (except segment 00)
1790 function delSegment(tag, index)
1791 if (istable(tag.SEG[0])) then
1792 local i
1793 if (type(index)=="string") then index=tonumber(index,10) end
1794 if (index > 0) then
1795 table.remove(tag.SEG, index)
1796 for i=0, #tag.SEG do
1797 tag.SEG[i].index=("%02d"):format(i)
1798 end
1799 end
1800 if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end
1801 return tag
1802 end
1803 end
1804
1805 ---
1806 -- edit uid 3rd party cash
1807 function edit3rdUid(mapid, uid, data)
1808 mapid=("%06x"):format(tonumber(mapid, 10))
1809 data[46]=string.sub(mapid, 1 ,2)
1810 data[47]=string.sub(mapid, 3 ,4)
1811 data[48]=string.sub(mapid, 5 ,6)
1812 return fix3rdPartyCash1(uid, data)
1813 end
1814
1815 ---
1816 -- edit balance 3rd party cash
1817 function edit3rdCash(new_cash, uid, data)
1818 new_cash=("%04x"):format(new_cash)
1819 data[32]=string.sub(new_cash, 0,2)
1820 data[33]=string.sub(new_cash, 3,4)
1821 data[34]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
1822 data[35]=string.sub(new_cash, 0,2)
1823 data[36]=string.sub(new_cash, 3,4)
1824 data[37]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
1825 data[39]=string.sub(new_cash, 0,2)
1826 data[40]=string.sub(new_cash, 3,4)
1827 data[41]='00'
1828 data[42]='00'
1829 data[43]='00'
1830 data[44]='00'
1831 return fix3rdPartyCash1(uid, data)
1832 end
1833
1834 ---
1835 -- edit 3rd-party cash
1836 function edit3rdPartyCash1(tag, x)
1837 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1838 if (confirm("\nedit Balance?")) then
1839 local new_cash=input("enter new Balance≤\nwithout comma and without currency-sign! (0-65535)", "100")
1840 tag.SEG[x].data=edit3rdCash(new_cash, uid, tag.SEG[x].data)
1841 end
1842 -- change User-ID (used for online-account-mapping)
1843 if (confirm("\nedit UserID-Mapping?")) then
1844 local new_mapid=input("enter new UserID (6-digit value)", "012345")
1845 tag.SEG[x].data=edit3rdUid(new_mapid, uid, tag.SEG[x].data)
1846 end
1847 if (confirm("\nedit Stamp?")) then
1848 local new_stamp=input("enter new Stamp", getSegmentStamp(tag.SEG[x]))
1849 tag.SEG[x].data=editStamp(new_stamp, uid, tag.SEG[x].data)
1850 new_stamp=getSegmentStamp(tag.SEG[x], 'true')
1851 print("stamp_bytes: "..#new_stamp)
1852 -- replace stamp in 'block 1' also
1853 io.write("editing stamp in Block 1 also ")
1854 for i=20, (20+#new_stamp-1) do
1855 tag.SEG[x].data[i]=new_stamp[i-19]
1856 io.write(".");
1857 end
1858 print(" done")
1859 -- fix known checksums
1860 tag.SEG[x].data=fix3rdPartyCash1(uid, tag.SEG[x].data)
1861 end
1862 return tag
1863 end
1864
1865 ---
1866 -- edit Legic Cash
1867 function editLegicCash(data)
1868 local limit, curr, balance, rid, tcv
1869 -- currency of balance & limit
1870 curr=currency[data[8]..data[9]]
1871 -- maximum balance
1872 limit=string.format("%4.2f", tonumber(data[10]..data[11]..data[12], 16)/100)
1873 -- current balance
1874 balance=string.format("%4.2f", tonumber(data[15]..data[16]..data[17], 16)/100)
1875 -- reader-id who wrote last transaction
1876 rid=tonumber(data[18]..data[19]..data[20], 16)
1877 -- transaction counter value
1878 tcv=tonumber(data[29], 16)
1879
1880 -- edit currency
1881 if (confirm(accyan.."change Currency?"..acoff)) then
1882 for k, v in pairs(currency) do io.write(k .. " = " .. v .. "\n") end
1883 curr=input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[8]..data[9])
1884 data[8]=string.sub(curr, 1, 2)
1885 data[9]=string.sub(curr, 3, 4)
1886 end
1887
1888 -- edit limit
1889 if (confirm(accyan.."change Limit?"..acoff)) then
1890 limit=string.format("%06x", input(accyan.."enter the Decimal for the new Limit:"..acoff, limit))
1891 data[10]=string.sub(limit, 1, 2)
1892 data[11]=string.sub(limit, 3, 4)
1893 data[12]=string.sub(limit, 5, 6)
1894 end
1895
1896 -- edit balance
1897 if (confirm(accyan.."change Balance?"..acoff)) then
1898 balance=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, balance))
1899 print("Balance: "..balance)
1900 data[15]=string.sub(balance, 1, 2)
1901 data[16]=string.sub(balance, 3, 4)
1902 data[17]=string.sub(balance, 5, 6)
1903 end
1904
1905 -- edit transaction-counter
1906 if (confirm(accyan.."change Transaction-Counter?"..acoff)) then
1907 tcv=string.format("%02x", input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[29]))
1908 data[29]=tcv
1909 end
1910
1911 -- edit reader.id
1912 if (confirm(accyan.."change Last-Reader-ID?"..acoff)) then
1913 rid=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, rid))
1914 print("Balance: "..balance)
1915 data[18]=string.sub(rid, 1, 2)
1916 data[19]=string.sub(rid, 3, 4)
1917 data[20]=string.sub(rid, 5, 6)
1918 end
1919
1920 return fixLegicCash(data)
1921 end
1922
1923 ---
1924 -- chack for signature of a 'Legic-Cash-Segment'
1925 function check4LegicCash(data)
1926 if(#data==32) then
1927 local stamp_len=(#data-25)
1928 local stamp=""
1929 for i=0, stamp_len-1 do
1930 stamp=stamp..data[i].." "
1931 end
1932 if (data[7]=="01") then
1933 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12))) == data[13]..data[14]) then
1934 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20))) == data[21]..data[22]) then
1935 if (("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29))) == data[30]..data[31]) then
1936 io.write(accyan.."Legic-Cash Segment detected "..acoff)
1937 return true
1938 end
1939 end
1940 end
1941 end
1942 end
1943 return false
1944 end
1945
1946 ---
1947 -- chack for signature of a '3rd Party Cash-Segment' - not all bytes know until yet !!
1948 function check43rdPartyCash1(uid, data)
1949 if(#data==95) then
1950 -- too explicit checking will avoid fixing ;-)
1951 if (string.find(compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)), data[31]),"valid")) then
1952 --if (compareCrc(utils.Crc8Legic(uid..data[32]..data[33]), data[34])=="valid") then
1953 --if (compareCrc(utils.Crc8Legic(uid..data[35]..data[36]), data[37])=="valid") then
1954 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)), data[62])=="valid") then
1955 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)), data[89])=="valid") then
1956 io.write(accyan.."3rd Party Cash-Segment detected "..acoff)
1957 return true
1958 --end
1959 --end
1960 --end
1961 --end
1962 end
1963 end
1964 return false
1965 end
1966
1967 --- CRC related ---
1968 ---
1969 -- build segmentCrc credentials
1970 function segmentCrcCredentials(tag, segid)
1971 if (istable(tag.SEG[0])) then
1972 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1973 cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4]
1974 return cred
1975 else return print(acyellow.."Master-Token / unsegmented Tag!"..acoff) end
1976 end
1977
1978 ---
1979 -- build kghCrc credentials
1980 function kghCrcCredentials(tag, segid)
1981 if (istable(tag) and istable(tag.SEG[0])) then
1982 local x='00'
1983 if (type(segid)=="string") then segid=tonumber(segid,10) end
1984 if (segid>0) then x='93' end
1985 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP)
1986 cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x
1987 for i=0, #tag.SEG[segid].data-2 do
1988 cred = cred..tag.SEG[segid].data[i]
1989 end
1990 return cred
1991 end
1992 end
1993
1994 ---
1995 -- compare two bytes
1996 function compareCrc(calc, guess)
1997 calc=("%02x"):format(calc)
1998 if (calc==guess) then return acgreen.."valid"..acoff
1999 else return acred.."error "..acoff..calc.."!="..guess end
2000 end
2001
2002 ---
2003 -- compare 4 bytes
2004 function compareCrc16(calc, guess)
2005 calc=("%04x"):format(calc)
2006 if (calc==guess) then return acgreen.."valid"..acoff
2007 else return acred.."error "..acoff..calc.."!="..guess end
2008 end
2009
2010 ---
2011 -- repair / fix crc's of a 'Legic-Cash-Segment'
2012 function fixLegicCash(data)
2013 if(#data==32 and data[7]=="01") then
2014 local crc1, crc2, crc3
2015 -- set shadow-balance equal to balance
2016 data[23]=data[15]
2017 data[24]=data[16]
2018 data[25]=data[17]
2019 -- set shadow-last-reader to last-reader
2020 data[26]=data[18]
2021 data[27]=data[19]
2022 data[28]=data[20]
2023 -- calculate all crc's
2024 crc1=("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12)))
2025 crc2=("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20)))
2026 crc3=("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29)))
2027 -- set crc's
2028 data[13]=string.sub(crc1, 1, 2)
2029 data[14]=string.sub(crc1, 3, 4)
2030 data[21]=string.sub(crc2, 1, 2)
2031 data[22]=string.sub(crc2, 3, 4)
2032 data[30]=string.sub(crc3, 1, 2)
2033 data[31]=string.sub(crc3, 3, 4)
2034 return data
2035 end
2036 end
2037
2038 ---
2039 -- repair / fix (yet known) crc's of a '3rd Party Cash-Segment' - not all bytes know until yet !!
2040 function fix3rdPartyCash1(uid, data)
2041 if(#data==95) then
2042 -- checksum 1
2043 data[31]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)))
2044 -- checksum 2
2045 data[34]=("%02x"):format(utils.Crc8Legic(uid..data[32]..data[33]))
2046 -- checksum 3
2047 data[37]=("%02x"):format(utils.Crc8Legic(uid..data[35]..data[36]))
2048 -- checksum 4
2049 data[55]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 46, 54)))
2050 -- checksum 5
2051 data[62]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)))
2052 -- checksum 6
2053 data[73]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 63, 72)))
2054 -- checksum 7
2055 data[89]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)))
2056 return data
2057 end
2058 end
2059
2060 ---
2061 -- calculate Master-Token crc
2062 function calcMtCrc(bytes)
2063 --print(#bytes)
2064 local cmd=bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[7]..bytes[6]..bytes[8]
2065 local len=(tonumber(0xfc ,10)-("%d"):format('0x'..bytes[7]))
2066 for i=1, len do
2067 cmd=cmd..bytes[8+i]
2068 end
2069 local res=("%02x"):format(utils.Crc8Legic(cmd))
2070 return res
2071 end
2072
2073 ---
2074 -- calculate segmentCRC for a given segment
2075 function calcSegmentCrc(tag, segid)
2076 if (istable(tag.SEG[0])) then
2077 -- check if a 'Kaber Group Header' exists
2078 local data=segmentCrcCredentials(tag, segid)
2079 return ("%02x"):format(utils.Crc8Legic(data))
2080 end
2081 end
2082
2083 ---
2084 -- calcuate kghCRC for a given segment
2085 function calcKghCrc(tag, segid)
2086 if (istable(tag.SEG[0])) then
2087 -- check if a 'Kaber Group Header' exists
2088 local i
2089 local data=kghCrcCredentials(tag, segid)
2090 return ("%02x"):format(utils.Crc8Legic(data))
2091 end
2092 end
2093
2094 ---
2095 -- check all segmnet-crc
2096 function checkAllSegCrc(tag)
2097 if (istable(tag.SEG[0])) then
2098 for i=0, #tag.SEG do
2099 crc=calcSegmentCrc(tag, i)
2100 tag.SEG[i].crc=crc
2101 end
2102 else return print(acyellow.."Master-Token / unsegmented Tag"..acoff) end
2103 end
2104
2105 ---
2106 -- check all segmnet-crc
2107 function checkAllKghCrc(tag)
2108 if (istable(tag.SEG[0])) then
2109 for i=0, #tag.SEG do
2110 crc=calcKghCrc(tag, i)
2111 if (tag.SEG[i].kgh) then
2112 tag.SEG[i].data[#tag.SEG[i].data-1]=crc
2113 end
2114 end
2115 end
2116 end
2117
2118 ---
2119 -- validate segmentCRC for a given segment
2120 function checkSegmentCrc(tag, segid)
2121 local data=segmentCrcCredentials(tag, segid)
2122 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then
2123 return true
2124 end
2125 return false
2126 end
2127
2128 ---
2129 -- validate kghCRC to segment in tag-table
2130 function checkKghCrc(tag, segid)
2131 if (type(tag.SEG[segid])=='table') then
2132 if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then
2133 local data=kghCrcCredentials(tag, segid)
2134 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end
2135 else return false; end
2136 else oops(acred.."'Kaba Group header' detected but no Segment-Data found"..ansocolors.reset) end
2137 end
2138
2139 ---
2140 -- helptext for modify-mode
2141 function modifyHelp()
2142 local t=[[
2143
2144 Data I/O Segment Manipulation Token-Data
2145 ----------------- -------------------- ---------------------
2146 rt => read Tag as => add Segment mt => make Token
2147 wt => write Tag es => edit Segment Header et => edit Token data
2148 ed => edit Segment Data tk => toggle KGH-Flag
2149 File I/O rs => remove Segment
2150 ----------------- cc => check Segment-CRC
2151 lf => load File ck => check KGH
2152 sf => save File ds => dump Segments
2153 xf => xor to File
2154
2155
2156 Virtual Tags tagMap (partial) known Segments
2157 -------------------------------- --------------------- ---------------------------
2158 ct => copy mainTag to backupTag mm => make (new) Map dlc => dump Legic-Cash
2159 tc => copy backupTag to mainTag em => edit Map submenu elc => edit Legic-Cash
2160 tt => switch mainTag & backupTag lm => load map from file d3p => dump 3rd-Party-Cash
2161 di => dump mainTag sm => save map to file e3p => edit 3rd-Party-Cash
2162 do => dump backupTag
2163
2164 h => this help q => quit
2165 ]]
2166 return t
2167 end
2168
2169 ---
2170 -- modify Tag (interactive)
2171 function modifyMode()
2172 local i, backupTAG, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes, tagMap
2173
2174 actions = {
2175 ---
2176 -- helptext
2177 ["h"] = function(x)
2178 print(" Version: "..version);
2179 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 ""))
2180 end,
2181 ---
2182 -- read real Tag with PM3 into virtual 'mainTAG'
2183 ["rt"] = function(x)
2184 inTAG=readFromPM3();
2185 --actions.di()
2186 end,
2187 ---
2188 -- write content of virtual 'mainTAG' to real Tag with PM3
2189 ["wt"] = function(x)
2190 writeToTag(inTAG)
2191 end,
2192 ---
2193 -- copy mainTAG to backupTAG
2194 ["ct"] = function(x)
2195 print(accyan.."copy mainTAG to backupTAG"..acoff)
2196 backupTAG=deepCopy(inTAG)
2197 end,
2198 ---
2199 -- copy backupTAG to mainTAG
2200 ["tc"] = function(x)
2201 print(accyan.."copy backupTAG to mainTAG"..acoff)
2202 inTAG=deepCopy(backupTAG)
2203 end,
2204 ---
2205 -- toggle between mainTAG and backupTAG
2206 ["tt"] = function(x)
2207 -- copy main to temp
2208 outTAG=deepCopy(inTAG)
2209 -- copy backup to main
2210 inTAG=deepCopy(backupTAG)
2211 print(accyan.."toggle to "..accyan..((currentTag=='inTAG') and "backupTAG" or "mainTAG")..acoff)
2212 if(currentTag=="inTAG") then currentTag='backupTAG'
2213 else currentTag='inTAG' end
2214 -- copy temp (main) to backup
2215 backupTAG=deepCopy(outTAG)
2216 end,
2217 ---
2218 -- load file into mainTAG
2219 ["lf"] = function(x)
2220
2221 if (type(x)=='string' and file_check(x)) then filename=x
2222 else filename=input("enter filename: ", "legic.temp") end
2223 inTAG=readFile(filename)
2224 -- check for existing tagMap
2225 if (file_check(filename..".map")) then
2226 if(confirm(accyan.."Mapping-File for "..acoff..filename..accyan.." found - load it also?"..acoff)) then
2227 tagMap=loadTagMap(filename..".map")
2228 end
2229 end
2230 end,
2231 ---
2232 -- save values of mainTAG to a file (xored with MCC of mainTAG)
2233 ["sf"] = function(x)
2234 if(istable(inTAG)) then
2235 outfile=input("enter filename:", "legic.temp")
2236 bytes=tagToBytes(inTAG)
2237 --bytes=xorBytes(bytes, inTAG.MCC)
2238 if (bytes) then
2239 writeFile(bytes, outfile)
2240 end
2241 end
2242 end,
2243 ---
2244 -- save values of mainTAG to a file (xored with 'specific' MCC)
2245 ["xf"] = function(x)
2246 if(istable(inTAG)) then
2247 outfile=input("enter filename:", "legic.temp")
2248 crc=input("enter new crc: ('00' for a plain dump)", inTAG.MCC)
2249 print("obfuscate witth: "..crc)
2250 bytes=tagToBytes(inTAG)
2251 bytes[5]=crc
2252 if (bytes) then
2253 writeFile(bytes, outfile)
2254 end
2255 end
2256 end,
2257 ---
2258 -- dump mainTAG (and all Segments)
2259 ["di"] = function(x)
2260 if (istable(inTAG)) then
2261 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2262 if(istable(inTAG.SEG[0])) then
2263 for i=0, #inTAG.SEG do
2264 if(check43rdPartyCash1(uid, inTAG.SEG[i].data)) then
2265 io.write(accyan.."in Segment index: "..inTAG.SEG[i].index ..acoff.. "\n")
2266 elseif(check4LegicCash(inTAG.SEG[i].data)) then
2267 io.write(accyan.."in Segment index: "..inTAG.SEG[i].index..acoff.."\n")
2268 lc=true;
2269 lci=inTAG.SEG[i].index;
2270 end
2271 end
2272 end
2273 print("\n"..dumpTag(inTAG).."\n")
2274 if (lc) then actions["dlc"](lci) end
2275 lc=false
2276 end
2277 end,
2278 ---
2279 -- dump backupTAG (and all Segments)
2280 ["do"] = function(x) if (istable(backupTAG)) then print("\n"..dumpTag(backupTAG).."\n") end end,
2281 ---
2282 -- create a empty tagMap
2283 ["mm"] = function(x)
2284 -- clear existing tagMap and init
2285 if (istable(inTAG)) then
2286 tagMap=makeTagMap()
2287 end
2288 end,
2289 ---
2290 -- edit a tagMap
2291 ["em"] = function(x)
2292 if (istable(inTAG)==false) then
2293 if (confirm("no mainTAG in memory!\nread from PM3?")) then
2294 actions['rt']()
2295 elseif (confirm("load from File?")) then
2296 actions['lf']()
2297 else return
2298 end
2299 end
2300 if (istable(tagMap)==false) then actions['mm']() end
2301 -- edit
2302 tagMap=editTagMap(inTAG, tagMap)
2303 end,
2304 ---
2305 -- save a tagMap
2306 ["sm"] = function(x)
2307 if (istable(tagMap)) then
2308 if (istable(tagMap) and #tagMap.mappings>0) then
2309 print(accyan.."Map contains "..acoff..#tagMap..accyan.." mappings"..acoff)
2310 saveTagMap(tagMap, input(accyan.."enter filename:"..acoff, "Legic.map"))
2311 else
2312 print(acyellow.."no mappings in tagMap!"..acoff)
2313 end
2314 end
2315 end,
2316 ---
2317 -- load a tagMap
2318 ["lm"] = function(x)
2319 tagMap=loadTagMap(input(accyan.."enter filename:"..acoff, "Legic.map"))
2320 end,
2321 ---
2322 -- dump single segment
2323 ["ds"] = function(x)
2324 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2325 else sel=selectSegment(inTAG) end
2326 if (sel) then print("\n"..(dumpSegment(inTAG, sel) or acred.."no Segments available") ..acoff.."\n") end
2327 end,
2328 ---
2329 -- edit segment header
2330 ["es"] = function(x)
2331 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2332 else sel=selectSegment(inTAG) end
2333 if (sel) then
2334 if(istable(inTAG.SEG[0])) then
2335 inTAG=editSegment(inTAG, sel)
2336 inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel])
2337 else print(acyellow.."no Segments in Tag"..acoff) end
2338 end
2339 end,
2340 ---
2341 -- add segment
2342 ["as"] = function(x)
2343 if (istable(inTAG.SEG[0])) then
2344 inTAG=addSegment(inTAG)
2345 inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1])
2346 inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG])
2347 else print(accyan.."Master-Token / unsegmented Tag!"..acoff)
2348 end
2349 end,
2350 ---
2351 -- remove segment
2352 ["rs"] = function(x)
2353 if (istable(inTAG.SEG[0])) then
2354 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2355 else sel=selectSegment(inTAG) end
2356 inTAG=delSegment(inTAG, sel)
2357 for i=0, #inTAG.SEG do
2358 inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i])
2359 end
2360 end
2361 end,
2362 ---
2363 -- edit data-portion of single segment
2364 ["ed"] = function(x)
2365 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2366 else sel=selectSegment(inTAG) end
2367 if (istable(inTAG.SEG[sel])) then
2368 inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data)
2369 end
2370 end,
2371 ---
2372 -- edit Tag (MCD, MSN, MCC etc.)
2373 ["et"] = function(x)
2374 if (istable(inTAG)) then
2375 editTag(inTAG)
2376 end
2377 end,
2378 ---
2379 -- make (dummy) Token
2380 ["mt"] = function(x) inTAG=makeToken(); actions.di() end,
2381 ---
2382 -- fix segment-crc on single segment
2383 ["ts"] = function(x)
2384 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2385 else sel=selectSegment(inTAG) end
2386 regenSegmentHeader(inTAG.SEG[sel])
2387 end,
2388 ---
2389 -- toggle kgh-crc-flag on a single segment
2390 ["tk"] = function(x)
2391 if (istable(inTAG) and istable(inTAG.SEG[0])) then
2392 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2393 else sel=selectSegment(inTAG) end
2394 if(inTAG.SEG[sel].kgh) then inTAG.SEG[sel].kgh=false
2395 else inTAG.SEG[sel].kgh=true end
2396 end
2397 end,
2398 ---
2399 -- calculate LegicCrc8
2400 ["k"] = function(x)
2401 if (type(x)=="string" and string.len(x)>0) then
2402 print(("%02x"):format(utils.Crc8Legic(x)))
2403 end
2404 end,
2405 ---
2406 -- noop
2407 ["xb"] = function(x)
2408 end,
2409 ---
2410 -- print string for LegicCrc8-calculation about single segment
2411 ["xc"] = function(x)
2412 if (istable(inTAG) and istable(inTAG.SEG[0])) then
2413 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2414 else sel=selectSegment(inTAG) end
2415 print("k "..kghCrcCredentials(inTAG, sel))
2416 end
2417 end,
2418 ---
2419 -- fix legic-cash checksums
2420 ["flc"] = function(x)
2421 if (type(x)=="string" and string.len(x)>0) then x=tonumber(x,10)
2422 else x=selectSegment(inTAG) end
2423 inTAG.SEG[x].data=fixLegicCash(inTAG.SEG[x].data)
2424 end,
2425 ---
2426 -- edit legic-cash values fixLegicCash(data)
2427 ["elc"] = function(x)
2428 x=autoSelectSegment(inTAG, "legiccash")
2429 inTAG.SEG[x].data=editLegicCash(inTAG.SEG[x].data)
2430 end,
2431 ---
2432 -- dump legic-cash human-readable
2433 ["dlc"] = function(x)
2434 -- if segment index was user defined
2435 if (type(x)=="string" and string.len(x)>0) then
2436 x=tonumber(x,10)
2437 print(string.format("User-Selected Index %02d", x))
2438 -- or try to find match
2439 else x=autoSelectSegment(inTAG, "legiccash") end
2440 -- dump it
2441 dumpLegicCash(inTAG, x)
2442 end,
2443 ---
2444 -- dump 3rd-party-cash-segment
2445 ["d3p"] = function(x)
2446 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2447 -- if segment index was user defined
2448 if (type(x)=="string" and string.len(x)>0) then
2449 x=tonumber(x,10)
2450 print(string.format("User-Selected Index %02d", x))
2451 -- or try to find match
2452 else x=autoSelectSegment(inTAG, "3rdparty") end
2453 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
2454 uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2455 if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then
2456 dump3rdPartyCash1(inTAG, x)
2457 end
2458 end
2459 end,
2460 ---
2461 -- dump 3rd-party-cash-segment (raw blocks and checksums over 'known areas')
2462 ["r3p"] = function(x)
2463 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2464 -- if segment index was user defined
2465 if (type(x)=="string" and string.len(x)>0) then
2466 x=tonumber(x,10)
2467 print(string.format("User-Selected Index %02d", x))
2468 -- or try to find match
2469 else x=autoSelectSegment(inTAG, "3rdparty") end
2470 print3rdPartyCash1(inTAG, x)
2471 end,
2472 ---
2473 -- edit 3rd-party-cash-segment values (Balance, Mapping-UID, Stamp)
2474 ["e3p"] = function(x)
2475 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2476 -- if segment index was user defined
2477 if (type(x)=="string" and string.len(x)>0) then
2478 x=tonumber(x,10)
2479 print(string.format("User-Selected Index %02d", x))
2480 -- or try to find match
2481 else x=autoSelectSegment(inTAG, "3rdparty") end
2482 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
2483 inTAG=edit3rdPartyCash1(inTAG, x)
2484 dump3rdPartyCash1(inTAG, x)
2485 end
2486 end,
2487 ---
2488 -- force fixing 3rd-party-checksums
2489 ["f3p"] = function(x)
2490 if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10)
2491 else x=selectSegment(inTAG) end
2492 if (istable(inTAG.SEG[x])) then
2493 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2494 inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data)
2495 end
2496 end,
2497 ---
2498 -- get stamp from single segment
2499 ["gs"] = function(x)
2500 if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10)
2501 else x=selectSegment(inTAG) end
2502 local stamp=getSegmentStamp(inTAG.SEG[x])
2503 print("Stamp : "..stamp)
2504 stamp=str2bytes(stamp)
2505 print("lenght: "..#stamp)
2506 end,
2507 ---
2508 -- calculate crc16
2509 ["c6"] = function(x) local crc16=string.format("%4.04x", utils.Crc16(x))
2510 print(string.sub(crc16, 0,2).." "..string.sub(crc16, 3,4))
2511 end,
2512 ---
2513 -- check & fix segments-crc of all segments
2514 ["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end,
2515 ---
2516 -- set backup-area-bytes to '00'
2517 ["cb"] = function(x)
2518 if (istable(inTAG)) then
2519 print(accyan.."purge BackupArea"..acoff)
2520 inTAG=clearBackupArea(inTAG)
2521 end
2522 end,
2523 ---
2524 -- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
2525 ["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end,
2526 ---
2527 -- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
2528 ["tac"] = function(x)
2529 if (colored_output) then
2530 colored_output=false
2531 else
2532 colored_output=true
2533 end
2534 load_colors(colored_output)
2535 end,
2536 }
2537 repeat
2538 -- defualt message / prompt
2539 ic=input("Legic command? ('h' for help - 'q' for quit)", "h")
2540 -- command actions decisions (first match, longer commands before shorter)
2541 if (type(actions[string.lower(string.sub(ic,0,3))])=='function') then
2542 actions[string.lower(string.sub(ic,0,3))](string.sub(ic,5))
2543 elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then
2544 actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4))
2545 elseif (type(actions[string.lower(string.sub(ic,0,1))])=='function') then
2546 actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3))
2547 else actions.h('') end
2548 until (string.sub(ic,0,1)=="q")
2549 end
2550
2551 ---
2552 -- main function
2553 function main(args)
2554 -- set init colors/switch (can be toggled with 'tac' => 'toggle ansicolors')
2555 load_colors(colored_output)
2556 if (#args == 0 ) then modifyMode() end
2557 --- variables
2558 local inTAG, backupTAG, outTAG, outfile, interactive, crc, ofs, cfs, dfs
2559 -- just a spacer for better readability
2560 print()
2561 --- parse arguments
2562 for o, a in getopt.getopt(args, 'hrmi:do:c:') do
2563 -- display help
2564 if o == "h" then return help(); end
2565 -- read tag from PM3
2566 if o == "r" then inTAG=readFromPM3() end
2567 -- input file
2568 if o == "i" then inTAG=readFile(a) end
2569 -- dump virtual-Tag
2570 if o == "d" then dfs=true end
2571 -- interacive modifying
2572 if o == "m" then interactive=true; modifyMode() end
2573 -- xor (e.g. for clone or plain file)
2574 if o == "c" then cfs=true; crc=a end
2575 -- output file
2576 if o == "o" then outfile=a; ofs=true end
2577 end
2578
2579 -- file conversion (output to file)
2580 if (ofs) then
2581 -- dump infile / tag-read
2582 if (dfs) then
2583 print("-----------------------------------------")
2584 print(dumpTag(inTAG))
2585 end
2586 bytes=tagToBytes(inTAG)
2587 if (cfs) then
2588 -- xor willl be done in function writeFile
2589 -- with the value of byte[5]
2590 bytes[5]=crc
2591 end
2592 -- write to outfile
2593 if (bytes) then
2594 writeFile(bytes, outfile)
2595 --- read real tag into virtual tag
2596 -- inTAG=readFromPM3() end
2597 --- or simply use the bytes that where wriiten
2598 inTAG=bytesToTag(bytes, inTAG)
2599 -- show new content
2600 if (dfs) then
2601 print("-----------------------------------------")
2602 print(dumpTag(inTAG))
2603 end
2604 end
2605 end
2606
2607 end
2608
2609 ---
2610 -- script start
2611 main(args)
Impressum, Datenschutz