]>
Commit | Line | Data |
---|---|---|
733eb420 | 1 | --[[ |
2 | (example) Legic-Prime Layout with 'Kaba Group Header' | |
3 | +----+----+----+----+----+----+----+----+ | |
4 | 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f | | |
5 | +----+----+----+----+----+----+----+----+ | |
6 | 0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2| | |
7 | +----+----+----+----+----+----+----+----+ | |
8 | 0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1| | |
9 | +----+----+----+----+----+----+----+----+ | |
10 | 0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0| | |
11 | +----+----+----+----+----+----+----+----+ | |
12 | 0x20|UID1|UID2|kghC| | |
13 | +----+----+----+ | |
4e8fa8b4 | 14 | MCD = Manufacturer ID |
15 | MSN = Manufacturer SerialNumber | |
16 | 60 ea = DCF Low + DCF high | |
17 | 9f = raw byte which holds the bits for OLE,WRP,WRC,RD | |
18 | ff = unknown but important | |
19 | 00 = unimportant | |
20 | 11 = unknown but important | |
21 | Bck = header-backup-area | |
22 | 00 00 = Year (00 = 2000) & Week (not important) | |
23 | Seg = Segment Header | |
24 | SegC = Crc8 over the Segment Header | |
25 | Stp = Stamp (could be more as 4 - up to 7) | |
26 | UID = dec User-ID for online-Mapping | |
27 | kghC = crc8 over MCD + MSN0..MSN2 + UID | |
28 | ||
29 | ||
30 | (example) Legic-Cash on MIM256/1024 tag' (37 bytes) | |
31 | +----+----+----+----+----+----+----+----+ | |
32 | 0x00|Seg0|Seg1|Seg2|Seg3|SegC|STP0|STP1|STP2| | |
33 | +----+----+----+----+----+----+----+----+ | |
34 | 0x08|STP3|STP4|STP5|STP6| 01 |CURh|CURl|LIMh| | |
35 | +----+----+----+----+----+----+----+----+ | |
36 | 0x10|LIMm|LIMl|CHKh|CHKl|BALh|BALm|BALl|LRBh| | |
37 | +----+----+----+----+----+----+----+----+ | |
38 | 0x18|LRBm|LRBl|CHKh|CHKl|SHDh|SHDm|SHDl|LRSh| | |
39 | +----+----+----+----+----+----+----+----+ | |
40 | 0x20|LRSm|LRSl| CV |CHKh|CHKl| | |
41 | +----+----+----+----+----+ | |
42 | STP = Stamp (seems to be always 7 bytes) | |
43 | 01 = unknown but important | |
44 | CUR = currency in HEX (ISO 4217) | |
45 | LIM = Cash-Limit | |
46 | CHK = crc16 over byte-addr 0x05..0x12 | |
47 | BAL = Balance | |
48 | LRB = ID of the reader that changed the balance | |
49 | CHK = crc16 over BAL + LRB | |
50 | SHD = shadow Balance | |
51 | LRS = ID of the reader that changed the shadow balance (?? should be always the same as LRB) | |
52 | CV = Counter value for transactions | |
53 | CHK = crc16 over SHD + LRS + CV | |
733eb420 | 54 | --]] |
55 | ||
56 | example = "script run legic" | |
57 | author = "Mosci" | |
4e8fa8b4 | 58 | version = "1.0.1" |
733eb420 | 59 | desc = |
60 | [[ | |
61 | ||
62 | This script helps you to read, create and modify Legic Prime Tags (MIM22, MIM256, MIM1024) | |
63 | it's kinda interactive with following commands in three categories: | |
64 | ||
4e8fa8b4 | 65 | Data I/O Segment Manipulation Token-Data |
66 | ----------------- -------------------- ----------------- | |
67 | rt => read Tag as => add Segment mt => make Token | |
68 | wt => write Tag es => edit Segment Header et => edit Token data | |
69 | ct => copy io Tag ed => edit Segment Data tk => toggle KGH-Flag | |
7f0cb92e | 70 | tc => copy oi Tag rs => remove Segment |
4e8fa8b4 | 71 | cc => check Segment-CRC File I/O |
72 | di => dump inTag ck => check KGH ----------------- | |
73 | do => dump outTag lf => load File | |
74 | ds => dump Segments sf => save File | |
75 | lc => dump Legic-Cash xf => xor to File | |
76 | d3p => dump 3rd Party Cash | |
77 | r3p => raw 3rd Party Cash | |
78 | ||
733eb420 | 79 | |
733eb420 | 80 | rt: 'read tag' - reads a tag placed near to the PM3 |
81 | wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3 | |
82 | without the need of changing anything - MCD,MSN,MCC will be read from the tag | |
83 | before and applied to the output. | |
84 | ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not usefull yet, but inernally needed | |
85 | tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not usefull yet, but inernally needed | |
86 | di: 'dump inTag' - shows the current content of the 'virtual Tag' | |
87 | do: 'dump outTag' - shows the current content of the 'virtual outTag' | |
733eb420 | 88 | ds: 'dump Segments' - will show the content of a selected Segment |
89 | as: 'add Segment' - will add a 'empty' Segment to the inTag | |
90 | es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid) | |
91 | all other Segment-Header-Values are either calculated or not needed to edit (yet) | |
7f0cb92e | 92 | ed: 'edit data' - edit the Data of a Segment (ADF-Aera / Stamp & Payload specific Data) |
93 | et: 'edit Token' - edit Data of a Token (CDF-Area / SAM, SAM64, SAM63, IAM, GAM specific Data) | |
94 | mt: 'make Token' - create a Token 'from scratch' (guided) | |
733eb420 | 95 | rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token) |
96 | cc: 'check Segment-CRC'- checks & calculates (if check failed) the Segment-CRC of all Segments | |
97 | ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected | |
98 | 'Kaba Group Header CRC calculation' | |
99 | tk: 'toggle KGH' - toglle the (script-internal) flag for kgh-calculation for a segment | |
100 | xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment | |
4e8fa8b4 | 101 | dlc: 'dump Legic-Cash' - show balance and checksums of a legic-Cash Segment |
102 | d3p: 'dump 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd Party Cash-Segment | |
103 | r3p: 'raw 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd Party Cash-Segment | |
104 | e3p: 'edit 3rd Party' - edit Data in 3rd Party Cash Segment | |
733eb420 | 105 | lf: 'load file' - load a (xored) file from the local Filesystem into the 'virtual inTag' |
106 | sf: 'save file' - saves the 'virtual inTag' to the local Filesystem (xored with Tag-MCC) | |
107 | xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with choosen MCC - use '00' for plain values) | |
108 | ||
109 | ]] | |
4e8fa8b4 | 110 | currentTag="inTAG" |
7f0cb92e | 111 | --- |
112 | -- requirements | |
733eb420 | 113 | local utils = require('utils') |
114 | local getopt = require('getopt') | |
115 | ||
7f0cb92e | 116 | --- |
117 | -- global variables / defines | |
733eb420 | 118 | local bxor = bit32.bxor |
119 | local bbit = bit32.extract | |
120 | local input = utils.input | |
121 | local confirm = utils.confirm | |
122 | ||
4e8fa8b4 | 123 | --- |
124 | -- curency-codes for Legic-Cash-Segments (ISO 4217) | |
125 | local currency = { | |
126 | ["03d2"]="EUR", | |
127 | ["0348"]="USD", | |
128 | ["033A"]="GBP", | |
129 | ["02F4"]="CHF" | |
130 | } | |
131 | ||
7f0cb92e | 132 | --- |
733eb420 | 133 | -- This is only meant to be used when errors occur |
134 | function oops(err) | |
135 | print("ERROR: ",err) | |
136 | return nil, err | |
137 | end | |
138 | ||
139 | --- | |
140 | -- Usage help | |
141 | function help() | |
142 | print(desc) | |
7f0cb92e | 143 | print(version) |
733eb420 | 144 | print("Example usage") |
145 | print(example) | |
146 | end | |
147 | ||
148 | --- | |
149 | -- table check helper | |
150 | function istable(t) | |
151 | return type(t) == 'table' | |
152 | end | |
153 | ||
154 | --- | |
7f0cb92e | 155 | -- xor single byte |
733eb420 | 156 | function xorme(hex, xor, index) |
157 | if ( index >= 23 ) then | |
158 | return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) )) | |
159 | else | |
160 | return hex | |
161 | end | |
162 | end | |
163 | ||
164 | --- | |
165 | -- (de)obfuscate bytes | |
166 | function xorBytes(inBytes, crc) | |
167 | local bytes = {} | |
168 | for index = 1, #inBytes do | |
169 | bytes[index] = xorme(inBytes[index], crc, index) | |
170 | end | |
171 | if (#inBytes == #bytes) then | |
172 | -- replace crc | |
173 | bytes[5] = string.sub(crc,-2) | |
174 | return bytes | |
175 | else | |
176 | print("error: byte-count missmatch") | |
177 | return false | |
178 | end | |
179 | end | |
180 | ||
181 | --- | |
182 | -- check availability of file | |
183 | function file_check(file_name) | |
184 | local file_found=io.open(file_name, "r") | |
185 | if file_found==nil then | |
186 | return false | |
187 | else | |
188 | return true | |
189 | end | |
190 | end | |
191 | ||
192 | --- | |
193 | -- read file into virtual-tag | |
194 | function readFile(filename) | |
195 | local bytes = {} | |
196 | local tag = {} | |
197 | if (file_check(filename)==false) then | |
198 | return oops("input file: "..filename.." not found") | |
199 | else | |
200 | bytes = getInputBytes(filename) | |
201 | if (bytes == false) then return oops('couldnt get input bytes') | |
202 | else | |
203 | -- make plain bytes | |
204 | bytes = xorBytes(bytes,bytes[5]) | |
205 | -- create Tag for plain bytes | |
206 | tag=createTagTable() | |
207 | -- load plain bytes to tag-table | |
208 | tag=bytesToTag(bytes, tag) | |
209 | end | |
210 | end | |
211 | return tag | |
212 | end | |
213 | ||
214 | --- | |
215 | -- write bytes to file | |
733eb420 | 216 | function writeFile(bytes, filename) |
217 | if (filename~='MylegicClone.hex') then | |
218 | if (file_check(filename)) then | |
219 | local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?") | |
220 | if (answer==false) then return print("user abort") end | |
221 | end | |
222 | end | |
223 | local line | |
224 | local bcnt=0 | |
225 | local fho,err = io.open(filename, "w") | |
226 | if err then oops("OOps ... faild to open output-file ".. filename) end | |
227 | bytes=xorBytes(bytes, bytes[5]) | |
228 | for i = 1, #bytes do | |
229 | if (bcnt == 0) then | |
230 | line=bytes[i] | |
231 | elseif (bcnt <= 7) then | |
232 | line=line.." "..bytes[i] | |
233 | end | |
234 | if (bcnt == 7) then | |
235 | -- write line to new file | |
236 | fho:write(line.."\n") | |
237 | -- reset counter & line | |
238 | bcnt=-1 | |
239 | line="" | |
240 | end | |
241 | bcnt=bcnt+1 | |
242 | end | |
243 | fho:close() | |
244 | print("\nwrote ".. #bytes .." bytes to " .. filename) | |
245 | return true | |
246 | end | |
247 | ||
7f0cb92e | 248 | --- |
249 | -- put certain bytes into a new table | |
250 | function bytesToTable(bytes, bstart, bend) | |
251 | local t={} | |
252 | for i=0, (bend-bstart) do | |
253 | t[i]=bytes[bstart+i] | |
254 | end | |
255 | return t | |
256 | end | |
257 | ||
733eb420 | 258 | --- |
259 | -- read from pm3 into virtual-tag | |
260 | function readFromPM3() | |
261 | local tag, bytes, infile | |
262 | --if (confirm("is the Tag placed onto the Proxmak3 and ready for reading?")) then | |
263 | --print("reading Tag ...") | |
264 | --infile=input("input a name for the temp-file:", "legic.temp") | |
265 | --if (file_check(infile)) then | |
266 | -- local answer = confirm("\nthe output-file "..infile.." alredy exists!\nthis will delete the previous content!\ncontinue?") | |
267 | -- if (answer==false) then return print("user abort") end | |
268 | --end | |
269 | infile="legic.temp" | |
270 | core.console("hf legic reader") | |
271 | core.console("hf legic save "..infile) | |
272 | --print("read temp-file into virtual Tag ...") | |
273 | tag=readFile(infile) | |
274 | return tag | |
275 | --else return print("user abort"); end | |
276 | end | |
277 | ||
7f0cb92e | 278 | --- |
279 | -- write virtual Tag to real Tag | |
280 | function writeToTag(plainBytes, taglen, filename) | |
281 | local bytes | |
282 | if(utils.confirm("\nplace your empty tag onto the PM3 to read & write\n") == false) then | |
283 | return | |
284 | end | |
285 | ||
286 | -- write data to file | |
287 | if (taglen > 0) then | |
288 | WriteBytes = utils.input("enter number of bytes to write?", taglen) | |
289 | ||
290 | -- load file into pm3-buffer | |
291 | if (type(filename)~="string") then filename=input("filename to load to pm3-buffer?","legic.temp") end | |
292 | cmd = 'hf legic load '..filename | |
293 | core.console(cmd) | |
294 | ||
295 | -- write pm3-buffer to Tag | |
296 | for i=0, WriteBytes do | |
297 | if ( i<5 or i>6) then | |
298 | cmd = ('hf legic write 0x%02x 0x01'):format(i) | |
299 | core.console(cmd) | |
300 | --print(cmd) | |
301 | elseif (i == 6) then | |
302 | -- write DCF in reverse order (requires 'mosci-patch') | |
303 | cmd = 'hf legic write 0x05 0x02' | |
304 | core.console(cmd) | |
305 | --print(cmd) | |
306 | else | |
307 | print("skipping byte 0x05 - will be written next step") | |
308 | end | |
309 | utils.Sleep(0.2) | |
310 | end | |
311 | end | |
312 | end | |
313 | ||
733eb420 | 314 | --- |
315 | -- read file into table | |
316 | function getInputBytes(infile) | |
317 | local line | |
318 | local bytes = {} | |
319 | local fhi,err = io.open(infile) | |
320 | if err then oops("faild to read from file ".. infile); return false; end | |
321 | while true do | |
322 | line = fhi:read() | |
323 | if line == nil then break end | |
324 | for byte in line:gmatch("%w+") do | |
325 | table.insert(bytes, byte) | |
326 | end | |
327 | end | |
328 | fhi:close() | |
329 | print(#bytes .. " bytes from "..infile.." loaded") | |
330 | return bytes | |
331 | end | |
332 | ||
333 | --- | |
733eb420 | 334 | -- create tag-table helper |
335 | function createTagTable() | |
336 | local t={ | |
337 | ['MCD'] = '00', | |
338 | ['MSN0']= '11', | |
339 | ['MSN1']= '22', | |
340 | ['MSN2']= '33', | |
341 | ['MCC'] = 'cc', | |
342 | ['DCFl']= 'ff', | |
343 | ['DCFh']= 'ff', | |
344 | ['Type']= 'GAM', | |
345 | ['OLE'] = 0, | |
346 | ['Stamp_len']= 18, | |
347 | ['WRP'] = '00', | |
348 | ['WRC'] = '00', | |
349 | ['RD'] = '00', | |
350 | ['raw'] = '9f', | |
351 | ['SSC'] = 'ff', | |
352 | ['data']= {}, | |
353 | ['bck'] = {}, | |
354 | ['MTC'] = {}, | |
355 | ['SEG'] = {} | |
356 | } | |
357 | return t | |
358 | end | |
359 | ||
360 | --- | |
361 | -- put bytes into tag-table | |
362 | function bytesToTag(bytes, tag) | |
363 | if(istable(tag)) then | |
364 | tag.MCD =bytes[1]; | |
365 | tag.MSN0=bytes[2]; | |
366 | tag.MSN1=bytes[3]; | |
367 | tag.MSN2=bytes[4]; | |
368 | tag.MCC =bytes[5]; | |
369 | tag.DCFl=bytes[6]; | |
370 | tag.DCFh=bytes[7]; | |
371 | tag.raw =bytes[8]; | |
372 | tag.SSC =bytes[9]; | |
373 | tag.Type=getTokenType(tag.DCFl); | |
374 | tag.OLE=bbit("0x"..tag.DCFl,7,1) | |
375 | tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4)) | |
376 | tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3)) | |
377 | tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1)) | |
378 | tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10)) | |
379 | tag.data=bytesToTable(bytes, 10, 13) | |
380 | tag.Bck=bytesToTable(bytes, 14, 20) | |
381 | tag.MTC=bytesToTable(bytes, 21, 22) | |
382 | ||
383 | print("Tag-Type: ".. tag.Type) | |
384 | if (tag.Type=="SAM" and #bytes>23) then | |
385 | tag=segmentsToTag(bytes, tag) | |
386 | print((#tag.SEG+1).." Segment(s) found") | |
7f0cb92e | 387 | -- unsegmented Master-Token |
388 | -- only tag-data | |
389 | else | |
390 | for i=0, #tag.Bck do | |
391 | table.insert(tag.data, tag.Bck[i]) | |
392 | end | |
393 | tag.data[#tag.data]=tag.MTC[0] | |
394 | tag.Bck=nil | |
395 | --tag.MTC[0]=tag.MTC[1] | |
396 | --tag.MTC[1]=nil | |
733eb420 | 397 | end |
398 | print(#bytes.." bytes for Tag processed") | |
399 | return tag | |
400 | end | |
401 | return oops("tag is no table in: bytesToTag ("..type(tag)..")") | |
402 | end | |
403 | ||
404 | --- | |
7f0cb92e | 405 | -- read Tag-Table in bytes-table |
406 | function tagToBytes(tag) | |
733eb420 | 407 | if (istable(tag)) then |
7f0cb92e | 408 | local bytes = {} |
409 | local i, i2 | |
410 | -- main token-data | |
411 | table.insert(bytes, tag.MCD) | |
412 | table.insert(bytes, tag.MSN0) | |
413 | table.insert(bytes, tag.MSN1) | |
414 | table.insert(bytes, tag.MSN2) | |
415 | table.insert(bytes, tag.MCC) | |
416 | table.insert(bytes, tag.DCFl) | |
417 | table.insert(bytes, tag.DCFh) | |
418 | table.insert(bytes, tag.raw) | |
419 | table.insert(bytes, tag.SSC) | |
420 | -- raw token data | |
421 | for i=0, #tag.data do | |
422 | table.insert(bytes, tag.data[i]) | |
733eb420 | 423 | end |
7f0cb92e | 424 | -- backup data |
425 | if(istable(tag.Bck)) then | |
426 | for i=0, #tag.Bck do | |
427 | table.insert(bytes, tag.Bck[i]) | |
733eb420 | 428 | end |
733eb420 | 429 | end |
7f0cb92e | 430 | -- token-create-time / master-token crc |
431 | for i=0, #tag.MTC do | |
432 | table.insert(bytes, tag.MTC[i]) | |
733eb420 | 433 | end |
7f0cb92e | 434 | -- process segments |
435 | if (type(tag.SEG[0])=='table') then | |
436 | for i=0, #tag.SEG do | |
437 | for i2=1, #tag.SEG[i].raw+1 do | |
438 | table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2]) | |
733eb420 | 439 | end |
7f0cb92e | 440 | table.insert(bytes, #bytes+1, tag.SEG[i].crc) |
441 | for i2=0, #tag.SEG[i].data-1 do | |
442 | table.insert(bytes, #bytes+1, tag.SEG[i].data[i2]) | |
733eb420 | 443 | end |
733eb420 | 444 | end |
7f0cb92e | 445 | end |
446 | -- fill with zeros | |
447 | for i=#bytes+1, 1024 do | |
448 | table.insert(bytes, i, '00') | |
449 | end | |
450 | print(#bytes.." bytes of Tag dumped") | |
451 | return bytes | |
452 | end | |
453 | return oops("tag is no table in tagToBytes ("..type(tag)..")") | |
733eb420 | 454 | end |
455 | ||
456 | --- | |
7f0cb92e | 457 | -- make token |
458 | function makeToken() | |
459 | local mt={ | |
460 | ['Type'] = {"SAM", "SAM63", "SAM64", "IAM", "GAM"}, | |
461 | ['DCF'] = {"60ea", "31fa", "30fa", "80fa", "f0fa"}, | |
462 | ['WRP'] = {"15", "2", "2", "2", "2"}, | |
463 | ['WRC'] = {"01", "02", "02", "00", "00"}, | |
464 | ['RD'] = {"01", "00", "00", "00", "00"}, | |
465 | ['Stamp'] = {"00", "00", "00", "00", "00"}, | |
466 | ['Segment'] = {"0d", "c0", "04", "00", "be", "01", "02", "03", "04", "01", "02", "03", "04"} | |
467 | } | |
468 | ttype="" | |
469 | for k, v in pairs(mt.Type) do | |
470 | ttype=ttype..k..") "..v.." " | |
733eb420 | 471 | end |
7f0cb92e | 472 | mtq=tonumber(input("select number for Token-Type\n"..ttype, '1'), 10) |
473 | if (type(mtq)~="number") then return print("selection invalid!") | |
474 | elseif (mtq>#mt.Type) then return print("selection invalid!") | |
475 | else print("Token-Type '"..mt.Type[mtq].."' selected") end | |
476 | local raw=calcHeaderRaw(mt.WRP[mtq], mt.WRC[mtq], mt.RD[mtq]) | |
477 | local mtCRC="00" | |
478 | ||
479 | bytes={"01", "02", "03", "04", "cb", string.sub(mt.DCF[mtq], 0, 2), string.sub(mt.DCF[mtq], 3), raw, | |
480 | "00", "00", "00", "00", "00", "00", "00", "00", | |
481 | "00", "00", "00", "00", "00", "00"} | |
482 | if (mtq==1) then | |
483 | for i=0, #mt.Segment do | |
484 | table.insert(bytes, mt.Segment[i]) | |
733eb420 | 485 | end |
7f0cb92e | 486 | bytes[9]="ff" |
733eb420 | 487 | end |
7f0cb92e | 488 | -- fill bytes |
489 | for i=#bytes, 1023 do table.insert(bytes, "00") end | |
490 | -- if Master-Token -> calc Master-Token-CRC | |
491 | if (mtq>1) then bytes[22]=calcMtCrc(bytes) end | |
492 | local tempTag=createTagTable() | |
493 | -- remove segment if MasterToken | |
494 | if (mtq>1) then tempTag.SEG[0]=nil end | |
495 | return bytesToTag(bytes, tempTag) | |
733eb420 | 496 | end |
497 | ||
7f0cb92e | 498 | --- |
499 | -- edit token-data | |
500 | function editTag(tag) | |
501 | -- for simulation it makes sense to edit everything | |
4e8fa8b4 | 502 | local edit_sim="MCD MSN0 MSN1 MSN2 MCC DCFl DCFh WRP WRC RD" |
7f0cb92e | 503 | -- on real tags it makes only sense to edit DCF, WRP, WRC, RD |
504 | local edit_real="DCFl DCFh WRP WRC RD" | |
505 | if (confirm("do you want to edit non-writeable values (e.g. for simulation)?")) then | |
506 | edit_tag=edit_sim | |
507 | else edit_tag=edit_real end | |
508 | ||
509 | if(istable(tag)) then | |
510 | for k,v in pairs(tag) do | |
511 | if(type(v)~="table" and type(v)~="boolean" and string.find(edit_tag, k)) then | |
512 | tag[k]=input("value for: "..k, v) | |
733eb420 | 513 | end |
514 | end | |
515 | ||
7f0cb92e | 516 | if (tag.Type=="SAM") then ttype="Header"; else ttype="Stamp"; end |
517 | if (confirm("do you want to edit "..ttype.." Data?")) then | |
518 | -- master-token specific | |
519 | if(istable(tag.Bck)==false) then | |
520 | -- stamp-data length=(0xfc-DCFh) | |
521 | -- on MT: SSC holds the Starting Stamp Character (Stamp0) | |
522 | tag.SSC=input(ttype.."0: ", tag.SSC) | |
523 | -- rest of stamp-bytes are in tag.data 0..n | |
524 | for i=0, (tonumber(0xfc ,10)-("%d"):format('0x'..tag.DCFh))-2 do | |
525 | tag.data[i]=input(ttype.. i+1 ..": ", tag.data[i]) | |
733eb420 | 526 | end |
733eb420 | 527 | else |
7f0cb92e | 528 | --- on credentials byte7 should always be 9f and byte8 ff |
529 | -- on Master-Token not (even on SAM63/64 not) | |
530 | -- tag.SSC=input(ttype.."0: ", tag.SSC) | |
531 | for i=0, #tag.data do | |
532 | tag.data[i]=input(ttype.. i ..": ", tag.data[i]) | |
533 | end | |
534 | end | |
733eb420 | 535 | end |
7f0cb92e | 536 | |
537 | bytes=tagToBytes(tag) | |
538 | ||
539 | --- check data-consistency (calculate tag.raw) | |
540 | bytes[8]=calcHeaderRaw(tag.WRP, tag.WRC, tag.RD) | |
541 | ||
542 | --- Master-Token specific | |
543 | -- should be triggered if a SAM was converted to a non-SAM (user-Token to Master-Token) | |
544 | -- or a Master-Token has being edited (also SAM64 & SAM63 - which are in fact Master-Token) | |
545 | if(tag.Type~="SAM" or bytes[6]..bytes[7]~="60ea") then | |
546 | -- calc new Master-Token crc | |
547 | bytes[22]=calcMtCrc(bytes) | |
548 | else | |
549 | -- ensure tag.SSC set to 'ff' on credential-token (SAM) | |
550 | bytes[9]='ff' | |
551 | -- if a Master-Token was converted to a Credential-Token | |
552 | -- lets unset the Time-Area to 00 00 (will contain Stamp-Data on MT) | |
553 | bytes[21]='00' | |
554 | bytes[22]='00' | |
733eb420 | 555 | end |
556 | ||
7f0cb92e | 557 | tag=bytesToTag(bytes, tag) |
733eb420 | 558 | end |
559 | end | |
560 | ||
561 | --- | |
7f0cb92e | 562 | -- calculates header-byte (addr 0x07) |
563 | function calcHeaderRaw(wrp, wrc, rd) | |
564 | local res | |
565 | wrp=("%02x"):format(tonumber(wrp, 10)) | |
566 | rd=tonumber(rd, 16) | |
567 | res=("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0)) | |
568 | return res | |
733eb420 | 569 | end |
570 | ||
571 | --- | |
572 | -- dump virtual Tag-Data | |
573 | function dumpTag(tag) | |
574 | local i, i2 | |
575 | local res | |
576 | local dp=0 | |
577 | local raw="" | |
578 | -- sytstem area | |
579 | res ="\nCDF: System Area" | |
580 | res= res.."\n"..dumpCDF(tag) | |
7f0cb92e | 581 | -- segments (user-token area) |
582 | if(tag.Type=="SAM") then | |
733eb420 | 583 | res = res.."\n\nADF: User Area" |
584 | for i=0, #tag.SEG do | |
585 | res=res.."\n"..dumpSegment(tag, i).."\n" | |
586 | end | |
587 | end | |
588 | return res | |
589 | end | |
590 | ||
733eb420 | 591 | --- |
592 | -- get segmemnt-data from byte-table | |
593 | function getSegmentData(bytes, start, index) | |
594 | local segment={ | |
595 | ['index'] = '00', | |
596 | ['flag'] = 'c', | |
597 | ['valid'] = 0, | |
598 | ['last'] = 0, | |
599 | ['len'] = 13, | |
600 | ['raw'] = {'00', '00', '00', '00'}, | |
601 | ['WRP'] = 4, | |
602 | ['WRC'] = 0, | |
603 | ['RD'] = 0, | |
604 | ['crc'] = '00', | |
605 | ['data'] = {}, | |
606 | ['kgh'] = false | |
607 | } | |
608 | if (bytes[start]) then | |
609 | local i | |
610 | -- #index | |
611 | segment.index = index | |
612 | -- flag = high nibble of byte 1 | |
613 | segment.flag = string.sub(bytes[start+1],0,1) | |
614 | -- valid = bit 6 of byte 1 | |
615 | segment.valid = bbit("0x"..bytes[start+1],6,1) | |
616 | -- last = bit 7 of byte 1 | |
617 | segment.last = bbit("0x"..bytes[start+1],7,1) | |
618 | -- len = (byte 0)+(bit0-3 of byte 1) | |
619 | segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16) | |
620 | -- raw segment header | |
621 | segment.raw = {bytes[start], bytes[start+1], bytes[start+2], bytes[start+3]} | |
622 | -- wrp (write proteted) = byte 2 | |
623 | segment.WRP = tonumber(bytes[start+2],16) | |
624 | -- wrc (write control) - bit 4-6 of byte 3 | |
625 | segment.WRC = tonumber(bbit("0x"..bytes[start+3],4,3),16) | |
626 | -- rd (read disabled) - bit 7 of byte 3 | |
627 | segment.RD = tonumber(bbit("0x"..bytes[start+3],7,1),16) | |
628 | -- crc byte 4 | |
629 | segment.crc = bytes[start+4] | |
630 | -- segment-data starts at segment.len - segment.header - segment.crc | |
631 | for i=0, (segment.len-5) do | |
632 | segment.data[i]=bytes[start+5+i] | |
633 | end | |
634 | return segment | |
635 | else return false; | |
636 | end | |
637 | end | |
638 | ||
639 | --- | |
640 | -- put segments from byte-table to tag-table | |
641 | function segmentsToTag(bytes, tag) | |
642 | if(#bytes>23) then | |
643 | local start=23 | |
644 | local i=-1 | |
645 | if (istable(tag)) then | |
646 | repeat | |
647 | i=i+1 | |
648 | tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i)) | |
649 | if (tag.Type=="SAM") then | |
650 | if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end | |
651 | end | |
652 | start=start+tag.SEG[i].len | |
653 | until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126) | |
654 | return tag | |
655 | else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end | |
656 | else print("no Segments: must be a MIM22") end | |
657 | end | |
658 | ||
733eb420 | 659 | --- |
7f0cb92e | 660 | -- regenerate segment-header (after edit) |
661 | function regenSegmentHeader(segment) | |
662 | local seg=segment | |
663 | local raw = segment.raw | |
733eb420 | 664 | local i |
7f0cb92e | 665 | -- len bit0..7 | len=12bit=low nibble of byte1..byte0 |
666 | raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8)) | |
667 | -- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh? | |
668 | raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),4.4)..bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8)) | |
669 | -- WRP | |
670 | raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8)) | |
671 | -- WRC + RD | |
672 | raw[4]=("%02x"):format(bbit("0x"..("%03x"):format(seg.WRC),4,3)..bbit("0x"..("%02x"):format(seg.RD*128),0,8)) | |
673 | -- flag | |
674 | seg.flag=string.sub(raw[2],0,1) | |
675 | --print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4]) | |
676 | if(#seg.data>(seg.len-5)) then | |
677 | print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5) | |
678 | print("Data-Length has being reduced: removing ".. #seg.data-(seg.len-5) .." bytes from Payload"); | |
679 | for i=(seg.len-5), #seg.data-1 do | |
680 | table.remove(seg.data) | |
733eb420 | 681 | end |
7f0cb92e | 682 | elseif (#seg.data<(seg.len-5)) then |
683 | print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5) | |
684 | print("Data-Length has being extended: adding "..(seg.len-5)-#seg.data.." bytes to Payload"); | |
685 | for i=#seg.data, (seg.len-5)-1 do | |
686 | table.insert(seg.data, '00') | |
733eb420 | 687 | end |
7f0cb92e | 688 | end |
689 | return seg | |
733eb420 | 690 | end |
691 | ||
692 | --- | |
7f0cb92e | 693 | -- determine TagType (bits 0..6 of DCFlow) |
694 | function getTokenType(DCFl) | |
695 | --[[ | |
696 | 0x00–0x2f IAM | |
697 | 0x30–0x6f SAM | |
698 | 0x70–0x7f GAM | |
699 | ]]-- | |
700 | local tt = tonumber(bbit("0x"..DCFl,0,7),10) | |
701 | if (tt >= 0 and tt <= 47) then tt = "IAM" | |
702 | elseif (tt == 49) then tt = "SAM63" | |
703 | elseif (tt == 48) then tt = "SAM64" | |
704 | elseif (tt >= 50 and tt <= 111) then tt = "SAM" | |
705 | elseif (tt >= 112 and tt <= 127) then tt = "GAM" | |
706 | else tt = "???" end | |
707 | return tt | |
708 | end | |
733eb420 | 709 | |
710 | --- | |
7f0cb92e | 711 | -- dump tag-system area |
712 | function dumpCDF(tag) | |
713 | local res="" | |
714 | local i=0 | |
715 | local raw="" | |
733eb420 | 716 | local bytes |
7f0cb92e | 717 | if (istable(tag)) then |
718 | res = res.."MCD: "..tag.MCD..", MSN: "..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..", MCC: "..tag.MCC.."\n" | |
719 | res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n" | |
720 | res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..((tag.raw=='9f') and (", SSC="..tag.SSC.."\n") or "\n") | |
721 | ||
722 | -- credential (end-user tag) | |
723 | if (tag.Type=="SAM") then | |
724 | res = res.."Remaining Header Area\n" | |
725 | for i=0, (#tag.data) do | |
726 | res = res..tag.data[i].." " | |
733eb420 | 727 | end |
7f0cb92e | 728 | res = res.."\nBackup Area\n" |
729 | for i=0, (#tag.Bck) do | |
730 | res = res..tag.Bck[i].." " | |
731 | end | |
732 | res = res.."\nTime Area\n" | |
733 | for i=0, (#tag.MTC) do | |
734 | res = res..tag.MTC[i].." " | |
735 | end | |
736 | ||
737 | -- Master Token specific | |
733eb420 | 738 | else |
7f0cb92e | 739 | res = res .."Master-Token Area\nStamp: " |
740 | res= res..tag.SSC.." " | |
741 | for i=0, tag.Stamp_len-2 do | |
742 | res = res..tag.data[i].." " | |
733eb420 | 743 | end |
7f0cb92e | 744 | res=res.."\nunused payload\n" |
745 | for i=0, (#tag.data-tag.Stamp_len-1) do | |
746 | res = res..tag.data[i].." " | |
733eb420 | 747 | end |
7f0cb92e | 748 | bytes=tagToBytes(tag) |
749 | local mtcrc=calcMtCrc(bytes) | |
750 | res=res.."\nMaster-Token CRC: " | |
751 | res = res ..tag.MTC[1].." ("..((tag.MTC[1]==mtcrc) and "valid" or "error")..")" | |
752 | end | |
753 | return res | |
754 | else print("no valid Tag in dumpCDF") end | |
755 | end | |
756 | ||
757 | --- | |
758 | -- dump single segment | |
759 | function dumpSegment(tag, index) | |
760 | local i=index | |
761 | local i2 | |
762 | local dp=0 --data-position in table | |
763 | local res="" --result | |
764 | local raw="" --raw-header | |
765 | -- segment | |
766 | if ( (istable(tag.SEG[i])) and tag.Type=="SAM") then | |
767 | if (istable(tag.SEG[i].raw)) then | |
768 | for k,v in pairs(tag.SEG[i].raw) do | |
769 | raw=raw..v.." " | |
770 | end | |
771 | end | |
772 | ||
773 | -- segment header | |
774 | res = res.."Segment "..("%02d"):format(tag.SEG[i].index)..": " | |
775 | res = res .."raw header:"..string.sub(raw,0,-2)..", flag="..tag.SEG[i].flag..", (valid="..("%x"):format(tag.SEG[i].valid)..", last="..("%x"):format(tag.SEG[i].last).."), " | |
776 | res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", " | |
777 | res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." " | |
778 | res = res .."("..(checkSegmentCrc(tag, i) and "valid" or "error")..")" | |
779 | raw="" | |
780 | ||
781 | -- WRC protected | |
782 | if (tag.SEG[i].WRC>0) then | |
4e8fa8b4 | 783 | res = res .."\nWRC protected area:\n" |
7f0cb92e | 784 | for i2=dp, tag.SEG[i].WRC-1 do |
785 | res = res..tag.SEG[i].data[dp].." " | |
786 | dp=dp+1 | |
787 | end | |
788 | end | |
789 | ||
790 | -- WRP mprotected | |
4e8fa8b4 | 791 | if ((tag.SEG[i].WRP-tag.SEG[i].WRC)>0) then |
792 | res = res .."\nRemaining write protected area:\n" | |
793 | for i2=dp, (tag.SEG[i].WRP-tag.SEG[i].WRC)-1 do | |
7f0cb92e | 794 | res = res..tag.SEG[i].data[dp].." " |
795 | dp=dp+1 | |
796 | end | |
797 | end | |
798 | ||
799 | -- payload | |
800 | if (#tag.SEG[i].data-dp>0) then | |
801 | res = res .."\nRemaining segment payload:\n" | |
802 | for i2=dp, #tag.SEG[i].data-2 do | |
803 | res = res..tag.SEG[i].data[dp].." " | |
804 | dp=dp+1 | |
805 | end | |
806 | if (tag.SEG[i].kgh) then | |
807 | res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and "valid" or "error")..")" | |
808 | else res = res..tag.SEG[i].data[dp] end | |
809 | end | |
810 | dp=0 | |
811 | return res | |
812 | else | |
813 | return print("Segment not found") | |
733eb420 | 814 | end |
815 | end | |
816 | ||
817 | --- | |
818 | -- edit segment helper | |
819 | function editSegment(tag, index) | |
820 | local k,v | |
821 | local edit_possible="valid len RD WRP WRC Stamp Payload" | |
822 | if (istable(tag.SEG[index])) then | |
823 | for k,v in pairs(tag.SEG[index]) do | |
824 | if(string.find(edit_possible,k)) then | |
825 | tag.SEG[index][k]=tonumber(input(k..": ", v),10) | |
826 | end | |
827 | end | |
828 | else print("Segment with Index: "..("%02d"):format(index).." not found in Tag") | |
829 | return false | |
830 | end | |
831 | regenSegmentHeader(tag.SEG[index]) | |
832 | print("\n"..dumpSegment(tag, index).."\n") | |
833 | return tag | |
834 | end | |
835 | ||
836 | --- | |
837 | -- edit Segment Data | |
838 | function editSegmentData(data) | |
4e8fa8b4 | 839 | local lc=check4LegicCash(data) |
840 | io.write("\n") | |
733eb420 | 841 | if (istable(data)) then |
842 | for i=0, #data-1 do | |
843 | data[i]=input("Data"..i..": ", data[i]) | |
844 | end | |
4e8fa8b4 | 845 | if (lc) then data=fixLegicCash(data) end |
733eb420 | 846 | return data |
847 | else | |
848 | print("no Segment-Data found") | |
849 | end | |
850 | end | |
851 | ||
852 | --- | |
853 | -- list available segmets in virtual tag | |
854 | function segmentList(tag) | |
855 | local i | |
856 | local res = "" | |
857 | if (istable(tag.SEG[0])) then | |
858 | for i=0, #tag.SEG do | |
859 | res = res .. tag.SEG[i].index .. " " | |
860 | end | |
861 | return res | |
862 | else print("no Segments found in Tag") | |
863 | return false | |
864 | end | |
865 | end | |
866 | ||
867 | --- | |
868 | -- helper to selecting a segment | |
869 | function selectSegment(tag) | |
870 | local sel | |
7f0cb92e | 871 | if (istable(tag.SEG[0])) then |
733eb420 | 872 | print("availabe Segments:\n"..segmentList(tag)) |
873 | sel=input("select Segment: ", '00') | |
874 | sel=tonumber(sel,10) | |
875 | if (sel) then return sel | |
876 | else return '0' end | |
877 | else | |
878 | print("\nno Segments found") | |
879 | return false | |
880 | end | |
881 | end | |
882 | ||
883 | --- | |
884 | -- add segment | |
885 | function addSegment(tag) | |
886 | local i | |
887 | local segment={ | |
888 | ['index'] = '00', | |
889 | ['flag'] = 'c', | |
890 | ['valid'] = 1, | |
891 | ['last'] = 1, | |
892 | ['len'] = 13, | |
893 | ['raw'] = {'0d', '40', '04', '00'}, | |
894 | ['WRP'] = 4, | |
895 | ['WRC'] = 0, | |
896 | ['RD'] = 0, | |
897 | ['crc'] = '00', | |
898 | ['data'] = {}, | |
899 | ['kgh'] = false | |
900 | } | |
901 | if (istable(tag.SEG[0])) then | |
902 | tag.SEG[#tag.SEG].last=0 | |
903 | table.insert(tag.SEG, segment) | |
904 | for i=0, 8 do | |
905 | tag.SEG[#tag.SEG].data[i]=("%02x"):format(i) | |
906 | end | |
907 | tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG) | |
908 | return tag | |
909 | else | |
910 | print("no Segment-Table found") | |
911 | end | |
912 | end | |
913 | ||
914 | --- | |
7f0cb92e | 915 | -- delete segment (except segment 00) |
733eb420 | 916 | function delSegment(tag, index) |
7f0cb92e | 917 | if (istable(tag.SEG[0])) then |
733eb420 | 918 | local i |
919 | if (type(index)=="string") then index=tonumber(index,10) end | |
920 | if (index > 0) then | |
921 | table.remove(tag.SEG, index) | |
922 | for i=0, #tag.SEG do | |
923 | tag.SEG[i].index=("%02d"):format(i) | |
924 | end | |
925 | end | |
926 | if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end | |
927 | return tag | |
928 | end | |
7f0cb92e | 929 | end |
930 | ||
4e8fa8b4 | 931 | --- |
932 | -- return bytes 'sstrat' to 'send' from a table | |
933 | function dumpTable(tab, header, tstart, tend) | |
934 | res="" | |
935 | for i=tstart, tend do | |
936 | res=res..tab[i].." " | |
937 | end | |
938 | if (string.len(header)==0) then return res | |
939 | else return (header.." #"..(tend-tstart+1).."\n"..res) end | |
940 | end | |
941 | ||
942 | function dump3rdPartyCash1(tag , seg) | |
943 | local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2 | |
944 | local stamp=tag.SEG[seg].data[0].." "..tag.SEG[seg].data[1].." "..tag.SEG[seg].data[2] | |
945 | local datastamp=tag.SEG[seg].data[20].." "..tag.SEG[seg].data[21].." "..tag.SEG[seg].data[22] | |
946 | local balance=tonumber(tag.SEG[seg].data[32]..tag.SEG[seg].data[33] ,16) | |
947 | local balancecrc=utils.Crc8Legic(uid..tag.SEG[seg].data[32]..tag.SEG[seg].data[33]) | |
948 | local mirror=tonumber(tag.SEG[seg].data[35]..tag.SEG[seg].data[36] ,16) | |
949 | local mirrorcrc=utils.Crc8Legic(uid..tag.SEG[seg].data[35]..tag.SEG[seg].data[36]) | |
950 | local lastbal0=tonumber(tag.SEG[seg].data[39]..tag.SEG[seg].data[40] ,16) | |
951 | local lastbal1=tonumber(tag.SEG[seg].data[41]..tag.SEG[seg].data[42] ,16) | |
952 | local lastbal2=tonumber(tag.SEG[seg].data[43]..tag.SEG[seg].data[44] ,16) | |
953 | ||
954 | test="" | |
955 | -- display decoded/known stuff | |
956 | print("\n------------------------------") | |
957 | print("Tag-ID:\t\t "..uid) | |
958 | print("Stamp:\t\t "..stamp) | |
959 | print("UID-Mapping: \t\t"..("%06d"):format(tonumber(tag.SEG[seg].data[46]..tag.SEG[seg].data[47]..tag.SEG[seg].data[48], 16))) | |
960 | print("------------------------------") | |
961 | print("checksum 1:\t\t "..tag.SEG[seg].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 19, 30)), tag.SEG[seg].data[31])..")") | |
962 | print("checksum 2:\t\t "..tag.SEG[seg].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 32, 33)), tag.SEG[seg].data[34])..")") | |
963 | print("checksum 3:\t\t "..tag.SEG[seg].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 35, 36)), tag.SEG[seg].data[37])..")") | |
964 | ||
965 | print("checksum 4:\t\t "..tag.SEG[seg].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 46, 54)), tag.SEG[seg].data[55])..")") | |
966 | print("checksum 5:\t\t "..tag.SEG[seg].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 56, 61)), tag.SEG[seg].data[62])..")") | |
967 | print("checksum 6:\t\t "..tag.SEG[seg].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 63, 72)), tag.SEG[seg].data[73])..")") | |
968 | print("checksum 7:\t\t "..tag.SEG[seg].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 74, 88)), tag.SEG[seg].data[89])..")") | |
969 | print("------------------------------") | |
970 | print(string.format("Balance:\t\t %3.2f", balance/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[34])..")") | |
971 | print(string.format("Shadow:\t\t\t %3.2f", mirror/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[37])..")") | |
972 | print("------------------------------") | |
973 | print(string.format("History 1:\t\t %3.2f", lastbal0/100)) | |
974 | print(string.format("History 2:\t\t %3.2f", lastbal1/100)) | |
975 | print(string.format("History 3:\t\t %3.2f", lastbal2/100)) | |
976 | print("------------------------------\n") | |
977 | end | |
978 | --- | |
979 | -- compare two bytes | |
980 | function compareCrc(calc, guess) | |
981 | calc=("%02x"):format(calc) | |
982 | if (calc==guess) then return "valid" | |
983 | else return "error "..calc.."!="..guess end | |
984 | end | |
985 | ||
986 | --- | |
987 | -- compare 4 bytes | |
988 | function compareCrc16(calc, guess) | |
989 | calc=("%04x"):format(calc) | |
990 | if (calc==guess) then return "valid" | |
991 | else return "error "..calc.."!="..guess end | |
992 | end | |
993 | ||
994 | --- | |
995 | -- repair / fix (yet known) crc's of a '3rd Party Cash-Segment' | |
996 | -- not all bytes know until yet !! | |
997 | function fix3rdPartyCash1(uid, data) | |
998 | if(#data==95) then | |
999 | -- checksum 1 | |
1000 | data[31]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30))) | |
1001 | -- checksum 2 | |
1002 | data[34]=("%02x"):format(utils.Crc8Legic(uid..data[32]..data[33])) | |
1003 | -- checksum 3 | |
1004 | data[37]=("%02x"):format(utils.Crc8Legic(uid..data[35]..data[36])) | |
1005 | -- checksum 4 | |
1006 | data[55]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 46, 54))) | |
1007 | -- checksum 5 | |
1008 | data[62]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61))) | |
1009 | -- checksum 6 | |
1010 | data[73]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 63, 72))) | |
1011 | -- checksum 7 | |
1012 | data[89]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88))) | |
1013 | return data | |
1014 | end | |
1015 | end | |
1016 | ||
1017 | --- | |
1018 | -- edit uid 3rd party cash | |
1019 | function edit3rdUid(mapid, uid, data) | |
1020 | mapid=("%06x"):format(tonumber(mapid, 10)) | |
1021 | data[46]=string.sub(mapid, 0 ,2) | |
1022 | data[47]=string.sub(mapid, 3 ,4) | |
1023 | data[48]=string.sub(mapid, 5 ,6) | |
1024 | return fix3rdPartyCash1(uid, data) | |
1025 | end | |
1026 | ||
1027 | --- | |
1028 | -- edit balance 3rd party cash | |
1029 | function edit3rdCash(new_cash, uid, data) | |
1030 | new_cash=("%04x"):format(new_cash) | |
1031 | data[32]=string.sub(new_cash, 0,2) | |
1032 | data[33]=string.sub(new_cash, 3,4) | |
1033 | data[34]=("%02x"):format(utils.Crc8Legic(uid..new_cash)) | |
1034 | data[35]=string.sub(new_cash, 0,2) | |
1035 | data[36]=string.sub(new_cash, 3,4) | |
1036 | data[37]=("%02x"):format(utils.Crc8Legic(uid..new_cash)) | |
1037 | data[39]=string.sub(new_cash, 0,2) | |
1038 | data[40]=string.sub(new_cash, 3,4) | |
1039 | data[41]='00' | |
1040 | data[42]='00' | |
1041 | data[43]='00' | |
1042 | data[44]='00' | |
1043 | return fix3rdPartyCash1(uid, data) | |
1044 | end | |
1045 | ||
1046 | --- | |
1047 | -- repair / fix crc's of a 'Legic-Cash-Segment' | |
1048 | function fixLegicCash(data) | |
1049 | if(#data==32 and data[7]=="01") then | |
1050 | local crc1, crc2, crc3 | |
1051 | crc1=("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12))) | |
1052 | crc2=("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20))) | |
1053 | crc3=("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29))) | |
1054 | data[13]=string.sub(crc1, 0, 2) | |
1055 | data[14]=string.sub(crc1, 3, 4) | |
1056 | data[21]=string.sub(crc2, 0, 2) | |
1057 | data[22]=string.sub(crc2, 3, 4) | |
1058 | data[30]=string.sub(crc3, 0, 2) | |
1059 | data[31]=string.sub(crc3, 3, 4) | |
1060 | return data | |
1061 | end | |
1062 | end | |
1063 | ||
1064 | --- | |
1065 | -- chack for signature of a '3rd Party Cash-Segment' | |
1066 | -- not all bytes know until yet !! | |
1067 | function check43rdPartyCash1(uid, data) | |
1068 | if(#data==95) then | |
1069 | -- too explicit checking will avoid fixing ;-) | |
1070 | if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)), data[31])=="valid") then | |
1071 | --if (compareCrc(utils.Crc8Legic(uid..data[32]..data[33]), data[34])=="valid") then | |
1072 | --if (compareCrc(utils.Crc8Legic(uid..data[35]..data[36]), data[37])=="valid") then | |
1073 | --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)), data[62])=="valid") then | |
1074 | --if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)), data[89])=="valid") then | |
1075 | io.write("3rd Party Cash-Segment detected ") | |
1076 | return true | |
1077 | --end | |
1078 | --end | |
1079 | --end | |
1080 | --end | |
1081 | end | |
1082 | end | |
1083 | return false | |
1084 | end | |
1085 | ||
1086 | --- | |
1087 | -- chack for signature of a 'Legic-Cash-Segment' | |
1088 | function check4LegicCash(data) | |
1089 | if(#data==32) then | |
1090 | local stamp_len=(#data-25) | |
1091 | local stamp="" | |
1092 | for i=0, stamp_len-1 do | |
1093 | stamp=stamp..data[i].." " | |
1094 | end | |
1095 | if (data[7]=="01") then | |
1096 | if (("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12))) == data[13]..data[14]) then | |
1097 | if (("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20))) == data[21]..data[22]) then | |
1098 | if (("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29))) == data[30]..data[31]) then | |
1099 | io.write("Legic-Cash Segment detected ") | |
1100 | return true | |
1101 | end | |
1102 | end | |
1103 | end | |
1104 | end | |
1105 | end | |
1106 | return false | |
1107 | end | |
7f0cb92e | 1108 | --- |
1109 | -- calculate Master-Token crc | |
1110 | function calcMtCrc(bytes) | |
1111 | --print(#bytes) | |
1112 | local cmd=bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[7]..bytes[6]..bytes[8] | |
1113 | local len=(tonumber(0xfc ,10)-("%d"):format('0x'..bytes[7])) | |
1114 | for i=1, len do | |
1115 | cmd=cmd..bytes[8+i] | |
1116 | end | |
1117 | local res=("%02x"):format(utils.Crc8Legic(cmd)) | |
1118 | return res | |
1119 | end | |
1120 | ||
1121 | --- | |
1122 | -- check all segmnet-crc | |
1123 | function checkAllSegCrc(tag) | |
1124 | if (istable(tag.SEG[0])) then | |
1125 | for i=0, #tag.SEG do | |
1126 | crc=calcSegmentCrc(tag, i) | |
1127 | tag.SEG[i].crc=crc | |
1128 | end | |
1129 | else return print("Matser-Token / unsegmented Tag") end | |
1130 | end | |
1131 | ||
1132 | --- | |
1133 | -- check all segmnet-crc | |
1134 | function checkAllKghCrc(tag) | |
1135 | if (istable(tag.SEG[0])) then | |
1136 | for i=0, #tag.SEG do | |
1137 | crc=calcKghCrc(tag, i) | |
1138 | if (tag.SEG[i].kgh) then | |
1139 | tag.SEG[i].data[#tag.SEG[i].data-1]=crc | |
1140 | end | |
1141 | end | |
1142 | end | |
1143 | end | |
1144 | ||
1145 | --- | |
1146 | -- build kghCrc credentials | |
1147 | function kghCrcCredentials(tag, segid) | |
1148 | if (istable(tag) and istable(tag.SEG[0])) then | |
1149 | local x='00' | |
1150 | if (type(segid)=="string") then segid=tonumber(segid,10) end | |
1151 | if (segid>0) then x='93' end | |
1152 | local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP) | |
1153 | cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x | |
1154 | for i=0, #tag.SEG[segid].data-2 do | |
1155 | cred = cred..tag.SEG[segid].data[i] | |
1156 | end | |
1157 | return cred | |
1158 | end | |
1159 | end | |
1160 | ||
1161 | --- | |
1162 | -- validate kghCRC to segment in tag-table | |
1163 | function checkKghCrc(tag, segid) | |
1164 | if (type(tag.SEG[segid])=='table') then | |
1165 | if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then | |
1166 | local data=kghCrcCredentials(tag, segid) | |
1167 | if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end | |
1168 | else return false; end | |
1169 | else oops("'Kaba Group header' detected but no Segment-Data found") end | |
1170 | end | |
1171 | ||
1172 | --- | |
1173 | -- calcuate kghCRC for a given segment | |
1174 | function calcKghCrc(tag, segid) | |
1175 | if (istable(tag.SEG[0])) then | |
1176 | -- check if a 'Kaber Group Header' exists | |
1177 | local i | |
1178 | local data=kghCrcCredentials(tag, segid) | |
1179 | return ("%02x"):format(utils.Crc8Legic(data)) | |
1180 | end | |
1181 | end | |
1182 | ||
1183 | --- | |
1184 | -- build segmentCrc credentials | |
1185 | function segmentCrcCredentials(tag, segid) | |
1186 | if (istable(tag.SEG[0])) then | |
1187 | local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2 | |
1188 | cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4] | |
1189 | return cred | |
1190 | else return print("Master-Token / unsegmented Tag!") end | |
1191 | end | |
1192 | ||
1193 | --- | |
1194 | -- validate segmentCRC for a given segment | |
1195 | function checkSegmentCrc(tag, segid) | |
1196 | local data=segmentCrcCredentials(tag, segid) | |
1197 | if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then | |
1198 | return true | |
1199 | end | |
1200 | return false | |
1201 | end | |
1202 | ||
1203 | --- | |
1204 | -- calculate segmentCRC for a given segment | |
1205 | function calcSegmentCrc(tag, segid) | |
1206 | if (istable(tag.SEG[0])) then | |
1207 | -- check if a 'Kaber Group Header' exists | |
1208 | local data=segmentCrcCredentials(tag, segid) | |
1209 | return ("%02x"):format(utils.Crc8Legic(data)) | |
1210 | end | |
1211 | end | |
733eb420 | 1212 | |
1213 | --- | |
1214 | -- helptext for modify-mode | |
1215 | function modifyHelp() | |
1216 | local t=[[ | |
1217 | ||
4e8fa8b4 | 1218 | Data I/O Segment Manipulation Token-Data |
1219 | ----------------- -------------------- ----------------- | |
1220 | rt => read Tag as => add Segment mt => make Token | |
1221 | wt => write Tag es => edit Segment Header et => edit Token data | |
1222 | ct => copy io Tag ed => edit Segment Data tk => toggle KGH-Flag | |
7f0cb92e | 1223 | tc => copy oi Tag rs => remove Segment |
4e8fa8b4 | 1224 | tt => toggle Tag cc => check Segment-CRC File I/O |
1225 | di => dump inTag ck => check KGH ----------------- | |
1226 | do => dump outTag e3p => edit 3rd Party Cash lf => load File | |
1227 | ds => dump Segment sf => save File | |
1228 | dlc => dump Legic-Cash xf => xor to File | |
1229 | d3p => dump 3rd Party Cash | |
1230 | r3p => raw 3rd Party Cash | |
1231 | ||
1232 | ||
1233 | q => quit | |
733eb420 | 1234 | ]] |
1235 | return t | |
1236 | end | |
1237 | ||
4e8fa8b4 | 1238 | |
733eb420 | 1239 | --- |
1240 | -- modify Tag (interactive) | |
1241 | function modifyMode() | |
4e8fa8b4 | 1242 | local i, backupTAG, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes |
1243 | ||
733eb420 | 1244 | actions = { |
1245 | ["h"] = function(x) | |
4e8fa8b4 | 1246 | print(" Version: "..version); |
1247 | print(modifyHelp().."\n".."tags im Memory: "..(istable(inTAG) and ((currentTag=='inTAG') and "*mainTAG" or "mainTAG") or "").." "..(istable(backupTAG) and ((currentTag=='backupTAG') and "*backupTAG" or "backupTAG") or "")) | |
733eb420 | 1248 | end, |
4e8fa8b4 | 1249 | ["rt"] = function(x) |
1250 | inTAG=readFromPM3(); | |
1251 | --actions.di() | |
1252 | end, | |
733eb420 | 1253 | ["wt"] = function(x) |
7f0cb92e | 1254 | if(istable(inTAG.SEG)) then |
733eb420 | 1255 | local taglen=22 |
7f0cb92e | 1256 | if (istable(inTAG.Bck)) then |
733eb420 | 1257 | for i=0, #inTAG.SEG do |
1258 | taglen=taglen+inTAG.SEG[i].len+5 | |
1259 | end | |
7f0cb92e | 1260 | end |
4e8fa8b4 | 1261 | |
1262 | local uid_old=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
733eb420 | 1263 | -- read new tag (output tag) |
1264 | outTAG=readFromPM3() | |
1265 | outbytes=tagToBytes(outTAG) | |
1266 | -- copy 'inputbuffer' to 'outputbuffer' | |
1267 | inTAG.MCD = outbytes[1] | |
1268 | inTAG.MSN0 = outbytes[2] | |
1269 | inTAG.MSN1 = outbytes[3] | |
1270 | inTAG.MSN2 = outbytes[4] | |
1271 | inTAG.MCC = outbytes[5] | |
7f0cb92e | 1272 | -- recheck all segments-crc/kghcrc (only on a credential) |
1273 | if(istable(inTAG.Bck)) then | |
733eb420 | 1274 | checkAllSegCrc(inTAG) |
1275 | checkAllKghCrc(inTAG) | |
4e8fa8b4 | 1276 | local uid_new=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 |
1277 | for i=0, #inTAG.SEG do | |
1278 | if (check43rdPartyCash1(uid_old, inTAG.SEG[i].data)) then | |
1279 | io.write(" - fixing known checksums ... ") | |
1280 | inTAG.SEG[i].data=fix3rdPartyCash1(uid_new, inTAG.SEG[i].data) | |
1281 | io.write(" done\n") | |
1282 | end | |
1283 | end | |
7f0cb92e | 1284 | end |
733eb420 | 1285 | --get bytes from ready outTAG |
1286 | bytes=tagToBytes(inTAG) | |
7f0cb92e | 1287 | -- mater-token-crc |
1288 | if (inTAG.Type~="SAM") then bytes[22]=calcMtCrc(bytes) end | |
733eb420 | 1289 | if (bytes) then |
1290 | writeFile(bytes, 'MylegicClone.hex') | |
1291 | writeToTag(bytes, taglen, 'MylegicClone.hex') | |
7f0cb92e | 1292 | actions.rt('') |
733eb420 | 1293 | end |
1294 | end | |
1295 | end, | |
4e8fa8b4 | 1296 | --- |
1297 | -- switich and copy virtual tags | |
1298 | ||
733eb420 | 1299 | ["ct"] = function(x) |
4e8fa8b4 | 1300 | print("copy mainTAG to backupTAG") |
1301 | outTAG=deepCopy(inTAG) | |
1302 | backupTAG=deepCopy(inTAG) | |
733eb420 | 1303 | end, |
1304 | ["tc"] = function(x) | |
4e8fa8b4 | 1305 | print("copy backupTAG to mainTAG") |
1306 | inTAG=deepCopy(backupTAG) | |
1307 | end, | |
1308 | ["tt"] = function(x) | |
1309 | print("toggle to "..((currentTag=='inTAG') and "backupTAG" or "mainTAG")) | |
1310 | if(currentTag=="inTAG") then | |
1311 | outTAG=deepCopy(inTAG) | |
1312 | inTAG=deepCopy(backupTAG) | |
1313 | currentTag='backupTAG' | |
1314 | else | |
1315 | inTAG=deepCopy(outTAG) | |
1316 | currentTag='inTAG' | |
1317 | end | |
733eb420 | 1318 | end, |
1319 | ["lf"] = function(x) | |
7f0cb92e | 1320 | if (file_check(x)) then filename=x |
1321 | else filename=input("enter filename: ", "legic.temp") end | |
733eb420 | 1322 | inTAG=readFile(filename) |
1323 | end, | |
1324 | ["sf"] = function(x) | |
1325 | if(istable(inTAG)) then | |
1326 | outfile=input("enter filename:", "legic.temp") | |
1327 | bytes=tagToBytes(inTAG) | |
1328 | --bytes=xorBytes(bytes, inTAG.MCC) | |
1329 | if (bytes) then | |
1330 | writeFile(bytes, outfile) | |
1331 | end | |
1332 | end | |
1333 | end, | |
1334 | ["xf"] = function(x) | |
1335 | if(istable(inTAG)) then | |
1336 | outfile=input("enter filename:", "legic.temp") | |
1337 | crc=input("enter new crc: ('00' for a plain dump)", inTAG.MCC) | |
1338 | print("obfuscate witth: "..crc) | |
1339 | bytes=tagToBytes(inTAG) | |
1340 | bytes[5]=crc | |
1341 | if (bytes) then | |
1342 | writeFile(bytes, outfile) | |
1343 | end | |
1344 | end | |
1345 | end, | |
4e8fa8b4 | 1346 | ["di"] = function(x) |
1347 | if (istable(inTAG)) then | |
1348 | local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1349 | if(istable(inTAG.SEG[0])) then | |
1350 | for i=0, #inTAG.SEG do | |
1351 | if(check43rdPartyCash1(uid, inTAG.SEG[i].data)) then | |
1352 | io.write("in Segment index: "..inTAG.SEG[i].index.."\n") | |
1353 | elseif(check4LegicCash(inTAG.SEG[i].data)) then | |
1354 | io.write("in Segment index: "..inTAG.SEG[i].index.."\n") | |
1355 | lc=true; | |
1356 | lci=inTAG.SEG[i].index; | |
1357 | end | |
1358 | end | |
1359 | end | |
1360 | print("\n"..dumpTag(inTAG).."\n") | |
1361 | if (lc) then actions["dlc"](lci) end | |
1362 | end | |
1363 | end, | |
1364 | ["do"] = function(x) if (istable(backupTAG)) then print("\n"..dumpTag(backupTAG).."\n") end end, | |
733eb420 | 1365 | ["ds"] = function(x) |
7f0cb92e | 1366 | if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10) |
1367 | else sel=selectSegment(inTAG) end | |
733eb420 | 1368 | if (sel) then print("\n"..(dumpSegment(inTAG, sel) or "no Segments available").."\n") end |
1369 | end, | |
1370 | ["es"] = function(x) | |
7f0cb92e | 1371 | if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10) |
1372 | else sel=selectSegment(inTAG) end | |
733eb420 | 1373 | if (sel) then |
7f0cb92e | 1374 | if(istable(inTAG.SEG[0])) then |
733eb420 | 1375 | inTAG=editSegment(inTAG, sel) |
1376 | inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel]) | |
1377 | else print("no Segments in Tag") end | |
1378 | end | |
1379 | end, | |
1380 | ["as"] = function(x) | |
1381 | if (istable(inTAG.SEG[0])) then | |
1382 | inTAG=addSegment(inTAG) | |
1383 | inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1]) | |
1384 | inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG]) | |
7f0cb92e | 1385 | else print("Master-Token / unsegmented Tag!") |
733eb420 | 1386 | end |
1387 | end, | |
1388 | ["rs"] = function(x) | |
7f0cb92e | 1389 | if (istable(inTAG.SEG[0])) then |
1390 | if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10) | |
1391 | else sel=selectSegment(inTAG) end | |
733eb420 | 1392 | inTAG=delSegment(inTAG, sel) |
1393 | for i=0, #inTAG.SEG do | |
1394 | inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i]) | |
1395 | end | |
1396 | end | |
1397 | end, | |
1398 | ["ed"] = function(x) | |
7f0cb92e | 1399 | if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10) |
1400 | else sel=selectSegment(inTAG) end | |
733eb420 | 1401 | if (sel) then |
1402 | inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data) | |
1403 | end | |
1404 | end, | |
7f0cb92e | 1405 | ["et"] = function(x) |
1406 | if (istable(inTAG)) then | |
1407 | editTag(inTAG) | |
1408 | end | |
1409 | end, | |
1410 | ["mt"] = function(x) inTAG=makeToken(); actions.di() end, | |
733eb420 | 1411 | ["ts"] = function(x) |
7f0cb92e | 1412 | if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10) |
1413 | else sel=selectSegment(inTAG) end | |
733eb420 | 1414 | regenSegmentHeader(inTAG.SEG[sel]) |
1415 | end, | |
1416 | ["tk"] = function(x) | |
7f0cb92e | 1417 | if (istable(inTAG) and istable(inTAG.SEG[0])) then |
1418 | if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10) | |
1419 | else sel=selectSegment(inTAG) end | |
733eb420 | 1420 | if(inTAG.SEG[sel].kgh) then inTAG.SEG[sel].kgh=false |
1421 | else inTAG.SEG[sel].kgh=true end | |
7f0cb92e | 1422 | end |
733eb420 | 1423 | end, |
1424 | ["k"] = function(x) | |
7f0cb92e | 1425 | if (type(x)=="string" and string.len(x)>0) then |
733eb420 | 1426 | print(("%02x"):format(utils.Crc8Legic(x))) |
7f0cb92e | 1427 | end |
733eb420 | 1428 | end, |
4e8fa8b4 | 1429 | ["xb"] = function(x) |
1430 | end, | |
733eb420 | 1431 | ["xc"] = function(x) |
7f0cb92e | 1432 | if (istable(inTAG) and istable(inTAG.SEG[0])) then |
1433 | if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10) | |
1434 | else sel=selectSegment(inTAG) end | |
1435 | print("k "..kghCrcCredentials(inTAG, sel)) | |
1436 | end | |
733eb420 | 1437 | end, |
4e8fa8b4 | 1438 | ["dlc"] = function(x) |
1439 | local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1440 | -- if segment index was user defined | |
1441 | if (type(x)=="string" and string.len(x)>0) then | |
1442 | x=tonumber(x,10) | |
1443 | print(string.format("User-Selected Index %02d", x)) | |
1444 | -- or try to find match | |
1445 | else x=autoSelectSegment(inTAG, "legiccash") end | |
1446 | local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1447 | if (istable(inTAG.SEG[x])) then | |
1448 | io.write("in Segment "..inTAG.SEG[x].index.." :\n") | |
1449 | print("--------------------------------\n\tLegic-Cash Values\n--------------------------------") | |
1450 | local limit, curr, balance, rid, tcv | |
1451 | -- currency of balance & limit | |
1452 | curr=currency[inTAG.SEG[x].data[8]..inTAG.SEG[x].data[9]] | |
1453 | -- maximum balance | |
1454 | limit=string.format("%4.2f", tonumber(inTAG.SEG[x].data[10]..inTAG.SEG[x].data[11]..inTAG.SEG[x].data[12], 16)/100) | |
1455 | -- current balance | |
1456 | balance=string.format("%4.2f", tonumber(inTAG.SEG[x].data[15]..inTAG.SEG[x].data[16]..inTAG.SEG[x].data[17], 16)/100) | |
1457 | -- reader-id who wrote last transaction | |
1458 | rid=tonumber(inTAG.SEG[x].data[18]..inTAG.SEG[x].data[19]..inTAG.SEG[x].data[20], 16) | |
1459 | -- transaction counter value | |
1460 | tcv=tonumber(inTAG.SEG[x].data[29], 16) | |
1461 | print("Currency:\t\t "..curr) | |
1462 | print("Limit:\t\t\t "..limit) | |
1463 | print("Balance:\t\t "..balance) | |
1464 | print("Transaction Counter:\t "..tcv) | |
1465 | print("Reader-ID:\t\t "..rid.."\n--------------------------------\n") | |
1466 | end | |
1467 | --end | |
1468 | end, | |
1469 | ["df"] = function(x) | |
1470 | actions["lf"](x) | |
1471 | res="" | |
1472 | for i=0, #inTAG.SEG[1].data do | |
1473 | res=res..inTAG.SEG[1].data[i] | |
1474 | end | |
1475 | print(res) | |
1476 | end, | |
1477 | ["d3p"] = function(x) | |
1478 | local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1479 | -- if segment index was user defined | |
1480 | if (type(x)=="string" and string.len(x)>0) then | |
1481 | x=tonumber(x,10) | |
1482 | print(string.format("User-Selected Index %02d", x)) | |
1483 | -- or try to find match | |
1484 | else x=autoSelectSegment(inTAG, "3rdparty") end | |
1485 | ||
1486 | if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then | |
1487 | uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1488 | if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then | |
1489 | dump3rdPartyCash1(inTAG, x) | |
1490 | end | |
1491 | end | |
1492 | end, | |
1493 | ["r3p"] = function(x) | |
1494 | local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1495 | -- if segment index was user defined | |
1496 | if (type(x)=="string" and string.len(x)>0) then | |
1497 | x=tonumber(x,10) | |
1498 | print(string.format("User-Selected Index %02d", x)) | |
1499 | -- or try to find match | |
1500 | else x=autoSelectSegment(inTAG, "3rdparty") end | |
1501 | local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1502 | if (istable(inTAG.SEG[x])) then | |
1503 | print("\n\t\tStamp : "..dumpTable(inTAG.SEG[x].data, "", 0 , 2)) | |
1504 | print("\t\tBlock 0: "..dumpTable(inTAG.SEG[x].data, "", 3 , 18)) | |
1505 | print() | |
1506 | print("\t\tBlock 1: "..dumpTable(inTAG.SEG[x].data, "", 19, 30)) | |
1507 | print("checksum 1: Tag-ID .. Block 1 => LegicCrc8 = "..inTAG.SEG[x].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 19, 30)), inTAG.SEG[x].data[31])..")") | |
1508 | print() | |
1509 | print("\t\tBlock 2: "..dumpTable(inTAG.SEG[x].data, "", 32, 33)) | |
1510 | print("checksum 2: Block 2 => LegicCrc8 = "..inTAG.SEG[x].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 32, 33)), inTAG.SEG[x].data[34])..")") | |
1511 | print() | |
1512 | print("\t\tBlock 3: "..dumpTable(inTAG.SEG[x].data, "", 35, 36)) | |
1513 | print("checksum 3: Block 3 => LegicCrc8 = "..inTAG.SEG[x].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 35, 36)), inTAG.SEG[x].data[37])..")") | |
1514 | print() | |
1515 | print("\t\tyet unknown: "..inTAG.SEG[x].data[38]) | |
1516 | print() | |
1517 | print("\t\tHisatory 1: "..dumpTable(inTAG.SEG[x].data, "", 39, 40)) | |
1518 | print("\t\tHisatory 2: "..dumpTable(inTAG.SEG[x].data, "", 41, 42)) | |
1519 | print("\t\tHisatory 3: "..dumpTable(inTAG.SEG[x].data, "", 43, 44)) | |
1520 | print() | |
1521 | print("\t\tyet unknown: "..inTAG.SEG[x].data[45]) | |
1522 | print() | |
1523 | print("\t\tKGH-UID HEX: "..dumpTable(inTAG.SEG[x].data, "", 46, 48)) | |
1524 | print("\t\tBlock 4: "..dumpTable(inTAG.SEG[x].data, "", 49, 54)) | |
1525 | print("checksum 4: Tag-ID .. KGH-UID .. Block 4 => LegicCrc8 = "..inTAG.SEG[x].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 46, 54)), inTAG.SEG[x].data[55])..")") | |
1526 | print() | |
1527 | print("\t\tBlock 5: "..dumpTable(inTAG.SEG[x].data, "", 56, 61)) | |
1528 | print("checksum 5: Tag-ID .. Block 5 => LegicCrc8 = "..inTAG.SEG[x].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 56, 61)), inTAG.SEG[x].data[62])..")") | |
1529 | print() | |
1530 | print("\t\tBlock 6: "..dumpTable(inTAG.SEG[x].data, "", 63, 72)) | |
1531 | print("checksum 6: Tag-ID .. Block 6 => LegicCrc8 = "..inTAG.SEG[x].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 63, 72)), inTAG.SEG[x].data[73])..")") | |
1532 | print() | |
1533 | print("\t\tBlock 7: "..dumpTable(inTAG.SEG[x].data, "", 74, 88)) | |
1534 | print("checksum 7: Tag-ID .. Block 7 => LegicCrc8 = "..inTAG.SEG[x].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 74, 88)), inTAG.SEG[x].data[89])..")") | |
1535 | print() | |
1536 | print("\t\tBlock 8: "..dumpTable(inTAG.SEG[x].data, "", 90, 94)) | |
1537 | end | |
1538 | end, | |
1539 | ["e3p"] = function(x) | |
1540 | local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1541 | -- if segment index was user defined | |
1542 | if (type(x)=="string" and string.len(x)>0) then | |
1543 | x=tonumber(x,10) | |
1544 | print(string.format("User-Selected Index %02d", x)) | |
1545 | -- or try to find match | |
1546 | else x=autoSelectSegment(inTAG, "3rdparty") end | |
1547 | ||
1548 | if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then | |
1549 | --if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then | |
1550 | -- change Balance | |
1551 | if (confirm("\nedit Balance?")) then | |
1552 | local new_cash=input("enter new Balance without comma or currency", "100") | |
1553 | inTAG.SEG[x].data=edit3rdCash(new_cash, uid, inTAG.SEG[x].data) | |
1554 | end | |
1555 | -- change User-ID (used for online-account-mapping) | |
1556 | if (confirm("\nedit UserID-Mapping?")) then | |
1557 | local new_mapid=input("enter new UserID (6-digit value)", "012345") | |
1558 | inTAG.SEG[x].data=edit3rdUid(new_mapid, uid, inTAG.SEG[x].data) | |
1559 | end | |
1560 | if (confirm("\nedit Stamp?")) then | |
1561 | local new_stamp=input("enter new Stamp", getSegmentStamp(inTAG.SEG[x])) | |
1562 | inTAG.SEG[x].data=editStamp(new_stamp, uid, inTAG.SEG[x].data) | |
1563 | new_stamp=getSegmentStamp(inTAG.SEG[x], 'true') | |
1564 | print("stamp_bytes: "..#new_stamp) | |
1565 | -- replace stamp in 'block 1' also | |
1566 | io.write("editing stamp in Block 1 also ") | |
1567 | for i=20, (20+#new_stamp-1) do | |
1568 | inTAG.SEG[x].data[i]=new_stamp[i-19] | |
1569 | io.write("."); | |
1570 | end | |
1571 | print(" done") | |
1572 | -- fix known checksums | |
1573 | inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data) | |
1574 | end | |
1575 | ||
1576 | -- print out new settings | |
1577 | dump3rdPartyCash1(inTAG, x) | |
1578 | --end | |
1579 | end | |
1580 | end, | |
1581 | ["gs"] = function(x) | |
1582 | if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10) | |
1583 | else x=selectSegment(inTAG) end | |
1584 | local stamp=getSegmentStamp(inTAG.SEG[x]) | |
1585 | print("Stamp : "..stamp) | |
1586 | stamp=str2bytes(stamp) | |
1587 | print("lenght: "..#stamp) | |
1588 | end, | |
1589 | ["c6"] = function(x) local crc16=string.format("%4.04x", utils.Crc16(x)) | |
1590 | print(string.sub(crc16, 0,2).." "..string.sub(crc16, 3,4)) | |
1591 | end, | |
733eb420 | 1592 | ["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end, |
4e8fa8b4 | 1593 | ["cb"] = function(x) |
1594 | if (istable(inTAG)) then | |
1595 | print("purge BackupArea") | |
1596 | inTAG=clearBackupArea(inTAG) | |
1597 | end | |
1598 | end, | |
1599 | ["f3p"] = function(x) | |
1600 | if(type(x)=="string" and string.len(x)>=2) then x=tonumber(x, 10) | |
1601 | else x=selectSegment(inTAG) end | |
1602 | if (istable(inTAG.SEG[x])) then | |
1603 | local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2 | |
1604 | inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data) | |
1605 | end | |
1606 | end, | |
733eb420 | 1607 | ["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end, |
733eb420 | 1608 | } |
1609 | print("modify-modus! enter 'h' for help or 'q' to quit") | |
1610 | repeat | |
1611 | ic=input("Legic command? ('h' for help - 'q' for quit)", "h") | |
1612 | -- command actions | |
4e8fa8b4 | 1613 | if (type(actions[string.lower(string.sub(ic,0,3))])=='function') then |
1614 | actions[string.lower(string.sub(ic,0,3))](string.sub(ic,5)) | |
733eb420 | 1615 | elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then |
1616 | actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4)) | |
4e8fa8b4 | 1617 | elseif (type(actions[string.lower(string.sub(ic,0,1))])=='function') then |
1618 | actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3)) | |
7f0cb92e | 1619 | else actions.h('') end |
733eb420 | 1620 | until (string.sub(ic,0,1)=="q") |
1621 | end | |
1622 | ||
4e8fa8b4 | 1623 | function clearBackupArea(tag) |
1624 | for i=1, #tag.Bck do | |
1625 | tag.Bck[i]='00' | |
1626 | end | |
1627 | return tag | |
1628 | end | |
1629 | ||
1630 | function getSegmentStamp(seg, bytes) | |
1631 | local stamp="" | |
1632 | local stamp_len=7 | |
1633 | --- the 'real' stamp on MIM is not really easy to tell for me since the 'data-block' covers stamp0..stampn+data0..datan | |
1634 | -- there a no stamps longer than 7 bytes & they are write-protected by default , and I have not seen user-credntials | |
1635 | -- with stamps smaller 3 bytes (except: Master-Token) | |
1636 | -- WRP -> Read/Write Protection | |
1637 | -- WRC -> Read/Write Condition | |
1638 | -- RD depends on WRC - if WRC > 0 and RD=1: only reader with matching #WRC of Stamp-bytes in thier Database have Read-Access to the Tag | |
1639 | if (seg.WRP<7) then stamp_len=(seg.WRP) end | |
1640 | for i=1, (stamp_len) do | |
1641 | stamp=stamp..seg.data[i-1] | |
1642 | end | |
1643 | if (bytes) then | |
1644 | stamp=str2bytes(stamp) | |
1645 | return stamp | |
1646 | else return stamp end | |
1647 | end | |
1648 | ||
1649 | function str2bytes(s) | |
1650 | local res={} | |
1651 | if (string.len(s)%2~=0) then return print("stamp should be a even hexstring e.g.: deadbeef or 0badc0de") end | |
1652 | for i=1, string.len(s), 2 do | |
1653 | table.insert(res, string.sub(s,i,(i+1))) | |
1654 | end | |
1655 | return res | |
1656 | end | |
1657 | ||
1658 | function editStamp(new_stamp, uid, data) | |
1659 | local stamp=str2bytes(new_stamp) | |
1660 | for i=0, #stamp-1 do | |
1661 | data[i]=stamp[i+1] | |
1662 | end | |
1663 | -- now fill in stamp | |
1664 | for i=0, (string.len(new_stamp)/2)-1 do | |
1665 | data[i]=stamp[i+1] | |
1666 | end | |
1667 | return fix3rdPartyCash1(uid, data) | |
1668 | end | |
1669 | ||
1670 | function autoSelectSegment(tag, s) | |
1671 | local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2 | |
1672 | local x=#tag.SEG+1 | |
1673 | local res = false | |
1674 | io.write("autoSelect ") | |
1675 | --- search for desired segment-type | |
1676 | -- 3rd Party Segment | |
1677 | if (s=="3rdparty") then | |
1678 | repeat | |
1679 | io.write(". ") | |
1680 | x=x-1 | |
1681 | res=check43rdPartyCash1(uid, tag.SEG[x].data) | |
1682 | until ( res or x==0 ) | |
1683 | end | |
1684 | -- Legic-Cash Segment | |
1685 | if (s=="legiccash") then | |
1686 | repeat | |
1687 | io.write(". ") | |
1688 | x=x-1 | |
1689 | res=check4LegicCash(tag.SEG[x].data) | |
1690 | until ( res or x==0 ) | |
1691 | end | |
1692 | --- | |
1693 | -- segment found | |
1694 | if (res) then | |
1695 | io.write("\nautoselected Index: "..string.format("%02d", x).."\n") | |
1696 | return x | |
1697 | end | |
1698 | --- | |
1699 | -- nothing found | |
1700 | io.write("no Segment found\n") | |
1701 | return -1 | |
1702 | end | |
1703 | ||
7f0cb92e | 1704 | --- main function |
733eb420 | 1705 | function main(args) |
1706 | if (#args == 0 ) then modifyMode() end | |
1707 | --- variables | |
4e8fa8b4 | 1708 | local inTAG, backupTAG, outTAG, outfile, interactive, crc, ofs, cfs, dfs |
733eb420 | 1709 | -- just a spacer for better readability |
1710 | print() | |
1711 | --- parse arguments | |
1712 | for o, a in getopt.getopt(args, 'hrmi:do:c:') do | |
1713 | -- display help | |
1714 | if o == "h" then return help(); end | |
1715 | -- read tag from PM3 | |
1716 | if o == "r" then inTAG=readFromPM3() end | |
1717 | -- input file | |
1718 | if o == "i" then inTAG=readFile(a) end | |
1719 | -- dump virtual-Tag | |
1720 | if o == "d" then dfs=true end | |
1721 | -- interacive modifying | |
1722 | if o == "m" then interactive=true; modifyMode() end | |
1723 | -- xor (e.g. for clone or plain file) | |
1724 | if o == "c" then cfs=true; crc=a end | |
1725 | -- output file | |
1726 | if o == "o" then outfile=a; ofs=true end | |
1727 | end | |
1728 | ||
1729 | -- file conversion (output to file) | |
1730 | if (ofs) then | |
1731 | -- dump infile / tag-read | |
1732 | if (dfs) then | |
1733 | print("-----------------------------------------") | |
1734 | print(dumpTag(inTAG)) | |
1735 | end | |
1736 | bytes=tagToBytes(inTAG) | |
733eb420 | 1737 | if (cfs) then |
7f0cb92e | 1738 | -- xor willl be done in function writeFile |
1739 | -- with the value of byte[5] | |
733eb420 | 1740 | bytes[5]=crc |
1741 | end | |
1742 | -- write to outfile | |
1743 | if (bytes) then | |
1744 | writeFile(bytes, outfile) | |
7f0cb92e | 1745 | --- read real tag into virtual tag |
1746 | -- inTAG=readFromPM3() end | |
1747 | --- or simply use the bytes that where wriiten | |
733eb420 | 1748 | inTAG=bytesToTag(bytes, inTAG) |
1749 | -- show new content | |
1750 | if (dfs) then | |
1751 | print("-----------------------------------------") | |
7f0cb92e | 1752 | print(dumpTag(inTAG)) |
733eb420 | 1753 | end |
1754 | end | |
1755 | end | |
1756 | ||
1757 | end | |
1758 | ||
4e8fa8b4 | 1759 | -- Creates a complete/deep copy of the data |
1760 | function deepCopy(object) | |
1761 | local lookup_table = {} | |
1762 | local function _copy(object) | |
1763 | if type(object) ~= "table" then | |
1764 | return object | |
1765 | elseif lookup_table[object] then | |
1766 | return lookup_table[object] | |
1767 | end | |
1768 | local new_table = {} | |
1769 | lookup_table[object] = new_table | |
1770 | for index, value in pairs(object) do | |
1771 | new_table[_copy(index)] = _copy(value) | |
1772 | end | |
1773 | return setmetatable(new_table, getmetatable(object)) | |
1774 | end | |
1775 | return _copy(object) | |
1776 | end | |
1777 | ||
733eb420 | 1778 | --- start |
1779 | main(args) |