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