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 | +----+----+----+ |
14 | --]] |
15 | |
16 | example = "script run legic" |
17 | author = "Mosci" |
18 | desc = |
19 | [[ |
20 | |
21 | This script helps you to read, create and modify Legic Prime Tags (MIM22, MIM256, MIM1024) |
22 | it's kinda interactive with following commands in three categories: |
23 | |
24 | Data I/O Segment Manipulation File I/O |
25 | ------------------ -------------------- --------------- |
26 | rt => read Tag ds => dump Segments lf => load File |
27 | wt => write Tag as => add Segment sf => save File |
28 | ct => copy io Tag es => edit Segment xf => xor File |
29 | tc => copy oi Tag ed => edit Data |
30 | di => dump inTag rs => remove Segment |
31 | do => dump outTag cc => check Segment-CRC |
32 | ck => check KGH |
33 | tk => toggle KGH-Flag |
34 | q => quit xc => get KGH-Str h => this Help |
35 | |
36 | Data I/O |
37 | rt: 'read tag' - reads a tag placed near to the PM3 |
38 | wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3 |
39 | without the need of changing anything - MCD,MSN,MCC will be read from the tag |
40 | before and applied to the output. |
41 | ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not usefull yet, but inernally needed |
42 | tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not usefull yet, but inernally needed |
43 | di: 'dump inTag' - shows the current content of the 'virtual Tag' |
44 | do: 'dump outTag' - shows the current content of the 'virtual outTag' |
45 | |
46 | Segment Manipulation |
47 | (all manipulations happens only in the 'virtual inTAG' - they need to be written with 'wt' to take effect) |
48 | ds: 'dump Segments' - will show the content of a selected Segment |
49 | as: 'add Segment' - will add a 'empty' Segment to the inTag |
50 | es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid) |
51 | all other Segment-Header-Values are either calculated or not needed to edit (yet) |
52 | ed: 'edit data' - edit the Data of a Segment (Stamp & Payload) |
53 | rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token) |
54 | cc: 'check Segment-CRC'- checks & calculates (if check failed) the Segment-CRC of all Segments |
55 | ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected |
56 | 'Kaba Group Header CRC calculation' |
57 | tk: 'toggle KGH' - toglle the (script-internal) flag for kgh-calculation for a segment |
58 | xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment |
59 | |
60 | Input/Output |
61 | lf: 'load file' - load a (xored) file from the local Filesystem into the 'virtual inTag' |
62 | sf: 'save file' - saves the 'virtual inTag' to the local Filesystem (xored with Tag-MCC) |
63 | xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with choosen MCC - use '00' for plain values) |
64 | |
65 | ]] |
66 | |
67 | --- requirements |
68 | local utils = require('utils') |
69 | local getopt = require('getopt') |
70 | |
71 | --- global variables / defines |
72 | local bxor = bit32.bxor |
73 | local bbit = bit32.extract |
74 | local input = utils.input |
75 | local confirm = utils.confirm |
76 | |
77 | --- Error-Handling & Helper |
78 | -- This is only meant to be used when errors occur |
79 | function oops(err) |
80 | print("ERROR: ",err) |
81 | return nil, err |
82 | end |
83 | |
84 | --- |
85 | -- Usage help |
86 | function help() |
87 | print(desc) |
88 | print("Example usage") |
89 | print(example) |
90 | end |
91 | |
92 | --- |
93 | -- table check helper |
94 | function istable(t) |
95 | return type(t) == 'table' |
96 | end |
97 | |
98 | --- |
99 | -- put certain bytes into a new table |
100 | function bytesToTable(bytes, bstart, bend) |
101 | local t={} |
102 | for i=0, (bend-bstart) do |
103 | t[i]=bytes[bstart+i] |
104 | end |
105 | return t |
106 | end |
107 | |
108 | --- |
109 | -- xor byte (if addr >= 0x22 - start counting from 1 => 23) |
110 | function xorme(hex, xor, index) |
111 | if ( index >= 23 ) then |
112 | return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) )) |
113 | else |
114 | return hex |
115 | end |
116 | end |
117 | |
118 | --- |
119 | -- (de)obfuscate bytes |
120 | function xorBytes(inBytes, crc) |
121 | local bytes = {} |
122 | for index = 1, #inBytes do |
123 | bytes[index] = xorme(inBytes[index], crc, index) |
124 | end |
125 | if (#inBytes == #bytes) then |
126 | -- replace crc |
127 | bytes[5] = string.sub(crc,-2) |
128 | return bytes |
129 | else |
130 | print("error: byte-count missmatch") |
131 | return false |
132 | end |
133 | end |
134 | |
135 | --- |
136 | -- check availability of file |
137 | function file_check(file_name) |
138 | local file_found=io.open(file_name, "r") |
139 | if file_found==nil then |
140 | return false |
141 | else |
142 | return true |
143 | end |
144 | end |
145 | |
146 | --- |
147 | -- read file into virtual-tag |
148 | function readFile(filename) |
149 | local bytes = {} |
150 | local tag = {} |
151 | if (file_check(filename)==false) then |
152 | return oops("input file: "..filename.." not found") |
153 | else |
154 | bytes = getInputBytes(filename) |
155 | if (bytes == false) then return oops('couldnt get input bytes') |
156 | else |
157 | -- make plain bytes |
158 | bytes = xorBytes(bytes,bytes[5]) |
159 | -- create Tag for plain bytes |
160 | tag=createTagTable() |
161 | -- load plain bytes to tag-table |
162 | tag=bytesToTag(bytes, tag) |
163 | end |
164 | end |
165 | return tag |
166 | end |
167 | |
168 | --- |
169 | -- write bytes to file |
170 | -- write to file |
171 | function writeFile(bytes, filename) |
172 | if (filename~='MylegicClone.hex') then |
173 | if (file_check(filename)) then |
174 | local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?") |
175 | if (answer==false) then return print("user abort") end |
176 | end |
177 | end |
178 | local line |
179 | local bcnt=0 |
180 | local fho,err = io.open(filename, "w") |
181 | if err then oops("OOps ... faild to open output-file ".. filename) end |
182 | bytes=xorBytes(bytes, bytes[5]) |
183 | for i = 1, #bytes do |
184 | if (bcnt == 0) then |
185 | line=bytes[i] |
186 | elseif (bcnt <= 7) then |
187 | line=line.." "..bytes[i] |
188 | end |
189 | if (bcnt == 7) then |
190 | -- write line to new file |
191 | fho:write(line.."\n") |
192 | -- reset counter & line |
193 | bcnt=-1 |
194 | line="" |
195 | end |
196 | bcnt=bcnt+1 |
197 | end |
198 | fho:close() |
199 | print("\nwrote ".. #bytes .." bytes to " .. filename) |
200 | return true |
201 | end |
202 | |
203 | --- |
204 | -- read from pm3 into virtual-tag |
205 | function readFromPM3() |
206 | local tag, bytes, infile |
207 | --if (confirm("is the Tag placed onto the Proxmak3 and ready for reading?")) then |
208 | --print("reading Tag ...") |
209 | --infile=input("input a name for the temp-file:", "legic.temp") |
210 | --if (file_check(infile)) then |
211 | -- local answer = confirm("\nthe output-file "..infile.." alredy exists!\nthis will delete the previous content!\ncontinue?") |
212 | -- if (answer==false) then return print("user abort") end |
213 | --end |
214 | infile="legic.temp" |
215 | core.console("hf legic reader") |
216 | core.console("hf legic save "..infile) |
217 | --print("read temp-file into virtual Tag ...") |
218 | tag=readFile(infile) |
219 | return tag |
220 | --else return print("user abort"); end |
221 | end |
222 | |
223 | --- |
224 | -- read file into table |
225 | function getInputBytes(infile) |
226 | local line |
227 | local bytes = {} |
228 | local fhi,err = io.open(infile) |
229 | if err then oops("faild to read from file ".. infile); return false; end |
230 | while true do |
231 | line = fhi:read() |
232 | if line == nil then break end |
233 | for byte in line:gmatch("%w+") do |
234 | table.insert(bytes, byte) |
235 | end |
236 | end |
237 | fhi:close() |
238 | print(#bytes .. " bytes from "..infile.." loaded") |
239 | return bytes |
240 | end |
241 | |
242 | --- |
243 | -- read Tag-Table in bytes-table |
244 | function tagToBytes(tag) |
245 | if (istable(tag)) then |
246 | local bytes = {} |
247 | local i, i2 |
248 | -- main token-data |
249 | table.insert(bytes, tag.MCD) |
250 | table.insert(bytes, tag.MSN0) |
251 | table.insert(bytes, tag.MSN1) |
252 | table.insert(bytes, tag.MSN2) |
253 | table.insert(bytes, tag.MCC) |
254 | table.insert(bytes, tag.DCFl) |
255 | table.insert(bytes, tag.DCFh) |
256 | table.insert(bytes, tag.raw) |
257 | table.insert(bytes, tag.SSC) |
258 | -- raw token data |
259 | for i=0, #tag.data do |
260 | table.insert(bytes, tag.data[i]) |
261 | end |
262 | -- backup data |
263 | for i=0, #tag.Bck do |
264 | table.insert(bytes, tag.Bck[i]) |
265 | end |
266 | -- token-create-time / master-token crc |
267 | for i=0, #tag.MTC do |
268 | table.insert(bytes, tag.MTC[i]) |
269 | end |
270 | -- process segments |
271 | if (type(tag.SEG[0])=='table') then |
272 | for i=0, #tag.SEG do |
273 | for i2=1, #tag.SEG[i].raw+1 do |
274 | table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2]) |
275 | end |
276 | table.insert(bytes, #bytes+1, tag.SEG[i].crc) |
277 | for i2=0, #tag.SEG[i].data-1 do |
278 | table.insert(bytes, #bytes+1, tag.SEG[i].data[i2]) |
279 | end |
280 | end |
281 | end |
282 | -- fill with zeros |
283 | for i=#bytes+1, 1024 do |
284 | table.insert(bytes, i, '00') |
285 | end |
286 | print(#bytes.." bytes of Tag dumped") |
287 | return bytes |
288 | end |
289 | return oops("tag is no table in tagToBytes ("..type(tag)..")") |
290 | end |
291 | |
292 | --- virtual TAG functions |
293 | -- create tag-table helper |
294 | function createTagTable() |
295 | local t={ |
296 | ['MCD'] = '00', |
297 | ['MSN0']= '11', |
298 | ['MSN1']= '22', |
299 | ['MSN2']= '33', |
300 | ['MCC'] = 'cc', |
301 | ['DCFl']= 'ff', |
302 | ['DCFh']= 'ff', |
303 | ['Type']= 'GAM', |
304 | ['OLE'] = 0, |
305 | ['Stamp_len']= 18, |
306 | ['WRP'] = '00', |
307 | ['WRC'] = '00', |
308 | ['RD'] = '00', |
309 | ['raw'] = '9f', |
310 | ['SSC'] = 'ff', |
311 | ['data']= {}, |
312 | ['bck'] = {}, |
313 | ['MTC'] = {}, |
314 | ['SEG'] = {} |
315 | } |
316 | return t |
317 | end |
318 | |
319 | --- |
320 | -- put bytes into tag-table |
321 | function bytesToTag(bytes, tag) |
322 | if(istable(tag)) then |
323 | tag.MCD =bytes[1]; |
324 | tag.MSN0=bytes[2]; |
325 | tag.MSN1=bytes[3]; |
326 | tag.MSN2=bytes[4]; |
327 | tag.MCC =bytes[5]; |
328 | tag.DCFl=bytes[6]; |
329 | tag.DCFh=bytes[7]; |
330 | tag.raw =bytes[8]; |
331 | tag.SSC =bytes[9]; |
332 | tag.Type=getTokenType(tag.DCFl); |
333 | tag.OLE=bbit("0x"..tag.DCFl,7,1) |
334 | tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4)) |
335 | tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3)) |
336 | tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1)) |
337 | tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10)) |
338 | tag.data=bytesToTable(bytes, 10, 13) |
339 | tag.Bck=bytesToTable(bytes, 14, 20) |
340 | tag.MTC=bytesToTable(bytes, 21, 22) |
341 | |
342 | print("Tag-Type: ".. tag.Type) |
343 | if (tag.Type=="SAM" and #bytes>23) then |
344 | tag=segmentsToTag(bytes, tag) |
345 | print((#tag.SEG+1).." Segment(s) found") |
346 | end |
347 | print(#bytes.." bytes for Tag processed") |
348 | return tag |
349 | end |
350 | return oops("tag is no table in: bytesToTag ("..type(tag)..")") |
351 | end |
352 | |
353 | --- |
354 | -- dump tag-system area |
355 | function dumpCDF(tag) |
356 | local res="" |
357 | local i=0 |
358 | local raw="" |
359 | if (istable(tag)) then |
360 | res = res.."MCD: "..tag.MCD..", MSN: "..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..", MCC: "..tag.MCC.."\n" |
361 | res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n" |
362 | res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..", SSC="..tag.SSC.."\n" |
363 | |
364 | -- credential |
365 | if (tag.raw..tag.SSC=="9fff") then |
366 | res = res.."Remaining Header Area\n" |
367 | for i=0, (#tag.data) do |
368 | res = res..tag.data[i].." " |
369 | end |
370 | res = res.."\nBackup Area\n" |
371 | for i=0, (#tag.Bck) do |
372 | res = res..tag.Bck[i].." " |
373 | end |
374 | res = res.."\nTime Area\n" |
375 | for i=0, (#tag.MTC) do |
376 | res = res..tag.MTC[i].." " |
377 | end |
378 | |
379 | -- Master Token |
380 | else |
381 | res = res .."Master-Token Area\n" |
382 | for i=0, (#tag.data) do |
383 | res = res..tag.data[i].." " |
384 | end |
385 | for i=0, (#tag.Bck) do |
386 | res = res..tag.Bck[i].." " |
387 | end |
388 | for i=0, (#tag.MTC-1) do |
389 | res = res..tag.MTC[i].." " |
390 | end |
391 | res = res .. " MT-CRC: "..tag.MTC[1] |
392 | end |
393 | return res |
394 | else print("no valid Tag in dumpCDF") end |
395 | end |
396 | |
397 | --- |
398 | -- dump single segment |
399 | function dumpSegment(tag, index) |
400 | local i=index |
401 | local i2 |
402 | local dp=0 --data-position in table |
403 | local res="" --result |
404 | local raw="" --raw-header |
405 | -- segment |
406 | if ( (istable(tag.SEG[i])) and tag.Type=="SAM") then |
407 | if (istable(tag.SEG[i].raw)) then |
408 | for k,v in pairs(tag.SEG[i].raw) do |
409 | raw=raw..v.." " |
410 | end |
411 | end |
412 | |
413 | -- segment header |
414 | res = res.."Segment "..("%02d"):format(tag.SEG[i].index)..": " |
415 | 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).."), " |
416 | res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", " |
417 | res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." " |
418 | res = res .."("..(checkSegmentCrc(tag, i) and "valid" or "error")..")" |
419 | raw="" |
420 | |
421 | -- WRC protected |
422 | if (tag.SEG[i].WRC>0) then |
423 | res = res .."\nWRC protected area (Stamp):\n" |
424 | for i2=dp, tag.SEG[i].WRC-1 do |
425 | res = res..tag.SEG[i].data[dp].." " |
426 | dp=dp+1 |
427 | end |
428 | end |
429 | |
430 | -- WRP mprotected |
431 | if (tag.SEG[i].WRP>tag.SEG[i].WRC) then |
432 | res = res .."\nRemaining write protected area (Stamp):\n" |
433 | for i2=dp, tag.SEG[i].WRP-tag.SEG[i].WRC-1 do |
434 | res = res..tag.SEG[i].data[dp].." " |
435 | dp=dp+1 |
436 | end |
437 | end |
438 | |
439 | -- payload |
440 | if (#tag.SEG[i].data-dp>0) then |
441 | res = res .."\nRemaining segment payload:\n" |
442 | for i2=dp, #tag.SEG[i].data-2 do |
443 | res = res..tag.SEG[i].data[dp].." " |
444 | dp=dp+1 |
445 | end |
446 | if (tag.SEG[i].kgh) then |
447 | res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and "valid" or "error")..")" |
448 | else res = res..tag.SEG[i].data[dp] end |
449 | end |
450 | dp=0 |
451 | return res |
452 | else |
453 | return print("Segment not found") |
454 | end |
455 | end |
456 | |
457 | --- |
458 | -- check all segmnet-crc |
459 | function checkAllSegCrc(tag) |
460 | for i=0, #tag.SEG do |
461 | crc=calcSegmentCrc(tag, i) |
462 | tag.SEG[i].crc=crc |
463 | end |
464 | end |
465 | |
466 | --- |
467 | -- check all segmnet-crc |
468 | function checkAllKghCrc(tag) |
469 | for i=0, #tag.SEG do |
470 | crc=calcKghCrc(tag, i) |
471 | if (tag.SEG[i].kgh) then |
472 | tag.SEG[i].data[#tag.SEG[i].data-1]=crc |
473 | end |
474 | end |
475 | end |
476 | |
477 | --- |
478 | -- dump virtual Tag-Data |
479 | function dumpTag(tag) |
480 | local i, i2 |
481 | local res |
482 | local dp=0 |
483 | local raw="" |
484 | -- sytstem area |
485 | res ="\nCDF: System Area" |
486 | res= res.."\n"..dumpCDF(tag) |
487 | -- segments (user area) |
488 | if(istable(tag.SEG[0])) then |
489 | res = res.."\n\nADF: User Area" |
490 | for i=0, #tag.SEG do |
491 | res=res.."\n"..dumpSegment(tag, i).."\n" |
492 | end |
493 | end |
494 | return res |
495 | end |
496 | |
497 | --- |
498 | -- determine TagType (bits 0..6 of DCFlow) |
499 | function getTokenType(DCFl) |
500 | --[[ |
501 | 0x00–0x2f IAM |
502 | 0x30–0x6f SAM |
503 | 0x70–0x7f GAM |
504 | ]]-- |
505 | local tt = tonumber(bbit("0x"..DCFl,0,7),10) |
506 | if (tt >= 0 and tt <= 47) then tt = "IAM" |
507 | elseif (tt == 49) then tt = "SAM63" |
508 | elseif (tt == 48) then tt = "SAM64" |
509 | elseif (tt >= 50 and tt <= 111) then tt = "SAM" |
510 | elseif (tt >= 112 and tt <= 127) then tt = "GAM" |
511 | else tt = "???" end |
512 | return tt |
513 | end |
514 | |
515 | --- |
516 | -- regenerate segment-header (after edit) |
517 | function regenSegmentHeader(segment) |
518 | local seg=segment |
519 | local raw = segment.raw |
520 | local i |
521 | -- len bit0..7 | len=12bit=low nibble of byte1..byte0 |
522 | raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8)) |
523 | -- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh? |
524 | raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),4.4)..bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8)) |
525 | -- WRP |
526 | raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8)) |
527 | -- WRC + RD |
528 | raw[4]=("%02x"):format(bbit("0x"..("%03x"):format(seg.WRC),4,3)..bbit("0x"..("%02x"):format(seg.RD*128),0,8)) |
529 | -- flag |
530 | seg.flag=string.sub(raw[2],0,1) |
531 | --print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4]) |
532 | if(#seg.data>(seg.len-5)) then |
533 | print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5) |
534 | print("Data-Length has being reduced: removing ".. #seg.data-(seg.len-5) .." bytes from Payload"); |
535 | for i=(seg.len-5), #seg.data-1 do |
536 | table.remove(seg.data) |
537 | end |
538 | elseif (#seg.data<(seg.len-5)) then |
539 | print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5) |
540 | print("Data-Length has being extended: adding "..(seg.len-5)-#seg.data.." bytes to Payload"); |
541 | for i=#seg.data, (seg.len-5)-1 do |
542 | table.insert(seg.data, '00') |
543 | end |
544 | end |
545 | return seg |
546 | end |
547 | |
548 | --- |
549 | -- get segmemnt-data from byte-table |
550 | function getSegmentData(bytes, start, index) |
551 | local segment={ |
552 | ['index'] = '00', |
553 | ['flag'] = 'c', |
554 | ['valid'] = 0, |
555 | ['last'] = 0, |
556 | ['len'] = 13, |
557 | ['raw'] = {'00', '00', '00', '00'}, |
558 | ['WRP'] = 4, |
559 | ['WRC'] = 0, |
560 | ['RD'] = 0, |
561 | ['crc'] = '00', |
562 | ['data'] = {}, |
563 | ['kgh'] = false |
564 | } |
565 | if (bytes[start]) then |
566 | local i |
567 | -- #index |
568 | segment.index = index |
569 | -- flag = high nibble of byte 1 |
570 | segment.flag = string.sub(bytes[start+1],0,1) |
571 | -- valid = bit 6 of byte 1 |
572 | segment.valid = bbit("0x"..bytes[start+1],6,1) |
573 | -- last = bit 7 of byte 1 |
574 | segment.last = bbit("0x"..bytes[start+1],7,1) |
575 | -- len = (byte 0)+(bit0-3 of byte 1) |
576 | segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16) |
577 | -- raw segment header |
578 | segment.raw = {bytes[start], bytes[start+1], bytes[start+2], bytes[start+3]} |
579 | -- wrp (write proteted) = byte 2 |
580 | segment.WRP = tonumber(bytes[start+2],16) |
581 | -- wrc (write control) - bit 4-6 of byte 3 |
582 | segment.WRC = tonumber(bbit("0x"..bytes[start+3],4,3),16) |
583 | -- rd (read disabled) - bit 7 of byte 3 |
584 | segment.RD = tonumber(bbit("0x"..bytes[start+3],7,1),16) |
585 | -- crc byte 4 |
586 | segment.crc = bytes[start+4] |
587 | -- segment-data starts at segment.len - segment.header - segment.crc |
588 | for i=0, (segment.len-5) do |
589 | segment.data[i]=bytes[start+5+i] |
590 | end |
591 | return segment |
592 | else return false; |
593 | end |
594 | end |
595 | |
596 | --- |
597 | -- put segments from byte-table to tag-table |
598 | function segmentsToTag(bytes, tag) |
599 | if(#bytes>23) then |
600 | local start=23 |
601 | local i=-1 |
602 | if (istable(tag)) then |
603 | repeat |
604 | i=i+1 |
605 | tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i)) |
606 | if (tag.Type=="SAM") then |
607 | if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end |
608 | end |
609 | start=start+tag.SEG[i].len |
610 | until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126) |
611 | return tag |
612 | else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end |
613 | else print("no Segments: must be a MIM22") end |
614 | end |
615 | |
616 | --- CRC calculation and validation |
617 | -- build kghCrc credentials |
618 | function kghCrcCredentials(tag, segid) |
619 | local x='00' |
620 | if (type(segid)=="string") then segid=tonumber(segid,10) end |
621 | if (segid>0) then x='93' end |
622 | local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP) |
623 | cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x |
624 | for i=0, #tag.SEG[segid].data-2 do |
625 | cred = cred..tag.SEG[segid].data[i] |
626 | end |
627 | return cred |
628 | end |
629 | |
630 | --- |
631 | -- validate kghCRC to segment in tag-table |
632 | function checkKghCrc(tag, segid) |
633 | if (type(tag.SEG[segid])=='table') then |
634 | if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then |
635 | local data=kghCrcCredentials(tag, segid) |
636 | if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end |
637 | else return false; end |
638 | else oops("'Kaba Group header' detected but no Segment-Data found") end |
639 | end |
640 | |
641 | --- |
642 | -- calcuate kghCRC for a given segment |
643 | function calcKghCrc(tag, segid) |
644 | -- check if a 'Kaber Group Header' exists |
645 | local i |
646 | local data=kghCrcCredentials(tag, segid) |
647 | return ("%02x"):format(utils.Crc8Legic(data)) |
648 | end |
649 | |
650 | --- |
651 | -- build segmentCrc credentials |
652 | function segmentCrcCredentials(tag, segid) |
653 | local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2 |
654 | cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4] |
655 | return cred |
656 | end |
657 | |
658 | --- |
659 | -- validate segmentCRC for a given segment |
660 | function checkSegmentCrc(tag, segid) |
661 | local data=segmentCrcCredentials(tag, segid) |
662 | if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then |
663 | return true |
664 | end |
665 | return false |
666 | end |
667 | |
668 | --- |
669 | -- calculate segmentCRC for a given segment |
670 | function calcSegmentCrc(tag, segid) |
671 | -- check if a 'Kaber Group Header' exists |
672 | local data=segmentCrcCredentials(tag, segid) |
673 | return ("%02x"):format(utils.Crc8Legic(data)) |
674 | end |
675 | |
676 | --- create master-token |
677 | |
678 | --- |
679 | -- write virtual Tag to real Tag |
680 | -- write clone-data to tag |
681 | function writeToTag(plainBytes, taglen, filename) |
682 | local bytes |
683 | if(utils.confirm("\nplace your empty tag onto the PM3 to read & write\n") == false) then |
684 | return |
685 | end |
686 | |
687 | -- write data to file |
688 | if (taglen > 0) then |
689 | WriteBytes = utils.input("enter number of bytes to write?", taglen) |
690 | |
691 | -- load file into pm3-buffer |
692 | if (type(filename)~="string") then filename=input("filename to load to pm3-buffer?","legic.temp") end |
693 | cmd = 'hf legic load '..filename |
694 | core.console(cmd) |
695 | |
696 | -- write pm3-buffer to Tag |
697 | for i=0, WriteBytes do |
698 | if ( i<5 or i>6) then |
699 | cmd = ('hf legic write 0x%02x 0x01'):format(i) |
700 | core.console(cmd) |
701 | --print(cmd) |
702 | elseif (i == 6) then |
703 | -- write DCF in reverse order (requires 'mosci-patch') |
704 | cmd = 'hf legic write 0x05 0x02' |
705 | core.console(cmd) |
706 | --print(cmd) |
707 | else |
708 | print("skipping byte 0x05 - will be written next step") |
709 | end |
710 | utils.Sleep(0.2) |
711 | end |
712 | end |
713 | end |
714 | |
715 | --- |
716 | -- edit segment helper |
717 | function editSegment(tag, index) |
718 | local k,v |
719 | local edit_possible="valid len RD WRP WRC Stamp Payload" |
720 | if (istable(tag.SEG[index])) then |
721 | for k,v in pairs(tag.SEG[index]) do |
722 | if(string.find(edit_possible,k)) then |
723 | tag.SEG[index][k]=tonumber(input(k..": ", v),10) |
724 | end |
725 | end |
726 | else print("Segment with Index: "..("%02d"):format(index).." not found in Tag") |
727 | return false |
728 | end |
729 | regenSegmentHeader(tag.SEG[index]) |
730 | print("\n"..dumpSegment(tag, index).."\n") |
731 | return tag |
732 | end |
733 | |
734 | --- |
735 | -- edit Segment Data |
736 | function editSegmentData(data) |
737 | if (istable(data)) then |
738 | for i=0, #data-1 do |
739 | data[i]=input("Data"..i..": ", data[i]) |
740 | end |
741 | return data |
742 | else |
743 | print("no Segment-Data found") |
744 | end |
745 | end |
746 | |
747 | --- |
748 | -- list available segmets in virtual tag |
749 | function segmentList(tag) |
750 | local i |
751 | local res = "" |
752 | if (istable(tag.SEG[0])) then |
753 | for i=0, #tag.SEG do |
754 | res = res .. tag.SEG[i].index .. " " |
755 | end |
756 | return res |
757 | else print("no Segments found in Tag") |
758 | return false |
759 | end |
760 | end |
761 | |
762 | --- |
763 | -- helper to selecting a segment |
764 | function selectSegment(tag) |
765 | local sel |
766 | if (istable(tag)) then |
767 | print("availabe Segments:\n"..segmentList(tag)) |
768 | sel=input("select Segment: ", '00') |
769 | sel=tonumber(sel,10) |
770 | if (sel) then return sel |
771 | else return '0' end |
772 | else |
773 | print("\nno Segments found") |
774 | return false |
775 | end |
776 | end |
777 | |
778 | --- |
779 | -- add segment |
780 | function addSegment(tag) |
781 | local i |
782 | local segment={ |
783 | ['index'] = '00', |
784 | ['flag'] = 'c', |
785 | ['valid'] = 1, |
786 | ['last'] = 1, |
787 | ['len'] = 13, |
788 | ['raw'] = {'0d', '40', '04', '00'}, |
789 | ['WRP'] = 4, |
790 | ['WRC'] = 0, |
791 | ['RD'] = 0, |
792 | ['crc'] = '00', |
793 | ['data'] = {}, |
794 | ['kgh'] = false |
795 | } |
796 | if (istable(tag.SEG[0])) then |
797 | tag.SEG[#tag.SEG].last=0 |
798 | table.insert(tag.SEG, segment) |
799 | for i=0, 8 do |
800 | tag.SEG[#tag.SEG].data[i]=("%02x"):format(i) |
801 | end |
802 | tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG) |
803 | return tag |
804 | else |
805 | print("no Segment-Table found") |
806 | end |
807 | end |
808 | |
809 | --- |
810 | -- |
811 | function delSegment(tag, index) |
812 | local i |
813 | if (type(index)=="string") then index=tonumber(index,10) end |
814 | if (index > 0) then |
815 | table.remove(tag.SEG, index) |
816 | for i=0, #tag.SEG do |
817 | tag.SEG[i].index=("%02d"):format(i) |
818 | end |
819 | end |
820 | if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end |
821 | return tag |
822 | end |
823 | |
824 | --- |
825 | -- helptext for modify-mode |
826 | function modifyHelp() |
827 | local t=[[ |
828 | |
829 | Data I/O Segment Manipulation File I/O |
830 | ------------------ -------------------- --------------- |
831 | rt => read Tag ds => dump Segments lf => load File |
832 | wt => write Tag as => add Segment sf => save File |
833 | ct => copy io Tag es => edit Segment xf => xor File |
834 | tc => copy oi Tag ed => edit Data |
835 | di => dump inTag rs => remove Segment |
836 | do => dump outTag cc => check Segment-CRC |
837 | ck => check KGH |
838 | tk => toggle KGH-Flag |
839 | q => quit xc => get KGH-Str h => this Help |
840 | ]] |
841 | return t |
842 | end |
843 | |
844 | --- |
845 | -- modify Tag (interactive) |
846 | function modifyMode() |
847 | local i, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes |
848 | actions = { |
849 | ["h"] = function(x) |
850 | print(modifyHelp().."\n".."tags im Memory:"..(istable(inTAG) and " inTAG" or "")..(istable(outTAG) and " outTAG" or "")) |
851 | end, |
852 | ["rt"] = function(x) inTAG=readFromPM3(); actions['di']('') end, |
853 | ["wt"] = function(x) |
854 | if(istable(inTAG)) then |
855 | local taglen=22 |
856 | for i=0, #inTAG.SEG do |
857 | taglen=taglen+inTAG.SEG[i].len+5 |
858 | end |
859 | -- read new tag (output tag) |
860 | outTAG=readFromPM3() |
861 | outbytes=tagToBytes(outTAG) |
862 | -- copy 'inputbuffer' to 'outputbuffer' |
863 | inTAG.MCD = outbytes[1] |
864 | inTAG.MSN0 = outbytes[2] |
865 | inTAG.MSN1 = outbytes[3] |
866 | inTAG.MSN2 = outbytes[4] |
867 | inTAG.MCC = outbytes[5] |
868 | -- recheck all segments-crc/kghcrc |
869 | checkAllSegCrc(inTAG) |
870 | checkAllKghCrc(inTAG) |
871 | --get bytes from ready outTAG |
872 | bytes=tagToBytes(inTAG) |
873 | if (bytes) then |
874 | writeFile(bytes, 'MylegicClone.hex') |
875 | writeToTag(bytes, taglen, 'MylegicClone.hex') |
876 | actions['rt']('') |
877 | end |
878 | end |
879 | end, |
880 | ["ct"] = function(x) |
881 | print("copy virtual input-TAG to output-TAG") |
882 | outTAG=inTAG |
883 | end, |
884 | ["tc"] = function(x) |
885 | print("copy virtual output-TAG to input-TAG") |
886 | inTAG=outTAG |
887 | end, |
888 | ["lf"] = function(x) |
889 | filename=input("enter filename: ", "legic.temp") |
890 | inTAG=readFile(filename) |
891 | end, |
892 | ["sf"] = function(x) |
893 | if(istable(inTAG)) then |
894 | outfile=input("enter filename:", "legic.temp") |
895 | bytes=tagToBytes(inTAG) |
896 | --bytes=xorBytes(bytes, inTAG.MCC) |
897 | if (bytes) then |
898 | writeFile(bytes, outfile) |
899 | end |
900 | end |
901 | end, |
902 | ["xf"] = function(x) |
903 | if(istable(inTAG)) then |
904 | outfile=input("enter filename:", "legic.temp") |
905 | crc=input("enter new crc: ('00' for a plain dump)", inTAG.MCC) |
906 | print("obfuscate witth: "..crc) |
907 | bytes=tagToBytes(inTAG) |
908 | bytes[5]=crc |
909 | if (bytes) then |
910 | writeFile(bytes, outfile) |
911 | end |
912 | end |
913 | end, |
914 | ["di"] = function(x) if (istable(inTAG)) then print("\n"..dumpTag(inTAG).."\n") end end, |
915 | ["do"] = function(x) if (istable(outTAG)) then print("\n"..dumpTag(outTAG).."\n") end end, |
916 | ["ds"] = function(x) |
917 | sel=selectSegment(inTAG) |
918 | if (sel) then print("\n"..(dumpSegment(inTAG, sel) or "no Segments available").."\n") end |
919 | end, |
920 | ["es"] = function(x) |
921 | sel=selectSegment(inTAG) |
922 | if (sel) then |
923 | if(istable(inTAG.SEG)) then |
924 | inTAG=editSegment(inTAG, sel) |
925 | inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel]) |
926 | else print("no Segments in Tag") end |
927 | end |
928 | end, |
929 | ["as"] = function(x) |
930 | if (istable(inTAG.SEG[0])) then |
931 | inTAG=addSegment(inTAG) |
932 | inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1]) |
933 | inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG]) |
934 | else print("unsegmented Tag!") |
935 | end |
936 | end, |
937 | ["rs"] = function(x) |
938 | if (istable(inTAG)) then |
939 | sel=selectSegment(inTAG) |
940 | inTAG=delSegment(inTAG, sel) |
941 | for i=0, #inTAG.SEG do |
942 | inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i]) |
943 | end |
944 | end |
945 | end, |
946 | ["ed"] = function(x) |
947 | sel=selectSegment(inTAG) |
948 | if (sel) then |
949 | inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data) |
950 | end |
951 | end, |
952 | ["ts"] = function(x) |
953 | sel=selectSegment(inTAG) |
954 | regenSegmentHeader(inTAG.SEG[sel]) |
955 | end, |
956 | ["tk"] = function(x) |
957 | sel=selectSegment(inTAG) |
958 | if(inTAG.SEG[sel].kgh) then inTAG.SEG[sel].kgh=false |
959 | else inTAG.SEG[sel].kgh=true end |
960 | end, |
961 | ["k"] = function(x) |
962 | print(("%02x"):format(utils.Crc8Legic(x))) |
963 | end, |
964 | ["xc"] = function(x) |
965 | --get credential-string for kgh-crc on certain segment |
966 | --usage: xc <segment-index> |
967 | print("k "..kghCrcCredentials(inTAG, x)) |
968 | end, |
969 | ["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end, |
970 | ["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end, |
971 | ["q"] = function(x) end, |
972 | } |
973 | print("modify-modus! enter 'h' for help or 'q' to quit") |
974 | repeat |
975 | ic=input("Legic command? ('h' for help - 'q' for quit)", "h") |
976 | -- command actions |
977 | if (type(actions[string.lower(string.sub(ic,0,1))])=='function') then |
978 | actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3)) |
979 | elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then |
980 | actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4)) |
981 | else actions['h']('') end |
982 | until (string.sub(ic,0,1)=="q") |
983 | end |
984 | |
985 | --- main |
986 | function main(args) |
987 | if (#args == 0 ) then modifyMode() end |
988 | --- variables |
989 | local inTAG, outTAG, outfile, interactive, crc, ofs, cfs, dfs |
990 | -- just a spacer for better readability |
991 | print() |
992 | --- parse arguments |
993 | for o, a in getopt.getopt(args, 'hrmi:do:c:') do |
994 | -- display help |
995 | if o == "h" then return help(); end |
996 | -- read tag from PM3 |
997 | if o == "r" then inTAG=readFromPM3() end |
998 | -- input file |
999 | if o == "i" then inTAG=readFile(a) end |
1000 | -- dump virtual-Tag |
1001 | if o == "d" then dfs=true end |
1002 | -- interacive modifying |
1003 | if o == "m" then interactive=true; modifyMode() end |
1004 | -- xor (e.g. for clone or plain file) |
1005 | if o == "c" then cfs=true; crc=a end |
1006 | -- output file |
1007 | if o == "o" then outfile=a; ofs=true end |
1008 | end |
1009 | |
1010 | -- file conversion (output to file) |
1011 | if (ofs) then |
1012 | -- dump infile / tag-read |
1013 | if (dfs) then |
1014 | print("-----------------------------------------") |
1015 | print(dumpTag(inTAG)) |
1016 | end |
1017 | bytes=tagToBytes(inTAG) |
1018 | -- xor with given crc |
1019 | if (cfs) then |
1020 | bytes[5]=crc |
1021 | end |
1022 | -- write to outfile |
1023 | if (bytes) then |
1024 | writeFile(bytes, outfile) |
1025 | -- reed new content into virtual tag |
1026 | |
1027 | inTAG=bytesToTag(bytes, inTAG) |
1028 | -- show new content |
1029 | if (dfs) then |
1030 | print("-----------------------------------------") |
1031 | print(dumpTag(outTAG)) |
1032 | end |
1033 | end |
1034 | end |
1035 | |
1036 | end |
1037 | |
1038 | --- start |
1039 | main(args) |