]>
Commit | Line | Data |
---|---|---|
79b19c5f BRH |
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 | |
f43b1038 TH |
64 | high = not evenparity(string.sub(stream,0,12)) and 1 or 0 |
65 | low = evenparity(string.sub(stream,13)) and 1 or 0 | |
79b19c5f BRH |
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) |