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