Rock the Cash Box

Dec 4, 2019 · 17 min read


ATM Hacking isn’t new, there are plenty of known issues with ATMs including skimmers and (very) outdated software. When you ask someone about ATMs (especially in security) the response is often “ATMs aren’t secure!” but how do you educate people about ATMs and how do you inspire students to look into physical hardware and security? As part of the Collegiate Pentesting Competition (CPTC for short) jrwr and I were tasked with figuring out how to make the ATMs operational, and it was one of the most positive experiences I had working on a project. I am grateful for the chance to have worked with him and hope to work with him again soon! Our task was to figure out how these ATMs worked, reverse engineer them, and get them working for the CPTC competition less than two months away. In no way do we condone hacking real ATMs, and all the ATMs in this project were purchased for the competition and never connected to real payment processing networks!

A herd of ATMs roaming into their temporary habitat

Our task was to make “payment” processing possible on the ATMs using their existing hardware with as few modifications as possible. During this process we evaluated many different possible designs on how to implement the payment processor. All of this could (and has been) run off the Raspberry Pi, however in an effort to provide the greatest learning experience for competitors, we elected to implement a relay system. Our final result needed only some slight reconfiguration — dial-in number and encryption keys.

Data Flow for Processing

Only the ATMs and the Raspberry Pi made use of Dial-Up, the rest was done over a combination of UDP and HTTP traffic to simulate the payment processing system. Getting there however, was quite an adventure…

Getting Started

These 1500 Series ATMs are still in use in many places, however they have upgraded modules — either an external payment processing device (what we were simulating) or an upgraded head unit which replaces the core components and supports ethernet (using an upgraded but very similar version of the protocol)

(Localized) External Payment Processor device (ATM Dialup to Ethernet)

Knowing very little about these ATMs, except they had at least one RJ-11 connection and their operator password and encryption keys, the first task was to connect everything up. jrwr ‘s previous experience with appliances and some payment networks, we went ahead and configured the ATMs using as many 0’s or spaces (0x30 and 0x20 respectively) to attempt to track the protocol. Although dial-up protocols are straightforward in composition, they aren’t really used (or documented) day to day anymore. Working with Google, we found many older commands and protocols, some of which were useful, but many of which were not. Eventually, we were able to find enough AT commands to make it possible to dial a Raspberry Pi and communicate over the line. Making use of the PBX systems, we were able to communicate between the Raspberry Pis and ATMs without any external interfaces or phone infrastructure.

Dial-Up isn’t exactly the most complicated protocol but it is also not a protocol people generally use day to day anymore. After consulting Google, we were able to find enough AT commands to make it possible to dial out (in?) to the Raspberry Pi.

If you are curious what AT commands are supported by most modems, this is one of the most centralized guides I was able to find was from none other then USRobotics’s (USR) old user manuals!

Interacting with the modem is best done via minicom or screen when directly interfacing with it however that poses a problem since we need to also interact with it and not have screen doing anything we don’t expect. After quite a bit of searching we found a tool called interceptty, a tool almost as old as the ATMs themselves. The sole purpose of interceptty was to allow us to see what messages were being exchanged between the ATMs/Modems and our software. This tool is probably one of the most indispensable tools for dealing with serial data since we can easily replay or monitor transactions.

First step, getting the modem to answer incoming calls. This was a bit tricker because it required the modem and the ATM to automatically dial/redial. It turned out the ATM has quite a few settings available for modem configuration including redial times and a bunch of other options. Sadly all these options are behind an operator code which makes it a bit impractical
for an easy attack. However, since many of these ATMs use dial-up, it is possible to sit on the line and answer messages.

Getting the Modem To Auto Answer via screen:


Example to run: interceptty -o /tmp/debug.log /dev/ttyACM0 /tmp/debug0 Then just use /tmp/debug0 instead of the raw device to monitor the connection.

Getting data byte by byte and the true and proven asdfasdfasjlfdasdfasdfadsfsdf for responsiveness testing

The result? Data! Sort of…


The first thing we noticed was the data essentially repeated, and that there was some non-ascii characters in the messages. However, many online decoders (xlate) won’t decode all the data. As much as we tried to figure out how to get the ATM to respond, it simply just wouldn’t budge. However, if you’re very good at picking out ASCII in the message you’ll notice repeating messages.





<something unprintable>






Which makes it clear what is happening — we’re getting the modem in most of our dumps and unprintable data is very likely the ATM trying to connect.

Looking into that data:

FF FF FF FF 96 E0 40 77 77 77 77 77 77 77

This message kept repeating meaning it was some form of data coming in. Whether it was from the modem or the ATM we didn’t know. But many protocols (including wireless) make use of preambles which allow a computer or data processor a way to differentiate the data from the noise efficiently and without wasting too much processing time. My theory was that either the 0xFF’s or the 0x77’s were a preamble/handshake and the machine would respond back with its data if given the proper handshake.

To make more sense of this, its easier to visualize the way the signal looks:

Visualization of the data coming from the serial data of the modem

The final 0 and 1 are “real” data in this example. As you can see, with a pattern such as 0x77 or 0xFF we can easily see where the handshake or start message is. Without it, we aren’t very sure. So I tested this.

The first attempt was to send back what we were given in an attempt to see
if the ATM responded to a specific protocol message. If it is sending a specific message at least sending something wrong but in the same format should trigger it to respond (hopefully) with an error. At least that was the thought. Initial testing showed no differences in the responses.

Unable to find a solution around this, we started searching having hit a roadblock on processing. While documentation is not posted on the Hyosung website, the Tranax ATM documentation is very plentiful online, and after some searching we had a breakthrough. We discovered the provisional protocol guide, which gave us a clearer picture on how to make the ATMs speak. Once again, these ATMs are not the most modern. In fact on reseller sites they point out the fact these ATMs support up to 3DES encryption and in the high end models even support Windows XP!

Decoding and Processing

With the provisional guide in place, we could start making sense of the data, or at least attempt to. The first thing that became apparent was the ENQ (02 in ascii), which we need to send to the terminal once it connects.

From the documentation:

Modem operates at 300, 1200, or 2400 baud
7 Data Bits, 1 Stop Bit
Even Parity
First character is STX and last two characters are ETX and an LRC composed of the body of the message without the STX and ETX. (ENQ)

Using this information we can start to see the proper structure of a message and in addition we can now start to see the proper flow for the data:

ATM Processing Request: Send ENQ -> ATM Responds -> You Respond -> You ACK -> You send EOT -> Disconnect
ATM Processing Request: Send ENQ -> ATM Responds -> You Respond -> You ACK -> You send EOT -> Disconnect
Flow Diagram for ATM Communication over Dial-Up

This opens up two questions: How do we know the terminal is connected and two, what messages need to be set to get the ATM to activate and eventually accept transactions?

Screen capture from the Protocol Guide indicating supported protocols and reset options

On a hunch, we set our data processor to respond after the ATM sends a series of 0xFF’s in the assumption that this as the ATM’s preamble or some form of signal triggering a normal processor to identify the device on the other side was indeed an ATM. The first step was to request ENQ to the ATM and a quick pySerial client did the job, however the ATM simply responded with the same data again! It turns out the Tranax protocol is based off of an old protocol known was VISANET (from 1981) which was heavily documented back in 1994 on Phrack. Stumbling on these, jrwr attempted to use our processing code sending able to identify that the ATM needed needed multiple ETX messages sent to it in order to get it to trigger a response.

The hidden piece we needed to make the ATM respond, from 1994

After some tinkering the ATM responded with a message:


It became immediately apparent the ATM was not sending valid ENQ characters at the beginning of this transaction. Neither of us worked with modems, but looking up old modem guides we suspected that 7 bits + 1 stop bit could mean that the 8th bit could be stripped.

So 11111111 would be 01111111. We attempted to make a buffer that packed all the data stripping the 8th bit. This did have a result, and ended up pointing us in the right direction to how to process the data. And in retrospect, probably a poor direction to go down, however in this post we are trying to explore the challenges of looking into very old protocols and some of the wrong decisions made along the way.

NORMAL: 10000010 10110010 10110010 10110010 10110010 10110010
10110010 10110010 10110010 10011100 00000011 00000000
SHIFTED: 00000101 01100101 01100101 01100101 01100101 01100101
01100101 01100101 01100101 00111000 00000110 00000000

Which did translate data: <05>eeeeeeee8<06>, with proper control characters. However repeated attempts to decode the data, simply yielded invalid data based on the protocol document. Another mystery was that sending back ENQ (0x03) resulted in query from the ATM.

Further attempting to read the messages resulting in clearly invalid data:


If only ATMs used such simple protocol! But without any other knowledge, we started suspecting the whole message was encrypted. We attempted to use the known master key to decode the messages but resulted in an even more confusing dump of data, so we eliminated this as a possibility
after several revisions and moved onto looking back at the fact we were on an old protocol.

The key was ASCII.. ASCII is 7 bits, what we see today with ascii being 1 byte (8 bits) is simply Unicode/ANSI not original ASCII. Originally however the 8th bit had meaning in old protocols. Often, it was used as a parity bit of even or odd parity. The real challenge with this was being able to process this real time in the original implementation. We had picked pySerial to use originally, however it was simply unreliable in getting a single byte at a time. We instead switched to python and os.write to handle the serial processing. Messages became reliable and we were finally able to reproduce transmissions.

With reliable messages coming in, we realized that the 8th bit was a parity bit on the 8th bit with 7 bits of ASCII. As it turns out, resulted in data! Manually unpacking the bytes we were able to begin to see the message and what fields were used (and unused). Since the ATM protocol supports optional and debug headers, it took a while to align the bytes and see which protocol messages were actually being sent.

STX 02
TID 32 32 32 32 32 32 32 32 20 20 20 20 20 20 20
FS 1c
TAC 36 30
FS 1c
?? 1b 32
FS 1c
ETX 03
LRC 10

One interesting finding here was that the Terminal ID (TID) can only bet set to a max of 8 characters, while the protocol supports up to 15. The remaining characters are simply space filled, during our testing we set the 8 characters to spaces (0x20) so the whole Terminal ID was spaces. This made it much easier to find and partition between bytes.

The code began to take shape — the main portion of the code handled answering the ATM then handle messages once the preamble/handshake, and then send the messages to the message processing code which would handle responses and validation. The initial messages all came back as 0x95 (or 0x15 / NAK) it took quite a bit of time to figure this out, but the original calculation of the LRC was completely invalid. After simplifying all the code and refactoring we were able to clean up and validate the message processing.

We had two options, the protocol specification references a standard lookup table for LRC validation, but computing LRC is easy enough we just ran our own processing for the LRC messages. With validation in place, we could begin to generate our own messages.

Unfortunately we still were getting the invalid message replies from the ATM which we realized must be the encryption key.

From the documentation, there is an apparent ANSI standard for managing and securing PIN numbers on devices (American National Standard For PIN Management and Security X.9.8) and states that the pin code will be 64-bits in length and each nibble being stored as an ASCII value (0–9&A-F) representing the hex characters and are oriented left-to-right.

For our ATMs we set the encryption master key to be all 0’s, however there are several characters which indicate different keys (left and right) but sending the separate control characters { ~ and [ resulted in only invalid messages. Instead we attempted to use DES. The difference between 3DES and DES on the ATM was simply having two separate key blocks but the encryption process was the same, and there is only a single key block that is encrypted
not the whole message. The API protocol specifies in detail the different requirements for the key block(s) and additional consideration when making use of a mult-host ATM setup. In that setup multiple hosts can handle the ATM requests instead of a single host handling multiple ATMs.

To generate the key blocks we made use of an old library called pyDES which supports both TDES and DES encryption. Encryption with our static key of all 0’s was simple:

The next part was formatting and putting this into the message, we ended up with a message that looked like:


Once we got the proper message formatted, we were then able to start sending messages to the ATM and it responded!

But unfortunately we got another 0x15! To understand what happened, we need to look at the transaction codes. In the second message we got 50, or 3530 in hex. Using our handy lookup table we were able to identify easily that the ATM requires a host download response.

What happened? This took a bit of research, but referencing the manual it is a host total request for which the ATM needs a response. This contains information the ATM needs to operate as well as general metadata information that helps the ATM run. This includes the date, the number
of inquiries and transfers, and other information. As far as we could tell, these don’t directly affect the ATM, but do show up in the status data when in the operator menu.

jrwr peering into the newly “operational” ATM with the out of service error

The final step was to figure out how to make the ATMs respond to transactions. For this we targeted the most common transaction “withdraw from savings”.

All further messages from the ATM required sequence numbers and other information to be exchanged. The current code was not capable of easily parsing messages sent by the ATM so a full rewrite was in order
(or almost a full rewrite). The first step was to build a better processor for messages and secondly properly read the header in both debug mode and normal mode and pull the proper headers out.

This meant taking code that looked like this:

And turning it into something a bit better (though cleaned up further since) like this:

Making Bank

One of the design requirements was to make it possible to separate the serial processing from the data processing of the payment processor. To do this, I made use of a transaction that handled most of the work, and a base loop that handled the initial serial logic. By far, the most complicated part of the processor was the serial handler. The ATM can send multiple messages and the only indication a message is starting or ending is STX and ETX. If a message contained a start of message and end of message, it would be sent to the processing code. Once this happened, the serial processor would listen for a success or failure message and send the appropriate reply. If any error took place, the best possible option was to close the program. This may seem like a strange way to clear out errors, but due to the modem, we had to close the program to release control of the line. We attempted to find a solution for this, but didn’t see any way to access the modem pins directly from the UART.

Starting at read() you can see roughly how the processor handles messages.

Serial Processor Logic

The task remained how to process the incoming transactions as well as handle proper error responses for each of them. To handle this, the payment processing was broken out into two separate classes — one class to handle the transactions, and the other to handle the protocol.

The ATMs operate in one of two modes: batch mode and single mode, with batch mode being the more complicated and required a keep-alive connection to the payment processor (modem). We elected for the simpler mode of single transactions which meant the ATMs dialed in for every transaction. Because the ATMs would dial in we could the protocol handling class to read the message, decode the message, and finally forward the transaction onto a specific transaction handler based on the specific transaction.

PBX and USB Serial Modems

While we had 10 ATMs in use there were only two PABX (PBX Systems) units each several ATMs connected to to each unit, which were in turn connected to a single USB modem on a Raspberry Pi. Because each transaction took place separately, the ATMs would attempt to connect and wait a short period for the line to be free before attempting again. The added benefit of doing one transaction at a time was the automatic retries for each transaction, so if a timeout did occur, we could handle it and get the data processed.

One major challenge we didn’t plan for was the strangeness of the modems. At times the modems would not respond at all to AT+COMMANDs, other times they would simply print hundreds of 0x0A characters nonstop, and other times, they would only get half the transaction message. To over come this we elected to increase the termination rate of the Serial Handler. For every transaction whether it was failed or was successful, we automatically terminated the program and let systemd restart the program. As part of our service file, we launched a simple script that started and then terminated screen to resolve the response issues and nonstop 0x0A characters.

The “0x0A” issue…

The there were three main message types supported: transactions, reversals, and downloads. Downloads were processed directly on the Raspberry Pi hosts to ensure the ATMs were ready, the rest was sent via UDP to the payment processor. (Note: Both HTTP and UDP were used in an effort to be a finding for teams and aid in the discovery of the ATM processing.) Once at the payment processing code the messages were once again decoded and then the actual transactions took place.

In an effort to simplify the protocol processing code was separated out and had the DinoBank API handling and error responses. ATM Messages for transactions are identical except for balance and transaction response code. In all, there are over 20 different transaction response codes, however we only used less than 8. In particular we use 000 (success), 111 (failure), 019 (system error), 007 (insufficient funds), and 001 (card expired) to name a few. To make this as real as possible, the code was able to validate PIN numbers, card expiration, track2 data, and validate transaction balances before performing. One interesting trick we put into the Payment Processing code was automatic rejection of reversals — essentially eating any money that failed to get transferred.

Reason -00010.00 I should not be a payment processor

Another interesting challenge presented to us was the balance amounts. Most currency is measured in dollars and cents. However the ATMs all processed amounts in cents with all values left filled. So $20.00 would in fact be 002000. This made it mandatory to convert all data from the API into data we could safely send to the ATM, however the ATMs also do not process balance data or totals — they must be sent by the response messages as the amount1 field. This lead to some very interesting transactions with values that made no sense: -00010.00 and 21..00 to name a few.

Wrapping Up

As the students participated, they were tasked with identifying vulnerabilities in the ATMs and payment processing network. However, we were surprised to find many teams did not know what dial-up was, and further, some teams attempted redirect the ATMs to IP addresses. The ATMs were filled with Jamaican currency due to its similarities to the US Dollar but being cheaper, many teams found it suspicious when they requested $20 and got $100 in Jamaican currency instead. Teams also attempted to brute-force their way into the ATMs, but only one team discovered the operator menu.

Because ATMs like these have no modern hardware or peripherals, the only attack vector for these is essentially sitting on the phone line which is thankfully obvious and relatively difficult.

Future Work

The goal of this project was to allow students to learn about embedded systems, ATMs, and gain a better understand of security beyond clients and servers. We think we did a pretty good job with it in CPTC’s DinoBank, but there’s always more to do! Our goal for this project will be to publish the python code shortly and eventually provide several lessons to allow students to learn protocol processing and serial/modem communications using these same ATMs.

If you have questions or are interested in future work check our our twitters — spiceywasabi and jrwr

CPTC dog mascot tax!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade