Skip to content

Commit 9fa5fa4

Browse files
bettseskotopes
andauthored
Picopass read card using nr-mac (#79)
Co-authored-by: あく <alleteam@gmail.com>
1 parent 3fb7ec7 commit 9fa5fa4

11 files changed

+346
-44
lines changed

picopass_i.h

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ enum PicopassCustomEvent {
5151
PicopassCustomEventDictAttackUpdateView,
5252
PicopassCustomEventLoclassGotMac,
5353
PicopassCustomEventLoclassGotStandardKey,
54+
PicopassCustomEventNrMacSaved,
5455

5556
PicopassCustomEventPollerSuccess,
5657
PicopassCustomEventPollerFail,

protocol/picopass_listener.c

+77-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "picopass_i.h"
12
#include "picopass_listener_i.h"
23
#include "picopass_keys.h"
34

@@ -299,6 +300,61 @@ PicopassListenerCommand
299300
return command;
300301
}
301302

303+
PicopassListenerCommand picopass_listener_save_mac(PicopassListener* instance, uint8_t* rx_data) {
304+
PicopassListenerCommand command = PicopassListenerCommandSilent;
305+
Picopass* picopass = instance->context;
306+
307+
PicopassDevice* dev = picopass->dev;
308+
309+
const uint8_t* csn = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
310+
const uint8_t* epurse = instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data;
311+
312+
FuriString* temp_str = furi_string_alloc();
313+
FuriString* filename = furi_string_alloc();
314+
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
315+
316+
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
317+
furi_string_cat_printf(filename, "%02x", csn[i]);
318+
}
319+
furi_string_cat_printf(filename, "_");
320+
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
321+
furi_string_cat_printf(filename, "%02x", epurse[i]);
322+
}
323+
324+
furi_string_printf(
325+
temp_str, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, furi_string_get_cstr(filename), ".mac");
326+
do {
327+
// Open file
328+
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
329+
330+
if(!flipper_format_write_hex(file, "NR-MAC", rx_data + 1, PICOPASS_BLOCK_LEN)) break;
331+
332+
FURI_LOG_D(
333+
TAG,
334+
"Saved nr-mac: %02x %02x %02x %02x %02x %02x %02x %02x",
335+
// Skip command byte [0]
336+
rx_data[1],
337+
rx_data[2],
338+
rx_data[3],
339+
rx_data[4],
340+
rx_data[5],
341+
rx_data[6],
342+
rx_data[7],
343+
rx_data[8]);
344+
345+
notification_message(picopass->notifications, &sequence_double_vibro);
346+
command = PicopassListenerCommandStop;
347+
view_dispatcher_send_custom_event(
348+
picopass->view_dispatcher, PicopassCustomEventNrMacSaved);
349+
} while(0);
350+
351+
furi_string_free(temp_str);
352+
furi_string_free(filename);
353+
flipper_format_free(file);
354+
355+
return command;
356+
}
357+
302358
PicopassListenerCommand
303359
picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) {
304360
PicopassListenerCommand command = PicopassListenerCommandSilent;
@@ -310,23 +366,31 @@ PicopassListenerCommand
310366
// Since nr isn't const in loclass_opt_doBothMAC_2() copy buffer
311367
uint8_t rx_data[9] = {};
312368
bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
313-
loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
369+
bool no_key = picopass_is_memset(key, 0x00, PICOPASS_BLOCK_LEN);
314370

315-
#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
316-
if(memcmp(&rx_data[5], rmac, 4)) {
317-
// Bad MAC from reader, do not send a response.
318-
FURI_LOG_I(TAG, "Got bad MAC from reader");
319-
// Reset the cipher state since we don't do it in READCHECK
320-
picopass_listener_init_cipher_state(instance);
371+
if(no_key) {
372+
// We're emulating a partial dump of an iClass SE card and should capture the NR and MAC
373+
command = picopass_listener_save_mac(instance, rx_data);
321374
break;
322-
}
375+
} else {
376+
loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
377+
378+
#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
379+
if(memcmp(&rx_data[5], rmac, PICOPASS_MAC_LEN)) {
380+
// Bad MAC from reader, do not send a response.
381+
FURI_LOG_I(TAG, "Got bad MAC from reader");
382+
// Reset the cipher state since we don't do it in READCHECK
383+
picopass_listener_init_cipher_state(instance);
384+
break;
385+
}
323386
#endif
324387

325-
bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
326-
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
327-
if(error != NfcErrorNone) {
328-
FURI_LOG_D(TAG, "Failed tx update response: %d", error);
329-
break;
388+
bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
389+
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
390+
if(error != NfcErrorNone) {
391+
FURI_LOG_D(TAG, "Failed tx update response: %d", error);
392+
break;
393+
}
330394
}
331395

332396
command = PicopassListenerCommandProcessed;

protocol/picopass_poller.c

+130-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "picopass_i.h"
12
#include "picopass_poller_i.h"
23

34
#include "../loclass/optimized_cipher.h"
@@ -95,7 +96,7 @@ NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
9596
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
9697

9798
PicopassBlock block = {};
98-
error = picopass_poller_read_block(instance, 1, &block);
99+
error = picopass_poller_read_block(instance, PICOPASS_CONFIG_BLOCK_INDEX, &block);
99100
if(error != PicopassErrorNone) {
100101
instance->state = PicopassPollerStateFail;
101102
break;
@@ -116,6 +117,27 @@ NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
116117
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
117118
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
118119

120+
error = picopass_poller_read_block(instance, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, &block);
121+
if(error != PicopassErrorNone) {
122+
instance->state = PicopassPollerStateFail;
123+
break;
124+
}
125+
memcpy(
126+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data,
127+
block.data,
128+
sizeof(PicopassBlock));
129+
FURI_LOG_D(
130+
TAG,
131+
"epurse %02x%02x%02x%02x%02x%02x%02x%02x",
132+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[0],
133+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[1],
134+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[2],
135+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[3],
136+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[4],
137+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[5],
138+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[6],
139+
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data[7]);
140+
119141
error = picopass_poller_read_block(instance, 5, &block);
120142
if(error != PicopassErrorNone) {
121143
instance->state = PicopassPollerStateFail;
@@ -165,10 +187,114 @@ NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
165187

166188
if(instance->data->pacs.se_enabled) {
167189
FURI_LOG_D(TAG, "SE enabled");
168-
instance->state = PicopassPollerStateFail;
190+
instance->state = PicopassPollerStateNrMacAuth;
169191
} else {
170192
instance->state = PicopassPollerStateAuth;
171193
}
194+
return command;
195+
}
196+
197+
NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) {
198+
NfcCommand command = NfcCommandContinue;
199+
Picopass* picopass = instance->context;
200+
PicopassDevice* dev = picopass->dev;
201+
202+
uint8_t* csn = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
203+
uint8_t* epurse = instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data;
204+
205+
FuriString* temp_str = furi_string_alloc();
206+
FuriString* filename = furi_string_alloc();
207+
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
208+
PicopassMac mac = {};
209+
210+
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
211+
furi_string_cat_printf(filename, "%02x", csn[i]);
212+
}
213+
furi_string_cat_printf(filename, "_");
214+
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
215+
furi_string_cat_printf(filename, "%02x", epurse[i]);
216+
}
217+
218+
furi_string_printf(
219+
temp_str, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, furi_string_get_cstr(filename), ".mac");
220+
221+
FURI_LOG_D(TAG, "Looking for %s", furi_string_get_cstr(temp_str));
222+
uint8_t nr_mac[PICOPASS_BLOCK_LEN];
223+
224+
// Presume failure unless all steps are successful and the state is made "read block"
225+
instance->state = PicopassPollerStateFail;
226+
do {
227+
//check for file
228+
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break;
229+
// FURI_LOG_D(TAG, "Found %s", furi_string_get_cstr(temp_str));
230+
231+
furi_string_printf(temp_str, "NR-MAC");
232+
if(!flipper_format_read_hex(
233+
file, furi_string_get_cstr(temp_str), nr_mac, PICOPASS_BLOCK_LEN))
234+
break;
235+
memcpy(mac.data, nr_mac + 4, PICOPASS_MAC_LEN);
236+
/*
237+
FURI_LOG_D(
238+
TAG,
239+
"Read nr-mac: %02x %02x %02x %02x %02x %02x %02x %02x",
240+
nr_mac[0],
241+
nr_mac[1],
242+
nr_mac[2],
243+
nr_mac[3],
244+
nr_mac[4],
245+
nr_mac[5],
246+
nr_mac[6],
247+
nr_mac[7]);
248+
FURI_LOG_D(
249+
TAG, "MAC: %02x %02x %02x %02x", mac.data[0], mac.data[1], mac.data[2], mac.data[3]);
250+
*/
251+
252+
uint8_t ccnr[12] = {};
253+
PicopassReadCheckResp read_check_resp = {};
254+
PicopassError error = picopass_poller_read_check(instance, &read_check_resp);
255+
if(error == PicopassErrorTimeout) {
256+
instance->event.type = PicopassPollerEventTypeCardLost;
257+
instance->callback(instance->event, instance->context);
258+
instance->state = PicopassPollerStateDetect;
259+
break;
260+
} else if(error != PicopassErrorNone) {
261+
FURI_LOG_E(TAG, "Read check failed: %d", error);
262+
break;
263+
}
264+
memcpy(ccnr, read_check_resp.data, sizeof(PicopassReadCheckResp)); // last 4 bytes left 0
265+
266+
/*
267+
FURI_LOG_D(
268+
TAG,
269+
"CCNR: %02x %02x %02x %02x %02x %02x %02x %02x",
270+
ccnr[0],
271+
ccnr[1],
272+
ccnr[2],
273+
ccnr[3],
274+
ccnr[4],
275+
ccnr[5],
276+
ccnr[6],
277+
ccnr[7]);
278+
*/
279+
280+
//use mac
281+
PicopassCheckResp check_resp = {};
282+
error = picopass_poller_check(instance, nr_mac, &mac, &check_resp);
283+
if(error == PicopassErrorNone) {
284+
memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
285+
if(instance->mode == PicopassPollerModeRead) {
286+
picopass_poller_prepare_read(instance);
287+
instance->state = PicopassPollerStateReadBlock;
288+
// Set to non-zero keys to allow emulation
289+
memset(instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0xff, PICOPASS_BLOCK_LEN);
290+
memset(instance->data->AA1[PICOPASS_SECURE_KC_BLOCK_INDEX].data, 0xff, PICOPASS_BLOCK_LEN);
291+
}
292+
}
293+
294+
} while(false);
295+
furi_string_free(temp_str);
296+
furi_string_free(filename);
297+
flipper_format_free(file);
172298

173299
return command;
174300
}
@@ -234,7 +360,7 @@ NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
234360
loclass_opt_doReaderMAC(ccnr, div_key, mac.data);
235361

236362
PicopassCheckResp check_resp = {};
237-
error = picopass_poller_check(instance, &mac, &check_resp);
363+
error = picopass_poller_check(instance, NULL, &mac, &check_resp);
238364
if(error == PicopassErrorNone) {
239365
FURI_LOG_I(TAG, "Found key");
240366
memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
@@ -457,6 +583,7 @@ static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPo
457583
[PicopassPollerStateSelect] = picopass_poller_select_handler,
458584
[PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler,
459585
[PicopassPollerStateCheckSecurity] = picopass_poller_check_security,
586+
[PicopassPollerStateNrMacAuth] = picopass_poller_nr_mac_auth,
460587
[PicopassPollerStateAuth] = picopass_poller_auth_handler,
461588
[PicopassPollerStateReadBlock] = picopass_poller_read_block_handler,
462589
[PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler,

protocol/picopass_poller_i.c

+7-2
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,20 @@ PicopassError
161161

162162
PicopassError picopass_poller_check(
163163
PicopassPoller* instance,
164+
uint8_t* nr,
164165
PicopassMac* mac,
165166
PicopassCheckResp* check_resp) {
166167
PicopassError ret = PicopassErrorNone;
168+
uint8_t null_arr[4] = {};
167169

168170
do {
169171
bit_buffer_reset(instance->tx_buffer);
170172
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_CHECK);
171-
uint8_t null_arr[4] = {};
172-
bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
173+
if(nr) {
174+
bit_buffer_append_bytes(instance->tx_buffer, nr, 4);
175+
} else {
176+
bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
177+
}
173178
bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
174179

175180
NfcError error = nfc_poller_trx(

protocol/picopass_poller_i.h

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ typedef enum {
2020
PicopassPollerStateSelect,
2121
PicopassPollerStatePreAuth,
2222
PicopassPollerStateCheckSecurity,
23+
PicopassPollerStateNrMacAuth,
2324
PicopassPollerStateAuth,
2425
PicopassPollerStateReadBlock,
2526
PicopassPollerStateWriteBlock,
@@ -75,6 +76,7 @@ PicopassError
7576

7677
PicopassError picopass_poller_check(
7778
PicopassPoller* instance,
79+
uint8_t* nr,
7880
PicopassMac* mac,
7981
PicopassCheckResp* check_resp);
8082

0 commit comments

Comments
 (0)