]> cvs.zerfleddert.de Git - proxmark3-svn/blob - client/scripts/Legic_clone.lua
CHG: the mifare Auth command can make use of a random nonce aswell.
[proxmark3-svn] / client / scripts / Legic_clone.lua
1 --[[
2 script to create a clone-dump with new crc
3 Author: mosci
4 my Fork: https://github.com/icsom/proxmark3.git
5 Upstream: https://github.com/Proxmark/proxmark3.git
6
7 1. read tag-dump, xor byte 22..end with byte 0x05 of the inputfile
8 2. write to outfile
9 3. set byte 0x05 to newcrc
10 4. until byte 0x21 plain like in inputfile
11 5. from 0x22..end xored with newcrc
12 6. calculate new crc on each segment (needs to know the new MCD & MSN0..2)
13
14 simplest usage:
15 read a valid legic tag with 'hf legic reader'
16 save the dump with 'hf legic save orig.hex'
17 place your 'empty' tag on the reader and run 'script run Legic_clone -i orig.hex -w'
18 you will see some output like:
19 read 1024 bytes from legic_dumps/j_0000.hex
20
21 place your empty tag onto the PM3 to read and display the MCD & MSN0..2
22 the values will be shown below
23 confirm whnen ready [y/n] ?y
24 #db# setting up legic card
25 #db# MIM 256 card found, reading card ...
26 #db# Card read, use 'hf legic decode' or
27 #db# 'data hexsamples 8' to view results
28 0b ad c0 de <- !! here you'll see the MCD & MSN of your empty tag, which has to be typed in manually as seen below !!
29 type in MCD as 2-digit value - e.g.: 00 (default: 79 )
30 > 0b
31 type in MSN0 as 2-digit value - e.g.: 01 (default: 28 )
32 > ad
33 type in MSN1 as 2-digit value - e.g.: 02 (default: d1 )
34 > c0
35 type in MSN2 as 2-digit value - e.g.: 03 (default: 43 )
36 > de
37 MCD:0b, MSN:ad c0 de, MCC:79 <- this crc is calculated from the MCD & MSN and must match the one on yout empty tag
38
39 wrote 1024 bytes to myLegicClone.hex
40 enter number of bytes to write? (default: 86 )
41
42 loaded 1024 samples
43 #db# setting up legic card
44 #db# MIM 256 card found, writing 0x00 - 0x01 ...
45 #db# write successful
46 ...
47 #db# setting up legic card
48 #db# MIM 256 card found, writing 0x56 - 0x01 ...
49 #db# write successful
50 proxmark3>
51
52 the default value (number of bytes to write) is calculated over all valid segments and should be ok - just hit enter, wait until write has finished
53 and your clone should be ready (except there has to be a additional KGH-CRC to be calculated - which credentials are unknown until yet)
54
55 the '-w' switch will only work with my fork - it needs the binary legic_crc8 which is not part of the proxmark3-master-branch
56 also the ability to write DCF is not possible with the proxmark3-master-branch
57 but creating dumpfile-clone files will be possible (without valid segment-crc - this has to done manually with)
58
59
60 (example) Legic-Prime Layout with 'Kaba Group Header'
61 +----+----+----+----+----+----+----+----+
62 0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
63 +----+----+----+----+----+----+----+----+
64 0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
65 +----+----+----+----+----+----+----+----+
66 0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
67 +----+----+----+----+----+----+----+----+
68 0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
69 +----+----+----+----+----+----+----+----+
70 0x20|UID1|UID2|kghC|
71 +----+----+----+
72
73 MCD= ManufacturerID (1 Byte)
74 MSN0..2= ManufactureSerialNumber (3 Byte)
75 MCC= CRC (1 Byte) calculated over MCD,MSN0..2
76 DCF= DecrementalField (2 Byte) 'credential' (enduser-Tag) seems to have always DCF-low=0x60 DCF-high=0xea
77 Bck0..5= Backup (6 Byte) Bck0 'dirty-flag', Bck1..5 SegmentHeader-Backup
78 BCC= BackupCRC (1 Byte) CRC calculated over Bck1..5
79 Seg0..3= SegmentHeader (on MIM 4 Byte )
80 SegC= SegmentCRC (1 Byte) calculated over MCD,MSN0..2,Seg0..3
81 Stp0..n= Stamp0... (variable length) length = Segment-Len - UserData - 1
82 UID0..n= UserDater (variable length - with KGH hex 0x00-0x63 / dec 0-99) length = Segment-Len - WRP - WRC - 1
83 kghC= KabaGroupHeader (1 Byte + addr 0x0c must be 0x11)
84 as seen on ths example: addr 0x05..0x08 & 0x0c must have been set to this values - otherwise kghCRC will not be created by a official reader (not accepted)
85 --]]
86
87 example = "Script create a clone-dump of a dump from a Legic Prime Tag"
88 author = "Mosci"
89 desc =
90 [[
91 This is a script which create a clone-dump of a dump from a Legic Prime Tag (MIM256 or MIM1024)
92 (created with 'hf legic save my_dump.hex')
93 requiered arguments:
94 -i <input file> (file to read data from)
95
96 optional arguments :
97 -h - Help text
98 -o <output file> - requieres option -c to be given
99 -c <new-tag crc> - requieres option -o to be given
100 -d - Display content of found Segments
101 -s - Display summary at the end
102 -w - write directly to Tag - a file myLegicClone.hex wille be generated also
103
104 e.g.:
105 hint: using the CRC '00' will result in a plain dump ( -c 00 )
106
107 Examples :
108 script run legic_clone -i my_dump.hex -o my_clone.hex -c f8
109 script run legic_clone -i my_dump.hex -d -s
110 ]]
111
112 local utils = require('utils')
113 local getopt = require('getopt')
114 local bxor = bit32.bxor
115
116 -- we need always 2 digits
117 function prepend_zero(s)
118 if (string.len(s)==1) then return "0" .. s
119 else
120 if (string.len(s)==0) then return "00"
121 else return s
122 end
123 end
124 end
125
126 ---
127 -- This is only meant to be used when errors occur
128 function oops(err)
129 print("ERROR: ",err)
130 return nil, err
131 end
132
133 ---
134 -- Usage help
135 function help()
136 print(desc)
137 print("Example usage")
138 print(example)
139 end
140
141 -- Check availability of file
142 function file_check(file_name)
143 local file_found=io.open(file_name, "r")
144 if file_found==nil then
145 file_found=false
146 else
147 file_found=true
148 end
149 return file_found
150 end
151
152 --- xor-wrapper
153 -- xor all from addr 0x22 (start counting from 1 => 23)
154 function xorme(hex, xor, index)
155 if ( index >= 23 ) then
156 return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
157 else
158 return hex
159 end
160 end
161
162 -- read input-file into array
163 function getInputBytes(infile)
164 local line
165 local bytes = {}
166
167 local fhi,err = io.open(infile)
168 if err then print("OOps ... faild to read from file ".. infile); return false; end
169
170 while true do
171 line = fhi:read()
172 if line == nil then break end
173
174 for byte in line:gmatch("%w+") do
175 table.insert(bytes, byte)
176 end
177 end
178
179 fhi:close()
180
181 print("\nread ".. #bytes .." bytes from ".. infile)
182 return bytes
183 end
184
185 -- write to file
186 function writeOutputBytes(bytes, outfile)
187 local line
188 local bcnt=0
189 local fho,err = io.open(outfile,"w")
190 if err then print("OOps ... faild to open output-file ".. outfile); return false; end
191
192 for i = 1, #bytes do
193 if (bcnt == 0) then
194 line=bytes[i]
195 elseif (bcnt <= 7) then
196 line=line.." "..bytes[i]
197 end
198 if (bcnt == 7) then
199 -- write line to new file
200 fho:write(line.."\n")
201 -- reset counter & line
202 bcnt=-1
203 line=""
204 end
205 bcnt=bcnt+1
206 end
207 fho:close()
208 print("\nwrote ".. #bytes .." bytes to " .. outfile)
209 return true
210 end
211
212 -- xore certain bytes
213 function xorBytes(inBytes, crc)
214 local bytes = {}
215 for index = 1, #inBytes do
216 bytes[index] = xorme(inBytes[index], crc, index)
217 end
218 if (#inBytes == #bytes) then
219 -- replace crc
220 bytes[5] = string.sub(crc,-2)
221 return bytes
222 else
223 print("error: byte-count missmatch")
224 return false
225 end
226 end
227
228 -- get raw segment-data
229 function getSegmentData(bytes, start, index)
230 local raw, len, valid, last, wrp, wrc, rd, crc
231 local segment = {}
232 segment[0] = bytes[start].." "..bytes[start+1].." "..bytes[start+2].." "..bytes[start+3]
233 -- flag = high nibble of byte 1
234 segment[1] = string.sub(bytes[start+1],0,1)
235
236 -- valid = bit 6 of byte 1
237 segment[2] = tonumber(bit32.extract("0x"..bytes[start+1],6,1),16)
238
239 -- last = bit 7 of byte 1
240 segment[3] = tonumber(bit32.extract("0x"..bytes[start+1],7,1),16)
241
242 -- len = (byte 0)+(bit0-3 of byte 1)
243 segment[4] = tonumber(("%03x"):format(tonumber(bit32.extract("0x"..bytes[start+1],0,3),16)..tonumber(bytes[start],16)),16)
244
245 -- wrp (write proteted) = byte 2
246 segment[5] = tonumber(bytes[start+2])
247
248 -- wrc (write control) - bit 4-6 of byte 3
249 segment[6] = tonumber(bit32.extract("0x"..bytes[start+3],4,3),16)
250
251 -- rd (read disabled) - bit 7 of byte 3
252 segment[7] = tonumber(bit32.extract("0x"..bytes[start+3],7,1),16)
253
254 -- crc byte 4
255 segment[8] = bytes[start+4]
256
257 -- segment index
258 segment[9] = index
259
260 -- # crc-byte
261 segment[10] = start+4
262 return segment
263 end
264
265 --- Kaba Group Header
266 -- checks if a segment does have a kghCRC
267 -- returns boolean false if no kgh has being detected or the kghCRC if a kgh was detected
268 function CheckKgh(bytes, segStart, segEnd)
269 if (bytes[8]=='9f' and bytes[9]=='ff' and bytes[13]=='11') then
270 local i
271 local data = {}
272 segStart=tonumber(segStart,10)
273 segEnd=tonumber(segEnd,10)
274 local dataLen = segEnd-segStart-5
275 --- gather creadentials for verify
276 local WRP = bytes[(segStart+2)]
277 local WRC = ("%02x"):format(tonumber(bit32.extract("0x"..bytes[segStart+3],4,3),16))
278 local RD = ("%02x"):format(tonumber(bit32.extract("0x"..bytes[segStart+3],7,1),16))
279 local XX = "00"
280 cmd = bytes[1]..bytes[2]..bytes[3]..bytes[4]..WRP..WRC..RD..XX
281 for i=(segStart+5), (segStart+5+dataLen-2) do
282 cmd = cmd..bytes[i]
283 end
284 local KGH=("%02x"):format(utils.Crc8Legic(cmd))
285 if (KGH==bytes[segEnd-1]) then
286 return KGH
287 else
288 return false
289 end
290 else
291 return false
292 end
293 end
294
295 -- get only the addresses of segemnt-crc's and the length of bytes
296 function getSegmentCrcBytes(bytes)
297 local start=23
298 local index=0
299 local crcbytes = {}
300 repeat
301 seg = getSegmentData(bytes,start,index)
302 crcbytes[index]= seg[10]
303 start = start + seg[4]
304 index = index + 1
305 until (seg[3] == 1 or tonumber(seg[9]) == 126 )
306 crcbytes[index] = start
307 return crcbytes
308 end
309
310 -- print segment-data (hf legic decode like)
311 function displaySegments(bytes)
312 --display segment header(s)
313 start=23
314 index="00"
315
316 --repeat until last-flag ist set to 1 or segment-index has reached 126
317 repeat
318 wrc=""
319 wrp=""
320 pld=""
321 Seg = getSegmentData(bytes,start,index)
322 KGH = CheckKgh(bytes,start,(start+tonumber(Seg[4],10)))
323 printSegment(Seg)
324
325 -- wrc
326 if(Seg[6]>0) then
327 print("WRC protected area:")
328 -- length of wrc = wrc
329 for i=1, Seg[6] do
330 -- starts at (segment-start + segment-header + segment-crc)-1
331 wrc = wrc..bytes[(start+4+1+i)-1].." "
332 end
333 print(wrc)
334 elseif(Seg[5]>0) then
335 print("Remaining write protected area:")
336 -- length of wrp = (wrp-wrc)
337 for i=1, (Seg[5]-Seg[6]) do
338 -- starts at (segment-start + segment-header + segment-crc + wrc)-1
339 wrp = wrp..bytes[(start+4+1+Seg[6]+i)-1].." "
340 end
341 print(wrp)
342 end
343
344 -- payload
345 print("Remaining segment payload:")
346 --length of payload = segment-len - segment-header - segment-crc - wrp -wrc
347 for i=1, (Seg[4]-4-1-Seg[5]-Seg[6]) do
348 -- starts at (segment-start + segment-header + segment-crc + segment-wrp + segemnt-wrc)-1
349 pld = pld..bytes[(start+4+1+Seg[5]+Seg[6]+i)-1].." "
350 end
351 print(pld)
352 if (KGH) then print("'Kaba Group Header' detected"); end
353 start = start+Seg[4]
354 index = prepend_zero(tonumber(Seg[9])+1)
355
356 until (Seg[3] == 1 or tonumber(Seg[9]) == 126 )
357 end
358
359 -- print Segment values
360 function printSegment(SegmentData)
361 res = "\nSegment "..SegmentData[9]..": "
362 res = res.. "raw header="..SegmentData[0]..", "
363 res = res.. "flag="..SegmentData[1].." (valid="..SegmentData[2].." last="..SegmentData[3].."), "
364 res = res.. "len="..("%04d"):format(SegmentData[4])..", "
365 res = res.. "WRP="..prepend_zero(SegmentData[5])..", "
366 res = res.. "WRC="..prepend_zero(SegmentData[6])..", "
367 res = res.. "RD="..SegmentData[7]..", "
368 res = res.. "crc="..SegmentData[8]
369 print(res)
370 end
371
372 -- write clone-data to tag
373 function writeToTag(plainBytes)
374 local SegCrcs = {}
375 if(utils.confirm("\nplace your empty tag onto the PM3 to read and display the MCD & MSN0..2\nthe values will be shown below\n confirm when ready") == false) then
376 return
377 end
378
379 -- gather MCD & MSN from new Tag - this must be enterd manually
380 cmd = 'hf legic read 0x00 0x04'
381 core.console(cmd)
382 print("\nthese are the MCD MSN0 MSN1 MSN2 from the Tag that has being read:")
383 cmd = 'data hexsamples 4'
384 core.console(cmd)
385 print("^^ use this values as input for the following answers (one 2-digit-value per question/answer):")
386 -- enter MCD & MSN (in hex)
387 MCD = utils.input("type in MCD as 2-digit value - e.g.: 00", plainBytes[1])
388 MSN0 = utils.input("type in MSN0 as 2-digit value - e.g.: 01", plainBytes[2])
389 MSN1 = utils.input("type in MSN1 as 2-digit value - e.g.: 02", plainBytes[3])
390 MSN2 = utils.input("type in MSN2 as 2-digit value - e.g.: 03", plainBytes[4])
391
392 -- calculate crc8 over MCD & MSN
393 cmd = MCD..MSN0..MSN1..MSN2
394 MCC = ("%02x"):format(utils.Crc8Legic(cmd))
395 print("MCD:"..MCD..", MSN:"..MSN0.." "..MSN1.." "..MSN2..", MCC:"..MCC)
396
397 -- calculate new Segment-CRC for each valid segment
398 SegCrcs = getSegmentCrcBytes(plainBytes)
399 for i=0, (#SegCrcs-1) do
400 -- SegCrcs[i]-4 = address of first byte of segmentHeader (low byte segment-length)
401 segLen=tonumber(("%1x"):format(tonumber(bit32.extract("0x"..plainBytes[(SegCrcs[i]-3)],0,3),16))..("%02x"):format(tonumber(plainBytes[SegCrcs[i]-4],16)),16)
402 segStart=(SegCrcs[i]-4)
403 segEnd=(SegCrcs[i]-4+segLen)
404 KGH=CheckKgh(plainBytes,segStart,segEnd)
405 if (KGH) then
406 print("'Kaba Group Header' detected - re-calculate...")
407 end
408 cmd = MCD..MSN0..MSN1..MSN2..plainBytes[SegCrcs[i]-4]..plainBytes[SegCrcs[i]-3]..plainBytes[SegCrcs[i]-2]..plainBytes[SegCrcs[i]-1]
409 plainBytes[SegCrcs[i]] = ("%02x"):format(utils.Crc8Legic(cmd))
410 end
411
412 -- apply MCD & MSN to plain data
413 plainBytes[1] = MCD
414 plainBytes[2] = MSN0
415 plainBytes[3] = MSN1
416 plainBytes[4] = MSN2
417 plainBytes[5] = MCC
418
419 -- prepare plainBytes for writing (xor plain data with new MCC)
420 bytes = xorBytes(plainBytes, MCC)
421
422 -- write data to file
423 if (writeOutputBytes(bytes, "myLegicClone.hex")) then
424 WriteBytes = utils.input("enter number of bytes to write?", SegCrcs[#SegCrcs])
425
426 -- load file into pm3-buffer
427 cmd = 'hf legic load myLegicClone.hex'
428 core.console(cmd)
429
430 -- write pm3-buffer to Tag
431 for i=0, WriteBytes do
432 if ( i<5 or i>6) then
433 cmd = ('hf legic write 0x%02x 0x01'):format(i)
434 core.console(cmd)
435 elseif (i == 6) then
436 -- write DCF in reverse order (requires 'mosci-patch')
437 cmd = 'hf legic write 0x05 0x02'
438 core.console(cmd)
439 else
440 print("skipping byte 0x05 - will be written next step")
441 end
442 utils.Sleep(0.2)
443 end
444 end
445 end
446
447 -- main function
448 function main(args)
449 -- some variables
450 local i=0
451 local oldcrc, newcrc, infile, outfile
452 local bytes = {}
453 local segments = {}
454
455 -- parse arguments for the script
456 for o, a in getopt.getopt(args, 'hwsdc:i::o:') do
457 -- output file
458 if o == "o" then
459 outfile = a
460 ofs = true
461 if (file_check(a)) then
462 local answer = utils.confirm("\nthe output-file "..a.." alredy exists!\nthis will delete the previous content!\ncontinue?")
463 if (answer==false) then return oops("quiting") end
464 end
465 end
466 -- input file
467 if o == "i" then
468 infile = a
469 if (file_check(infile)==false) then
470 return oops("input file: "..infile.." not found")
471 else
472 bytes = getInputBytes(infile)
473 oldcrc = bytes[5]
474 ifs = true
475 if (bytes == false) then return oops('couldnt get input bytes') end
476 end
477 i = i+1
478 end
479 -- new crc
480 if o == "c" then
481 newcrc = a
482 ncs = true
483 end
484 -- display segments switch
485 if o == "d" then ds = true; end
486 -- display summary switch
487 if o == "s" then ss = true; end
488 -- write to tag switch
489 if o == "w" then ws = true; end
490 -- help
491 if o == "h" then return help() end
492 end
493
494 if (not ifs) then return oops("option -i <input file> is required but missing") end
495
496 -- bytes to plain
497 bytes = xorBytes(bytes, oldcrc)
498
499 -- show segments (works only on plain bytes)
500 if (ds) then
501 print("+------------------------------------------- Segments -------------------------------------------+")
502 displaySegments(bytes);
503 end
504
505 if (ofs and ncs) then
506 -- xor bytes with new crc
507 newBytes = xorBytes(bytes, newcrc)
508 -- write output
509 if (writeOutputBytes(newBytes, outfile)) then
510 -- show summary if requested
511 if (ss) then
512 -- information
513 res = "\n+-------------------------------------------- Summary -------------------------------------------+"
514 res = res .."\ncreated clone_dump from\n\t"..infile.." crc: "..oldcrc.."\ndump_file:"
515 res = res .."\n\t"..outfile.." crc: "..string.sub(newcrc,-2)
516 res = res .."\nyou may load the new file with: hf legic load "..outfile
517 res = res .."\n\nif you don't write to tag immediately ('-w' switch) you will need to recalculate each segmentCRC"
518 res = res .."\nafter writing this dump to a tag!"
519 res = res .."\n\na segmentCRC gets calculated over MCD,MSN0..3,Segment-Header0..3"
520 res = res .."\ne.g. (based on Segment00 of the data from "..infile.."):"
521 res = res .."\nhf legic crc8 "..bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[23]..bytes[24]..bytes[25]..bytes[26]
522 -- this can not be calculated without knowing the new MCD, MSN0..2
523 print(res)
524 end
525 end
526 else
527 if (ss) then
528 -- show why the output-file was not written
529 print("\nnew file not written - some arguments are missing ..")
530 print("output file: ".. (ofs and outfile or "not given"))
531 print("new crc: ".. (ncs and newcrc or "not given"))
532 end
533 end
534 -- write to tag
535 if (ws and #bytes == 1024) then
536 writeToTag(bytes)
537 end
538 end
539
540 -- call main with arguments
541 main(args)
Impressum, Datenschutz