text
[RRG-proxmark3.git] / client / luascripts / hf_mf_uscuid_prog.lua
blob344747f90805021fda9b381dcbd29556a87a44a5
1 local cmds = require('commands')
2 local getopt = require('getopt')
3 local lib14a = require('read14a')
4 local utils = require('utils')
5 local cl = require('ansicolors')
6 local bxor = bit32.bxor
9 copyright = '\nLicensed under GNU GPL v3.0. Team orangeBlue.'
10 author = 'Team '..cl.yellow..'orange'..cl.cyan..'Blue'..cl.reset -- Disinformation
11 version = 'v1.0.1'
12 desc = [[
13 Script to set UID on USCUID using any means possible.
15 This script is compatible with the ICs listed below:
16 * GDMIC
17 * UCUID
18 * M1-7B
19 * Other chips, showing up as "Gen 4 GDM"
21 This script does *NOT* claim full compatibility with the ICs listed below:
22 * UFUID
23 * PFUID*
25 Why?
26 Unfortunately, these are cut down versions.
27 Checks show that they only acknowledge bytes 0-1, 7, 8, and 15 of the configuration.
29 * WARNING: The config commands are inversed. Nothing will work.
31 Ready to start?
33 Set the first 2 bytes of your config to 7AFF and use -t 4.
36 example = [[
37 -- Set UID 7 bytes long via 20-23 wakeup
38 1. script run hf_mf_uscuid_prog -t 2 -u 04A72B85489F51
40 -- Set UID 4 bytes long via 40-43 wakeup
41 2. script run hf_mf_uscuid_prog -t 4 -u A72B571
43 -- Read sector 0
44 3. script run hf_mf_uscuid_prog -S 0
46 usage = [[
47 script run hf_mf_uscuid_uid_prog [-h] [-u <uid>] [-t] [-3] [-s <signature>] [-w 1] [-R -B <blk>] [-S -E <sec>] [-g -c -b -2 -7 -d -a -n -r <0/1>]
49 arguments = [[
50 -h this help
51 -t Magic wakeup type (2 for 0x20, 4 for 0x40)
52 -u New tag UID
53 -s New signature data
55 -3 Update UID for F3 Perso
56 -w 1 Wipe tag (take caution!)
58 -B Read backdoor block
59 -E Read backdoor sector
60 -R Read block
61 -S Read sector
63 [ConfigStar]
64 Unmarked data will not be edited.
66 How to use:
67 To ENABLE an option, pass "1"
68 To DISABLE an option, pass "0"
70 -a Magic auth
71 -b Block key B if readable by ACL
72 -c Gen1 command (1 for 20-23; 0 for 40-43)
73 -d Shadow mode
74 -g Gen1 mode
75 -n Static encrypted nonces
76 -r Signature sector
77 -2 Gen2/CUID mode
78 -7 CL2 mode (1 for F0 unfused; 0 for off)
81 changelog = [[
82 Welcome, proxmark user!
83 Here's a secret changelog of this script as its' life started.
85 v0.1 - Initial developer release. Super unstable, coding the basic UID programming functions, mainly 7 byte because it was just easier.
86 v0.6 - Basic UID editor release. Flexible programming of UIDs (4<->7 byte conversions and programming)
87 v0.7 - Signature support! If USCUID supports it, so should we.
88 v0.8a - Incomplete release. Try the newly added functions at your own risk!
89 v0.8 - Now with wiping the tag! Why not. Helps.
90 v0.9 - Manual configurator. Very well.
91 v1.0 - Memory access. Just like in the proxmark client.
93 -- [[ Start introducing functions that get called later on ]] --
94 -- give up
95 local function oops(err)
96 print(cl.red.."[!]"..cl.reset..' ERROR:', err)
97 core.clearCommandBuffer()
98 return nil, err
99 end
101 local function help()
102 print(copyright)
103 print(author)
104 print(version)
105 print(desc)
106 print(cl.cyan..'Usage'..cl.reset)
107 print(usage)
108 print(cl.cyan..'Arguments'..cl.reset)
109 print(arguments)
110 print(cl.cyan..'Example usage'..cl.reset)
111 print(example)
114 -- Sorry, didn't care to figure out custom bit amounts with the 14a lua lib. So here's this thing
115 -- 20/23
116 local function wupc2()
117 return {
118 [0] = 'hf 14a raw -akb 7 20',
119 [1] = 'hf 14a raw -k 23',
123 -- 40/43
124 local function wupc()
125 return{
126 [0] = 'hf 14a raw -akb 7 40',
127 [1] = 'hf 14a raw -k 43',
131 local function makenuid(uid)
132 core.console('ana nuid -d '..uid)
135 local function sendCmds(cmds)
136 for i = 0, #cmds do
137 if cmds[i] then
138 core.console(cmds[i])
139 core.clearCommandBuffer()
144 local function wakeupmagic(writetype)
145 if writetype == "2" then
146 sendCmds(wupc2())
147 elseif writetype == "4" then
148 sendCmds(wupc())
152 local function calculate_block0(useruid)
153 local uidbytes = utils.ConvertHexToBytes(useruid)
154 local i = 1
155 local bcc = bxor(uidbytes[i], uidbytes[i+1]);
157 -- floor division
158 local length = #useruid // 2;
160 -- bcc
161 for i = 3, length, 1 do
162 bcc = bxor(bcc, uidbytes[i])
165 -- block0
166 local block0 = ""
167 for i = 1, length, 1 do
168 block0 = block0..string.format('%02X', uidbytes[i])
171 return block0..string.format('%02X', bcc)
174 local function cltwo_block0(uid)
175 payload = uid
176 payload = payload .. "884400000000000000"
177 return payload
180 local function SectorHeader(sector)
181 if sector == nil then return end
183 print("["..cl.yellow.."="..cl.reset.."] # | sector "..cl.green..string.format("%02d", sector)..cl.reset.." / "..cl.green..string.format("0x%02X", sector)..cl.reset)
184 print("["..cl.yellow.."="..cl.reset.."] ----+------------------------------------------------")
187 local function BlockParser(data, block)
188 if data == nil or block == nil then return end
189 if block == "0" or block == 0 then -- for block 0
190 print("["..cl.yellow.."="..cl.reset.."] "..string.format("%02d", block).." | "..cl.red..string.sub(data,1,2).." "..string.sub(data,3,4).." "..string.sub(data,5,6).." "..string.sub(data,7,8).." "..string.sub(data,9,10).." "..string.sub(data,11,12).." "..string.sub(data,13,14).." "..string.sub(data,15,16).." "..string.sub(data,17,18).." "..string.sub(data,19,20).." "..string.sub(data,21,22).." "..string.sub(data,23,24).." "..string.sub(data,25,26).." "..string.sub(data,27,28).." "..string.sub(data,29,30).." "..string.sub(data,31,32)..cl.reset)
191 elseif (block+1)%4 == 0 then -- for ST
192 print("["..cl.yellow.."="..cl.reset.."] "..string.format("%02d", block).." | "..cl.yellow..string.sub(data,1,2).." "..string.sub(data,3,4).." "..string.sub(data,5,6).." "..string.sub(data,7,8).." "..string.sub(data,9,10).." "..string.sub(data,11,12).." "..cl.magenta..string.sub(data,13,14).." "..string.sub(data,15,16).." "..string.sub(data,17,18).." "..cl.reset..string.sub(data,19,20).." "..cl.yellow..string.sub(data,21,22).." "..string.sub(data,23,24).." "..string.sub(data,25,26).." "..string.sub(data,27,28).." "..string.sub(data,29,30).." "..string.sub(data,31,32)..cl.reset)
193 else
194 print("["..cl.yellow.."="..cl.reset.."] "..string.format("%02d", block).." | "..string.sub(data,1,2).." "..string.sub(data,3,4).." "..string.sub(data,5,6).." "..string.sub(data,7,8).." "..string.sub(data,9,10).." "..string.sub(data,11,12).." "..string.sub(data,13,14).." "..string.sub(data,15,16).." "..string.sub(data,17,18).." "..string.sub(data,19,20).." "..string.sub(data,21,22).." "..string.sub(data,23,24).." "..string.sub(data,25,26).." "..string.sub(data,27,28).." "..string.sub(data,29,30).." "..string.sub(data,31,32))
198 local function sendRaw(rawdata, keep)
199 flags = lib14a.ISO14A_COMMAND.ISO14A_RAW + lib14a.ISO14A_COMMAND.ISO14A_APPEND_CRC
200 if keep == true then flags = flags + lib14a.ISO14A_COMMAND.ISO14A_NO_DISCONNECT end
201 local command = Command:newMIX{cmd = cmds.CMD_HF_ISO14443A_READER,
202 arg1 = flags, -- Send raw
203 arg2 = string.len(rawdata) / 2, -- arg2 contains the length, which is half the length of the ASCII-string rawdata
204 data = rawdata
206 local ignore_response = false
207 local result, err = command:sendMIX(ignore_response)
208 if result then
209 --local count,cmd,arg1,arg2,arg3,data = bin.unpack('LLLLH512',result)
210 local p = command.parse(result)
211 arg1 = p["arg1"]
212 data = p["data"]
213 returned_bytes = string.sub(data, 1, arg1 * 2)
214 if #returned_bytes > 0 then return returned_bytes else return nil end
217 -- Functions to work with configuration data (E000, E100 cmds)
218 local function readconf()
219 configbuffer = sendRaw("E000", true)
220 if string.len(configbuffer) ~= 36 then
221 oops("Tag sent wrong length of config!")
222 lib14a.disconnect()
223 return 1
225 return utils.ConvertHexToBytes(string.sub(configbuffer, 1, 32))
228 local function writeconf(configbuffer)
229 configbuffer=utils.ConvertBytesToHex(configbuffer)
230 print(cl.yellow.."[|]".. cl.reset .." The new config is: "..configbuffer)
231 if sendRaw("E100", true) == "0A" then
232 if sendRaw(configbuffer, true) == "0A" then
233 print(cl.yellow.."[/]".. cl.reset .." Config updated successfully")
234 else
235 oops("Tag did not ACK config update!")
236 lib14a.disconnect()
237 return 1
239 else oops("Tag did not ACK `E100` command!")
240 lib14a.disconnect()
241 return 1
244 -- End config functions
246 -- [[ All have been created ]] --
249 function main(args)
250 if args == nil or #args == 0 then return help() end
251 -- Save data to process
252 local writetype = "4"
253 local uid = nil
254 local payload = nil
255 local f3perso = false
256 local signature = nil
257 local wipe = false
259 local targetblk = nil
260 local targetbblk = nil
261 local targetbsec = nil
262 local targetsec = nil
263 -- ConfigStar data. These are all booleans. If nil, ignored.
264 local gen1 = nil
265 local gen1com = nil
266 local keyblock = nil
267 local cuid = nil
268 local cl2mode = nil -- Sorry, I'll only parse either 5A or 00. You can turn on the rest yourself using perso
269 local shadowmode = nil
270 local magicauth = nil
271 local statenc = nil
272 local sigsec = nil
274 local configwrite = nil
275 -- End of ConfigStar
276 -- Parse arguments
277 -- Note: wrong order of arguments makes the script just not work. Like in some cases the script dies and doesnt call anything, in others it wants data for bool arguments. DESIGN???
278 for o,a in getopt.getopt(args, 'g:c:b:2:7:d:a:n:r:u:t:s:R:B:S:E:hw3') do
279 if o == "h" then return help() end
280 if o == 'u' then uid = a end
281 if o == 's' then signature = a end
282 if o == 't' then writetype = a end
283 if o == '3' then f3perso = true end
284 if o == 'R' then targetblk = a end
285 if o == 'B' then targetbblk = a end
286 if o == 'S' then targetsec = a end
287 if o == 'E' then targetbsec = a end
288 if o == 'w' then wipe = true end
289 -- So one odd thing I noticed is the bool args like -h, -w don't work without a 2nd argument. So you now must do -h 1.. what? Why?
290 -- ConfigStar
291 if o == 'g' then if a == "1" then gen1 = true elseif a == "0" then gen1 = false end end
292 if o == 'c' then if a == "1" then gen1com= true elseif a == "0" then gen1com= false end end
293 if o == 'b' then if a == "1" then keyblock= true elseif a == "0" then keyblock= false end end
294 if o == '2' then if a == "1" then cuid= true elseif a == "0" then cuid= false end end
295 if o == '7' then if a == "1" then cl2mode= true elseif a == "0" then cl2mode= false end end
296 if o == 'd' then if a == "1" then shadowmode = true elseif a == "0" then shadowmode = false end end
297 if o == 'a' then if a == "1" then magicauth= true elseif a == "0" then magicauth= false end end
298 if o == 'n' then if a == "1" then statenc= true elseif a == "0" then statenc= false end end
299 if o == 'r' then if a == "1" then sigsec = true elseif a == "0" then sigsec= false end end
302 if gen1 ~= nil or gen1com~= nil or keyblock~= nil or cuid~= nil or cl2mode~= nil or shadowmode~= nil or magicauth~= nil or statenc~= nil or sigsec~= nil then
303 configwrite = true
306 if targetbblk then if tonumber(targetbblk) > 63 then oops("Block is above 63") return 1 end end
307 if targetblk then if tonumber(targetblk) > 63 then oops("Block is above 63") return 1 end end
308 if targetsec then if tonumber(targetsec) > 15 then oops("Sector is above 15") return 1 end end
309 if targetbsec then if tonumber(targetbsec) > 15 then oops("Sector is above 15") return 1 end end
311 -- Alright, here's the logic.
312 -- 1. Set the write type (0x20, 0x40, 8000 auth, etc...)
313 -- 2. Get UID length
314 -- 3. Form data to write
315 -- 4. Issue commands
316 if wipe == true then
318 print(cl.red.."[/]"..cl.reset.." Wipe issued! Nullifying other arguments!")
319 print(cl.red.."[-]"..cl.reset.." DO NOT REMOVE YOUR TAG!")
321 uid = nil
322 signature = nil
323 configwrite = nil
325 wakeupmagic(writetype)
326 if sendRaw("F000", true) ~= "0A" then
327 oops("DANGER! Tag did not ACK wipe command. The field has NOT been reset.")
328 print("[ ] If you think the wipe succeeded, immediately do this:")
329 print("hf 14a raw -kc E100; hf 14a raw -c 7AFF0000000000000000000000000008")
330 return 1
333 writeconf(utils.ConvertHexToBytes("7AFF0000000000000000005A00000008"))
335 sendRaw("F800", true) -- here you only wipe the backdoor blocks and they're not super critical so might as well not check.
336 sendRaw("A000", true) -- By this point I just rely on the tag.
337 sendRaw("DE7715B8040804000000000000000000", true)
339 for i =0, 15 do
340 blk=string.format("%02x", 4 * i + 3):gsub("0x","")
341 sendRaw("A0"..blk, true)
342 sendRaw("FFFFFFFFFFFFFF078069FFFFFFFFFFFF", true)
343 sendRaw("A8"..blk, true)
344 sendRaw("FFFFFFFFFFFFFF078069FFFFFFFFFFFF", true)
347 sendRaw("A807", true)
348 sendRaw("75CCB59C9BED70F0F8694B791BEA7BCC", true)
349 print(cl.yellow.."[-]"..cl.reset.." Wipe completed successfully")
350 lib14a.disconnect()
353 -- Separator
354 if targetblk or targetbblk or targetsec or targetbsec then
355 uid = nil
356 signature = nil
357 configwrite = nil
358 wakeupmagic(writetype)
359 print("")
361 if targetblk or targetsec then
362 if targetblk then
363 data = sendRaw("30"..string.format("%02x", targetblk), false)
366 if targetblk then
367 -- floor division...
368 SectorHeader(targetblk // 4)
369 else
370 SectorHeader(targetsec)
373 if targetblk then
374 BlockParser(data, targetblk)
375 else
376 for i=0, 3 do
377 BlockParser(sendRaw("30"..string.format("%02x", targetsec * 4 + i), true), targetsec * 4 + i)
381 elseif targetbblk or targetbsec then
382 if targetbblk then
383 data = sendRaw("38"..string.format("%02x", targetbblk), false)
386 if targetbblk then
387 -- floor division
388 SectorHeader(targetbblk // 4)
389 else
390 SectorHeader(targetbsec)
393 if targetbblk then
394 BlockParser(data, targetbblk)
395 else
396 for i =0, 3 do
397 BlockParser(sendRaw("38"..string.format("%02x", targetbsec * 4 + i), true), targetbsec * 4 + i)
400 -- Actually is there an sprintf_hex in lua?
402 lib14a.disconnect()
405 -- Separator
406 if uid then
407 if writetype == "2" or writetype == "4" then
408 if string.len(uid) == 8 then
409 payload = calculate_block0(uid)
410 -- Calculate BCC
411 -- Append SAK
412 payload = payload .. "08"
413 -- Empty manuf bytes
414 payload = payload .. "04000000000000000000"
415 elseif string.len(uid) == 14 then
416 -- Same logic, but with raw anticollision data because that's what the tag accepts. :P
417 payload = calculate_block0("88"..string.sub(uid, 1, 6))
418 payload = payload .. "04"
419 payload = payload .. calculate_block0(string.sub(uid, 7, 14))
420 payload = payload .. "08"
421 payload = payload .. "00000000"
425 core.clearCommandBuffer()
426 -- Now, let's write! 1. We wake up the tag in magic mode.
427 -- 2. We will deal with the "easier" 7 byte UID stuff
428 if string.len(uid) == 14 then
430 wakeupmagic(writetype)
431 if f3perso == true then
432 print("[?] WARNING: F3 perso write is set, but 7 byte UID is passed. Ignoring -3 argument")
435 local configdata = readconf()
437 if configdata[10] ~= 0x5A and configdata[10] ~= 0xC3 and configdata[10] ~= 0xA5 then -- Enable CL2 mode if necessary
438 print("[?] WARNING: Tag is not in 7 byte UID mode. Automatically updating to F0 unfused")
439 print(cl.yellow.."[-]".. cl.reset .." This is because the configuration byte responsible for CL2 was not found to be equal to 0x5A, 0xC3 or 0xA5, but rather: ".. string.format("%02x", configdata[10]))
440 print(cl.yellow.."[\\]".. cl.reset .." The old config is: ".. utils.ConvertBytesToHex(configdata))
441 configdata[10] = 0x5A
442 writeconf(configdata)
445 if sendRaw("A800", true) ~= "0A" then
446 oops("Tag did not ACK `A800` command!")
447 lib14a.disconnect()
448 return 1
451 print("[?] WARNING: nUID should be updated with this value:")
452 print(makenuid(uid))
453 print(cl.yellow.."[/]".. cl.reset .." Use `--f3d` to update nUID for Perso F3 only.")
455 if sendRaw(payload, true) ~= "0A" then
456 oops("Tag did not ACK data to write!")
457 lib14a.disconnect()
458 return 1
461 print(cl.yellow.."[-]".. cl.reset .." Updating real block 0")
462 if sendRaw("A000", true) ~= "0A" then
463 oops("Tag did not ACK `A000` command!")
464 lib14a.disconnect()
465 return 1
468 if sendRaw(cltwo_block0(uid), false) ~="0A" then
469 oops("Tag did not ACK data to write!")
472 -- Now, let's work with 4 byte UIDs.
473 elseif string.len(uid) == 8 then
475 wakeupmagic(writetype)
476 local configdata = readconf()
478 if configdata[10] == 0x69 or f3perso == true then -- If we have Perso: F3, then write backdoor blk 1
480 if f3perso == true then
481 print ("[?] WARNING: F3 flag enabled. Updating UID used for F3 perso")
484 if sendRaw("A801", true) ~= "0A" then
485 oops("Tag did not ACK `A801` command!")
486 lib14a.disconnect()
487 return 1
490 else -- Otherwise write real block 0.
491 if configdata[10] == 0x5a or configdata[10] == 0xc3 or configdata[10] == 0xa5 then -- Disable CL2 if necessary
492 print("[?] WARNING: Tag is not in 4 byte UID mode. Automatically disabling")
493 print(cl.yellow.."[-]".. cl.reset .." This is because the configuration byte responsible for CL2 was found to be equal to: ".. string.format("%02x", configdata[10]))
494 print(cl.yellow.."[\\]".. cl.reset .." The old config is: ".. utils.ConvertBytesToHex(configdata))
495 configdata[10] = 0x00
496 writeconf(configdata)
499 if sendRaw("A000", true) ~= "0A" then
500 oops("Tag did not ACK `A000` command!")
501 lib14a.disconnect()
502 return 1
506 if sendRaw(payload, false) ~= "0A" then
507 oops("Tag did not ACK data to write!")
512 -- Separator
513 if signature then
514 wakeupmagic(writetype)
515 local configdata = readconf()
516 if configdata[14] ~= 0x5A then
517 print("[?] WARNING: Signature sector is not enabled. Automatically enabling")
518 configdata[14] = 0x5A
519 writeconf(configdata)
522 if sendRaw("A805", true) ~= "0A" then
523 oops("Tag did not ACK `A805` command!")
524 lib14a.disconnect()
525 return 1
528 if sendRaw(string.sub(signature,1,32), true) ~= "0A" then
529 oops("Tag did not ACK data 1 to write!")
530 lib14a.disconnect()
531 return 1
534 if sendRaw("A806", true) ~= "0A" then
535 oops("Tag did not ACK `A806` command!")
536 lib14a.disconnect()
537 return 1
540 if sendRaw(string.sub(signature,33,64), false) ~= "0A" then
541 oops("Tag did not ACK data 2 to write!")
542 lib14a.disconnect()
543 return 1
547 if configwrite then
549 print(cl.yellow.."[|]"..cl.reset.." Welcome to ConfigStar!")
550 wakeupmagic(writetype)
551 config = readconf()
553 if (gen1 == false and magicauth == false) or ((config[1]==0x85 and config[2] == 0x00) and magicauth==false) or ((config[12]==0x00) and gen1 == false) then
554 oops("What you are about to do is potentially dangerous. \nIf you really want to continue (potentially leaving your tag in an unusable state), enter this line as given, without quotation marks:\n \"yes\"")
555 local ans = io.read()
556 if ans ~="yes" then
557 lib14a.disconnect()
558 return 1
559 else
560 print(cl.red.."[/]"..cl.reset.." Brace yourself.")
564 -- Baby oh baby
565 -- Prepare for disappointment
566 if gen1 == true then
567 config[1] = 0x7A
568 config[2] = 0xFF
569 elseif gen1 == false then
570 config[1] = 0x85
571 config[2] = 0x00
574 if gen1com == true then
575 config[3] = 0x85
576 elseif gen1com == false then
577 config[3] = 0x00
580 if keyblock == true then
581 config[7] = 0x5A
582 elseif keyblock == false then
583 config[7] = 0x00
586 if cuid == true then
587 config[8] = 0x5A
588 elseif cuid == false then
589 config[8] = 0x00
592 if cl2mode == true then
593 config[10] = 0x5A
594 elseif cl2mode == false then
595 config[10] = 0x00
598 if shadowmode == true then
599 config[11] = 0x5A
600 elseif shadowmode == false then
601 config[11] = 0x00
604 if magicauth == true then
605 config[12] = 0x5A
606 elseif magicauth == false then
607 config[12] = 0x00
610 if statenc == true then
611 config[13] = 0x5A
612 elseif statenc == false then
613 config[13] = 0x00
616 if sigsec == true then
617 config[14] = 0x5A
618 elseif sigsec == false then
619 config[14] = 0x00
622 writeconf(config)
623 print(cl.yellow.."[\\]"..cl.reset.." Completed!")
624 lib14a.disconnect()
628 main(args)