text
[RRG-proxmark3.git] / client / luascripts / ntag_hammertime.lua
blobfc9168ae564d61471d2c69267389d1071ad0c2ed
1 local getopt = require('getopt')
2 local lib14a = require('read14a')
3 local cmds = require('commands')
4 local ansicolors = require('ansicolors')
6 copyright = 'Copyright 2020 A. Ozkal, released under GPLv2+.'
7 author = 'Ave'
8 version = 'v2.1.3'
9 desc = [[
10 This script writes a bunch of random blocks to a NTAG or MFUL card to test its actual write limits
12 example = [[
13 script run ntag_hammertime -w 1000 -r 50 -z 50 -f 5 -s 4 -e 129
15 usage = [[
16 script run ntag_hammertime [-h] [-w <writecount>] [-r <readevery>] [-z <reselectevery>] [-f <maximumfails>] [-s <writestartblock>] [-e <writeendblock>]
18 arguments = [[
19 -h : This help
20 -w <writeroundcount> : Amount of write rounds to be done to each block (optional, default: 100)
21 -r <readevery> : Verify frequency (reads and checks written values every x rounds, optional, default: 10)
22 -z <reselectevery> : Reselect frequency (reselects card once every x rounds, optional, default: 10)
23 -f <maximumfails> : Maximum consequent fails (read/write) that will trigger a fail state (optional, default: 3)
24 -s <writestartblock> : Block number for writes to be started to (optional, inclusive, decimal, default: 4)
25 -e <writeendblock> : Block number for writes to be ended on (optional, inclusive, decimal, default: 129)
28 local function help()
29 print(author)
30 print(version)
31 print(desc)
32 print(ansicolors.cyan..'Usage'..ansicolors.reset)
33 print(usage)
34 print(ansicolors.cyan..'Arguments'..ansicolors.reset)
35 print(arguments)
36 print(ansicolors.cyan..'Example usage'..ansicolors.reset)
37 print(example)
38 end
40 function randhex(len)
41 result = ""
42 for i = 1,len,1
44 -- 48-57 numbers, 65-70 a-f
45 hex = math.random(0, 15)
46 if hex >= 10 then
47 hex = hex + 7
48 end
49 result = result..string.char(48 + hex)
50 end
51 return result
52 end
54 -- Used to send raw data to the firmware to subsequently forward the data to the card.
55 -- from mifareplus.lua
56 local function sendRaw(rawdata, crc, power)
57 -- print(("<sent>: %s"):format(rawdata))
59 local flags = lib14a.ISO14A_COMMAND.ISO14A_RAW
60 if crc then
61 flags = flags + lib14a.ISO14A_COMMAND.ISO14A_APPEND_CRC
62 end
63 if power then
64 flags = flags + lib14a.ISO14A_COMMAND.ISO14A_NO_DISCONNECT
65 end
67 local command = Command:newMIX{cmd = cmds.CMD_HF_ISO14443A_READER,
68 arg1 = flags, -- Send raw
69 arg2 = string.len(rawdata) / 2, -- arg2 contains the length, which is half the length of the ASCII-string rawdata
70 data = rawdata
72 local ignore_response = false
73 local result, err = command:sendMIX(ignore_response)
74 if result then
75 --unpack the first 4 parts of the result as longs, and the last as an extremely long string to later be cut down based on arg1, the number of bytes returned
76 local count,cmd,arg1,arg2,arg3,data = bin.unpack('LLLLH512',result)
78 returned_bytes = string.sub(data, 1, arg1 * 2)
79 if #returned_bytes > 0 then
80 -- print(("<recvd>: %s"):format(returned_bytes)) -- need to multiply by 2 because the hex digits are actually two bytes when they are strings
81 return returned_bytes
82 else
83 return nil
84 end
85 else
86 print("Error sending the card raw data.")
87 return nil
88 end
89 end
91 local function selectCard(keepField, arg2, attemptCount)
92 for i = 1,attemptCount,1
94 if lib14a.read(keepField, arg2) then
95 return true
96 end
97 end
98 return false
99 end
102 -- The main entry point
103 function main(args)
104 failcounter = 0
106 -- param defaults
107 loopcount = 100
108 verifyevery = 10
109 reselectevery = 10
110 failmax = 3
111 blockstart = 4
112 blockend = 129
114 -- Read the parameters
115 for o, a in getopt.getopt(args, 'hw:r:z:f:s:e:') do
116 if o == 'h' then return help() end
117 if o == 'w' then loopcount = tonumber(a) end
118 if o == 'r' then verifyevery = tonumber(a) end
119 if o == 'z' then reselectevery = tonumber(a) end
120 if o == 'f' then failmax = tonumber(a) end
121 if o == 's' then blockstart = tonumber(a) end
122 if o == 'e' then blockend = tonumber(a) end
125 starttime = os.time()
127 if selectCard(true, false, 3) ~= true then
128 return print("Select failed.")
130 for i = 1,loopcount,1
132 for block = blockstart,blockend,1
134 data = randhex(8)
135 print(i..": Writing "..data.." to block "..block..".")
136 blockhex = string.format("%02x", block)
137 result = sendRaw("A2"..blockhex..data, true, true)
138 if result then -- if false/nil, that's a fail right there
139 print(ansicolors.green.."Got "..result.."."..ansicolors.reset) -- We want this to be 0A
140 failcounter = 0
141 else
142 print(ansicolors.red.."Write FAILED."..ansicolors.reset)
143 failcounter = failcounter + 1
144 goto continue
147 if i % verifyevery == 0 then
148 result = sendRaw("30"..blockhex, true, true)
149 if result then -- if false, that's a fail right there
150 result = string.sub(result, 0, 8)
151 if result ~= data then
152 print(ansicolors.red.."Read IMPROPER, supposed to be "..data..", got "..result.."."..ansicolors.reset)
153 failcounter = failcounter + 1
154 goto continue
155 else
156 print(ansicolors.green.."Read matches the write."..ansicolors.reset)
157 failcounter = 0
159 else
160 print(ansicolors.red.."Read FAILED."..ansicolors.reset)
161 failcounter = failcounter + 1
162 goto continue
165 ::continue::
167 if failcounter >= failmax then
168 -- close field
169 lib14a.read(false, false)
170 return print(ansicolors.red.."Test failed after "..(os.time() - starttime).." seconds, "..(i*(blockend-blockstart)).." writes and "..math.floor((i*(blockend-blockstart))/verifyevery).." reads."..ansicolors.reset)
174 if i % reselectevery == 0 then
175 -- reselect
176 sendRaw("", false, false)
177 if selectCard(true, false, 3) ~= true then
178 return print("Reselect failed.")
180 print("Reselected card, current rate: "..(i*(blockend-blockstart))/(os.time() - starttime).." writes/s.")
184 -- close field
185 lib14a.read(false, false)
186 print("Successfully completed test in "..(os.time() - starttime).." seconds, did "..(loopcount*(blockend-blockstart)).." writes and "..math.floor((loopcount*(blockend-blockstart))/verifyevery).." reads.")
189 main(args)