MagTek USB Card Reader Hacking 2
So back at it, now with some code to decode the common financial card:
Let start out by showing the end results of scanning my Freebirds card:
# ./main.py
Please swipe your card now:
Raw String: %B???????????^FANATIC/FREEBIRDS^4211?;????????????=????????????
Card Holder: FANATIC/FREEBIRDS
Card Number: ????-????-????-????
Expiration Date: 11/42
As you can see we still have our raw string, this is being decoded from the code I used last time. However now I have the Card Holder’s name, Card Number, and Expiration date, this format was all outlined quite well on Wikipedia .
- Start sentinel
- one character (generally ‘%’)
- Format code=”B”
- one character (alpha only)
- Primary account number (PAN)
- up to 19 characters. Usually, but not always, matches the credit card number printed on the front of the card.
- Field Separator
- one character (generally ‘^’)
- Name
- two to 26 characters
- Field Separator
- one character (generally ‘^’)
- Expiration date
- four characters in the form YYMM.
- Service code
- three characters
- Discretionary data
- may include Pin Verification Key Indicator (PVKI, 1 character), PIN Verification Value (PVV, 4 characters), Card Verification Value or Card Verification Code (CVV or CVC, 3 characters)
- End sentinel
- one character (generally ‘?’)
Once I had this format it was just a matter of using some Python magic to make it happen:
# attempt to identify data types
start_sentinel = sdata[0]
format_code = sdata[1]
## FORMAT TYPE B
if start_sentinel + format_code == '%B':
track1 = sdata[2:].split(';')[0]
data = track1.split('^')
# financial card
try:
card_no = data[0]
name = data[1].strip()
expyear = data[2][0:2]
expmon = data[2][2:4]
print 'Card Holder: %s' % name
print 'Card Number: %s' % prettycard(card_no)
print 'Expiration Date: %s/%s' % (expmon, expyear)
except:
pass
I also found a small bug in how I was handeling the card reader, you see the card reader has built in memory which will hold all swipes not yet processed. If for some reason a card was swiped when not expected (lets say when not running the script), it would be stored in memory and wait for next read (this would pile up with the new swipe).
To resolve this I run through the device loop just to clear the buffer:
def cleardevice():
'''Clear the devices memory'''
while True:
try:
results = device.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize)
except usb.core.USBError as e:
if e.args[1] == 'Operation timed out' :
break # timeout and swiped means we are done
I also noticed the device I have places a E in the format type if it had issues reading the card.
If a error is returned in track 1 I request a re-swipe:
# A format type of E is a error
while sdata[1] == 'E':
print 'Failed to read card..'
sdata = swipeme()
And the full code I wrote looks something like this (very sloppy I know):
#!/usr/bin/env python
# MagTek MSR100 Mini Swipe Card Reader
# Written By: nessy
#
# Some Thanks need to go out to
# http://www.micahcarrick.com/credit-card-reader-pyusb.html
# for helping me get on the right track
import usb.core
import usb.util
from re import findall
# MagTek Device MSR100 Mini Swipe
vendorid = 0x0801
productid = 0x0001
# Small function to return card numbers in blocks of 4
def prettycard(card_no):
card = '-'.join(findall("[0-9][0-9][0-9][0-9]", card_no))
return card
# Define our Character Map per Reference Manual
# http://www.magtek.com/documentation/public/99875206-17.01.pdf
chrMap = {
4: 'a',
5: 'b',
6: 'c',
7: 'd',
8: 'e',
9: 'f',
10: 'g',
11: 'h',
12: 'i',
13: 'j',
14: 'k',
15: 'l',
16: 'm',
17: 'n',
18: 'o',
19: 'p',
20: 'q',
21: 'r',
22: 's',
23: 't',
24: 'u',
25: 'v',
26: 'w',
27: 'x',
28: 'y',
29: 'z',
30: '1',
31: '2',
32: '3',
33: '4',
34: '5',
35: '6',
36: '7',
37: '8',
38: '9',
39: '0',
40: 'KEY_ENTER',
41: 'KEY_ESCAPE',
42: 'KEY_BACKSPACE',
43: 'KEY_TAB',
44: ' ',
45: '-',
46: '=',
47: '[',
48: ']',
49: '\\',
51: ';',
52: '\'',
53: '`',
54: ',',
55: '.',
56: '/',
57: 'KEY_CAPSLOCK'
}