| 1 | local cmds = require('commands') |
| 2 | local getopt = require('getopt') |
| 3 | local bin = require('bin') |
| 4 | local lib14a = require('read14a') |
| 5 | local utils = require('utils') |
| 6 | local md5 = require('md5') |
| 7 | local dumplib = require('html_dumplib') |
| 8 | local toys = require('default_toys') |
| 9 | |
| 10 | example =[[ |
| 11 | script run tnp3dump |
| 12 | script run tnp3dump -n |
| 13 | script run tnp3dump -p |
| 14 | script run tnp3dump -k aabbccddeeff |
| 15 | script run tnp3dump -k aabbccddeeff -n |
| 16 | script run tnp3dump -o myfile |
| 17 | script run tnp3dump -n -o myfile |
| 18 | script run tnp3dump -p -o myfile |
| 19 | script run tnp3dump -k aabbccddeeff -n -o myfile |
| 20 | ]] |
| 21 | author = "Iceman" |
| 22 | usage = "script run tnp3dump -k <key> -n -p -o <filename>" |
| 23 | desc =[[ |
| 24 | This script will try to dump the contents of a Mifare TNP3xxx card. |
| 25 | It will need a valid KeyA in order to find the other keys and decode the card. |
| 26 | Arguments: |
| 27 | -h : this help |
| 28 | -k <key> : Sector 0 Key A. |
| 29 | -n : Use the nested cmd to find all keys |
| 30 | -p : Use the precalc to find all keys |
| 31 | -o : filename for the saved dumps |
| 32 | ]] |
| 33 | local RANDOM = '20436F707972696768742028432920323031302041637469766973696F6E2E20416C6C205269676874732052657365727665642E20' |
| 34 | local TIMEOUT = 2000 -- Shouldn't take longer than 2 seconds |
| 35 | local DEBUG = false -- the debug flag |
| 36 | local numBlocks = 64 |
| 37 | local numSectors = 16 |
| 38 | --- |
| 39 | -- A debug printout-function |
| 40 | function dbg(args) |
| 41 | if not DEBUG then |
| 42 | return |
| 43 | end |
| 44 | |
| 45 | if type(args) == "table" then |
| 46 | local i = 1 |
| 47 | while result[i] do |
| 48 | dbg(result[i]) |
| 49 | i = i+1 |
| 50 | end |
| 51 | else |
| 52 | print("###", args) |
| 53 | end |
| 54 | end |
| 55 | --- |
| 56 | -- This is only meant to be used when errors occur |
| 57 | function oops(err) |
| 58 | print("ERROR: ",err) |
| 59 | end |
| 60 | --- |
| 61 | -- Usage help |
| 62 | function help() |
| 63 | print(desc) |
| 64 | print("Example usage") |
| 65 | print(example) |
| 66 | end |
| 67 | -- |
| 68 | -- Exit message |
| 69 | function ExitMsg(msg) |
| 70 | print( string.rep('--',20) ) |
| 71 | print( string.rep('--',20) ) |
| 72 | print(msg) |
| 73 | print() |
| 74 | end |
| 75 | |
| 76 | local function readdumpkeys(infile) |
| 77 | t = infile:read("*all") |
| 78 | len = string.len(t) |
| 79 | local len,hex = bin.unpack(("H%d"):format(len),t) |
| 80 | return hex |
| 81 | end |
| 82 | |
| 83 | local function waitCmd() |
| 84 | local response = core.WaitForResponseTimeout(cmds.CMD_ACK,TIMEOUT) |
| 85 | if response then |
| 86 | local count,cmd,arg0 = bin.unpack('LL',response) |
| 87 | if(arg0==1) then |
| 88 | local count,arg1,arg2,data = bin.unpack('LLH511',response,count) |
| 89 | return data:sub(1,32) |
| 90 | else |
| 91 | return nil, "Couldn't read block.." |
| 92 | end |
| 93 | end |
| 94 | return nil, "No response from device" |
| 95 | end |
| 96 | |
| 97 | local function main(args) |
| 98 | |
| 99 | print( string.rep('--',20) ) |
| 100 | print( string.rep('--',20) ) |
| 101 | |
| 102 | local keyA |
| 103 | local cmd |
| 104 | local err |
| 105 | local useNested = false |
| 106 | local usePreCalc = false |
| 107 | local cmdReadBlockString = 'hf mf rdbl %d A %s' |
| 108 | local input = "dumpkeys.bin" |
| 109 | local outputTemplate = os.date("toydump_%Y-%m-%d_%H%M%S"); |
| 110 | |
| 111 | -- Arguments for the script |
| 112 | for o, a in getopt.getopt(args, 'hk:npo:') do |
| 113 | if o == "h" then return help() end |
| 114 | if o == "k" then keyA = a end |
| 115 | if o == "n" then useNested = true end |
| 116 | if o == "p" then usePreCalc = true end |
| 117 | if o == "o" then outputTemplate = a end |
| 118 | end |
| 119 | |
| 120 | -- validate input args. |
| 121 | keyA = keyA or '4b0b20107ccb' |
| 122 | if #(keyA) ~= 12 then |
| 123 | return oops( string.format('Wrong length of write key (was %d) expected 12', #keyA)) |
| 124 | end |
| 125 | |
| 126 | -- Turn off Debug |
| 127 | local cmdSetDbgOff = "hf mf dbg 0" |
| 128 | core.console( cmdSetDbgOff) |
| 129 | |
| 130 | result, err = lib14a.read1443a(false, true) |
| 131 | if not result then |
| 132 | return oops(err) |
| 133 | end |
| 134 | |
| 135 | core.clearCommandBuffer() |
| 136 | |
| 137 | -- Show tag info |
| 138 | print((' Found tag %s'):format(result.name)) |
| 139 | |
| 140 | dbg(('Using keyA : %s'):format(keyA)) |
| 141 | |
| 142 | --Trying to find the other keys |
| 143 | if useNested then |
| 144 | core.console( ('hf mf nested 1 0 A %s d'):format(keyA) ) |
| 145 | end |
| 146 | |
| 147 | core.clearCommandBuffer() |
| 148 | |
| 149 | local akeys = '' |
| 150 | if usePreCalc then |
| 151 | local pre = require('precalc') |
| 152 | akeys = pre.GetAll(result.uid) |
| 153 | else |
| 154 | print('Loading dumpkeys.bin') |
| 155 | local hex, err = utils.ReadDumpFile(input) |
| 156 | if not hex then |
| 157 | return oops(err) |
| 158 | end |
| 159 | akeys = hex:sub(0,12*16) |
| 160 | end |
| 161 | |
| 162 | -- Read block 0 |
| 163 | cmd = Command:new{cmd = cmds.CMD_MIFARE_READBL, arg1 = 0,arg2 = 0,arg3 = 0, data = keyA} |
| 164 | err = core.SendCommand(cmd:getBytes()) |
| 165 | if err then return oops(err) end |
| 166 | local block0, err = waitCmd() |
| 167 | if err then return oops(err) end |
| 168 | |
| 169 | -- Read block 1 |
| 170 | cmd = Command:new{cmd = cmds.CMD_MIFARE_READBL, arg1 = 1,arg2 = 0,arg3 = 0, data = keyA} |
| 171 | err = core.SendCommand(cmd:getBytes()) |
| 172 | if err then return oops(err) end |
| 173 | local block1, err = waitCmd() |
| 174 | if err then return oops(err) end |
| 175 | |
| 176 | local tmpHash = block0..block1..'%02x'..RANDOM |
| 177 | |
| 178 | local key |
| 179 | local pos = 0 |
| 180 | local blockNo |
| 181 | local blocks = {} |
| 182 | |
| 183 | print('Reading card data') |
| 184 | core.clearCommandBuffer() |
| 185 | |
| 186 | -- main loop |
| 187 | io.write('Reading blocks > ') |
| 188 | for blockNo = 0, numBlocks-1, 1 do |
| 189 | |
| 190 | if core.ukbhit() then |
| 191 | print("aborted by user") |
| 192 | break |
| 193 | end |
| 194 | |
| 195 | pos = (math.floor( blockNo / 4 ) * 12)+1 |
| 196 | key = akeys:sub(pos, pos + 11 ) |
| 197 | cmd = Command:new{cmd = cmds.CMD_MIFARE_READBL, arg1 = blockNo ,arg2 = 0,arg3 = 0, data = key} |
| 198 | local err = core.SendCommand(cmd:getBytes()) |
| 199 | if err then return oops(err) end |
| 200 | local blockdata, err = waitCmd() |
| 201 | if err then return oops(err) end |
| 202 | |
| 203 | |
| 204 | if blockNo%4 ~= 3 then |
| 205 | |
| 206 | if blockNo < 8 then |
| 207 | -- Block 0-7 not encrypted |
| 208 | blocks[blockNo+1] = ('%02d :: %s'):format(blockNo,blockdata) |
| 209 | else |
| 210 | -- blocks with zero not encrypted. |
| 211 | if string.find(blockdata, '^0+$') then |
| 212 | blocks[blockNo+1] = ('%02d :: %s'):format(blockNo,blockdata) |
| 213 | else |
| 214 | local baseStr = utils.ConvertHexToAscii(tmpHash:format(blockNo)) |
| 215 | local key = md5.sumhexa(baseStr) |
| 216 | local aestest = core.aes128_decrypt(key, blockdata) |
| 217 | local hex = utils.ConvertAsciiToBytes(aestest) |
| 218 | hex = utils.ConvertBytesToHex(hex) |
| 219 | blocks[blockNo+1] = ('%02d :: %s'):format(blockNo,hex) |
| 220 | io.write(blockNo..',') |
| 221 | end |
| 222 | end |
| 223 | else |
| 224 | -- Sectorblocks, not encrypted |
| 225 | blocks[blockNo+1] = ('%02d :: %s%s'):format(blockNo,key,blockdata:sub(13,32)) |
| 226 | end |
| 227 | end |
| 228 | io.write('\n') |
| 229 | |
| 230 | core.clearCommandBuffer() |
| 231 | |
| 232 | -- Print results |
| 233 | local bindata = {} |
| 234 | local emldata = '' |
| 235 | |
| 236 | for _,s in pairs(blocks) do |
| 237 | local slice = s:sub(8,#s) |
| 238 | local str = utils.ConvertBytesToAscii( |
| 239 | utils.ConvertHexToBytes(slice) |
| 240 | ) |
| 241 | emldata = emldata..slice..'\n' |
| 242 | for c in (str):gmatch('.') do |
| 243 | bindata[#bindata+1] = c |
| 244 | end |
| 245 | end |
| 246 | |
| 247 | print( string.rep('--',20) ) |
| 248 | |
| 249 | local uid = block0:sub(1,8) |
| 250 | local toytype = block1:sub(1,4) |
| 251 | local cardidLsw = block1:sub(9,16) |
| 252 | local cardidMsw = block1:sub(16,24) |
| 253 | local cardid = block1:sub(9,24) |
| 254 | local subtype = block1:sub(25,28) |
| 255 | |
| 256 | -- Write dump to files |
| 257 | if not DEBUG then |
| 258 | local foo = dumplib.SaveAsBinary(bindata, outputTemplate..'-'..uid..'.bin') |
| 259 | print(("Wrote a BIN dump to: %s"):format(foo)) |
| 260 | local bar = dumplib.SaveAsText(emldata, outputTemplate..'-'..uid..'.eml') |
| 261 | print(("Wrote a EML dump to: %s"):format(bar)) |
| 262 | end |
| 263 | |
| 264 | print( string.rep('--',20) ) |
| 265 | -- Show info |
| 266 | |
| 267 | local item = toys.Find(toytype, subtype) |
| 268 | if item then |
| 269 | print((' ITEM TYPE : %s - %s (%s)'):format(item[6],item[5], item[4]) ) |
| 270 | else |
| 271 | print((' ITEM TYPE : 0x%s 0x%s'):format(toytype, subtype)) |
| 272 | end |
| 273 | |
| 274 | print( (' UID : 0x%s'):format(uid) ) |
| 275 | print( (' CARDID : 0x%s'):format(cardid ) ) |
| 276 | print( string.rep('--',20) ) |
| 277 | |
| 278 | core.clearCommandBuffer() |
| 279 | end |
| 280 | main(args) |