MagTek USB Card Reader Hacking

So just the other day I received my MagTek MSR100 in the mail, this unit only cost me about $20 and I have to say I’m very satisfied with it.

cardreader

After opening the box it was delivered in I quickly noticed no documentation was provided. No worries I figured, this will make hacking at it that much more fun.

I started out by connecting the USB device to my Gentoo Linux laptop and swiped a card, I noticed on my console prompt the card data was spewed out. That is because this device acts like a HID Keyboard:

# lsusb -vv | grep "Mag-Tek Mini Swipe" -A50 | grep bInterfaceProtocol
      bInterfaceProtocol      1 Keyboard

With this information confirmed I figured I would approach it with the same method used in one my older post Python Device Hacking (Keyboard).

Using the k() function outlined in the Keyboard hacking post mentioned above, I received the following while scanning my Pluckers card:

>>> k()
KEY_LEFTSHIFT
KEY_5
KEY_LEFTSHIFT
KEY_B
KEY_7
KEY_2
KEY_3
KEY_1
KEY_5
KEY_3
KEY_7
KEY_7
KEY_1
KEY_0
KEY_5
KEY_6
KEY_6
KEY_2
KEY_3
KEY_2
KEY_5
KEY_0
KEY_4
KEY_7
KEY_6
KEY_7
KEY_9
KEY_1
KEY_3
KEY_9
KEY_7
KEY_0
KEY_3
KEY_8
KEY_LEFTSHIFT
KEY_SLASH
KEY_SEMICOLON
KEY_2
KEY_2
KEY_1
KEY_6
KEY_5
KEY_0
KEY_0
KEY_1
KEY_1
KEY_1
KEY_6
KEY_2
KEY_3
KEY_6
KEY_7
KEY_6
KEY_EQUAL
KEY_1
KEY_LEFTSHIFT
KEY_SLASH

This data looked similar to the results I seen on screen, but not quite.. and printing on the screen at the same time was pretty ugly:

%B723153771056323250476791397088?;221350011623672=1?

At that point I figured it was time for a better approach, and where best to look than our friend Google? A quick search brought up a article describing a similar project, unfortunately our devices must differ as his code didn’t quite work with my device. However this article showed me the PyUSB module.

Using the PyUSB module I whipped up a quick script:

>>> import usb.core
>>> import usb.util
>>>
>>> vendorid = 0x0801
>>> productid = 0x0001
>>> device.detach_kernel_driver(0)
>>> endpoint = device[0][(0,0)][0]
>>>
>>> device.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, timeout=5000)
array('B', [2, 0, 34, 0, 0, 0, 0, 0])
>>> device.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, timeout=5000)
array('B', [0, 0, 0, 0, 0, 0, 0, 0])
>>> device.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, timeout=5000)
array('B', [2, 0, 5, 0, 0, 0, 0, 0])
>>> device.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, timeout=5000)
array('B', [0, 0, 0, 0, 0, 0, 0, 0])
>>> device.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, timeout=5000)
array('B', [0, 0, 36, 0, 0, 0, 0, 0])

At this point I got stuck for a bit, you see the data above was not in ASCII format and did not match my earlier keyboard map. I did figure out the results by comparing onscreen output to my PyUSB output:

I took the known good data (from on screen):

%B723153771056323250476791397088?;221350011623672=1?

Comparing against PyUSB I noticed the 3rd column appeared to be the bits of data I needed:

[2, 0, 34, 0, 0, 0, 0, 0] = %
[2, 0, 5, 0, 0, 0, 0, 0]  = B
[0, 0, 36, 0, 0, 0, 0, 0] = 7
[0, 0, 31, 0, 0, 0, 0, 0] = 2
[0, 0, 32, 0, 0, 0, 0, 0] = 3
[0, 0, 30, 0, 0, 0, 0, 0] = 1
[0, 0, 34, 0, 0, 0, 0, 0] = 5
[0, 0, 32, 0, 0, 0, 0, 0] = 3
[0, 0, 36, 0, 0, 0, 0, 0] = 7
[0, 0, 36, 0, 0, 0, 0, 0] = 7
[0, 0, 30, 0, 0, 0, 0, 0] = 1
[0, 0, 39, 0, 0, 0, 0, 0] = 0
[0, 0, 34, 0, 0, 0, 0, 0] =
[0, 0, 35, 0, 0, 0, 0, 0]
[0, 0, 35, 0, 0, 0, 0, 0]
[0, 0, 31, 0, 0, 0, 0, 0]
[0, 0, 32, 0, 0, 0, 0, 0]
[0, 0, 31, 0, 0, 0, 0, 0]
[0, 0, 34, 0, 0, 0, 0, 0]
[0, 0, 39, 0, 0, 0, 0, 0]
[0, 0, 33, 0, 0, 0, 0, 0]
[0, 0, 36, 0, 0, 0, 0, 0]
[0, 0, 35, 0, 0, 0, 0, 0]
[0, 0, 36, 0, 0, 0, 0, 0]
[0, 0, 38, 0, 0, 0, 0, 0]
[0, 0, 30, 0, 0, 0, 0, 0]
[0, 0, 32, 0, 0, 0, 0, 0]
[0, 0, 38, 0, 0, 0, 0, 0]
[0, 0, 36, 0, 0, 0, 0, 0]
[0, 0, 39, 0, 0, 0, 0, 0]
[0, 0, 32, 0, 0, 0, 0, 0]
[0, 0, 37, 0, 0, 0, 0, 0]
[2, 0, 56, 0, 0, 0, 0, 0] = ?
[0, 0, 51, 0, 0, 0, 0, 0] = ;
[0, 0, 31, 0, 0, 0, 0, 0] = 2
[0, 0, 31, 0, 0, 0, 0, 0] = 2
[0, 0, 30, 0, 0, 0, 0, 0] = 1
[0, 0, 35, 0, 0, 0, 0, 0] = 6
[0, 0, 34, 0, 0, 0, 0, 0] = 5
[0, 0, 39, 0, 0, 0, 0, 0] = 0
[0, 0, 39, 0, 0, 0, 0, 0] = 0
[0, 0, 30, 0, 0, 0, 0, 0] = 1
[0, 0, 30, 0, 0, 0, 0, 0] = 1
[0, 0, 30, 0, 0, 0, 0, 0] = 1
[0, 0, 35, 0, 0, 0, 0, 0] = 6
[0, 0, 31, 0, 0, 0, 0, 0] = 2
[0, 0, 32, 0, 0, 0, 0, 0] = 3
[0, 0, 35, 0, 0, 0, 0, 0] = 6
[0, 0, 36, 0, 0, 0, 0, 0] = 7
[0, 0, 35, 0, 0, 0, 0, 0] = 6
[0, 0, 46, 0, 0, 0, 0, 0] = =
[0, 0, 30, 0, 0, 0, 0, 0] = 1
[2, 0, 56, 0, 0, 0, 0, 0] = ?
[0, 0, 88, 0, 0, 0, 0, 0]

After trying everything I could think I finally gave up and searched for a Developers Manual for the device. The PDF above gave me a keycode map which made complete sense on the above output (see snippet below):

../_images/keycode.jpg

This last piece of information was all I needed to decipher data from my device, and this was the code I ended up with:

#!/usr/bin/env python

# MagTek MSR100 Mini Swipe Card Reader
# Written By: Jeffrey Ness
#
# 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

# MagTek Device MSR100 Mini Swipe
vendorid = 0x0801
productid = 0x0001

# 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'
}

shiftchrMap = {
    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: '!',
    31: '@',
    32: '#',
    33: '$',
    34: '%',
    35: '^',
    36: '&',
    37: '*',
    38: '(',
    39: ')',
    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'
}

# find our device by id
device = usb.core.find(idVendor=vendorid, idProduct=productid)
if device is None:
    raise Exception('Could not find USB Card Reader')

# remove device from kernel, this should stop
# reader from printing to screen and remove /dev/input
if device.is_kernel_driver_active(0):
    try:
        device.detach_kernel_driver(0)
    except usb.core.USBError as e:
        raise Exception("Could not detatch kernel driver: %s" % str(e))

# load our devices configuration
try:
    device.set_configuration()
    device.reset()
except usb.core.USBError as e:
    raise Exception("Could not set configuration: %s" % str(e))

# get device endpoint information
endpoint = device[0][(0,0)][0]

swiped = False
data = []
datalist = []
print 'Swipe Card:'
while True:
    try:
        results = device.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, timeout=5)
        data += results
        datalist.append(results)
        swiped = True

    except usb.core.USBError as e:
        if e.args[1] == 'Operation timed out' and swiped:
            break # timeout and swiped means we are done

# create a list of 8 bit bytes and remove
# empty bytes
ndata = []
for d in datalist:
    if d.tolist() != [0, 0, 0, 0, 0, 0, 0, 0]:
        ndata.append(d.tolist())

# parse over our bytes and create string to final return
sdata = ''
for n in ndata:
    # handle non shifted letters
    if n[2] in chrMap and n[0] == 0:
        sdata += chrMap[n[2]]
    # handle shifted letters
    elif n[2] in shiftchrMap and n[0] == 2:
        sdata += shiftchrMap[n[2]]

print sdata

I’ve went ahead and uploaded this source to Github in case anyone likes to clone it https://github.com/jness/magtek_cardreader/blob/master/main.py

Using this code is very basic and is show below:

# ./main.py
Swipe Card:
%B723153771056323250476791397088?;221350011623672=1?

7 comments

  1. The codes referenced in the manual are USB Keyboard Scan Codes. See the Microsoft USB HID to PS/2 Keyboard Scan Code Translation Table.

    The version of the manual you referenced has been removed. This MSR100 User Manual replaced it.

    Micah Carrick’s product ID was 0x0002, which looks like a slightly more capable version of the hardware.

    SCANCODES_LOWER = {4: u'a', 5: u'b', 6: u'c', 7: u'd', 8: u'e', 9: u'f', 10: u'g', 11: u'h', 12: u'i', 13: u'j', 14: u'k', 15: u'l', 16: u'm', 17: u'n', 18: u'o', 19: u'p', 20: u'q', 21: u'r', 22: u's', 23: u't', 24: u'u', 25: u'v', 26: u'w', 27: u'x', 28: u'y', 29: u'z', 30: u'1', 31: u'2', 32: u'3', 33: u'4', 34: u'5', 35: u'6', 36: u'7', 37: u'8', 38: u'9', 39: u'0', 40: u'RET', 41: u'ESC', 42: u'BKSP', 43: u'TAB', 44: u' ', 45: u'-', 46: u'=', 47: u'[', 48: u']', 49: u'\', 50: u'UNK', 51: u';', 52: u"'", 53: u'`', 54: u',', 55: u'.', 56:'/', 88: u''}

    SCANCODES_UPPER = {4: u'A', 5: u'B', 6: u'C', 7: u'D', 8: u'E', 9: u'F', 10: u'G', 11: u'H', 12: u'I', 13: u'J', 14: u'K', 15: u'L', 16: u'M', 17: u'N', 18: u'O', 19: u'P', 20: u'Q', 21: u'R', 22: u'S', 23: u'T', 24: u'U', 25: u'V', 26: u'W', 27: u'X', 28: u'Y', 29: u'Z', 30: u'!', 31: u'@', 32: u'#', 33: u'$', 34: u'%', 35: u'^', 36: u'&', 37: u'*', 38: u'(', 39: u')', 40: u'RET', 41: u'ESC', 42: u'BKSP', 43: u'TAB', 44: u' ', 45: u'_', 46: u'+', 47: u'{', 48: u'}', 49: u'|', 50: u'UNK', 51: u':', 52: u'"', 53: u'~', 54: u'', 56:'?', 88: u''}

    def descan( sc ):
    out = []
    i = 0
    while ( i < len(sc) ):
    if sc[i] == 2:
    i = i + 2
    out.append( SCANCODES_UPPER[sc[i]] )
    elif sc[i] == 0:
    pass
    else:
    out.append( SCANCODES_LOWER[sc[i]] )
    i = i + 1
    return out

    ...then get the scancodes (scodes) from the device using dev.read and execute...
    data = descan( scodes )
    print ''.join(data)

    Thanks for the post! Yours and Micah’s certainly helped a great deal. Cheers.

  2. Thank you for your article. It has helped get me going. Do you have any advice on how to capture the output without it writing to the default terminal as well. The default terminal is the login screen and every swipe of a credit card posts the content of the mag strip to the login screen. Needless to say that is a huge issue.

    1. Well it has been a long time since I’ve touched my USB reader, but I believe the code above uses
      PyUSB to disable printing to the console (the USB reader is only a Keyboard emulator):


      # remove device from kernel, this should stop
      # reader from printing to screen and remove /dev/input
      if device.is_kernel_driver_active(0):
      try:
      device.detach_kernel_driver(0)
      except usb.core.USBError as e:
      raise Exception("Could not detatch kernel driver: %s" % str(e))

Leave a Reply

Your email address will not be published. Required fields are marked *