Recently, I got an MSR605X device (MSR stands for Magnetic Stripe Reader) and wanted to clone a magnetic stripe card for backup. After buying tons of different types of cards due to writing failures, it turned out there seemed to be an issue with the provider's program. It failed to write more than 64 hex digits (Based on my intuition, they were using int32 to store the data for writing, and in fact after I have my code run, I guess they were possibly using a TRACK_DATA_SIZE of 32). So, I had to find a way to write the card myself. The first step was getting the manual or driver from the provider, which I did weeks ago. I'm only now finding a bit of free time to start working on the program.

Background

There are some available open-source MSR605X drivers, but there's no guarantee that this device is supported (intuitively, it should be, but to avoid wasting time, I contacted the provider for the manual or driver). I'm very lucky that they were willing to provide the SDK directly. (By the way, they're definitely Chinese; the documentation is written in pure Chinese. I'm curious about what would happen if they sold to someone who doesn't understand it.)

One drawback is that they don't have a driver for Linux and they refused to provide the one for macOS (although they definitely have one, since there is a macOS program available on their CD). And this is one of the reasons preventing me from writing this program -- I have to reboot my machine to switch the OS and there have been prior cases where Windows updates may crash the GRUB for dual-boot.

Actually, this reboot to Windows indeed caused a slight inconvenience due to the Windows update on August 13th and pushed to me when I booted Windows yesterday. Check out this link for the discussions. -- Jerry at 2024-09-11

This post is primarily a record of my experiments with their SDK and the device.

The demo program

They provided a demo program for me to go through the procedure - the executable worked, however, I failed to build their project due to the use of vcl.h, which seems to be specific to the Borland C++ Builder's toolchain. And even for the compiled executable, I could not exactly read my stripe card correctly - no output was shown, just a read success message. So I have to start from scratch.

Trials and errors

Build the code

The first hurdle was creating a basic program to just check the device status. This seemingly simple task became a several-hour odyssey due to toolchain issues and compatibility problems with the provided MSR_API.dll, which is only compatible with 32-bit build tools. Here's a quick rundown of the process:

  1. Initial attempts with mingw32-make failed due to DLL format incompatibility.
  2. Tried using dlltool to generate a .def file and compile libMSR_API.a for static linking, but complication still failed.
  3. Use Dependencies.exe (an open-source alternative to the outdated depends.exe, which would hung on any dll I selected. It does not support Windows 10+) to analyze DLL dependencies.
  4. The tool showed the MSR_API.dll's dependencies were for i386 machines. Opted for a 32-bit version of MinGW based on an AI's suggestion.

After these steps, the code finally compiled. I initially used dynamic loading with LoadLibrary, which worked, but later switched to static compilation for simplicity.

With a runnable program in hand, I moved to implementing the reader and writer. After a few hours of late-night coding, I had something working, but writing data in the correct format remained an issue. At this point, I suspected the problem may be related to the specific stripe cards or perhaps some quirk in how they were manufactured.

Data format

The next day morning, when I woke up and lying on the bed, I was thinking the possibilities - not entirely sure if that is issue with cards, nor confirmed with the problem of the device. So my plan was to try writing some special values to check the logic.

There are three tracks to write:

  1. Track 1: Empty
  2. Track 2: 9 bytes of data
  3. Track 3: 35 bytes of data

As I have mentioned at the background section, the provided program can only write 64 hex digits, which only occupies 32 bytes, one of the reasons to the write failures on track 3. By the way, the data buffer is not the way I thought - the C++ API MSR_API.h allows a char * pointer to write the data to, so it should not be a issue for writing theoretically. But most likely both they and I choose to use the char arrays (I use the vector instead more precisely), and the array length may be the cause.

track1: [zeros]
 index    0 |  1 ...  8  ... 34
track2: [90]|[xx]...[xx]
track3: [23]|[xx]...........[xx]

The thing is that, for a standard stripe card there is a starting char (or more formally, Start Sentinel) at the beginning of the actual data stored, and in our case, the card is using 0x90 for track 2 and 0x23 for track 3 (from my case, maybe differ but I think those chars should be the same since they are for the start sentinel character). But from my attempts, it just cannot write the data as the read result, and can vary strangely and may even affect the first byte. And the device always report an error when writing. I almost thought this is a issue with the device internal things and have to contact the producer again. Fortunately, I noticed some easy-to-recognize invariants in the bit representation in some cases.

I quickly noticed there is a "swap and reverse" attribute in the bitwise representation for every byte except the first byte when the data are given in some "good" format (not quite sure what kind of good, but there shouldn't be too many zeros, looks like the leading zeros after the first byte may be ignored and would cause later bytes ignored if zeros are too many). I got this observation by filling all the byte as 0xFF except the first byte, and noticed the last byte still cannot be written correctly, but most other bytes are written as I expected, which is a good signal and motivate me to continue working. Then I found that there is a likely endian-related issue for the writing (which turns out not be the case, but it indeed somewhat make me successfully decode the write sequence according to the read sequence).

Another thing is that, I noticed the 0x24 bytes are persisted for all occurrences even other bytes are completely wrong. I didn't realize this is due to the palindromic nature at the beginning and took some detours, but it was understood later:

Hex    0x2  0x4
Bin   0010 0100
Swp =>0100 0010
Rev =>0010 0100

This is actually just reverse all the bits of a given byte, but because at the beginning I was thinking something related to endian which mistook me to the wrong direction, but anyway, it worked and allowed me to make the first clone of the stripe card.

So finally, I added a reverseByteBits method that takes the bits mask when writing the card. The logic is straightforward: we read the input byte's bits from right to left, writing each bit from left to right in the output byte.

public:
    static void reverseByteBits(std::vector<unsigned char>& data, uint64_t mask) {
        for (size_t i = 0; i < data.size() && i < TRACK_DATA_SIZE; i++) {
            if ((mask >> i) & 1) {
                data[i] = reverseBits(data[i]);
            }
        }
    }

private:
    static unsigned char reverseBits(unsigned char byte) {
        unsigned char reversed = 0;
        for (int i = 0; i < 8; i++) {
            reversed = (reversed << 1) | (byte & 1);
            byte >>= 1;
        }
        return reversed;
    }

GitHub