| 1 | -- |
| 2 | -- lf_bulk_program.lua - A tool to clone a large number of tags at once. |
| 3 | -- Updated 2017-04-18 |
| 4 | -- |
| 5 | -- The getopt-functionality is loaded from pm3/client/lualibs/getopt.lua |
| 6 | -- Have a look there for further details |
| 7 | getopt = require('getopt') |
| 8 | bit32 = require('bit32') |
| 9 | |
| 10 | usage = [[ script run lf_bulk_program.lua -f facility -b base_id_num -c count |
| 11 | |
| 12 | e.g: |
| 13 | script run lf_bulk_program.lua -f 1 -b 1000 -c 10 |
| 14 | ]] |
| 15 | author = "Brian Redbeard" |
| 16 | desc =[[ |
| 17 | Perform bulk enrollment of 26 bit H10301 style RFID Tags |
| 18 | For more info, check the comments in the code |
| 19 | ]] |
| 20 | |
| 21 | --[[Implement a function to simply visualize the bitstream in a text format |
| 22 | --This is especially helpful for troubleshooting bitwise math issues]]-- |
| 23 | function toBits(num,bits) |
| 24 | -- returns a table of bits, most significant first. |
| 25 | bits = bits or math.max(1, select(2, math.frexp(num))) |
| 26 | local t = {} -- will contain the bits |
| 27 | for b = bits, 1, -1 do |
| 28 | t[b] = math.fmod(num, 2) |
| 29 | num = math.floor((num - t[b]) / 2) |
| 30 | end |
| 31 | return table.concat(t) |
| 32 | end |
| 33 | |
| 34 | --[[Likely, I'm an idiot, but I couldn't find any parity functions in Lua |
| 35 | This can also be done with a combination of bitwise operations (in fact, |
| 36 | is the canonically "correct" way to do it, but my brain doesn't just |
| 37 | default to this and so counting some ones is good enough for me]]-- |
| 38 | local function evenparity(s) |
| 39 | local _, count = string.gsub(s, "1", "") |
| 40 | |
| 41 | local p = count % 2 |
| 42 | if (p == 0) then |
| 43 | return(false) |
| 44 | else |
| 45 | return(true) |
| 46 | end |
| 47 | end |
| 48 | |
| 49 | |
| 50 | local function isempty(s) |
| 51 | return s == nil or s == '' |
| 52 | end |
| 53 | |
| 54 | --[[The Proxmark3 "clone" functions expect the data to be in hex format so |
| 55 | take the card id number and facility ID as arguments and construct the |
| 56 | hex. This should be easy enough to extend to non 26bit formats]]-- |
| 57 | local function cardHex(i,f) |
| 58 | fac = bit32.lshift(f,16) |
| 59 | id = bit32.bor(i, fac) |
| 60 | stream=toBits(id,26) |
| 61 | |
| 62 | --As the function defaults to even parity and returns a boolean, |
| 63 | --perform a 'not' function to get odd parity |
| 64 | high = evenparity(string.sub(stream,0,12)) and 1 or 0 |
| 65 | low = not evenparity(string.sub(stream,13)) and 1 or 0 |
| 66 | bits = bit32.bor(bit32.lshift(id,1), low) |
| 67 | bits = bit32.bor(bits, bit32.lshift(high,25)) |
| 68 | |
| 69 | --Since the lua library bit32 is (obviously) 32 bits and we need to |
| 70 | --encode 36 bits to properly do a 26 bit tag with the preamble we need |
| 71 | --to create a higher order and lower order component which we will |
| 72 | --then assemble in the return. The math above defines the proper |
| 73 | --encoding as per HID/Weigand/etc. These bit flips are due to the |
| 74 | --format length check on bit 38 (cmdlfhid.c:64) and |
| 75 | --bit 31 (cmdlfhid.c:66). |
| 76 | preamble = bit32.bor(0, bit32.lshift(1,5)) |
| 77 | bits = bit32.bor(bits, bit32.lshift(1,26)) |
| 78 | |
| 79 | return ("%04x%08x"):format(preamble,bits) |
| 80 | |
| 81 | end |
| 82 | |
| 83 | local function main(args) |
| 84 | |
| 85 | --I really wish a better getopt function would be brought in supporting |
| 86 | --long arguments, but it seems this library was chosen for BSD style |
| 87 | --compatibility |
| 88 | for o, a in getopt.getopt(args, 'f:b:c:h') do |
| 89 | if o == 'f' then |
| 90 | if isempty(a) then |
| 91 | print("You did not supply a facility code, using 0") |
| 92 | facility = 0 |
| 93 | else |
| 94 | facility = a |
| 95 | end |
| 96 | elseif o == 'b' then |
| 97 | if isempty(a) then |
| 98 | print("You must supply the flag -b (base id)") |
| 99 | return |
| 100 | else |
| 101 | baseid = a |
| 102 | end |
| 103 | elseif o == 'c' then |
| 104 | if isempty(a) then |
| 105 | print("You must supply the flag -c (count)") |
| 106 | return |
| 107 | else |
| 108 | count = a |
| 109 | end |
| 110 | elseif o == 'h' then |
| 111 | print(desc) |
| 112 | print(usage) |
| 113 | return |
| 114 | end |
| 115 | end |
| 116 | |
| 117 | --Due to my earlier complaints about how this specific getopt library |
| 118 | --works, specifying ":" does not enforce supplying a value, thus we |
| 119 | --need to do these checks all over again. |
| 120 | |
| 121 | if isempty(baseid) then |
| 122 | print("You must supply the flag -b (base id)") |
| 123 | print(usage) |
| 124 | return |
| 125 | end |
| 126 | |
| 127 | if isempty(count) then |
| 128 | print("You must supply the flag -c (count)") |
| 129 | print(usage) |
| 130 | return |
| 131 | end |
| 132 | |
| 133 | --If the facility ID is non specified, ensure we code it as zero |
| 134 | if isempty(facility) then |
| 135 | print("Using 0 for the facility code as -f was not supplied") |
| 136 | facility = 0 |
| 137 | end |
| 138 | |
| 139 | --The next baseid + count function presents a logic/UX conflict |
| 140 | --where users specifying -c 1 (count = 1) would try to program two |
| 141 | --tags. This makes it so that -c 0 & -c 1 both code one tag, and all |
| 142 | --other values encode the expected amount. |
| 143 | if tonumber(count) > 0 then count = count -1 end |
| 144 | |
| 145 | endid = baseid + count |
| 146 | |
| 147 | for cardnum = baseid,endid do |
| 148 | local card = cardHex(cardnum, facility) |
| 149 | print("Press enter to program card "..cardnum..":"..facility.." (hex: "..card..")") |
| 150 | --This would be better with "press any key", but we'll take |
| 151 | --what we can get. |
| 152 | io.read() |
| 153 | core.console( ('lf hid clone %s'):format(card) ) |
| 154 | end |
| 155 | end |
| 156 | |
| 157 | |
| 158 | main(args) |