Skip to content

Commit

Permalink
Add manually, emulate refactor WIP
Browse files Browse the repository at this point in the history
Custom text_input keyboard modified from / courtesy of cool4uma
Add manually and file saving working (with minor quality-of-life bugs enumerated in README)
In-progress refactor from hardcoded emulate scene to precomputed and modular mag_helpers functions
README updates/overhaul
  • Loading branch information
zacharyweiss committed Jan 7, 2023
1 parent f36b54d commit 90804b1
Show file tree
Hide file tree
Showing 17 changed files with 1,031 additions and 132 deletions.
60 changes: 39 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
# magspoof_flipper
Very early WIP of MagSpoof for the Flipper Zero. Currently rewriting from the ground up.
WIP of MagSpoof for the Flipper Zero. Currently rewriting from the ground up. Interpolates work from Samy Kamkar's original MagSpoof project, dunaevai135's Flipper hackathon project, and the Flipper team's LF RFID app.

Interpolates work from Samy Kamkar's original MagSpoof project, dunaevai135's Flipper hackathon project, and the Flipper team's LF RFID app.
Many thanks to everyone who has helped in addition to those above, most notably: antirez for bitmapping suggestions, skotopes for RFID consultation, NVX + dlz for NFC consulation, davethepirate for EE insight and being a sounding board, and cool4uma for their work on custom text_input scenes — as well as everyone else I've had the pleasure of chatting with.

Courses of action to try in the event the LF coil signal is too weak:
- Attempt downstream modulation techniques, in addition to upstream, like the LF RFID worker does when writing
- Introduce a subcarrier at ~125kHz, and OOK modulate it at the desired freq of bits (~4kHz)
Using this README as coarse notes of what remains to be done; anyone is welcome to contribute!

## TODO
Emulation:
- Finish refactor from hardcoded test scene to mag_helpers (most notable change: precomputing bit output akin to devBioS's "RedSpoof" implementation of MagSpoof)
- Multi-track emulation, reverse track emulation
- Experimentation on timing and other parameters (zero prefix/between/suffix, interpacket delay, reverse vs non-reverse track, etc)
- Implement/integrate better bitmap than hacky first pass? antirez's better approach (from ProtoView) included at bottom of mag_helpers
- External TX option(s) — interface with original H-bridge design, also perhaps singular coil. Does GPIO have sufficient output for this? Need a capacitor to discharge from?
- Pursue skunkworks TX improvement ideas listed below

Scenes:
- Non-hardcoded emulation scene (using mag_helpers functions) that play loaded card data
- Emulation config scene. Be able to select between RFID / GPIO H-bridge / GPIO plain coil(?), modify timing (clock and interpacket), select track(s) to be emulated, toggle reverse track (?)
- Improved saved info display (better text wrapping options? remove and just include that info on the emulate scene? decode data to fields?)
- Edit saved card scene

File management:
- What is best way to save track data, and designate which tracks are in a file? Just use end sentinels to determine when loaded, or split it out into different fields?
- Parsing loaded files into relevent fields (would we need to specify card type as well, to decode correctly?)
- Modify manual add scene to allow editing and renaming of existing files
- Validation of card track data?
- Better cleanup / management of data during add manually

Known bugs:
- Currently there's a few functions that are unused, while the refactor is in progress. To avoid compilation errors relating to the unused functions, one must comment out `-Werror` in `site_scons/cc.scons` (or comment out the unused functions, the former is just easier/faster).
- Custom text input scene with expanded characterset (Add Manually) has odd behavior when navigating the keys near the numpad
- Track 1 data typically starts with a `%` sign. Unless escaped, it won't be displayed when printed, as C considers it a special character. To confirm: how does this impact the emulation when iterating through the chars? Does it get played correctly?

## Skunkworks ideas
Internal TX improvements:
- Attempt downstream modulation techniques, in addition to upstream, like the LF RFID worker does when writing, for stronger signal
- Implement using the timer system, rather than direct-writing to pins
- Use the NFC (HF RFID) coil instead of or in addition to the LF coil (this is promising in my mind; Samsung Wallet's discontinued magstripe emulation would've been over their NFC coil, most likely)
- Scrap all this and stick to using an external module for TX (could likely simplify to just a resistor and some coiled wire, rather than the full H-bridge build)

Other misc things to investigate / build:
- File format, manual add, saving / loading
- Ideal timing / speed
- Precomputing bit output, and then sending ("RedSpoof" by devBioS does this, as they say they had timing issues when computing the bits live)
- Reverse-track emulate?
- Tuning of parameters like pre-signal zeros?
- "Interpacket delay" like the RedSpoof implementation?
- (Less important) Any way to easily wrap text on screen, without having to manually calculate the number of chars that fit and splicing the string accordingly into lines?


HF coil notes:
~~NFC reader field can be turned on / off with `furi_hal_nfc_field_on();` and `furi_hal_nfc_field_off();` respectively, as seen in nfc_scene_field.c (used for debug purposes). Initial tests with `furi_hal_nfc_field_on();` are promising signal-wise, but the delay introduced by the wake/sleep initialization renders it impossible to toggle rapidly. At a lower level, that consists of `furi_hal_nfc_exit_sleep();` and `st25r3916TxRxOn();` to turn on, and `st25r3916TxRxOff();` and `furi_hal_nfc_start_sleep();` to turn off. May be worth trying directly (wake from sleep at setup, toggle on and off corresponding with bit direction, send to sleep on exit). Initial tests have been difficult to get work as some of the st25r3916 symbols are unresolved; need to figure out how to import/call it properly, or how to get another layer lower of control.~~
Testing with `furi_hal_nfc_ll_txrx_on();` and `furi_hal_nfc_ll_txrx_off();` does indeed create a nice strong signal on my 'scope (thanks @dlz#7721 for finding the wrapped functions), but no response from a mag reader; makes sense, was a long shot -- next step NFC testing would be lower-level control that lets us pull the coil high/low, rather than just producing the standard 13.56MHz signal and OOK modulating it.
- Use the NFC (HF RFID) coil instead of or in addition to the LF coil (likely unfruitful from initial tests; we can enable/disable the oscillating field, but even with transparent mode to the ST25R3916, it seems we don't get low-enough-level control to pull it high/low correctly)

External RX options (What is simplest read module?):
- Some UART mag reader (bulky, but likely easiest to read over GPIO, and means one can read all tracks)
- Square audio jack mag reader (compact, but will be harder to decode from GPIO. Also only will read track 2 without modification)
- USB HID input feasible? Flipper seemingly can't act as an HID host, is there any way to circumvent this or is it due to a hardware incompatibility? This would be the easiest / best option all-around if feasible.
241 changes: 241 additions & 0 deletions helpers/mag_helpers.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#include "mag_helpers.h"

#define GPIO_PIN_A &gpio_ext_pa6
#define GPIO_PIN_B &gpio_ext_pa7
#define RFID_PIN &gpio_rfid_carrier_out

#define ZERO_PREFIX 25 // n zeros prefix
#define ZERO_BETWEEN 53 // n zeros between tracks
#define ZERO_SUFFIX 25 // n zeros suffix
#define US_CLOCK 240
#define US_INTERPACKET 10

// bits per char on a given track
const uint8_t bitlen[] = {7, 5, 5};
// char offset by track
const int sublen[] = {32, 48, 48};
uint8_t bit_dir = 0;

void play_bit_rfid(uint8_t send_bit) {
// internal TX over RFID coil
bit_dir ^= 1;
furi_hal_gpio_write(RFID_PIN, bit_dir);
furi_delay_us(US_CLOCK);

if(send_bit) {
bit_dir ^= 1;
furi_hal_gpio_write(RFID_PIN, bit_dir);
}
furi_delay_us(US_CLOCK);

furi_delay_us(US_INTERPACKET);
}

/*static void play_bit_gpio(uint8_t send_bit) {
// external TX over motor driver wired to PIN_A and PIN_B
bit_dir ^= 1;
furi_hal_gpio_write(GPIO_PIN_A, bit_dir);
furi_hal_gpio_write(GPIO_PIN_B, !bit_dir);
furi_delay_us(US_CLOCK);
if(send_bit) {
bit_dir ^= 1;
furi_hal_gpio_write(GPIO_PIN_A, bit_dir);
furi_hal_gpio_write(GPIO_PIN_B, !bit_dir);
}
furi_delay_us(US_CLOCK);
furi_delay_us(US_INTERPACKET);
}*/

void rfid_tx_init() {
// initialize RFID system for TX
furi_hal_power_enable_otg();

furi_hal_ibutton_start_drive();
furi_hal_ibutton_pin_low();

// Initializing at GpioSpeedLow seems sufficient for our needs; no improvements seen by increasing speed setting

// this doesn't seem to make a difference, leaving it in
furi_hal_gpio_init(&gpio_rfid_data_in, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_rfid_data_in, false);

// false->ground RFID antenna; true->don't ground
// skotopes (RFID dev) say normally you'd want RFID_PULL in high for signal forming, while modulating RFID_OUT
// dunaevai135 had it low in their old code. Leaving low, as it doesn't seem to make a difference on my janky antenna
furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_nfc_irq_rfid_pull, false);

furi_hal_gpio_init(RFID_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);

// confirm this delay is needed / sufficient? legacy from hackathon...
furi_delay_ms(300);
}

void rfid_tx_reset() {
// reset RFID system
furi_hal_gpio_write(RFID_PIN, 0);

furi_hal_rfid_pins_reset();
furi_hal_power_disable_otg();
}

/*
static void gpio_tx_init() {
furi_hal_power_enable_otg();
gpio_item_configure_all_pins(GpioModeOutputPushPull);
}
static void gpio_tx_reset() {
gpio_item_set_pin(PIN_A, 0);
gpio_item_set_pin(PIN_B, 0);
gpio_item_set_pin(ENABLE_PIN, 0);
gpio_item_configure_all_pins(GpioModeAnalog);
furi_hal_power_disable_otg();
}
*/

void track_to_bits(uint8_t* bit_array, const char* track_data, uint8_t track_index) {
// convert individual track to bits

int tmp, crc, lrc = 0;
int i = 0;

// convert track data to bits
for(uint8_t j = 0; track_data[i] != '\0'; j++) {
crc = 1;
tmp = track_data[j] - sublen[track_index];

for(uint8_t k = 0; k < bitlen[track_index] - 1; k++) {
crc ^= tmp & 1;
lrc ^= (tmp & 1) << k;
bit_array[i] = tmp & 1;
i++;
tmp >>= 1;
}
bit_array[i] = crc;
i++;
}

// finish calculating final "byte" (LRC)
tmp = lrc;
crc = 1;
for(uint8_t j = 0; j < bitlen[track_index] - 1; j++) {
crc ^= tmp & 1;
bit_array[i] = tmp & 1;
i++;
tmp >>= 1;
}
bit_array[i] = crc;
i++;

// My makeshift end sentinel. All other values 0/1
bit_array[i] = 2;
i++;

//bool is_correct_length = (i == (strlen(track_data) * bitlen[track_index]));
//furi_assert(is_correct_length);
}

void mag_spoof_single_track_rfid(FuriString* track_str, uint8_t track_index) {
// Quick testing...

rfid_tx_init();

size_t from;
size_t to;

// TODO ';' in first track case
if(track_index == 0) {
from = furi_string_search_char(track_str, '%');
to = furi_string_search_char(track_str, '?', from);
} else if(track_index == 1) {
from = furi_string_search_char(track_str, ';');
to = furi_string_search_char(track_str, '?', from);
} else {
from = 0;
to = furi_string_size(track_str);
}
if(from >= to) {
return;
}
furi_string_mid(track_str, from, to - from + 1);

const char* data = furi_string_get_cstr(track_str);
uint8_t bit_array[(strlen(data) * bitlen[track_index]) + 1];
track_to_bits(bit_array, data, track_index);

FURI_CRITICAL_ENTER();
for(uint8_t i = 0; i < ZERO_PREFIX; i++) play_bit_rfid(0);
for(uint8_t i = 0; bit_array[i] != 2; i++) play_bit_rfid(bit_array[i] & 1);
for(uint8_t i = 0; i < ZERO_SUFFIX; i++) play_bit_rfid(0);
FURI_CRITICAL_EXIT();

rfid_tx_reset();
}

void mag_spoof_two_track_rfid(FuriString* track1, FuriString* track2) {
// Quick testing...

rfid_tx_init();

const char* data1 = furi_string_get_cstr(track1);
uint8_t bit_array1[(strlen(data1) * bitlen[0]) + 1];
const char* data2 = furi_string_get_cstr(track2);
uint8_t bit_array2[(strlen(data2) * bitlen[1]) + 1];

track_to_bits(bit_array1, data1, 0);
track_to_bits(bit_array2, data2, 1);

FURI_CRITICAL_ENTER();
for(uint8_t i = 0; i < ZERO_PREFIX; i++) play_bit_rfid(0);
for(uint8_t i = 0; bit_array1[i] != 2; i++) play_bit_rfid(bit_array1[i] & 1);
for(uint8_t i = 0; i < ZERO_BETWEEN; i++) play_bit_rfid(0);
for(uint8_t i = 0; bit_array2[i] != 2; i++) play_bit_rfid(bit_array2[i] & 1);
for(uint8_t i = 0; i < ZERO_SUFFIX; i++) play_bit_rfid(0);
FURI_CRITICAL_EXIT();

rfid_tx_reset();
}

//// @antirez's code from protoview for bitmapping. May want to refactor to use this...

/* Set the 'bitpos' bit to value 'val', in the specified bitmap
* 'b' of len 'blen'.
* Out of range bits will silently be discarded. */
void set_bit(uint8_t* b, uint32_t blen, uint32_t bitpos, bool val) {
uint32_t byte = bitpos / 8;
uint32_t bit = bitpos & 7;
if(byte >= blen) return;
if(val)
b[byte] |= 1 << bit;
else
b[byte] &= ~(1 << bit);
}

/* Get the bit 'bitpos' of the bitmap 'b' of 'blen' bytes.
* Out of range bits return false (not bit set). */
bool get_bit(uint8_t* b, uint32_t blen, uint32_t bitpos) {
uint32_t byte = bitpos / 8;
uint32_t bit = bitpos & 7;
if(byte >= blen) return 0;
return (b[byte] & (1 << bit)) != 0;
}

/*uint32_t convert_signal_to_bits(uint8_t *b, uint32_t blen, RawSamplesBuffer *s, uint32_t idx, uint32_t count, uint32_t rate) {
if (rate == 0) return 0; // We can't perform the conversion.
uint32_t bitpos = 0;
for (uint32_t j = 0; j < count; j++) {
uint32_t dur;
bool level;
raw_samples_get(s, j+idx, &level, &dur);
uint32_t numbits = dur / rate; // full bits that surely fit.
uint32_t rest = dur % rate; // How much we are left with.
if (rest > rate/2) numbits++; // There is another one.
while(numbits--) set_bit(b,blen,bitpos++,s[j].level);
}
return bitpos;
}*/
13 changes: 13 additions & 0 deletions helpers/mag_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "../mag_i.h"
#include <stdio.h>
#include <string.h>

void play_bit_rfid(uint8_t send_bit);
// void play_bit_gpio(uint8_t send_bit);
void rfid_tx_init();
void rfid_tx_reset();
void track_to_bits(uint8_t* bit_array, const char* track_data, uint8_t track_index);
void mag_spoof_single_track_rfid(FuriString* track_str, uint8_t track_index);
void mag_spoof_two_track_rfid(FuriString* track1, FuriString* track2);
void set_bit(uint8_t* b, uint32_t blen, uint32_t bitpos, bool val);
bool get_bit(uint8_t* b, uint32_t blen, uint32_t bitpos);
Loading

0 comments on commit 90804b1

Please sign in to comment.