recover_pk selftests: show curve & hash
[RRG-proxmark3.git] / client / luascripts / hf_legic.lua
blobe92b785993c147eaf1ad789ac99ebe92398d0046
1 --[[
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 +----+----+----+----+----+----+----+----+
16 0x20|UID1|UID2|kghC|
17 +----+----+----+
18 MCD = Manufacturer ID
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
23 00 = unimportant
24 11 = unknown but important
25 Bck = header-backup-area
26 00 00 = Year (00 = 2000) & Week (not important)
27 Seg = Segment Header
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)
49 LIM = Cash-Limit
50 CHK = crc16 over byte-addr 0x05..0x12
51 BAL = Balance
52 LRB = ID of the reader that changed the balance
53 CHK = crc16 over BAL + LRB
54 SHD = shadow Balance
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
66 +----+
67 MCD = Manufacturer ID
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 +----+----+----+----+----+----+
84 MCD = Manufacturer ID
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)
92 --]]
94 --[[
95 Known issues; needs to be fixed:
96 * last byte in last segment is handled incorrectly when it is the last bytes on the card itself (MIM256: => byte 256)
97 --]]
101 -- requirements
102 local utils = require('utils')
103 local getopt = require('getopt')
104 local ansicolors = require('ansicolors')
107 -- global variables / defines
108 -- local bxor = bit32.bxor
109 -- local bbit = bit32.extract
110 local input = utils.input
111 local confirm = utils.confirm
114 -- init ansicolor-values & ansicolors switch
115 local colored_output = true
116 local acoff = ""
117 local acgreen= ""
118 local accyan = ""
119 local acred = ""
120 local acyellow = ""
121 local acblue = ""
122 local acmagenta = ""
124 local acy = ansicolors.yellow
125 local acc = ansicolors.cyan
126 local acr = ansicolors.reset
128 --- Helper ---
130 -- default colors (change to whatever you want)
131 function load_colors(onoff)
132 if (onoff) then
133 -- colors
134 acgreen = ansicolors.green
135 accyan = ansicolors.cyan
136 acred = ansicolors.red
137 acyellow= ansicolors.yellow
138 acblue = ansicolors.blue
139 acmagenta= ansicolors.magenta
140 acoff = ansicolors.reset
142 acy = ansicolors.yellow
143 acc = ansicolors.cyan
144 acr = ansicolors.reset
145 else
146 -- 'no color'
147 acgreen = ""
148 accyan = ""
149 acred = ""
150 acyellow= ""
151 acblue = ""
152 acmagenta= ""
153 acoff = ""
155 acy = ""
156 acc = ""
157 acr = ""
162 example = "script run hf_legic"
163 author = "Mosci, uhei"
164 version = "1.0.5"
165 desc =
168 This script helps you to read, create and modify Legic Prime Tags ( MIM22, MIM256, MIM1024 )
169 The virtual tag (and therefore the file to be saved) is always a MIM1024 tag.
170 it's kinda interactive with following commands in three categories:
172 Data I/O Segment Manipulation Token-Data
173 ----------------- -------------------- -----------------
174 ]]..acy..[[rt]]..acr..[[ -> read Tag ]]..acy..[[as]]..acr..[[ -> add Segment ]]..acy..[[mt]]..acr..[[ -> make Token
175 ]]..acy..[[wt]]..acr..[[ -> write Tag ]]..acy..[[es]]..acr..[[ -> edit Segment Header ]]..acy..[[et]]..acr..[[ -> edit Token data
176 ]]..acy..[[ed]]..acr..[[ => edit Segment Data ]]..acy..[[tk]]..acr..[[ => toggle KGH-Flag
177 File I/O ]]..acy..[[rs]]..acr..[[ => remove Segment
178 ----------------- ]]..acy..[[cc]]..acr..[[ -> check Segment-CRC
179 ]]..acy..[[lf]]..acr..[[ -> load bin File ]]..acy..[[ck]]..acr..[[ -> check KGH
180 ]]..acy..[[sf]]..acr..[[ -> save eml/bin File ]]..acy..[[ds]]..acr..[[ -> dump Segments
181 ]]..acy..[[xf]]..acr..[[ -> xor to File
184 (partially) known Segments Virtual Tags Script Output
185 --------------------------- ------------------------------- ------------------------
186 ]]..acy..[[dlc]]..acr..[[ -> dump Legic-Cash ]]..acy..[[ct]]..acr..[[ -> copy mainTag to backupTag ]]..acy..[[tac]]..acr..[[ -> toggle ansicolors
187 ]]..acy..[[elc]]..acr..[[ -> edit Legic-Cash ]]..acy..[[tc]]..acr..[[ -> copy backupTag to mainTag
188 ]]..acy..[[d3p]]..acr..[[ -> dump 3rd-Party-Cash ]]..acy..[[tt]]..acr..[[ -> switch mainTag & backupTag
189 ]]..acy..[[e3p]]..acr..[[ -> edit 3rd-Party-Cash ]]..acy..[[di]]..acr..[[ -> dump mainTag
190 ]]..acy..[[do]]..acr..[[ => dump backupTag
192 rt: 'read tag' - reads a tag placed near to the PM3
193 wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3
194 without the need of changing anything - MCD,MSN,MCC will be read from the tag
195 before and applied to the output.
197 lf: 'load file' - load a (xored) binary file (*.bin) from the local Filesystem into the 'virtual inTag'
198 sf: 'save file' - saves the 'virtual inTag' to the local Filesystem as eml and bin (xored with Tag-MCC)
199 xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with chosen MCC - use '00' for plain values)
201 ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not useful yet, but inernally needed
202 tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not useful yet, but inernally needed
203 tt: 'toggle tag' - copy mainTag to BackupTag and backupTag to mainTag
205 di: 'dump mainTag' - shows the current content of the 'virtual Tag'
206 do: 'dump backupTag' - shows the current content of the 'virtual outTag'
207 ds: 'dump Segments' - will show the content of a selected Segment
208 as: 'add Segment' - will add a 'empty' Segment to the inTag
209 es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid)
210 all other Segment-Header-Values are either calculated or not needed to edit (yet)
211 ed: 'edit data' - edit the Data of a Segment (ADF-Aera / Stamp & Payload specific Data)
212 et: 'edit Token' - edit Data of a Token (CDF-Area / SAM, SAM64, SAM63, IAM, GAM specific Data)
213 mt: 'make Token' - create a Token 'from scratch' (guided)
214 rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token)
215 cc: 'check Segment-CRC' - checks & calculates (if check failed) the Segment-CRC of all Segments
216 ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected
217 'Kaba Group Header CRC calculation'
218 tk: 'toggle KGH' - toggle the (script-internal) flag for kgh-calculation for a segment
219 xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment
221 dlc: 'dump Legic-Cash' - show balance and checksums of a Legic-Cash Segment
222 elc: 'edit Legic-Cash' - edit values of a Legic-Cash Segment
224 d3p: 'dump 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd-Party Cash Segment
225 e3p: 'edit 3rd Party' - edit Data in 3rd-Party Cash Segment
227 tac: 'toggle ansicolors' - switch on and off the colored text-output of this script
228 default can be changed by setting the variable 'colored_output' to false
230 currentTag="inTAG"
233 -- curency-codes for Legic-Cash-Segments (ISO 4217)
234 local currency = {
235 ["03D2"]="EUR",
236 ["0348"]="USD",
237 ["033A"]="GBP",
238 ["02F4"]="CHF"
242 -- This is only meant to be used when errors occur
243 function oops(err)
244 print(acred.."ERROR: "..acoff ,err)
245 return nil, err
249 -- Usage help
250 function help()
251 -- the proxmark3 client can't handle such long strings
252 -- by breaking up at specific points it still looks good.
253 print(string.sub(desc, 0, 1961))
254 print(string.sub(desc, 1962, 3925))
255 print(string.sub(desc, 3926, #desc))
256 print("Version: "..version)
257 print("Example usage: "..example)
261 -- table check helper
262 function istable(t)
263 return type(t) == 'table'
267 -- To have two char string for a byte
268 local function padString(str)
269 if (#str == 1) then
270 return '0'..str
272 return str
276 -- creates a 'deep copy' of a table (a=b only references)
277 function deepCopy(object)
278 local lookup_table = {}
279 local function _copy(object)
280 if type(object) ~= "table" then
281 return object
282 elseif lookup_table[object] then
283 return lookup_table[object]
285 local new_table = {}
286 lookup_table[object] = new_table
287 for index, value in pairs(object) do
288 new_table[_copy(index)] = _copy(value)
290 return setmetatable(new_table, getmetatable(object))
292 return _copy(object)
296 -- xor single byte
297 function xorme(hex, xor, index)
298 if ( index >= 23 ) then
299 --return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
300 return string.format("%x", tonumber(hex,16) ~ tonumber(xor,16))
301 else
302 return hex
307 -- (de)obfuscate bytes
308 function xorBytes(inBytes, crc)
309 local bytes = {}
310 for index = 1, #inBytes do
311 bytes[index] = xorme(inBytes[index], crc, index)
313 if (#inBytes == #bytes) then
314 -- replace crc
315 bytes[5] = string.sub(crc,-2)
316 return bytes
317 else
318 print("error: byte-count missmatch")
319 return false
324 -- split csv-string into table
325 local function split(str, sep)
326 local sep = sep or ','
327 local fields={}
328 local matchfunc = string.gmatch(str, "([^"..sep.."]+)")
329 if not matchfunc then return {str} end
330 for str in matchfunc do
331 table.insert(fields, str)
333 return fields
337 -- join table with a separator
338 local function join(list, sep)
339 local sep = sep or ','
340 local len = #list
341 if len == 0 then return "" end
342 local s = list[1]
343 for i = 2, len do
344 s = s .. sep .. list[i]
346 return s
350 -- check availability of file
351 function file_check(file_name)
352 if not file_name then return false, "" end
354 local arr = split(file_name, ".")
355 local ext = table.remove(arr)
356 local name = join(arr, '.')
357 local path = core.search_file(name, "."..ext)
358 if (path == nil) then return false, "" end
360 local file_found = io.open(path, "r")
361 if file_found == nil then
362 return false, ""
363 else
364 file_found:close()
365 return true, path
370 -- put a string into a bytes-table
371 function str2bytes(s)
372 if (string.len(s)%2 ~= 0) then
373 return print("stamp should be a even hexstring e.g.: deadbeef or 0badc0de")
375 local res={}
376 for i=1, string.len(s), 2 do
377 table.insert(res, string.sub(s,i,(i+1)))
379 return res
383 -- put certain bytes into a new table
384 function bytesToTable(bytes, bstart, bend)
385 local t={}
386 for i=0, (bend-bstart) do
387 t[i]=padString(bytes[bstart+i])
389 return t
393 -- read file into table
394 function getInputBytes(infile)
395 local line
396 local bytes = {}
398 local arr = split(infile, ".")
399 local ext = table.remove(arr)
400 local name = join(arr, '.')
401 local path = core.search_file(name, "."..ext)
402 if (path == nil) then oops("failed to read from file ".. infile); return false; end
404 local fhi,err = io.open(path,"rb")
405 if err then oops("failed to read from file ".. path); return false; end
407 file_data = fhi:read("*a");
408 for i = 1, #file_data do
409 bytes[i] = string.format("%x",file_data:byte(i))
411 fhi:close()
412 if (bytes[7]=='00') then return false end
413 print(#bytes .. " bytes from "..path.." loaded")
414 return bytes
418 -- create tag-table helper
419 function createTagTable()
420 local t={
421 ['MCD'] = '00',
422 ['MSN0']= '11',
423 ['MSN1']= '22',
424 ['MSN2']= '33',
425 ['MCC'] = 'cc',
426 ['DCFl']= 'ff',
427 ['DCFh']= 'ff',
428 ['Type']= 'GAM',
429 ['OLE'] = 0,
430 ['Stamp_len']= 18,
431 ['WRP'] = '00',
432 ['WRC'] = '00',
433 ['RD'] = '00',
434 ['raw'] = '9f',
435 ['SSC'] = 'ff',
436 ['data']= {},
437 ['bck'] = {},
438 ['MTC'] = {},
439 ['SEG'] = {}
441 return t
445 -- put bytes into tag-table
446 function bytesToTag(bytes, tag)
447 if istable(tag) == false then return oops("tag is no table in: bytesToTag ("..type(tag)..")") end
449 tag.MCD =padString(bytes[1]);
450 tag.MSN0=padString(bytes[2]);
451 tag.MSN1=padString(bytes[3]);
452 tag.MSN2=padString(bytes[4]);
453 tag.MCC =padString(bytes[5]);
454 tag.DCFl=padString(bytes[6]);
455 tag.DCFh=padString(bytes[7]);
456 tag.raw =padString(bytes[8]);
457 tag.SSC =padString(bytes[9]);
458 tag.Type=getTokenType(tag.DCFl);
459 --tag.OLE=bbit("0x"..tag.DCFl,7,1)
460 tag.OLE=(tonumber(tag.DCFl, 16) >> 7) & 0x01
461 --tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4))
462 tag.WRP=string.format("%02d", tonumber(bytes[8], 16) & 0x0F)
463 --tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3))
464 tag.WRC = string.format("%02d", (tonumber(bytes[8], 16) >> 4) & 0x07)
465 --tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1))
466 tag.RD=string.format("%02d", (tonumber(bytes[8],16) >> 7) & 0x01)
467 if (tag.Type=="SAM" and tag.raw=='9f') then
468 tag.Stamp_len=(0xfc - tonumber(tag.DCFh,16) & 0xFF)
469 --tag.Stamp_len=(0xfc-bbit("0x"..tag.DCFh,0,8))
470 elseif (tag.Type=="SAM" and (tag.raw=='08' or tag.raw=='09')) then
471 tag.Stamp_len = tonumber(tag.raw,10)
473 tag.data=bytesToTable(bytes, 10, 13)
474 tag.Bck=bytesToTable(bytes, 14, 20)
475 tag.MTC=bytesToTable(bytes, 21, 22)
477 print(acgreen.."Tag-Type: ".. tag.Type..acoff)
478 if (tag.Type=="SAM" and #bytes>23) then
479 tag=segmentsToTag(bytes, tag)
480 print(acgreen..(#tag.SEG+1).." Segment(s) found"..acoff)
481 -- unsegmented Master-Token
482 -- only tag-data
483 else
484 for i=0, #tag.Bck do
485 table.insert(tag.data, tag.Bck[i])
487 tag.data[#tag.data]=tag.MTC[0]
488 tag.Bck=nil
489 --tag.MTC[0]=tag.MTC[1]
490 --tag.MTC[1]=nil
492 print(accyan..#bytes.." bytes for Tag processed"..acoff)
493 return tag
498 -- put segments from byte-table to tag-table
499 function segmentsToTag(bytes, tag)
500 if(#bytes>23) then
501 local start=23
502 local i=-1
503 if (istable(tag)) then
504 repeat
505 i=i+1
506 tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i))
507 if (tag.Type=="SAM") then
508 if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end
510 start=start+tag.SEG[i].len
511 until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126)
512 return tag
513 else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end
514 else print("no Segments: must be a MIM22") end
518 -- read Tag-Table in bytes-table
519 function tagToBytes(tag)
520 if istable(tag) == false then return oops("tag is no table in tagToBytes ("..type(tag)..")") end
522 local bytes = {}
523 local i, i2
524 -- main token-data
525 table.insert(bytes, tag.MCD)
526 table.insert(bytes, tag.MSN0)
527 table.insert(bytes, tag.MSN1)
528 table.insert(bytes, tag.MSN2)
529 table.insert(bytes, tag.MCC)
530 table.insert(bytes, tag.DCFl)
531 table.insert(bytes, tag.DCFh)
532 table.insert(bytes, tag.raw)
533 table.insert(bytes, tag.SSC)
534 -- raw token data
535 for i=0, #tag.data do
536 table.insert(bytes, tag.data[i])
538 -- backup data
539 if(istable(tag.Bck)) then
540 for i=0, #tag.Bck do
541 table.insert(bytes, tag.Bck[i])
544 -- token-create-time / master-token crc
545 for i=0, #tag.MTC do
546 table.insert(bytes, tag.MTC[i])
548 -- process segments
549 if (type(tag.SEG[0])=='table') then
550 for i=0, #tag.SEG do
551 for i2=1, #tag.SEG[i].raw+1 do
552 table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2])
554 table.insert(bytes, #bytes+1, tag.SEG[i].crc)
555 for i2=0, #tag.SEG[i].data-1 do
556 table.insert(bytes, #bytes+1, tag.SEG[i].data[i2])
560 -- fill with zeros
561 for i=#bytes+1, 1024 do
562 table.insert(bytes, i, '00')
564 return bytes
569 --- PM3 I/O ---
570 -- write virtual Tag to real Tag
571 function writeToTag(tag)
572 local bytes
573 local taglen = 22
574 local writeDCF = false
575 if(utils.confirm(acred.."\nPlace the (empty) Tag onto the PM3\nand confirm writing to this Tag: "..acoff) == false) then
576 return
578 if(utils.confirm(acred.."\nShould the decremental field (DCF) be written?: "..acoff) == true) then
579 writeDCF = true
582 -- get used bytes / tag-len
583 if (istable(tag.SEG)) then
584 if (istable(tag.Bck)) then
585 for i=0, #tag.SEG do
586 taglen = taglen + tag.SEG[i] . len
589 local uid_old = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
591 -- read new tag into memory so we can xor the new data with the new MCC
592 outTAG = readFromPM3()
593 outbytes = tagToBytes(outTAG)
594 -- copy 'inputbuffer' to 'outputbuffer'
595 tag.MCD = outbytes[1]
596 tag.MSN0 = outbytes[2]
597 tag.MSN1 = outbytes[3]
598 tag.MSN2 = outbytes[4]
599 tag.MCC = outbytes[5]
600 -- recheck all segments-crc/kghcrc (only on a credential)
601 if (istable(tag.Bck)) then
602 checkAllSegCrc(tag)
603 checkAllKghCrc(tag)
604 local uid_new = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
605 for i=0, #tag.SEG do
606 if (check43rdPartyCash1(uid_old, tag.SEG[i].data)) then
607 io.write(accyan.."\nfixing known checksums"..acoff.." ... ")
608 if (fix3rdPartyCash1(uid_new, tag.SEG[i].data)) then
609 io.write(acgreen.." done\n"..acoff)
610 else
611 oops("\nsomething went wrong at the repair of the 3rd-party-cash-segment")
616 bytes = tagToBytes(tag)
617 -- master-token-crc
618 if (tag.Type ~= "SAM") then
619 bytes[22] = calcMtCrc(bytes)
621 if (bytes) then
622 bytes = xorBytes(bytes,tag.MCC)
627 -- write data to file
628 if (taglen > 0) then
629 WriteBytes = input(acyellow.."enter number of bytes to write?"..acoff, taglen)
630 -- write pm3-buffer to Tag
631 for i=1, WriteBytes do
632 if (i > 7) then
633 cmd = ("hf legic wrbl -o %d -d %s "):format(i-1, padString(bytes[i]))
634 print(acgreen..cmd..acoff)
635 core.console(cmd)
636 core.clearCommandBuffer()
637 elseif (i == 7) then
638 if (writeDCF) then
639 -- write DCF in reverse order (requires 'mosci-patch')
640 cmd = ('hf legic wrbl -o 5 -d %s%s'):format(padString(bytes[i-1]), padString(bytes[i]))
641 print(acgreen..cmd..acoff)
642 core.console(cmd)
643 core.clearCommandBuffer()
644 else
645 print(acgreen.."skip byte 0x05-0x06 - DCF"..acoff)
647 elseif (i == 6) then
648 print(acgreen.."skip byte 0x05 - will be written next step"..acoff)
649 else
650 print(acgreen.."skip byte 0x00-0x04 - unwritable area"..acoff)
652 utils.Sleep(0.2)
657 --- File I/O ---
659 -- read file into virtual-tag
660 local function readFile(filename)
661 print(accyan)
662 local bytes = {}
663 local tag = {}
665 local res, path = file_check(filename)
666 if not res then
667 return oops("input file: "..acyellow..filename..acoff.." not found")
670 bytes = getInputBytes(path)
671 if bytes == false then return oops('couldnt get input bytes') end
673 -- make plain bytes
674 bytes = xorBytes(bytes,bytes[5])
675 print("create virtual tag from ".. #bytes .. " bytes")
676 -- create Tag for plain bytes
677 tag = createTagTable()
678 -- load plain bytes to tag-table
679 print(acoff)
680 tag = bytesToTag(bytes, tag)
682 return tag
685 local function save_BIN(data, filename)
686 local outfile
687 local counter = 1
688 local ext = ".bin"
689 local fn = filename..ext
691 -- Make sure we don't overwrite a file
692 local res, path = file_check(fn)
693 while res == false do
694 fn = filename..ext:gsub(ext, "-"..tostring(counter)..ext)
695 counter = counter + 1
696 res, path = file_check(fn)
699 outfile = io.open(path, 'wb')
701 local i = 1
702 while data[i] do
703 local byte = string.char(tonumber(data[i], 16))
704 outfile:write(byte)
705 i = i + 1
707 outfile:close()
708 return fn, #data
711 -- write bytes to file
712 function writeFile(bytes, filename)
713 local emlext = ".eml"
714 local res, path
715 if (filename ~= 'MyLegicClone') then
716 res, path = file_check(filename..emlext)
717 if res then
718 local answer = confirm("\nthe output-file "..path.." already exists!\nthis will delete the previous content!\ncontinue?")
719 if not answer then return print("user abort") end
722 local line
723 local bcnt = 0
724 local fho, err = io.open(path, "w")
725 if err then
726 return oops("OOps ... failed to open output-file ".. path)
729 bytes = xorBytes(bytes, bytes[5])
731 for i = 1, #bytes do
732 if (bcnt == 0) then
733 line = padString(bytes[i])
734 elseif (bcnt <= 7) then
735 line = line.." "..padString(bytes[i])
737 if (bcnt == 7) then
738 -- write line to new file
739 fho:write(line.."\n")
740 -- reset counter & line
741 bcnt = -1
742 line = ""
744 bcnt = bcnt + 1
746 fho:close()
748 print("\nwrote "..acyellow..(#bytes * 3)..acoff.." bytes to " ..acyellow..filename..emlext..acoff)
750 -- save binary
751 local fn_bin, fn_bin_num = save_BIN(bytes, filename)
752 if fn_bin and fn_bin_num then
753 print("\nwrote "..acyellow..fn_bin_num..acoff.." bytes to BINARY file "..acyellow..fn_bin..acoff)
756 return true
759 function getRandomTempName()
760 local upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
761 local lowerCase = "abcdefghijklmnopqrstuvwxyz"
763 local characterSet = upperCase .. lowerCase
765 local keyLength = 8
766 local output = ""
768 for i = 1, keyLength do
769 local rand = math.random(#characterSet)
770 output = output .. string.sub(characterSet, rand, rand)
773 output = "hf-legic-temp-" .. output
775 return output
779 -- read from pm3 into virtual-tag
780 function readFromPM3()
781 local tag, bytes, infile
782 infile=getRandomTempName()
783 core.console("hf legic dump -f "..infile)
784 tag=readFile(infile..".bin")
786 res, path = file_check(infile..".bin")
787 if res then os.remove(path) end
790 res, path = file_check(infile..".json")
791 if res then os.remove(path) end
792 return tag
795 --- Map related ---
797 -- make tagMap
798 local function makeTagMap()
799 local tagMap = {}
800 if (#tagMap == 0) then
801 tagMap['name'] = input(accyan.."enter Name for this Map: "..acoff , "newTagMap")
802 tagMap['mappings'] = {}
803 tagMap['crc8'] = {}
804 -- insert fixed Tag-CRC
805 table.insert(tagMap.crc8, {name = 'TAG-CRC', pos = 5, seq = {1, 4}})
806 tagMap['crc16'] = {}
808 print(accyan.."new tagMap created"..acoff)
809 return tagMap
813 -- save mapping to file
814 local function saveTagMap(map, filename)
816 local res, path
818 if #filename > 0 then
819 res, path = file_check(filename)
820 if res then
821 local answer = confirm("\nthe output-file "..acyellow..path..acoff.." alredy exists!\nthis will delete the previous content!\ncontinue?")
822 if not answer then return print("user abort") end
826 local line
827 local fho,err = io.open(path, "w")
828 if err then oops("OOps ... failed to open output-file "..acyellow..path..acoff) end
830 -- write line to new file
831 for k, v in pairs(map) do
832 if (istable(v)) then
833 for k2, v2 in pairs(v) do
834 if (k == 'mappings') then
835 fho:write(k..","..k2..","..v2['name']..","..v2['start']..","..v2['end']..","..((v2['highlight']) and "1" or "0").."\n")
836 elseif (k == "crc8") then
837 local tmp = ""
838 tmp = k..","..k2..","..v2['name']..","..v2['pos']..","
839 tmp=tmp..tbl2seqstr(v2['seq'])
840 fho:write(tmp.."\n")
843 else
844 fho:write(k..","..v.."\n")
847 fho:close()
848 return true
852 -- toggle higligh
853 local function toggleHighlight(tbl)
854 if (tbl['highlight']) then
855 tbl['highlight'] = false
856 else
857 tbl['highlight'] = true
859 return tbl
863 -- return table od seqence-string
864 local function seqstr2tbl(seqstr)
865 local s = split(seqstr)
866 local res = {}
867 if (#s >= 1) then
868 for sk, sv in pairs(s) do
869 s2 = split(sv, '-')
870 if(#s2 == 2) then
871 table.insert(res, s2[1])
872 table.insert(res, s2[2])
876 return res
880 -- return sequence-string from table
881 local function tbl2seqstr(seqtbl)
882 local res = ""
883 if (istable(seqtbl)) then
884 for sk, sv in pairs(seqtbl) do
885 res = res..sv..((sk%2==0) and "," or "-")
887 if (string.sub(res, string.len(res))== ",") then
888 res = string.sub(res, 1, string.len(res)-1)
891 return res
895 -- read map-file into map
896 function loadTagMap(filename)
897 local map={mappings={}, crc8={}, crc16={}}
898 local m=0
899 local c=0
900 local line, fields
901 local temp={}
902 local offset=0
904 local res, path = file_check(filename)
905 if not res then
906 return oops("input file: "..acyellow..filename..acoff.." not found")
907 else
909 local fhi,err = io.open(path)
910 while true do
911 line = fhi:read()
912 if line == nil then
913 break
914 else
915 fields = split(line)
917 if (#fields == 2) then
918 if (fields[1] == 'offset') then
919 offset = tonumber(fields[2],10)
921 -- map-name
922 map[fields[1]]=fields[2]
923 elseif (fields[1]=='mappings') then
924 m=m+1
925 temp={}
926 -- mapping
927 temp['name']=fields[3]
928 temp['start']=tonumber(fields[4], 10)
929 temp['end']=tonumber(fields[5], 10)
930 if(temp['start']>22) then
931 temp['start']=temp['start']+offset
932 temp['end']=temp['end']+offset
934 if (tonumber(fields[6], 10)==1) then temp['highlight']= true
935 else temp['highlight']= false end
936 table.insert(map['mappings'], m, temp)
937 elseif (fields[1]=='crc8') then
938 c=c+1
939 temp={}
940 -- crc8
941 temp['name']=fields[3]
942 temp['pos']=tonumber(fields[4], 10)+offset
943 local s=string.sub(line, string.len(fields[1]..","..fields[2]..","..fields[3]..",")+1, string.len(line))
944 temp['seq']=seqstr2tbl(s)
945 for k, v in pairs(temp['seq']) do
946 if(tonumber(v, 10)>22) then v=tonumber(v, 10)+offset end
947 temp['seq'][k]=tonumber(v, 10)
949 table.insert(map.crc8, temp)
952 fhi:close()
954 return map
958 -- dump tagMap (mappings only)
959 function dumpTagMap(tag, tagMap)
960 if(#tagMap.mappings>0) then
961 bytes=tagToBytes(tag)
962 local temp
963 local lastend=0
964 -- start display mappings
965 for k, v in pairs(tagMap.mappings) do
966 if ((lastend+1)<v['start']) then
967 print("...")
969 if (isPosCrc8(tagMap, v['start'])>0) then
970 if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, v['start']) ) ) then
971 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acgreen..v['name']..acoff)
972 else
973 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acred..v['name']..acoff)
975 else
976 io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..((v['highlight']) and acmagenta or acyellow)..v['name']..acoff)
979 temp = ""
980 while (#v['name'] + temp:len()) < 20 do temp = temp.." " end
982 for i=v['start'], v['end'] do
983 temp=temp..bytes[i].." "
986 print(temp)
987 lastend=v['end']
994 function isPosCrc8(tagMap, pos)
995 local res=0
996 if (#tagMap.crc8>0) then
997 for k, v in pairs(tagMap.crc8) do
998 if(v['pos']==pos) then res=k end
1001 return res
1005 -- check mapped crc
1006 function checkMapCrc8(tagMap, bytes, n)
1007 local res=false
1008 if (#tagMap.crc8>0) then
1009 if(istable(tagMap.crc8[n])) then
1010 temp=""
1011 for k2, v2 in pairs(tagMap.crc8[n]) do
1012 if (istable(v2)) then
1013 temp=temp..tbl2seqstr(v2)
1016 local tempres=""
1017 local tempres=getSequences(bytes, temp)
1018 tempres=("%02x"):format(utils.Crc8Legic(tempres))
1019 if (bytes[tagMap.crc8[n]['pos']]==tempres) then
1020 res=true
1024 return res
1028 -- edit existing Map
1029 function editTagMap(tag, tagMap)
1030 local t = [[
1031 ]]..acc..[[Data]]..acr..[[
1033 ]]..acy..[[dm]]..acr..[[ - show ]]..acy..[[dr]]..acr..[[ - dump raw
1035 ]]..acc..[[Mappings]]..acr..[[
1037 ]]..acy..[[im]]..acr..[[ - insert ]]..acy..[[am]]..acr..[[ - add
1038 ]]..acy..[[rm]]..acr..[[ - remove ]]..acy..[[mas]]..acr..[[ - map all segments
1040 ]]..acc..[[CRC8]]..acr..[[
1042 ]]..acy..[[ac8]]..acr..[[ - add ]]..acy..[[sc8]]..acr..[[ - show
1043 ]]..acy..[[rc8]]..acr..[[ - remove
1045 ]]..acy..[[q]]..acr..[[ - exit ]]..acy..[[h]]..acr..[[ - Help
1048 --if(#tagMap.mappings==0) then oops("no mappings in tagMap"); return tagMap end
1049 print("tagMap edit-mode submenu")
1050 repeat
1051 x=input('tagMap submenu:', 'h')
1052 if (x=='h') then print(t)
1053 elseif (x=='dm') then tagMmap=dumpTagMap(tag, tagMap)
1054 elseif (x=='dr') then tagMmap=dumpMap(tag, tagMap)
1055 elseif (x=='rc8') then
1056 if (istable(tagMap.crc8)) then
1057 local x1 = selectTableEntry(tagMap.crc8, "select number of CRC8 to remove:")
1058 if (istable(tagMap.crc8[x1])) then
1059 table.remove(tagMap.crc8, x1)
1062 elseif (x=='ac8') then
1063 local p=tonumber(input("enter byte-addr of crc8", '0'),10)
1064 if (p>0) then
1065 local i1=input("enter comma-seperated byte-sequences (e.g.: '1-4,23-26')", '1-4,23-26')
1066 local s1=split(i1, ',')
1067 if (#s1>0) then
1068 local temp={seq={}}
1069 for k, v in pairs(s1) do
1070 v1=split(v, '-')
1071 if(#v1==2) then
1072 table.insert(temp.seq, v1[1])
1073 table.insert(temp.seq, v1[2])
1076 temp['pos']=p
1077 temp['name']=input("enter a name for the CRC8", "CRC "..(#tagMap.crc8+1))
1078 table.insert(tagMap.crc8, temp)
1081 elseif (string.sub(x, 1, 3)=='sc8') then
1082 local bytes=tagToBytes(tag)
1083 local res, pos
1084 -- trigger manually by sc8 <'4digit' checkadd> <'seqeuence-string'>
1085 -- e.g.: sc8 0027 1-4,23-36
1086 if (string.len(x)>=9) then
1087 pos=tonumber(string.sub(x, 5, 8), 10)
1088 x=string.sub(x, 9, string.len(x))
1089 print("x: "..x.." - pos:"..pos)
1090 else
1091 x=selectTableEntry(tagMap.crc8, "select CRC:")
1092 if(istable(tagMap.crc8[x])) then
1093 pos=tagMap.crc8[x]['pos']
1094 x=tbl2seqstr(tagMap.crc8[x]['seq'])
1097 if (type(x)=='string') then
1098 res=("%02x"):format(utils.Crc8Legic(getSequences(bytes, x)))
1099 print(accyan.."Sequence:\t"..acoff..x)
1100 print(accyan.."Bytes:\t\t"..acoff..getSequences(bytes, x))
1101 print(accyan.."calculated: "..acoff..res..accyan.." bytes["..pos.."]: "..acoff..bytes[pos].." ("..compareCrc(utils.Crc8Legic(getSequences(bytes, x)), bytes[pos])..")")
1103 elseif (x=="tm") then
1104 x=selectTableEntry(tagMap.mappings, "select number of Mapping:")
1105 tagMap.mappings[x]=toggleHighlight(tagMap.mappings[x])
1106 elseif (x=='am') then tagMap=addMapping(tag, tagMap)
1107 elseif (x=='im') then tagMap=addMapping(tag, tagMap, selectTableEntry(tagMap.mappings, "select List-Position for insert:"))
1108 elseif (x=='rm') then tagMap=deleteMapping(tag, tagMap)
1109 elseif (x=='mas') then tagMap=mapTag(tagMap); tagMap=mapAllSegments(tag, tagMap)
1110 elseif (type(actions[string.sub(x, 3)])=='function') then actions[string.sub(x, 3)]()
1112 until x=='q'
1113 print("exit sub-Menu")
1114 return tagMap
1118 -- dump raw mapped and unmapped
1119 function dumpMap(tag, tagMap)
1120 local dstart=1
1121 local dend, cnt
1122 local bytes = tagToBytes(tag)
1123 local stats = getSegmentStats(bytes)
1124 dend=stats[#stats]['end']
1125 print(accyan.."Tag uses "..dend.." bytes:"..acoff)
1126 for i=dstart, dend do
1127 if (check4MappedByte(i, tagMap) and not check4MapCrc8(i, tagMap) and not check4Highlight(i, tagMap)) then io.write(""..acyellow)
1128 elseif (check4MapCrc8(i, tagMap)) then
1129 if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, i) ) ) then
1130 io.write(""..acgreen)
1131 else
1132 io.write(""..acred)
1134 else
1135 io.write(""..acoff)
1137 -- highlighted mapping
1138 if (check4Highlight(i, tagMap)) then io.write(""..acmagenta) end
1140 io.write(bytes[i])
1141 if (i%8==0) then io.write("\n")
1142 else io.write(" ") end
1145 io.write("\n"..acoff)
1149 -- show bytes used for crc-calculation
1150 function getSequences(bytes, seqstr)
1151 if (type(seqstr) ~= "string") then seqstr = input("enter comma-seperated sequences (e.g.: '1-4,23-26')", '1-4,23-26') end
1152 local seqs = split(seqstr, ',')
1153 local res = ""
1154 if(#seqs>0) then
1155 for k, v in pairs(seqs) do
1156 local seq = split(v,'-')
1157 if (#seq >= 2) then
1158 for i = seq[1], seq[2] do
1159 res = res..bytes[i].." "
1162 if(string.len(res)>0) then res = res.." " end
1164 else
1165 oops("no sequence found in '"..seqstr.."'")
1167 return res
1171 -- check if byte-addr is a know crc
1172 function check4MapCrc8(addr, tagMap)
1173 local res=false
1174 for i=1, #tagMap.crc8 do
1175 if (addr == tagMap.crc8[i]['pos']) then
1176 res=true
1179 return res
1183 -- check if byte-addr is a know crc
1184 function check4MapCrc16(addr, tagMap)
1185 local res=false
1186 for i=1, #tagMap.crc16 do
1187 if (addr == tagMap.crc16[i]['pos']) then
1188 res=true
1191 return res
1195 -- check if byte is mapped or not
1196 function check4MappedByte(addr, tagMap)
1197 local res=false
1198 for _, v in pairs(tagMap.mappings) do
1199 if (addr >= v['start'] and addr <= v['end'] ) then
1200 res= true
1203 return res
1207 -- check if byte is highlighted or not
1208 function check4Highlight(addr, tagMap)
1209 local res = false
1210 for _, v in pairs(tagMap.mappings) do
1211 if (addr >= v['start'] and addr <= v['end'] ) then
1212 res = v['highlight']
1215 return res
1219 -- add interactive mapping
1220 function addMapping(tag, tagMap, x)
1221 if (type(x) ~= "number") then x = #tagMap.mappings + 1 end
1222 local bytes = tagToBytes(tag)
1223 local myMapping = {}
1224 myMapping['name'] = input(accyan.."enter Maping-Name:"..acoff, string.format("mapping %d", #tagMap.mappings+1))
1225 myMapping['start'] = tonumber(input(accyan.."enter start-addr:"..acoff, '1'), 10)
1226 myMapping['end'] = tonumber(input(accyan.."enter end-addr:"..acoff, #bytes), 10)
1227 myMapping['highlight'] = confirm("set highlighted")
1228 table.insert(tagMap.mappings, x, myMapping)
1229 return tagMap
1233 -- delete mapping
1234 function deleteMapping(tag, tagMap)
1235 if(#tagMap.mappings>0) then
1236 local d = selectTableEntry(tagMap.mappings, "select number of Mapping to remove:")
1237 if (type(d)=='number') then
1238 table.remove(tagMap.mappings, d)
1239 else oops("deleteMapping: got type = "..type(d).." - expected type = 'number'")
1242 return tagMap
1246 -- select a mapping from a tagmap
1247 function selectTableEntry(table, action)
1248 if (type(action) ~= "string") then action = "select number of item:" end
1249 for k, v in pairs(table) do
1250 print(accyan..k..acoff.."\t-> "..accyan..v['name']..acoff)
1252 local res = tonumber(input(action , 0), 10)
1253 if (istable(table[res])) then
1254 return res
1255 else
1256 return false
1261 -- map all segments
1262 function mapAllSegments(tag, tagMap)
1263 local bytes=tagToBytes(tag)
1264 local WRP,WRC,WRPC
1265 segs=getSegmentStats(bytes)
1266 if (istable(segs)) then
1267 for k, v in pairs(segs) do
1268 -- wrp (write proteted) = byte 2
1269 WRP = tonumber(bytes[v['start']+2],16)
1270 -- wrc (write control) - bit 4-6 of byte 3
1271 --WRC = tonumber(bbit("0x"..bytes[v['start']+3],4,3),16)
1272 WRC = (tonumber(bytes[v['start']+3],16) >> 4) & 0x07
1273 --tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." HDR", v['start'], v['start']+3)
1274 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." CRC", v['start']+4, v['start']+4, true)
1275 table.insert(tagMap.crc8, {name = 'Segment '..("%02d"):format(v['index']).." CRC", pos=v['start']+4, seq={1,4,v['start'],v['start']+3}} )
1276 if(WRC>WRP) then
1277 WRPC=WRC
1278 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
1279 elseif (WRP>WRC and WRC>0) then
1280 WRPC=WRP
1281 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
1282 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+WRC+5, v['start']+5+WRP-1, true)
1283 else
1284 WRPC=WRP
1285 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+5, v['start']+5+WRP-1, true)
1287 tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." data", v['start']+5+WRPC, v['end'], false)
1290 print(#segs.." Segments mapped")
1291 else
1292 oops("autoMapSegments failed: no Segments found")
1294 return tagMap
1298 -- map all token data
1299 function mapTokenData(tagMap, mname, mstart, mend, mhigh)
1300 --if ( not mhigh ) then mhigh=false end
1301 local myMapping = {}
1302 myMapping['name'] = mname
1303 myMapping['start'] = mstart
1304 myMapping['end'] = mend
1305 myMapping['highlight'] = mhigh
1306 table.insert(tagMap.mappings, myMapping)
1307 return tagMap
1311 -- map a map
1312 function mapTag(tagMap)
1313 tagMap=makeTagMap()
1314 tagMap=mapTokenData(tagMap, 'Tag-ID', 1, 4, true)
1315 tagMap=mapTokenData(tagMap, 'Tag-CRC', 5, 5, false)
1316 tagMap=mapTokenData(tagMap, 'DCF', 6, 7, true)
1317 tagMap=mapTokenData(tagMap, 'THDR-Raw/Stamp-Len', 8, 8, true)
1318 tagMap=mapTokenData(tagMap, 'SSC', 9, 9, true)
1319 tagMap=mapTokenData(tagMap, 'Header', 10, 13, false)
1320 tagMap=mapTokenData(tagMap, 'Backup', 14, 19, true)
1321 tagMap=mapTokenData(tagMap, 'Bck-CRC', 20, 20, false)
1322 tagMap=mapTokenData(tagMap, 'TokenTime', 21, 22, false)
1323 return tagMap
1326 --- Dump Data ---
1328 -- dump virtual Tag-Data
1329 function dumpTag(tag)
1330 local i, i2
1331 local res
1332 local dp=0
1333 local raw=""
1334 -- sytstem area
1335 res =acyellow.."\nCDF: System Area"..acoff
1336 res= res.."\n"..dumpCDF(tag)
1337 -- segments (user-token area)
1338 if(tag.Type=="SAM" and tag.raw=='9f') then
1339 res = res..acyellow.."\n\nADF: User Area"..acoff
1340 for i=0, #tag.SEG do
1341 res=res.."\n"..dumpSegment(tag, i).."\n"
1344 return res
1348 -- dump tag-system area
1349 function dumpCDF(tag)
1350 local res=""
1351 local i=0
1352 local raw=""
1353 local bytes
1354 if (istable(tag)) then
1355 res = res..accyan.."MCD: "..acoff..tag.MCD..accyan.." MSN: "..acoff..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..accyan.." MCC: "..acoff..tag.MCC.."\n"
1356 res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n"
1357 res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..((tag.raw=='9f') and (", SSC="..tag.SSC.."\n") or "\n")
1359 -- credential (end-user tag)
1360 if (tag.Type=="SAM" and tag.raw=='9f') then
1361 res = res.."Remaining Header Area\n"
1362 for i=0, (#tag.data) do
1363 res = res..tag.data[i].." "
1365 res = res.."\nBackup Area\n"
1366 for i=0, (#tag.Bck) do
1367 res = res..tag.Bck[i].." "
1369 res = res.."\nTime Area\n"
1370 for i=0, (#tag.MTC) do
1371 res = res..tag.MTC[i].." "
1375 -- Master Token specific
1376 elseif (tag.Type~="SAM") then
1377 res = res .."Master-Token Area\nStamp: "
1378 res= res..tag.SSC.." "
1379 for i=0, tag.Stamp_len-2 do
1380 res = res..tag.data[i].." "
1382 res=res.."\nunused payload\n"
1383 for i=0, (#tag.data-tag.Stamp_len-1) do
1384 res = res..tag.data[i].." "
1386 bytes=tagToBytes(tag)
1387 local mtcrc=calcMtCrc(bytes)
1388 res=res.."\nMaster-Token CRC: "
1389 res = res ..tag.MTC[1].." ("..((tag.MTC[1]==mtcrc) and "valid" or "error")..")"
1392 -- 'Gantner User-Credential' specific
1393 elseif (tag.Type=="SAM" and (tag.raw=='08' or tag.raw=='09')) then
1394 print(acgreen.."Gantner Detected"..acoff)
1397 return res
1398 else print(acred.."no valid Tag in dumpCDF"..acoff) end
1402 -- dump single segment
1403 function dumpSegment(tag, index)
1404 local i=index
1405 local i2
1406 local dp=0 --data-position in table
1407 local res="" --result
1408 local raw="" --raw-header
1409 -- segment
1410 if ( (istable(tag.SEG[i])) and tag.Type=="SAM" and tag.raw=="9f") then
1411 if (istable(tag.SEG[i].raw)) then
1412 for k,v in pairs(tag.SEG[i].raw) do
1413 raw=raw..v.." "
1417 -- segment header
1418 res = res..accyan.."Segment "..("%02d"):format(tag.SEG[i].index)..acoff..": "
1419 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).."), "
1420 res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", "
1421 res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." "
1422 res = res .."("..(checkSegmentCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
1423 raw=""
1426 -- WRC protected
1427 if ((tag.SEG[i].WRC>0)) then
1428 res = res .."\nWRC protected area:\n"
1429 for i2=dp, dp+tag.SEG[i].WRC-1 do
1430 res = res..tag.SEG[i].data[i2].." "
1431 dp=dp+1
1435 -- WRP mprotected
1436 if (tag.SEG[i].WRP>tag.SEG[i].WRC) then
1437 res = res .."\nRemaining write protected area:\n"
1438 for i2=dp, dp+(tag.SEG[i].WRP-tag.SEG[i].WRC)-1 do
1439 res = res..tag.SEG[i].data[i2].." "
1440 dp=dp+1
1444 -- payload
1445 if (#tag.SEG[i].data-dp>0) then
1446 res = res .."\nRemaining segment payload:\n"
1447 for i2=dp, #tag.SEG[i].data-2 do
1448 res = res..tag.SEG[i].data[dp].." "
1449 dp=dp+1
1451 if (tag.SEG[i].kgh) then
1452 res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
1453 else res = res..tag.SEG[i].data[dp] end
1455 dp=0
1456 return res
1457 else
1458 return print(acred.."Segment not found"..acoff)
1463 -- return bytes 'sstrat' to 'send' from a table
1464 function dumpTable(tab, header, tstart, tend)
1465 res=""
1466 for i=tstart, tend do
1467 res=res..tab[i].." "
1469 if (#header == 0) then
1470 return res
1471 else
1472 return (header.." #"..(tend-tstart+1).."\n"..res)
1477 -- dump 3rd Party Cash
1478 function dump3rdPartyCash1(tag , seg)
1479 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1480 local stamp=tag.SEG[seg].data[0].." "..tag.SEG[seg].data[1].." "..tag.SEG[seg].data[2]
1481 local datastamp=tag.SEG[seg].data[20].." "..tag.SEG[seg].data[21].." "..tag.SEG[seg].data[22]
1482 local balance=tonumber(tag.SEG[seg].data[32]..tag.SEG[seg].data[33] ,16)
1483 local balancecrc=utils.Crc8Legic(uid..tag.SEG[seg].data[32]..tag.SEG[seg].data[33])
1484 local mirror=tonumber(tag.SEG[seg].data[35]..tag.SEG[seg].data[36] ,16)
1485 local mirrorcrc=utils.Crc8Legic(uid..tag.SEG[seg].data[35]..tag.SEG[seg].data[36])
1486 local lastbal0=tonumber(tag.SEG[seg].data[39]..tag.SEG[seg].data[40] ,16)
1487 local lastbal1=tonumber(tag.SEG[seg].data[41]..tag.SEG[seg].data[42] ,16)
1488 local lastbal2=tonumber(tag.SEG[seg].data[43]..tag.SEG[seg].data[44] ,16)
1490 test=""
1491 -- display decoded/known stuff
1492 print("\n------------------------------")
1493 print("Tag-ID:\t\t "..uid)
1494 print("Stamp:\t\t "..stamp)
1495 print("UID-Mapping: \t\t"..("%06d"):format(tonumber(tag.SEG[seg].data[46]..tag.SEG[seg].data[47]..tag.SEG[seg].data[48], 16)))
1496 print("------------------------------")
1497 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])..")")
1498 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])..")")
1499 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])..")")
1501 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])..")")
1502 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])..")")
1503 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])..")")
1504 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])..")")
1505 print("------------------------------")
1506 print(string.format("Balance:\t\t %3.2f", balance/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[34])..")")
1507 print(string.format("Shadow:\t\t\t %3.2f", mirror/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[37])..")")
1508 print("------------------------------")
1509 print(string.format("History 1:\t\t %3.2f", lastbal0/100))
1510 print(string.format("History 2:\t\t %3.2f", lastbal1/100))
1511 print(string.format("History 3:\t\t %3.2f", lastbal2/100))
1512 print("------------------------------\n")
1516 -- dump Legic-Cash data
1517 function dumpLegicCash(tag, x)
1519 if istable(tag.SEG[x]) == false then return end
1521 io.write("in Segment "..tag.SEG[x].index.." :\n")
1522 print("--------------------------------\n\tLegic-Cash Values\n--------------------------------")
1523 local limit, curr, balance, rid, tcv
1524 -- currency of balance & limit
1525 curr=string.upper(tag.SEG[x].data[8]..tag.SEG[x].data[9])
1526 if currency[curr] ~= nil then
1527 curr = currency[curr]
1529 -- maximum balance
1530 limit=string.format("%4.2f", tonumber(tag.SEG[x].data[10]..tag.SEG[x].data[11]..tag.SEG[x].data[12], 16)/100)
1531 -- current balance
1532 balance=string.format("%4.2f", tonumber(tag.SEG[x].data[15]..tag.SEG[x].data[16]..tag.SEG[x].data[17], 16)/100)
1533 -- reader-id who wrote last transaction
1534 rid=tonumber(tag.SEG[x].data[18]..tag.SEG[x].data[19]..tag.SEG[x].data[20], 16)
1535 -- transaction counter value
1536 tcv=tonumber(tag.SEG[x].data[29], 16)
1537 print("Currency:\t\t "..curr)
1538 print("Limit:\t\t\t "..limit)
1539 print("Balance:\t\t "..balance)
1540 print("Transaction Counter:\t "..tcv)
1541 print("Reader-ID:\t\t "..rid.."\n--------------------------------\n")
1545 -- raw 3rd-party
1546 function print3rdPartyCash1(tag, x)
1548 if istable(tag.SEG[x]) == false then return end
1550 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1551 print("\n\t\tStamp : "..dumpTable(tag.SEG[x].data, "", 0 , 2))
1552 print("\t\tBlock 0: "..dumpTable(tag.SEG[x].data, "", 3 , 18))
1553 print()
1554 print("\t\tBlock 1: "..dumpTable(tag.SEG[x].data, "", 19, 30))
1555 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])..")")
1556 print()
1557 print("\t\tBlock 2: "..dumpTable(tag.SEG[x].data, "", 32, 33))
1558 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])..")")
1559 print()
1560 print("\t\tBlock 3: "..dumpTable(tag.SEG[x].data, "", 35, 36))
1561 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])..")")
1562 print()
1563 print("\t\tyet unknown: "..tag.SEG[x].data[38])
1564 print()
1565 print("\t\tHisatory 1: "..dumpTable(tag.SEG[x].data, "", 39, 40))
1566 print("\t\tHisatory 2: "..dumpTable(tag.SEG[x].data, "", 41, 42))
1567 print("\t\tHisatory 3: "..dumpTable(tag.SEG[x].data, "", 43, 44))
1568 print()
1569 print("\t\tyet unknown: "..tag.SEG[x].data[45])
1570 print()
1571 print("\t\tKGH-UID HEX: "..dumpTable(tag.SEG[x].data, "", 46, 48))
1572 print("\t\tBlock 4: "..dumpTable(tag.SEG[x].data, "", 49, 54))
1573 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])..")")
1574 print()
1575 print("\t\tBlock 5: "..dumpTable(tag.SEG[x].data, "", 56, 61))
1576 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])..")")
1577 print()
1578 print("\t\tBlock 6: "..dumpTable(tag.SEG[x].data, "", 63, 72))
1579 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])..")")
1580 print()
1581 print("\t\tBlock 7: "..dumpTable(tag.SEG[x].data, "", 74, 88))
1582 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])..")")
1583 print()
1584 print("\t\tBlock 8: "..dumpTable(tag.SEG[x].data, "", 90, 94))
1587 --- Token related --
1589 -- make token
1590 function makeToken()
1591 local mt={
1592 ['Type'] = {"SAM", "SAM63", "SAM64", "IAM", "GAM"},
1593 ['DCF'] = {"60ea", "31fa", "30fa", "80fa", "f0fa"},
1594 ['WRP'] = {"15", "2", "2", "2", "2"},
1595 ['WRC'] = {"01", "02", "02", "00", "00"},
1596 ['RD'] = {"01", "00", "00", "00", "00"},
1597 ['Stamp'] = {"00", "00", "00", "00", "00"},
1598 ['Segment'] = {"0d", "c0", "04", "00", "be", "01", "02", "03", "04", "01", "02", "03", "04"}
1600 ttype=""
1601 for k, v in pairs(mt.Type) do
1602 ttype = ttype..k..") "..v.." "
1604 mtq = tonumber(input("select number for Token-Type\n"..ttype, '1'), 10)
1605 if (type(mtq) ~= "number") then return print("selection invalid!")
1606 elseif (mtq > #mt.Type) then return print("selection invalid!")
1607 else print("Token-Type '"..mt.Type[mtq].."' selected") end
1608 local raw = calcHeaderRaw(mt.WRP[mtq], mt.WRC[mtq], mt.RD[mtq])
1609 local mtCRC = "00"
1611 bytes = {"01", "02", "03", "04", "cb", string.sub(mt.DCF[mtq], 0, 2), string.sub(mt.DCF[mtq], 3), raw,
1612 "00", "00", "00", "00", "00", "00", "00", "00",
1613 "00", "00", "00", "00", "00", "00"}
1614 if (mtq == 1) then
1615 for i = 0, #mt.Segment do
1616 table.insert(bytes, mt.Segment[i])
1618 bytes[9] = "ff"
1620 -- fill bytes
1621 for i = #bytes, 1023 do table.insert(bytes, "00") end
1623 -- if Master-Token -> calc Master-Token-CRC
1624 if (mtq>1) then bytes[22] = calcMtCrc(bytes) end
1626 local tempTag = createTagTable()
1627 -- remove segment if MasterToken
1628 if (mtq>1) then tempTag.SEG[0] = nil end
1630 return bytesToTag(bytes, tempTag)
1634 -- edit token-data
1635 function editTag(tag)
1636 -- for simulation it makes sense to edit everything
1637 local edit_sim = "MCD MSN0 MSN1 MSN2 MCC DCFl DCFh WRP WRC RD"
1638 -- on real tags it makes only sense to edit DCF, WRP, WRC, RD
1639 local edit_real = "DCFl DCFh WRP WRC RD"
1640 if (confirm(acyellow.."do you want to edit non-writeable values (e.g. for simulation)?"..acoff)) then
1641 edit_tag = edit_sim
1642 else edit_tag = edit_real end
1644 if(istable(tag)) then
1645 for k,v in pairs(tag) do
1646 if(type(v) ~= "table" and type(v) ~= "boolean" and string.find(edit_tag, k)) then
1647 tag[k] = input("value for: "..accyan..k..acoff, v)
1651 if (tag.Type == "SAM") then ttype = "Header"; else ttype = "Stamp"; end
1652 if (confirm(acyellow.."do you want to edit "..ttype.." Data?"..acoff)) then
1653 -- master-token specific
1654 if(istable(tag.Bck) == false) then
1655 -- stamp-data length=(0xfc-DCFh)
1656 -- on MT: SSC holds the Starting Stamp Character (Stamp0)
1657 tag.SSC=input(ttype.."0: ", tag.SSC)
1658 -- rest of stamp-bytes are in tag.data 0..n
1659 for i=0, (tonumber(0xfc ,10)-("%d"):format('0x'..tag.DCFh))-2 do
1660 tag.data[i] = input(ttype.. i+1 ..": ", tag.data[i])
1662 else
1663 --- on credentials byte7 should always be 9f and byte8 ff
1664 -- on Master-Token not (even on SAM63/64 not)
1665 -- tag.SSC=input(ttype.."0: ", tag.SSC)
1666 for i=0, #tag.data do
1667 tag.data[i] = input(ttype.. i ..": ", tag.data[i])
1672 bytes = tagToBytes(tag)
1674 --- check data-consistency (calculate tag.raw)
1675 bytes[8] = calcHeaderRaw(tag.WRP, tag.WRC, tag.RD)
1677 --- Master-Token specific
1678 -- should be triggered if a SAM was converted to a non-SAM (user-Token to Master-Token)
1679 -- or a Master-Token has being edited (also SAM64 & SAM63 - which are in fact Master-Token)
1680 if(tag.Type ~= "SAM" or bytes[6]..bytes[7] ~= "60ea") then
1681 -- calc new Master-Token crc
1682 bytes[22] = calcMtCrc(bytes)
1683 else
1684 -- ensure tag.SSC set to 'ff' on credential-token (SAM)
1685 bytes[9] = 'ff'
1686 -- if a Master-Token was converted to a Credential-Token
1687 -- lets unset the Time-Area to 00 00 (will contain Stamp-Data on MT)
1688 bytes[21] = '00'
1689 bytes[22] = '00'
1692 tag = bytesToTag(bytes, tag)
1697 -- calculates header-byte (addr 0x07)
1698 function calcHeaderRaw(wrp, wrc, rd)
1699 wrp = ("%02x"):format(tonumber(wrp, 10))
1700 rd = tonumber(rd, 16)
1701 local res = ("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0))
1702 return res
1706 -- determine TagType (bits 0..6 of DCFlow)
1707 function getTokenType(DCFl)
1708 --[[
1709 0x00–0x2f IAM
1710 0x30–0x6f SAM
1711 0x70–0x7f GAM
1712 ]]--
1713 --local tt = bbit("0x"..DCFl,0,7)
1714 local tt = tonumber(DCFl, 16) & 0x7F
1715 if (tt >= 0 and tt <= 47) then tt = "IAM"
1716 elseif (tt == 49) then tt = "SAM63"
1717 elseif (tt == 48) then tt = "SAM64"
1718 elseif (tt >= 50 and tt <= 111) then tt = "SAM"
1719 elseif (tt >= 112 and tt <= 127) then tt = "GAM"
1720 else tt = "???" end
1721 return tt
1725 -- clear beackup-area of a virtual tag
1726 function clearBackupArea(tag)
1727 for i=1, #tag.Bck do
1728 tag.Bck[i]='00'
1730 return tag
1733 --- Segment related --
1735 -- get segmemnt-data from byte-table
1736 function getSegmentData(bytes, start, index)
1737 local segment={
1738 ['index'] = '00',
1739 ['flag'] = 'c',
1740 ['valid'] = 0,
1741 ['last'] = 0,
1742 ['len'] = 13,
1743 ['raw'] = {'00', '00', '00', '00'},
1744 ['WRP'] = 4,
1745 ['WRC'] = 0,
1746 ['RD'] = 0,
1747 ['crc'] = '00',
1748 ['data'] = {},
1749 ['kgh'] = false
1751 if (bytes[start]) then
1752 local i
1753 -- #index
1754 segment.index = index
1755 -- flag = high nibble of byte 1
1756 segment.flag = string.sub(bytes[start+1],0,1)
1757 -- valid = bit 6 of byte 1
1758 --segment.valid = bbit("0x"..bytes[start+1],6,1)
1759 segment.valid = (tonumber(bytes[start+1], 16) >> 6) & 0x01
1760 -- last = bit 7 of byte 1
1761 --segment.last = bbit("0x"..bytes[start+1],7,1)
1762 segment.last = (tonumber(bytes[start+1], 16) >> 7) & 0x01
1763 -- len = (byte 0)+(bit0-3 of byte 1)
1764 --segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16)
1765 segment.len = ((tonumber(bytes[start+1], 16) & 0x0F) << 8) + tonumber(bytes[start], 16)
1766 -- raw segment header
1767 segment.raw = {padString(bytes[start]), padString(bytes[start+1]), padString(bytes[start+2]), padString(bytes[start+3])}
1768 -- wrp (write proteted) = byte 2
1769 segment.WRP = tonumber(bytes[start+2],16)
1770 -- wrc (write control) - bit 4-6 of byte 3
1771 --segment.WRC = bbit("0x"..bytes[start+3],4,3)
1772 segment.WRC = (tonumber(bytes[start+3], 16) >> 4) & 0x07
1773 -- rd (read disabled) - bit 7 of byte 3
1774 --segment.RD = bbit("0x"..bytes[start+3],7,1)
1775 segment.RD = (tonumber(bytes[start+3],16) >> 7) & 0x01
1776 -- crc byte 4
1777 segment.crc = padString(bytes[start+4])
1778 -- segment-data starts at segment.len - segment.header - segment.crc
1779 for i=0, (segment.len-5) do
1780 segment.data[i]=padString(bytes[start+5+i])
1782 return segment
1783 else return false;
1788 -- get index, start-aadr, length and content
1789 function getSegmentStats(bytes)
1790 local sStats = {}
1791 local sValid, sLast, sLen, sStart, x
1792 sStart=23
1794 repeat
1795 local s={}
1796 -- valid = bit 6 of byte 1
1797 -- sValid = bbit("0x"..bytes[sStart+1],6,1)
1798 sValid = (tonumber(bytes[sStart+1],16) >> 6) & 0x01
1799 -- last = bit 7 of byte 1
1800 --sLast = bbit("0x"..bytes[sStart+1],7,1)
1801 sLast = (tonumber(bytes[sStart+1],16) >> 7) & 0x01
1802 -- len = (byte 0)+(bit0-3 of byte 1)
1803 --sLen = tonumber(bbit("0x"..bytes[sStart+1],0,4)..bytes[sStart],16)
1804 sLen = ((tonumber(bytes[start+1],16) & 0x0F) << 8) + tonumber(bytes[start],16)
1805 --print("index: "..("%02d"):format(x).." Len: "..sLen.." start:"..sStart.." end: "..(sStart+sLen-1))
1806 s['index']=x
1807 s['start']=sStart
1808 s['end']=sStart+sLen-1
1809 s['len']=sLen
1810 if ( (sStart+sLen-1)>sStart ) then
1811 table.insert(sStats, s)
1813 sStart=sStart+sLen
1814 x=x+1
1815 until (sLast==1 or sValid==0 or x==126)
1816 if (#sStats>0 ) then return sStats
1817 else return false; end
1821 -- regenerate segment-header (after edit)
1822 function regenSegmentHeader(segment)
1823 local seg=segment
1824 local raw = segment.raw
1825 local i
1826 -- len bit0..7 | len=12bit=low nibble of byte1..byte0
1827 --raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8))
1828 raw[1] = seg.len & 0xFF
1829 -- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh?
1830 --raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),8,4)+bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8))
1831 raW[2] = (seg.len >> 8) & 0xF | (seg.valid >> 6) | (seg.last >> 7)
1832 -- WRP
1833 -- raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8))
1834 raw[3] = seg.WRP
1835 -- WRC + RD
1836 raw[4]=("%02x"):format(tonumber((seg.WRC*16)+(seg.RD*128),10))
1837 -- flag
1838 seg.flag=string.sub(raw[2],0,1)
1839 --print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4])
1840 if(#seg.data>(seg.len-5)) then
1841 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
1842 print(acyellow.."Data-Length has being reduced:"..acgreen.." auto-removing "..acyellow.. #seg.data-(seg.len-5) ..acgreen .." bytes from Payload!"..acoff);
1843 for i=(seg.len-5), #seg.data-1 do
1844 table.remove(seg.data)
1846 elseif (#seg.data<(seg.len-5)) then
1847 print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
1848 print(acyellow.."Data-Length has being extended:"..acgreen.." auto-adding "..acyellow..(seg.len-5)-#seg.data ..acgreen .." bytes to Payload!"..acoff);
1849 for i=#seg.data, (seg.len-5-1) do
1850 table.insert(seg.data, '00')
1853 return seg
1857 -- edit segment helper
1858 function editSegment(tag, index)
1859 local k,v
1860 local edit_possible="valid len RD WRP WRC Stamp Payload"
1861 if (istable(tag.SEG[index])) then
1862 for k,v in pairs(tag.SEG[index]) do
1863 if(string.find(edit_possible,k)) then
1864 tag.SEG[index][k]=tonumber(input(accyan..k..acoff..": ", v),10)
1867 else print("Segment with Index: "..("%02d"):format(index).." not found in Tag")
1868 return false
1870 regenSegmentHeader(tag.SEG[index])
1871 print("\n"..dumpSegment(tag, index).."\n")
1872 return tag
1876 -- edit Segment Data
1877 function editSegmentData(data, uid)
1878 io.write("\n")
1879 if istable(data) == false then print("no Segment-Data found") end
1881 local lc = check4LegicCash(data, uid)
1883 for i=0, #data-1 do
1884 data[i]=input(accyan.."Data"..i..acoff..": ", data[i])
1886 if (lc) then
1887 data = fixLegicCash(data, uid)
1889 return data
1893 -- list available segmets in virtual tag
1894 function segmentList(tag)
1895 local i
1896 local res = ""
1897 if (istable(tag.SEG[0])) then
1898 for i=0, #tag.SEG do
1899 res = res .. tag.SEG[i].index .. " "
1901 return res
1902 else print("no Segments found in Tag")
1903 return false
1908 -- helper to selecting a segment
1909 function selectSegment(tag)
1910 local sel
1911 if (istable(tag.SEG[0])) then
1912 print("availabe Segments:\n"..segmentList(tag))
1913 sel=input("select Segment: ", '00')
1914 sel=tonumber(sel,10)
1915 if (sel) then return sel
1916 else return '0' end
1917 else
1918 print("\nno Segments found")
1919 return false
1924 -- add segment
1925 function addSegment(tag)
1926 local i
1927 local segment={
1928 ['index'] = '00',
1929 ['flag'] = 'c',
1930 ['valid'] = 1,
1931 ['last'] = 1,
1932 ['len'] = 13,
1933 ['raw'] = {'0d', '40', '04', '00'},
1934 ['WRP'] = 4,
1935 ['WRC'] = 0,
1936 ['RD'] = 0,
1937 ['crc'] = '00',
1938 ['data'] = {},
1939 ['kgh'] = false
1941 if (istable(tag.SEG[0])) then
1942 tag.SEG[#tag.SEG].last=0
1943 table.insert(tag.SEG, segment)
1944 for i=0, 8 do
1945 tag.SEG[#tag.SEG].data[i]=("%02x"):format(i)
1947 tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG)
1948 return tag
1949 else
1950 print("no Segment-Table found")
1955 -- get only the stamp-bytes of a segment
1956 function getSegmentStamp(seg, bytes)
1957 local stamp=""
1958 local stamp_len=7
1959 --- the 'real' stamp on MIM is not really easy to tell for me since the 'data-block' covers stamp0..stampn+data0..datan
1960 -- there a no stamps longer than 7 bytes & they are write-protected by default , and I have not seen user-credntials
1961 -- with stamps smaller 3 bytes (except: Master-Token)
1962 -- WRP -> Read/Write Protection
1963 -- WRC -> Read/Write Condition
1964 -- RD depends on WRC - if WRC > 0 and RD=1: only reader with matching #WRC of Stamp-bytes in their Database have Read-Access to the Tag
1965 if (seg.WRP<7) then stamp_len=(seg.WRP) end
1966 for i=1, (stamp_len) do
1967 stamp=stamp..seg.data[i-1]
1969 if (bytes) then
1970 stamp=str2bytes(stamp)
1971 return stamp
1972 else return stamp end
1976 -- edit stamp of a segment
1977 function editStamp(new_stamp, uid, data)
1978 local stamp=str2bytes(new_stamp)
1979 for i=0, #stamp-1 do
1980 data[i]=stamp[i+1]
1982 -- now fill in stamp
1983 for i=0, (string.len(new_stamp)/2)-1 do
1984 data[i]=stamp[i+1]
1986 return fix3rdPartyCash1(uid, data)
1990 -- autoselect special/known segments
1991 function autoSelectSegment(tag, s)
1992 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
1993 local x=#tag.SEG+1
1994 local res = false
1995 io.write("autoSelect ")
1996 --- search for desired segment-type
1997 -- 3rd Party Segment
1998 if (s=="3rdparty") then
1999 repeat
2000 io.write(". ")
2001 x=x-1
2002 res=check43rdPartyCash1(uid, tag.SEG[x].data)
2003 until ( res or x==0 )
2005 -- Legic-Cash Segment
2006 if (s=="legiccash") then
2007 repeat
2008 io.write(". ")
2009 x=x-1
2010 res=check4LegicCash(tag.SEG[x].data, uid)
2011 until ( res or x==0 )
2014 -- segment found
2015 if (res) then
2016 io.write("\nautoselected Index: "..string.format("%02d", x).."\n")
2017 return x
2020 -- nothing found
2021 io.write(acyellow.."no Segment found\n"..acoff)
2022 return -1
2026 -- delete segment (except segment 00)
2027 function delSegment(tag, index)
2028 if (istable(tag.SEG[0])) then
2029 local i
2030 if (type(index)=="string") then index=tonumber(index,10) end
2031 if (index > 0) then
2032 table.remove(tag.SEG, index)
2033 for i=0, #tag.SEG do
2034 tag.SEG[i].index=("%02d"):format(i)
2037 if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end
2038 return tag
2043 -- edit uid 3rd party cash
2044 function edit3rdUid(mapid, uid, data)
2045 mapid=("%06x"):format(tonumber(mapid, 10))
2046 data[46]=string.sub(mapid, 1 ,2)
2047 data[47]=string.sub(mapid, 3 ,4)
2048 data[48]=string.sub(mapid, 5 ,6)
2049 return fix3rdPartyCash1(uid, data)
2053 -- edit balance 3rd party cash
2054 function edit3rdCash(new_cash, uid, data)
2055 new_cash=("%04x"):format(new_cash)
2056 data[32]=string.sub(new_cash, 0,2)
2057 data[33]=string.sub(new_cash, 3,4)
2058 data[34]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
2059 data[35]=string.sub(new_cash, 0,2)
2060 data[36]=string.sub(new_cash, 3,4)
2061 data[37]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
2062 data[39]=string.sub(new_cash, 0,2)
2063 data[40]=string.sub(new_cash, 3,4)
2064 data[41]='00'
2065 data[42]='00'
2066 data[43]='00'
2067 data[44]='00'
2068 return fix3rdPartyCash1(uid, data)
2072 -- edit 3rd-party cash
2073 function edit3rdPartyCash1(tag, x)
2074 local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
2075 if (confirm("\nedit Balance?")) then
2076 local new_cash=input("enter new Balance≤\nwithout comma and without currency-sign! (0-65535)", "100")
2077 tag.SEG[x].data=edit3rdCash(new_cash, uid, tag.SEG[x].data)
2079 -- change User-ID (used for online-account-mapping)
2080 if (confirm("\nedit UserID-Mapping?")) then
2081 local new_mapid=input("enter new UserID (6-digit value)", "012345")
2082 tag.SEG[x].data=edit3rdUid(new_mapid, uid, tag.SEG[x].data)
2084 if (confirm("\nedit Stamp?")) then
2085 local new_stamp=input("enter new Stamp", getSegmentStamp(tag.SEG[x]))
2086 tag.SEG[x].data=editStamp(new_stamp, uid, tag.SEG[x].data)
2087 new_stamp=getSegmentStamp(tag.SEG[x], 'true')
2088 print("stamp_bytes: "..#new_stamp)
2089 -- replace stamp in 'block 1' also
2090 io.write("editing stamp in Block 1 also ")
2091 for i=20, (20+#new_stamp-1) do
2092 tag.SEG[x].data[i]=new_stamp[i-19]
2093 io.write(".");
2095 print(" done")
2096 -- fix known checksums
2097 tag.SEG[x].data=fix3rdPartyCash1(uid, tag.SEG[x].data)
2099 return tag
2103 -- edit Legic Cash
2104 function editLegicCash(data, uid)
2105 local limit, curr, balance, rid, tcv
2106 -- currency of balance & limit
2107 curr=currency[data[8]..data[9]]
2108 -- maximum balance
2109 limit=string.format("%4.2f", tonumber(data[10]..data[11]..data[12], 16)/100)
2110 -- current balance
2111 balance=string.format("%4.2f", tonumber(data[15]..data[16]..data[17], 16)/100)
2112 -- reader-id who wrote last transaction
2113 rid=tonumber(data[18]..data[19]..data[20], 16)
2114 -- transaction counter value
2115 tcv=tonumber(data[29], 16)
2117 -- edit currency
2118 if (confirm(accyan.."change Currency?"..acoff)) then
2119 for k, v in pairs(currency) do io.write(k .. " = " .. v .. "\n") end
2120 curr=input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[8]..data[9])
2121 data[8]=string.sub(curr, 1, 2)
2122 data[9]=string.sub(curr, 3, 4)
2125 -- edit limit
2126 if (confirm(accyan.."change Limit?"..acoff)) then
2127 limit=string.format("%06x", input(accyan.."enter the Decimal for the new Limit:"..acoff, limit))
2128 data[10]=string.sub(limit, 1, 2)
2129 data[11]=string.sub(limit, 3, 4)
2130 data[12]=string.sub(limit, 5, 6)
2133 -- edit balance
2134 if (confirm(accyan.."change Balance?"..acoff)) then
2135 balance=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, balance))
2136 print("Balance: "..balance)
2137 data[15]=string.sub(balance, 1, 2)
2138 data[16]=string.sub(balance, 3, 4)
2139 data[17]=string.sub(balance, 5, 6)
2142 -- edit transaction-counter
2143 if (confirm(accyan.."change Transaction-Counter?"..acoff)) then
2144 tcv=string.format("%02x", input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[29]))
2145 data[29]=tcv
2148 -- edit reader.id
2149 if (confirm(accyan.."change Last-Reader-ID?"..acoff)) then
2150 rid=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, rid))
2151 print("Balance: "..balance)
2152 data[18]=string.sub(rid, 1, 2)
2153 data[19]=string.sub(rid, 3, 4)
2154 data[20]=string.sub(rid, 5, 6)
2157 return fixLegicCash(data, uid)
2161 -- chack for signature of a 'Legic-Cash-Segment'
2162 function check4LegicCash(data, uid)
2164 print("### #data: " .. #data)
2165 if(#data==32) then
2166 local stamp_len=(#data-25)
2167 local stamp=""
2168 for i=0, stamp_len-1 do
2169 stamp=stamp..data[i].." "
2171 if (data[7]=="01") then
2172 if (("%04x"):format(utils.Crc16Legic(dumpTable(data, "", 0, 12), uid)) == data[13]..data[14]) then
2173 if (("%04x"):format(utils.Crc16Legic(dumpTable(data, "", 15, 20), uid)) == data[21]..data[22]) then
2174 if (("%04x"):format(utils.Crc16Legic(dumpTable(data, "", 23, 29), uid)) == data[30]..data[31]) then
2175 io.write(accyan.."Legic-Cash Segment detected "..acoff)
2176 return true
2182 return false
2186 -- chack for signature of a '3rd Party Cash-Segment' - not all bytes know until yet !!
2187 function check43rdPartyCash1(uid, data)
2188 if(#data==95) then
2189 -- too explicit checking will avoid fixing ;-)
2190 if (string.find(compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)), data[31]),"valid")) then
2191 --if (compareCrc(utils.Crc8Legic(uid..data[32]..data[33]), data[34])=="valid") then
2192 --if (compareCrc(utils.Crc8Legic(uid..data[35]..data[36]), data[37])=="valid") then
2193 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)), data[62])=="valid") then
2194 --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)), data[89])=="valid") then
2195 io.write(accyan.."3rd Party Cash-Segment detected "..acoff)
2196 return true
2197 --end
2198 --end
2199 --end
2200 --end
2203 return false
2206 --- CRC related ---
2208 -- build segmentCrc credentials
2209 function segmentCrcCredentials(tag, segid)
2210 if (istable(tag.SEG[0])) then
2211 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
2212 cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4]
2213 return cred
2214 else return print(acyellow.."Master-Token / unsegmented Tag!"..acoff) end
2218 -- build kghCrc credentials
2219 function kghCrcCredentials(tag, segid)
2220 if (istable(tag) and istable(tag.SEG[0])) then
2221 local x='00'
2222 if (type(segid)=="string") then segid=tonumber(segid,10) end
2223 if (segid>0) then x='93' end
2224 local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP)
2225 cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x
2226 for i=0, #tag.SEG[segid].data-2 do
2227 cred = cred..tag.SEG[segid].data[i]
2229 return cred
2234 -- compare two bytes
2235 function compareCrc(calc, guess)
2236 calc=("%02x"):format(calc)
2237 if (calc==guess) then return acgreen.."valid"..acoff
2238 else return acred.."error "..acoff..calc.."!="..guess end
2242 -- compare 4 bytes
2243 function compareCrc16(calc, guess)
2244 calc=("%04x"):format(calc)
2245 if (calc==guess) then return acgreen.."valid"..acoff
2246 else return acred.."error "..acoff..calc.."!="..guess end
2250 -- repair / fix crc's of a 'Legic-Cash-Segment'
2251 function fixLegicCash(data, uid)
2252 if(#data==32 and data[7]=="01") then
2253 local crc1, crc2, crc3
2254 -- set shadow-balance equal to balance
2255 data[23]=data[15]
2256 data[24]=data[16]
2257 data[25]=data[17]
2258 -- set shadow-last-reader to last-reader
2259 data[26]=data[18]
2260 data[27]=data[19]
2261 data[28]=data[20]
2262 -- calculate all crc's
2263 crc1=("%04x"):format(utils.Crc16Legic(dumpTable(data, "", 0, 12), uid))
2264 crc2=("%04x"):format(utils.Crc16Legic(dumpTable(data, "", 15, 20), uid))
2265 crc3=("%04x"):format(utils.Crc16Legic(dumpTable(data, "", 23, 29), uid))
2266 -- set crc's
2267 data[13]=string.sub(crc1, 1, 2)
2268 data[14]=string.sub(crc1, 3, 4)
2269 data[21]=string.sub(crc2, 1, 2)
2270 data[22]=string.sub(crc2, 3, 4)
2271 data[30]=string.sub(crc3, 1, 2)
2272 data[31]=string.sub(crc3, 3, 4)
2273 return data
2278 -- repair / fix (yet known) crc's of a '3rd Party Cash-Segment' - not all bytes know until yet !!
2279 function fix3rdPartyCash1(uid, data)
2280 if(#data==95) then
2281 -- checksum 1
2282 data[31]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)))
2283 -- checksum 2
2284 data[34]=("%02x"):format(utils.Crc8Legic(uid..data[32]..data[33]))
2285 -- checksum 3
2286 data[37]=("%02x"):format(utils.Crc8Legic(uid..data[35]..data[36]))
2287 -- checksum 4
2288 data[55]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 46, 54)))
2289 -- checksum 5
2290 data[62]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)))
2291 -- checksum 6
2292 data[73]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 63, 72)))
2293 -- checksum 7
2294 data[89]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)))
2295 return data
2300 -- calculate Master-Token crc
2301 function calcMtCrc(bytes)
2302 --print(#bytes)
2303 local cmd=bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[7]..bytes[6]..bytes[8]
2304 local len=(tonumber(0xfc ,10)-("%d"):format('0x'..bytes[7]))
2305 for i=1, len do
2306 cmd=cmd..bytes[8+i]
2308 local res=("%02x"):format(utils.Crc8Legic(cmd))
2309 return res
2313 -- calculate segmentCRC for a given segment
2314 function calcSegmentCrc(tag, segid)
2315 if (istable(tag.SEG[0])) then
2316 -- check if a 'Kaber Group Header' exists
2317 local data=segmentCrcCredentials(tag, segid)
2318 return ("%02x"):format(utils.Crc8Legic(data))
2323 -- calcuate kghCRC for a given segment
2324 function calcKghCrc(tag, segid)
2325 if (istable(tag.SEG[0])) then
2326 -- check if a 'Kaber Group Header' exists
2327 local i
2328 local data=kghCrcCredentials(tag, segid)
2329 return ("%02x"):format(utils.Crc8Legic(data))
2334 -- check all segment-crc
2335 function checkAllSegCrc(tag)
2336 if (istable(tag.SEG[0])) then
2337 for i=0, #tag.SEG do
2338 crc=calcSegmentCrc(tag, i)
2339 tag.SEG[i].crc=crc
2341 else return print(acyellow.."Master-Token / unsegmented Tag"..acoff) end
2345 -- check all segmnet-crc
2346 function checkAllKghCrc(tag)
2347 if (istable(tag.SEG[0])) then
2348 for i=0, #tag.SEG do
2349 crc=calcKghCrc(tag, i)
2350 if (tag.SEG[i].kgh) then
2351 tag.SEG[i].data[#tag.SEG[i].data-1]=crc
2358 -- validate segmentCRC for a given segment
2359 function checkSegmentCrc(tag, segid)
2360 local data=segmentCrcCredentials(tag, segid)
2361 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then
2362 return true
2364 return false
2368 -- validate kghCRC to segment in tag-table
2369 function checkKghCrc(tag, segid)
2370 if (type(tag.SEG[segid])=='table') then
2371 if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then
2372 local data=kghCrcCredentials(tag, segid)
2373 if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end
2374 else return false; end
2375 else oops(acred.."'Kaba Group header' detected but no Segment-Data found"..ansocolors.reset) end
2379 -- helptext for modify-mode
2380 function modifyHelp()
2381 local t=[[
2383 Data I/O Segment Manipulation Token-Data
2384 ----------------- -------------------- ---------------------
2385 ]]..acy..[[rt]]..acr..[[ => read Tag ]]..acy..[[as]]..acr..[[ => add Segment ]]..acy..[[mt]]..acr..[[ => make Token
2386 ]]..acy..[[wt]]..acr..[[ => write Tag ]]..acy..[[es]]..acr..[[ => edit Segment Header ]]..acy..[[et]]..acr..[[ => edit Token data
2387 ]]..acy..[[ed]]..acr..[[ => edit Segment Data ]]..acy..[[tk]]..acr..[[ => toggle KGH-Flag
2388 File I/O ]]..acy..[[rs]]..acr..[[ => remove Segment
2389 ----------------- ]]..acy..[[cc]]..acr..[[ => check Segment-CRC
2390 ]]..acy..[[lf]]..acr..[[ => load bin File ]]..acy..[[ck]]..acr..[[ => check KGH
2391 ]]..acy..[[sf]]..acr..[[ => save eml/bin File ]]..acy..[[ds]]..acr..[[ => dump Segments
2392 ]]..acy..[[xf]]..acr..[[ => xor to File
2395 Virtual Tags tagMap (partial) known Segments
2396 -------------------------------- --------------------- ---------------------------
2397 ]]..acy..[[ct]]..acr..[[ => copy mainTag to backupTag ]]..acy..[[mm]]..acr..[[ => make (new) Map ]]..acy..[[dlc]]..acr..[[ => dump Legic-Cash
2398 ]]..acy..[[tc]]..acr..[[ => copy backupTag to mainTag ]]..acy..[[em]]..acr..[[ => edit Map submenu ]]..acy..[[elc]]..acr..[[ => edit Legic-Cash
2399 ]]..acy..[[tt]]..acr..[[ => switch mainTag & backupTag ]]..acy..[[lm]]..acr..[[ => load map from file ]]..acy..[[d3p]]..acr..[[ => dump 3rd-Party-Cash
2400 ]]..acy..[[di]]..acr..[[ => dump mainTag ]]..acy..[[sm]]..acr..[[ => save map to file ]]..acy..[[e3p]]..acr..[[ => edit 3rd-Party-Cash
2401 ]]..acy..[[do]]..acr..[[ => dump backupTag
2403 ]]..acy..[[h]]..acr..[[ => this help ]]..acy..[[q]]..acr..[[ => quit
2405 return t
2409 -- modify Tag (interactive)
2410 function modifyMode()
2411 local i, backupTAG, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes, tagMap
2413 actions = {
2415 -- helptext
2416 ["h"] = function(x)
2417 print(" Version: "..acgreen..version..acr);
2418 print(modifyHelp())
2419 print("\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 ""))
2420 print("")
2421 end,
2423 -- read real Tag with PM3 into virtual 'mainTAG'
2424 ["rt"] = function(x)
2425 inTAG=readFromPM3();
2426 --actions.di()
2427 end,
2429 -- write content of virtual 'mainTAG' to real Tag with PM3
2430 ["wt"] = function(x)
2431 writeToTag(inTAG)
2432 end,
2434 -- copy mainTAG to backupTAG
2435 ["ct"] = function(x)
2436 print(accyan.."copy mainTAG to backupTAG"..acoff)
2437 backupTAG=deepCopy(inTAG)
2438 end,
2440 -- copy backupTAG to mainTAG
2441 ["tc"] = function(x)
2442 print(accyan.."copy backupTAG to mainTAG"..acoff)
2443 inTAG=deepCopy(backupTAG)
2444 end,
2446 -- toggle between mainTAG and backupTAG
2447 ["tt"] = function(x)
2448 -- copy main to temp
2449 outTAG=deepCopy(inTAG)
2450 -- copy backup to main
2451 inTAG=deepCopy(backupTAG)
2452 print(accyan.."toggle to "..accyan..((currentTag=='inTAG') and "backupTAG" or "mainTAG")..acoff)
2453 if(currentTag=="inTAG") then currentTag='backupTAG'
2454 else currentTag='inTAG' end
2455 -- copy temp (main) to backup
2456 backupTAG=deepCopy(outTAG)
2457 end,
2459 -- load file into mainTAG
2460 ["lf"] = function(x)
2461 if (x and not x=="" and type(x)=='string' and file_check(x)) then
2462 filename = x
2463 else
2464 filename = input("enter filename: ", "legic.temp")
2466 inTAG=readFile(filename)
2467 -- check for existing tagMap
2468 local res, path = file_check(filename..".map")
2469 if res then
2470 if(confirm(accyan.."Mapping-File for "..acoff..path..accyan.." found - load it also?"..acoff)) then
2471 tagMap=loadTagMap(filename..".map")
2474 end,
2476 -- save values of mainTAG to a file (xored with MCC of mainTAG)
2477 ["sf"] = function(x)
2478 if istable(inTAG) then
2479 outfile = input("enter filename:", "hf-legic-"..inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2)
2480 bytes = tagToBytes(inTAG)
2481 --bytes=xorBytes(bytes, inTAG.MCC)
2482 if (bytes) then
2483 writeFile(bytes, outfile)
2486 end,
2488 -- save values of mainTAG to a file (xored with 'specific' MCC)
2489 ["xf"] = function(x)
2490 if istable(inTAG) then
2491 outfile = input("enter filename:", "hf-legic-"..inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2)
2492 crc = input("enter new crc: ('00' for a plain dump)", inTAG.MCC)
2493 print("obfuscate with: "..crc)
2494 bytes=tagToBytes(inTAG)
2495 bytes[5]=crc
2496 if (bytes) then
2497 writeFile(bytes, outfile)
2500 end,
2502 -- dump mainTAG (and all Segments)
2503 ["di"] = function(x)
2504 if (istable(inTAG)) then
2505 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2506 if(istable(inTAG.SEG[0])) then
2507 for i=0, #inTAG.SEG do
2508 if(check43rdPartyCash1(uid, inTAG.SEG[i].data)) then
2509 io.write(accyan.."in Segment index: "..inTAG.SEG[i].index ..acoff.. "\n")
2510 elseif(check4LegicCash(inTAG.SEG[i].data, uid)) then
2511 io.write(accyan.."in Segment index: "..inTAG.SEG[i].index..acoff.."\n")
2512 lc=true;
2513 lci=inTAG.SEG[i].index;
2517 print("\n"..dumpTag(inTAG).."\n")
2518 if (lc) then actions["dlc"](lci) end
2519 lc=false
2521 end,
2523 -- dump backupTAG (and all Segments)
2524 ["do"] = function(x) if (istable(backupTAG)) then print("\n"..dumpTag(backupTAG).."\n") end end,
2526 -- create a empty tagMap
2527 ["mm"] = function(x)
2528 -- clear existing tagMap and init
2529 if (istable(inTAG)) then
2530 tagMap=makeTagMap()
2532 end,
2534 -- edit a tagMap
2535 ["em"] = function(x)
2536 if (istable(inTAG)==false) then
2537 if (confirm("no mainTAG in memory!\nread from PM3?")) then
2538 actions['rt']()
2539 elseif (confirm("load from File?")) then
2540 actions['lf']()
2541 else return
2544 if (istable(tagMap)==false) then actions['mm']() end
2545 -- edit
2546 tagMap=editTagMap(inTAG, tagMap)
2547 end,
2549 -- save a tagMap
2550 ["sm"] = function(x)
2551 if (istable(tagMap)) then
2552 if (istable(tagMap) and #tagMap.mappings>0) then
2553 print(accyan.."Map contains "..acoff..#tagMap..accyan.." mappings"..acoff)
2554 saveTagMap(tagMap, input(accyan.."enter filename:"..acoff, "Legic.map"))
2555 else
2556 print(acyellow.."no mappings in tagMap!"..acoff)
2559 end,
2561 -- load a tagMap
2562 ["lm"] = function(x)
2563 tagMap = loadTagMap(input(accyan.."enter filename:"..acoff, "Legic.map"))
2564 end,
2566 -- dump single segment
2567 ["ds"] = function(x)
2568 if (type(x)=="string" and string.len(x)>0) then
2569 sel = tonumber(x,10)
2570 else
2571 sel = selectSegment(inTAG)
2573 if (sel) then print("\n"..(dumpSegment(inTAG, sel) or acred.."no Segments available") ..acoff.."\n") end
2574 end,
2576 -- edit segment header
2577 ["es"] = function(x)
2578 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2579 else sel=selectSegment(inTAG) end
2580 if (sel) then
2581 if(istable(inTAG.SEG[0])) then
2582 inTAG=editSegment(inTAG, sel)
2583 inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel])
2584 else
2585 print(acyellow.."no Segments in Tag"..acoff)
2588 end,
2590 -- add segment
2591 ["as"] = function(x)
2592 if (istable(inTAG.SEG[0])) then
2593 inTAG=addSegment(inTAG)
2594 inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1])
2595 inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG])
2596 else print(accyan.."Master-Token / unsegmented Tag!"..acoff)
2598 end,
2600 -- remove segment
2601 ["rs"] = function(x)
2602 if (istable(inTAG.SEG[0])) then
2603 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2604 else sel=selectSegment(inTAG) end
2605 inTAG=delSegment(inTAG, sel)
2606 for i=0, #inTAG.SEG do
2607 inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i])
2610 end,
2612 -- edit data-portion of single segment
2613 ["ed"] = function(x)
2614 if (type(x) == "string" and string.len(x)>0) then sel=tonumber(x,10)
2615 else sel = selectSegment(inTAG) end
2616 if (istable(inTAG.SEG[sel])) then
2617 local uid = inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2618 inTAG.SEG[sel].data = editSegmentData(inTAG.SEG[sel].data, uid)
2620 end,
2622 -- edit Tag (MCD, MSN, MCC etc.)
2623 ["et"] = function(x)
2624 if (istable(inTAG)) then
2625 editTag(inTAG)
2627 end,
2629 -- make (dummy) Token
2630 ["mt"] = function(x) inTAG=makeToken(); actions.di() end,
2632 -- fix segment-crc on single segment
2633 ["ts"] = function(x)
2634 if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
2635 else sel=selectSegment(inTAG) end
2636 regenSegmentHeader(inTAG.SEG[sel])
2637 end,
2639 -- toggle kgh-crc-flag on a single segment
2640 ["tk"] = function(x)
2641 if (istable(inTAG) and istable(inTAG.SEG[0])) then
2642 if (type(x)=="string" and string.len(x)>0) then
2643 sel = tonumber(x,10)
2644 else
2645 sel = selectSegment(inTAG)
2647 if(inTAG.SEG[sel].kgh) then
2648 inTAG.SEG[sel].kgh = false
2649 else
2650 inTAG.SEG[sel].kgh = true
2653 end,
2655 -- calculate LegicCrc8
2656 ["k"] = function(x)
2657 if (type(x)=="string" and string.len(x)>0) then
2658 print(("%02x"):format(utils.Crc8Legic(x)))
2660 end,
2662 -- noop
2663 ["xb"] = function(x)
2664 end,
2666 -- print string for LegicCrc8-calculation about single segment
2667 ["xc"] = function(x)
2668 if (istable(inTAG) and istable(inTAG.SEG[0])) then
2669 if (type(x)=="string" and string.len(x)>0) then
2670 sel = tonumber(x,10)
2671 else
2672 sel = selectSegment(inTAG)
2674 print("k "..kghCrcCredentials(inTAG, sel))
2676 end,
2678 -- fix legic-cash checksums
2679 ["flc"] = function(x)
2680 if (type(x)=="string" and string.len(x)>0) then
2681 x = tonumber(x,10)
2682 else
2683 x = selectSegment(inTAG)
2685 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2686 inTAG.SEG[x].data=fixLegicCash(inTAG.SEG[x].data, uid)
2687 end,
2689 -- edit legic-cash values fixLegicCash(data, uid)
2690 ["elc"] = function(x)
2691 x=autoSelectSegment(inTAG, "legiccash")
2692 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2693 inTAG.SEG[x].data=editLegicCash(inTAG.SEG[x].data, uid)
2694 end,
2696 -- dump legic-cash human-readable
2697 ["dlc"] = function(x)
2698 -- if segment index was user defined
2699 if (type(x)=="string" and string.len(x)>0) then
2700 x=tonumber(x,10)
2701 print(string.format("User-Selected Index %02d", x))
2702 else
2703 -- or try to find match
2704 x = autoSelectSegment(inTAG, "legiccash")
2706 -- dump it
2707 dumpLegicCash(inTAG, x)
2708 end,
2710 -- dump 3rd-party-cash-segment
2711 ["d3p"] = function(x)
2712 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2713 -- if segment index was user defined
2714 if (type(x)=="string" and string.len(x)>0) then
2715 x=tonumber(x,10)
2716 print(string.format("User-Selected Index %02d", x))
2717 else
2718 -- or try to find match
2719 x = autoSelectSegment(inTAG, "3rdparty")
2721 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
2722 uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2723 if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then
2724 dump3rdPartyCash1(inTAG, x)
2727 end,
2729 -- dump 3rd-party-cash-segment (raw blocks and checksums over 'known areas')
2730 ["r3p"] = function(x)
2731 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2732 -- if segment index was user defined
2733 if (type(x)=="string" and string.len(x)>0) then
2734 x=tonumber(x,10)
2735 print(string.format("User-Selected Index %02d", x))
2736 else
2737 -- or try to find match
2738 x = autoSelectSegment(inTAG, "3rdparty")
2740 print3rdPartyCash1(inTAG, x)
2741 end,
2743 -- edit 3rd-party-cash-segment values (Balance, Mapping-UID, Stamp)
2744 ["e3p"] = function(x)
2745 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2746 -- if segment index was user defined
2747 if (type(x)=="string" and string.len(x)>0) then
2748 x=tonumber(x,10)
2749 print(string.format("User-Selected Index %02d", x))
2751 else
2752 -- or try to find match
2753 x = autoSelectSegment(inTAG, "3rdparty")
2755 if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
2756 inTAG=edit3rdPartyCash1(inTAG, x)
2757 dump3rdPartyCash1(inTAG, x)
2759 end,
2761 -- force fixing 3rd-party-checksums
2762 ["f3p"] = function(x)
2763 if(type(x)=="string" and string.len(x)>=2) then
2764 x = tonumber(x, 10)
2765 else
2766 x = selectSegment(inTAG)
2768 if (istable(inTAG.SEG[x])) then
2769 local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
2770 inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data)
2772 end,
2774 -- get stamp from single segment
2775 ["gs"] = function(x)
2776 if(type(x)=="string" and string.len(x)>=2) then
2777 x = tonumber(x, 10)
2778 else
2779 x = selectSegment(inTAG)
2781 local stamp=getSegmentStamp(inTAG.SEG[x])
2782 print("Stamp : "..stamp)
2783 stamp=str2bytes(stamp)
2784 print("length: "..#stamp)
2785 end,
2787 -- calculate crc16
2788 ["c6"] = function(x) local crc16=string.format("%4.04x", utils.Crc16(x))
2789 print(string.sub(crc16, 0,2).." "..string.sub(crc16, 3,4))
2790 end,
2792 -- check & fix segments-crc of all segments
2793 ["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end,
2795 -- set backup-area-bytes to '00'
2796 ["cb"] = function(x)
2797 if (istable(inTAG)) then
2798 print(accyan.."purge BackupArea"..acoff)
2799 inTAG=clearBackupArea(inTAG)
2801 end,
2803 -- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
2804 ["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end,
2806 -- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
2807 ["tac"] = function(x)
2808 if (colored_output) then
2809 colored_output=false
2810 else
2811 colored_output=true
2813 load_colors(colored_output)
2814 end,
2816 repeat
2817 -- default message / prompt
2818 ic=input("Legic command? ('"..acy.."h"..acr.."' for help - '"..acy.."q"..acr.."' for quit)", acy.."h"..acr)
2819 -- command actions decisions (first match, longer commands before shorter)
2820 if (type(actions[string.lower(string.sub(ic,0,3))])=='function') then
2821 actions[string.lower(string.sub(ic,0,3))](string.sub(ic,5))
2822 elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then
2823 actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4))
2824 elseif (type(actions[string.lower(string.sub(ic,0,1))])=='function') then
2825 actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3))
2826 else
2827 actions.h('')
2829 until (string.sub(ic,0,1)=="q")
2833 -- main function
2834 function main(args)
2835 -- set init colors/switch (can be toggled with 'tac' => 'toggle ansicolors')
2836 load_colors(colored_output)
2837 if (#args == 0 ) then modifyMode() end
2838 --- variables
2839 local inTAG, backupTAG, outTAG, outfile, interactive, crc
2840 local ofs=false
2841 local cfs=false
2842 local dfs=false
2843 -- just a spacer for better readability
2844 print()
2845 --- parse arguments
2846 for o, a in getopt.getopt(args, 'hrmi:do:c:') do
2847 -- display help
2848 if o == "h" then return help(); end
2849 -- read tag from PM3
2850 if o == "r" then inTAG=readFromPM3() end
2851 -- input file
2852 if o == "i" then inTAG=readFile(a) end
2853 -- dump virtual-Tag
2854 if o == "d" then dfs=true end
2855 -- interacive modifying
2856 if o == "m" then
2857 interactive = true
2858 modifyMode()
2860 -- xor (e.g. for clone or plain file)
2861 if o == "c" then
2862 cfs = true
2863 crc = a
2865 -- output file
2866 if o == "o" then
2867 outfile = a
2868 ofs = true
2872 -- file conversion (output to file)
2873 if ofs == false then return end
2875 -- dump infile / tag-read
2876 if (dfs) then
2877 print("-----------------------------------------")
2878 print(dumpTag(inTAG))
2881 bytes=tagToBytes(inTAG)
2882 if (cfs) then
2883 -- xor will be done in function writeFile
2884 -- with the value of byte[5]
2885 bytes[5]=crc
2888 -- write to outfile
2889 if (bytes) then
2891 if (outfile) then
2892 writeFile(bytes, outfile)
2894 --- read real tag into virtual tag
2895 -- inTAG=readFromPM3() end
2896 --- or simply use the bytes that where wriiten
2897 inTAG=bytesToTag(bytes, inTAG)
2898 -- show new content
2899 if (dfs) then
2900 print("-----------------------------------------")
2901 print(dumpTag(inTAG))
2908 -- script start
2909 main(args)