| 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 | |
| 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 | |
| 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" |
| 25 | --- |
| 26 | -- PrintAndLog |
| 27 | function prlog(...) |
| 28 | -- TODO; replace this with a call to the proper PrintAndLog |
| 29 | print(...) |
| 30 | end |
| 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) |
| 62 | file:write(hex.."\n") |
| 63 | end |
| 64 | file:close() |
| 65 | return destination |
| 66 | end, |
| 67 | } |
| 68 | |
| 69 | |
| 70 | |
| 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 | |
| 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 |
| 146 | return nil, ("Expected at least 18 bytes, got %d - this tag is not NDEF-compliant"):format(string.len(data)) |
| 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 |
| 208 | return oops("This tag is not NDEF-Compliant") |
| 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 |
| 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 | |
| 247 | end |
| 248 | main(args) |