text
[RRG-proxmark3.git] / client / luascripts / lf_hid_bulkclone.lua
blob0e74f82b53e8c9c1636f3553d779b4377ec3bfe1
1 --
2 -- lf_hid_bulkclone.lua - A tool to clone a large number of tags at once.
3 -- Updated 2017-04-18
4 -- Updated 2018-02-20 iceman
5 local getopt = require('getopt')
6 local ansicolors = require('ansicolors')
8 copyright = ''
9 author = "Brian Redbeard"
10 version = 'v1.0.2'
11 desc = [[
12 Perform bulk enrollment of 26 bit H10301 style RFID Tags
13 For more info, check the comments in the code
15 example = [[
17 script run lf_hid_bulkclone.lua -f 1 -b 1000 -c 10
19 usage = [[
20 script run lf_hid_bulkclone.lua -f facility -b base_id_num -c count
22 arguments = [[
23 -h : this help
24 -f : facility id
25 -b : starting card id
26 -c : count, number of cards to make
28 local DEBUG = true
29 --local bxor = bit32.bxor
30 local bor = bit32.bor
31 local lshift = bit32.lshift
32 ---
33 -- A debug printout-function
34 local function dbg(args)
35 if not DEBUG then return end
36 if type(args) == 'table' then
37 local i = 1
38 while args[i] do
39 dbg(args[i])
40 i = i+1
41 end
42 else
43 print('###', args)
44 end
45 end
46 ---
47 -- This is only meant to be used when errors occur
48 local function oops(err)
49 print('ERROR:', err)
50 core.clearCommandBuffer()
51 return nil, errr
52 end
53 ---
54 -- Usage help
55 local function help()
56 print(copyright)
57 print(author)
58 print(version)
59 print(desc)
60 print(ansicolors.cyan..'Usage'..ansicolors.reset)
61 print(usage)
62 print(ansicolors.cyan..'Arguments'..ansicolors.reset)
63 print(arguments)
64 print(ansicolors.cyan..'Example usage'..ansicolors.reset)
65 print(example)
66 end
67 ---
68 -- Exit message
69 local function exitMsg(msg)
70 print( string.rep('--',20) )
71 print( string.rep('--',20) )
72 print(msg)
73 print()
74 end
75 --[[Implement a function to simply visualize the bitstream in a text format
76 --This is especially helpful for troubleshooting bitwise math issues]]--
77 local function toBits(num,bits)
78 -- returns a table of bits, most significant first.
79 bits = bits or math.max(1, select(2, math.frexp(num)))
80 local t = {} -- will contain the bits
81 for b = bits, 1, -1 do
82 t[b] = math.fmod(num, 2)
83 num = math.floor((num - t[b]) / 2)
84 end
85 return table.concat(t)
86 end
88 --[[
89 Likely, I'm an idiot, but I couldn't find any parity functions in Lua
90 This can also be done with a combination of bitwise operations (in fact,
91 is the canonically "correct" way to do it, but my brain doesn't just
92 default to this and so counting some ones is good enough for me
93 ]]--
94 local function evenparity(s)
95 local _, count = string.gsub(s, '1', '')
96 local p = count % 2
97 if (p == 0) then
98 return false
99 else
100 return true
104 local function isempty(s)
105 return s == nil or s == ''
108 --[[
109 The Proxmark3 "clone" functions expect the data to be in hex format so
110 take the card id number and facility ID as arguments and construct the
111 hex. This should be easy enough to extend to non 26bit formats
112 ]]--
113 local function cardHex(i, f)
114 fac = lshift(f, 16)
115 id = bor(i, fac)
116 stream = toBits(id, 24)
118 --As the function defaults to even parity and returns a boolean,
119 --perform a 'not' function to get odd parity
120 high = evenparity(string.sub(stream,1,12)) and 1 or 0
121 low = not evenparity(string.sub(stream,13)) and 1 or 0
122 bits = bor( lshift(id, 1), low)
123 bits = bor( bits, lshift(high, 25))
125 --Since the lua library bit32 is (obviously) 32 bits and we need to
126 --encode 36 bits to properly do a 26 bit tag with the preamble we need
127 --to create a higher order and lower order component which we will
128 --then assemble in the return. The math above defines the proper
129 --encoding as per HID/Weigand/etc. These bit flips are due to the
130 --format length check on bit 38 (cmdlfhid.c:64) and
131 --bit 31 (cmdlfhid.c:66).
132 preamble = bor(0, lshift(1, 5))
133 bits = bor(bits, lshift(1, 26))
135 return ('%04x%08x'):format(preamble, bits)
138 -- main
139 local function main(args)
141 print( string.rep('--',20) )
142 print( string.rep('--',20) )
143 print()
145 if #args == 0 then return help() end
147 --I really wish a better getopt function would be brought in supporting
148 --long arguments, but it seems this library was chosen for BSD style
149 --compatibility
150 for o, a in getopt.getopt(args, 'f:b:c:h') do
151 if o == 'h' then return help() end
152 if o == 'f' then
153 if isempty(a) then
154 print('You did not supply a facility code, using 0')
155 facility = 0
156 else
157 facility = a
160 if o == 'b' then
161 if isempty(a) then return oops('You must supply the flag -b (base id)') end
162 baseid = a
164 if o == 'c' then
165 if isempty(a) then return oops('You must supply the flag -c (count)') end
166 count = a
170 --Due to my earlier complaints about how this specific getopt library
171 --works, specifying ':' does not enforce supplying a value, thus we
172 --need to do these checks all over again.
173 if isempty(baseid) then return oops('You must supply the flag -b (base id)') end
174 if isempty(count) then return oops('You must supply the flag -c (count)') end
176 --If the facility ID is non specified, ensure we code it as zero
177 if isempty(facility) then
178 print('Using 0 for the facility code as -f was not supplied')
179 facility = 0
182 --The next baseid + count function presents a logic/UX conflict
183 --where users specifying -c 1 (count = 1) would try to program two
184 --tags. This makes it so that -c 0 & -c 1 both code one tag, and all
185 --other values encode the expected amount.
186 if tonumber(count) > 0 then count = count - 1 end
188 endid = baseid + count
190 for cardnum = baseid, endid do
191 local card = cardHex(cardnum, facility)
192 print('Press enter to program card '..cardnum..':'..facility..' (hex: '..card..')')
193 --This would be better with 'press Enter', but we'll take what we can get.
194 io.read()
195 core.console( ('lf hid clone -r %s'):format(card) )
199 main(args)