1686e4d1 |
1 | |
2 | -- Ability to read what card is there |
3 | local getopt = require('getopt') |
4 | local cmds = require('commands') |
5 | local taglib = require('taglib') |
6 | |
7 | local desc = |
8 | [[This script will automatically recognize and dump full content of a NFC NDEF Initialized tag; non-initialized tags will be ignored. |
9 | |
b3123cf6 |
10 | It also write the dump to an eml-file <uid>.eml. |
11 | |
12 | (The difference between an .eml-file and a .bin-file is that the eml file contains |
13 | ASCII representation of the hex-data, with linebreaks between 'rows'. A .bin-file contains the |
14 | raw data, but when saving into that for, we lose the infromation about how the memory is structured. |
15 | For example: 24 bytes could be 6 blocks of 4 bytes, or vice versa. |
16 | Therefore, the .eml is better to use file when saving dumps.) |
17 | |
1686e4d1 |
18 | Arguments: |
19 | -d debug logging on |
20 | -h this help |
21 | |
22 | ]] |
23 | local example = "script run xxx" |
24 | local author = "Martin Holst Swende & Asper" |
1686e4d1 |
25 | --- |
26 | -- PrintAndLog |
27 | function prlog(...) |
28 | -- TODO; replace this with a call to the proper PrintAndLog |
29 | print(...) |
30 | end |
b3123cf6 |
31 | --- |
32 | -- This is only meant to be used when errors occur |
33 | function oops(err) |
34 | prlog("ERROR: ",err) |
35 | return nil,err |
36 | end |
37 | |
38 | |
39 | -- Perhaps this will be moved to a separate library at one point |
40 | local utils = { |
41 | --- Writes an eml-file. |
42 | -- @param uid - the uid of the tag. Used in filename |
43 | -- @param blockData. Assumed to be on the format {'\0\1\2\3,'\b\e\e\f' ..., |
44 | -- that is, blockData[row] contains a string with the actual data, not ascii hex representation |
45 | -- return filename if all went well, |
46 | -- @reurn nil, error message if unsuccessfull |
47 | writeDumpFile = function(uid, blockData) |
48 | local destination = string.format("%s.eml", uid) |
49 | local file = io.open(destination, "w") |
50 | if file == nil then |
51 | return nil, string.format("Could not write to file %s", destination) |
52 | end |
53 | local rowlen = string.len(blockData[1]) |
54 | |
55 | for i,block in ipairs(blockData) do |
56 | if rowlen ~= string.len(block) then |
57 | prlog(string.format("WARNING: Dumpdata seems corrupted, line %d was not the same length as line 1",i)) |
58 | end |
59 | |
60 | local formatString = string.format("H%d", string.len(block)) |
61 | local _,hex = bin.unpack(formatString,block) |
412afdb8 |
62 | file:write(hex.."\n") |
b3123cf6 |
63 | end |
64 | file:close() |
65 | return destination |
66 | end, |
67 | } |
68 | |
69 | |
70 | |
1686e4d1 |
71 | --- |
72 | -- Usage help |
73 | function help() |
74 | prlog(desc) |
75 | prlog("Example usage") |
76 | prlog(example) |
77 | end |
78 | |
79 | function debug(...) |
80 | if DEBUG then |
81 | prlog("debug:", ...) |
82 | end |
83 | end |
84 | |
1686e4d1 |
85 | |
86 | local function show(data) |
87 | if DEBUG then |
88 | local formatString = ("H%d"):format(string.len(data)) |
89 | local _,hexdata = bin.unpack(formatString, data) |
90 | debug("Hexdata" , hexdata) |
91 | end |
92 | end |
93 | --- Fire up a connection with a tag, return uid |
94 | -- @return UID if successfull |
95 | -- @return nil, errormessage if unsuccessfull |
96 | |
97 | local function open() |
98 | debug("Opening connection") |
99 | core.clearCommandBuffer() |
100 | local x = string.format("hf 14a raw -r -p -s") |
101 | debug(x) |
102 | core.console(x) |
103 | debug("done") |
104 | data, err = waitCmd(true) |
105 | if err then return oops(err) end |
106 | show(data) |
107 | local formatString = ("H%d"):format(string.len(data)) |
108 | local _,uid = bin.unpack(formatString, data) |
109 | return uid |
110 | end |
111 | --- Shut down tag communication |
112 | -- return no return values |
113 | local function close() |
114 | debug("Closing connection") |
115 | core.clearCommandBuffer() |
116 | local x = string.format("hf 14a raw -r") |
117 | debug(x) |
118 | core.console(x) |
119 | debug("done") |
120 | --data, err = waitCmd(true) |
121 | --data, err = waitCmd(false) |
122 | |
123 | end |
124 | |
125 | |
126 | ---_ Gets data from a block |
127 | -- @return {block, block+1, block+2, block+3} if successfull |
128 | -- @return nil, errormessage if unsuccessfull |
129 | local function getBlock(block) |
130 | local data, err |
131 | |
132 | core.clearCommandBuffer() |
133 | |
134 | local x = string.format("hf 14a raw -r -c -p 30 %02x", block) |
135 | debug(x) |
136 | core.console(x) |
137 | debug("done") |
138 | -- By now, there should be an ACK waiting from the device, since |
139 | -- we used the -r flag (don't read response). |
140 | |
141 | data, err = waitCmd(false) |
142 | if err then return oops(err) end |
143 | show(data) |
144 | |
145 | if string.len(data) < 18 then |
ba33066c |
146 | return nil, ("Expected at least 18 bytes, got %d - this tag is not NDEF-compliant"):format(string.len(data)) |
1686e4d1 |
147 | end |
148 | -- Now, parse out the block data |
149 | -- 0534 00B9 049C AD7F 4A00 0000 E110 1000 2155 |
150 | -- b0b0 b0b0 b1b1 b1b1 b2b2 b2b2 b3b3 b3b3 CRCC |
151 | b0 = string.sub(data,1,4) |
152 | b1 = string.sub(data,5,8) |
153 | b2 = string.sub(data,9,12) |
154 | b3 = string.sub(data,13,16) |
155 | return {b0,b1,b2,b3} |
156 | end |
157 | |
158 | |
159 | --- This function is a lua-implementation of |
160 | -- cmdhf14a.c:waitCmd(uint8_t iSelect) |
161 | function waitCmd(iSelect) |
162 | local response = core.WaitForResponseTimeout(cmds.CMD_ACK,1000) |
163 | if response then |
164 | local count,cmd,arg0,arg1,arg2 = bin.unpack('LLLL',response) |
165 | |
166 | local iLen = arg0 |
167 | if iSelect then iLen = arg1 end |
168 | debug(("Received %i octets (arg0:%d, arg1:%d)"):format(iLen, arg0, arg1)) |
169 | if iLen == 0 then return nil, "No response from tag" end |
170 | local recv = string.sub(response,count, iLen+count-1) |
171 | return recv |
172 | end |
173 | return nil, "No response from device" |
174 | end |
175 | |
176 | |
177 | |
178 | local function main( args) |
179 | debug("script started") |
180 | local err, data, data2,k,v,i |
181 | -- Read the parameters |
182 | for o, a in getopt.getopt(args, 'hd') do |
183 | if o == "h" then help() return end |
184 | if o == "d" then DEBUG = true end |
185 | end |
186 | |
187 | -- Info contained within the tag (block 0 example) |
188 | -- 0534 00B9 049C AD7F 4A00 0000 E110 1000 2155 |
189 | -- b0b0 b0b0 b1b1 b1b1 b2b2 b2b2 b3b3 b3b3 CRCC |
190 | -- MM?? ???? ???? ???? ???? ???? NNVV SS?? ---- |
191 | -- M = Manufacturer info |
192 | -- N = NDEF-Structure-Compliant (if value is E1) |
193 | -- V = NFC Forum Specification version (if 10 = v1.0) |
194 | |
195 | -- First, 'connect' (fire up the field) and get the uid |
196 | local uidHexstr = open() |
197 | |
198 | -- First, get blockt 3 byte 2 |
199 | local blocks, err = getBlock(0) |
200 | if err then return oops(err) end |
201 | -- Block 3 contains number of blocks |
202 | local b3chars = {string.byte(blocks[4], 1,4)} |
203 | local numBlocks = b3chars[3] * 2 + 6 |
204 | prlog("Number of blocks:", numBlocks) |
205 | |
206 | -- NDEF compliant? |
207 | if b3chars[1] ~= 0xE1 then |
411105e0 |
208 | return oops("This tag is not NDEF-Compliant") |
1686e4d1 |
209 | end |
210 | |
211 | local ndefVersion = b3chars[2] |
212 | |
213 | -- Block 1, byte 1 contains manufacturer info |
214 | local bl1_b1 = string.byte(blocks[1], 1) |
215 | local manufacturer = taglib.lookupManufacturer(bl1_b1) |
216 | |
217 | -- Reuse existing info |
218 | local blockData = {blocks[1],blocks[2],blocks[3],blocks[4]} |
219 | |
220 | |
221 | --[[ Due to the infineon my-d move bug |
222 | (if I send 30 0F i receive block0f+block00+block01+block02 insted of block0f+block10+block11+block12) |
223 | the only way to avoid this is to send the read command as many times as block numbers |
224 | removing bytes from 5 to 18 from each answer. |
225 | --]] |
226 | prlog("Dumping data...please wait") |
227 | for i=4,numBlocks-1,1 do |
228 | blocks, err = getBlock(i) |
229 | if err then return oops(err) end |
230 | table.insert(blockData,blocks[1]) |
231 | end |
232 | -- Deactivate field |
233 | close() |
234 | -- Print results |
235 | prlog(string.format("Tag manufacturer: %s", manufacturer)) |
236 | prlog(string.format("Tag UID: %s", uidHexstr)) |
237 | prlog(string.format("Tag NDEF version: 0x%02x", ndefVersion)) |
238 | |
239 | for k,v in ipairs(blockData) do |
240 | prlog(string.format("Block %02x: %02x %02x %02x %02x",k-1, string.byte(v, 1,4))) |
241 | end |
b3123cf6 |
242 | local filename, err = utils.writeDumpFile(uidHexstr, blockData) |
243 | if err then return oops(err) end |
244 | |
245 | prlog(string.format("Dumped data into %s", filename)) |
246 | |
1686e4d1 |
247 | end |
248 | main(args) |