Trace your ATM Transactions


Introduction

Generally when entrepreneurs decide to become ATM deployers, they do not have sufficient knowledge about ATM protocols and specifications. This is not needed as there are switching providers that can switch their ATM’s  transactions and provide them with adequate reporting.

Following this approach generally leaves them with a massive gap in terms of managing their terminals and merchants  correctly. ATM switching providers have the ability to decode the terminal status messages in real time and determine for example is a terminal has ran out of cash, if hardware is busy failing or even if a processing bank has gone offline.

This allows switching providers to have the upper hand over the smaller ATM deployers.

In this post I will show you how to develop a middleware where this pro active approach can be followed even with small ATM deployers. You will be able to see live cash levels of your terminals and monitor ATM transactions.

 

ATM Languages

ATM’s  and EFTPOS devices speak different languages and each terminal manufacturer might have their own custom implementation. Terminal developers  normally follow a few types of language implementations: Triton / NDC+ / AS2805 / ISO8583. As all of these will run on TCP using some sort of control protocol (VISAII / ACK Controlled) we need to decode the control elements of the protocol as well.

I will be using the Standard Triton Protocol over VISAII to demonstrate the ability to trace transactional protocols.

Most ATM’s have an TCP/IP setting that will enable you to point the ATM to an IP Address and port. Writing an Server component to listen to this is of course as easy as pie.

Conversion to ASCII

It is an easy approach to write a server application and listen on a post for incoming transactions but we will quickly notice that these message are encoded. This encoding it an easy hurdle (as long as there is no SSL component on the ATM)

StringToAscii Class – The readable encoding

The first class I’m going to create is an class that can convert encoded strings to readable ascii, this need the be based on the control characters specified in your protocol specification.

Each Hex encoded string needs to be mapped to the format you require. (<ETX> / <STX>) and a simple function can provide the conversion.

controls_dic = {
 0: '<NUL>', 1: '<SOH>', 2: '<STX>', 3: '<ETX>', 4: '<EOT>', 5: '<ENQ>', 6: '<ACK>',
 7: '<BEL>', 8: '<BS>' , 9: '<HT>' , 10: '<LF>' , 11: '<VT>' , 12: '<FF>' , 13: '<CR>' ,
14: '<SO>' , 15: '<SI>' , 16: '<DLE>', 17: '<DC1>', 18: '<DC2>', 19: '<DC3>',
20: '<DC4>', 21: '<NAK>', 22: '<SYN>', 23: '<ETB>', 24: '<CAN>', 25: '<EM>',
26: '<SUB>', 27: '<ESC>', 28: '<FS>' , 29: '<GS>' , 30: '<RS>' , 31: '<US>'
}

def Str2Ascii(s):
 r = ''
 for c in s:
 if ord(c) < 32:
 r = r + controls_dic[ord(c)]
 elif ord(c) >= 127:
 r = r + '<%d>' % ord(c)
 else:
 r = r + c
 return r

The reverse conversion is slightly more complex, but in essence its just a reverse function of the above.

reverse_dic5 = { 
'<NUL>': 0, '<SOH>': 1, '<STX>': 2, '<ETX>': 3, '<EOT>': 4, '<ENQ>': 5, '<ACK>': 6,
'<BEL>': 7, '<DLE>': 16, '<DC1>': 17, '<DC2>': 18, '<DC3>': 19,
'<DC4>': 20, '<NAK>': 21, '<SYN>': 22, '<ETB>': 23, '<CAN>': 24, 
'<SUB>': 26, '<ESC>': 27
}

reverse_dic4 = {
'<BS>' : 8, '<HT>' : 9, '<LF>' : 10, '<VT>' : 11, '<FF>' : 12, '<CR>' : 13,
'<SO>' : 14, '<SI>' : 15, '<EM>' : 25, '<FS>' : 28, '<GS>' : 29, '<RS>' : 30, '<US>' : 31
}

reverse_dicN = {
 '<130>':130, '<140>':140, '<150>':150, '<160>':160, '<170>':170, '<180>':180, '<190>':190, '<200>':200, '<210>':210, '<220>':220, '<230>':230, '<240>':240, '<250>':250, 
 '<131>':131, '<141>':141, '<151>':151, '<161>':161, '<171>':171, '<181>':181, '<191>':191, '<201>':201, '<211>':211, '<221>':221, '<231>':231, '<241>':241, '<251>':251, 
 '<132>':132, '<142>':142, '<152>':152, '<162>':162, '<172>':172, '<182>':182, '<192>':192, '<202>':202, '<212>':212, '<222>':222, '<232>':232, '<242>':242, '<252>':252, 
 '<133>':133, '<143>':143, '<153>':153, '<163>':163, '<173>':173, '<183>':183, '<193>':193, '<203>':203, '<213>':213, '<223>':223, '<233>':233, '<243>':243, '<253>':253, 
 '<134>':134, '<144>':144, '<154>':154, '<164>':164, '<174>':174, '<184>':184, '<194>':194, '<204>':204, '<214>':214, '<224>':224, '<234>':234, '<244>':244, '<254>':254, 
 '<135>':135, '<145>':145, '<155>':155, '<165>':165, '<175>':175, '<185>':185, '<195>':195, '<205>':205, '<215>':215, '<225>':225, '<235>':235, '<245>':245, '<255>':255, 
 '<136>':136, '<146>':146, '<156>':156, '<166>':166, '<176>':176, '<186>':186, '<196>':196, '<206>':206, '<216>':216, '<226>':226, '<236>':236, '<246>':246, 
'<127>' : 127, '<137>':137, '<147>':147, '<157>':157, '<167>':167, '<177>':177, '<187>':187, '<197>':197, '<207>':207, '<217>':217, '<227>':227, '<237>':237, '<247>':247, 
'<128>' : 128, '<138>':138, '<148>':148, '<158>':158, '<168>':168, '<178>':178, '<188>':188, '<198>':198, '<208>':208, '<218>':218, '<228>':228, '<238>':238, '<248>':248, 
'<129>' : 129, '<139>':139, '<149>':149, '<159>':159, '<169>':169, '<179>':179, '<189>':189, '<199>':199, '<209>':209, '<219>':219, '<229>':229, '<239>':239, '<249>':249 
}

def Ascii2Str(s):
 r = ''
 i = 0
 l = len(s)
 while i < l:
 if s[i] == '<':
 try:
 x = reverse_dic5[s[i:i+5]]
 r = r + chr(x)
 i = i + 5
 except KeyError:
 try:
 x = reverse_dic4[s[i:i+4]]
 r = r + chr(x)
 i = i + 4
 except KeyError:
 try:
 x = reverse_dicN[s[i:i+5]]
 r = r + chr(x)
 i = i + 5
 except KeyError:
 r = r + s[i]
 i = i + 1 
 else:
 r = r + s[i]
 i = i + 1
 return r

We can now pass a simulated transaction to the function and test the output and see if the class is correctly implemented acceding to the Triton Standard Protocol.

Message

message = '05 02 39 56 44 44 39 30 30 32 32 30 30 30 30 30 32 1C 31 31 1C 30 34 30 36 1C 34 32 31 36 34 36 30 30 30 30 30 30 30 30 30 38 3D 31 34 31 32 31 30 31 31 31 38 38 35 32 34 39 31 32 33 34 35 1C 30 30 30 30 32 30 30 30 1C 30 30 30 30 30 30 30 30 1C 38 36 32 37 42 38 36 33 34 37 41 30 33 37 37 35 1C 1C 1C 1C 5E 35 45 36 32 20 44 35 37 37 1C 03 6E '.replace(" ", "").decode("hex")

Result

<ENQ><STX>9U00009D       <FS>11<FS>0406<FS>4216460000000008=14121011188524912345<FS>00002000<FS>00000000<FS>8627B86347A03775<FS><FS><FS><FS>^5E62 D577<ETX>

Reversing this back to an encoded string we just need to pass it to the ASCIIToString Function of course.

Tracing Data

Now that we have all the field from the request from the ATM, the next step is to create a server component that can run and pass this transactional information to our processing party.

Creating a Server is not part of this post, but all source code is available in the gitbub repository. But the basic structure should be that you should create a incoming and sending socket, and poll for connections using a daemon thread.  Passing ALL information to the processing party, but saving the requests to the database.

Port Listening for ATM connections

 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

 self.sock.bind(('', int(listening_port)))
 self.sock.listen(5)

 

Port forwarding to Processor with SSL

fwd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 try:

 ssl_sock = ssl.wrap_socket(fwd)
 ssl_sock.settimeout(int(session.destination_timeout))
 ssl_sock.connect((session.destination_ip, int(session.destination_port)))
 ssl_sock.do_handshake()
 self.log.info('SSL Handshake Completed')

 self.log.debug('%s: Connected to switch %s:%s' % (
 session.SessionUUID, session.destination_ip, session.destination_port,))
 except socket.timeout:
 session.error = "Timeout connecting to switch"
 self.log.critical('%s: EE Cant Connect to %s:%s : %s' % (
 session.SessionUUID, session.destination_ip, session.destination_port, session.error,))
 ssl_sock.close()
 except socket.sslerror:
 session.error = "SSL Error connecting to TNS"
 self.log.critical('%s: EE Cant Connect to %s:%s : %s' % (
 session.SessionUUID, session.destination_ip, session.destination_port, session.error,))
 ssl_sock.close()
 except:
 session.error = "Unknown Error connecting to switch"
 self.log.exception('%s: EE Unknown Error Connecting to %s:%s : %s' % (
 session.SessionUUID, session.destination_ip, session.destination_port, session.error,))
 ssl_sock.close()
 else:
 ssl_sock.settimeout(None)
 RequestThread(session, newsock, ssl_sock).start()
 ResponseThread(session, ssl_sock, newsock).start()

When a specific Transaction type is received from the ATM you can dissect the readable ascii and save the request to database, when the corresponding session has a response then you can of course save the response information with the request.

In my code example I provide the implementation of request  and response saving as based on the Triton Specification. If you are using NDC+ or ISO 8583 then this will dramatically change.

Security

The security aspect of this project should be implemented in the raw socket components (SSL Sockets), if there is a need for SSL certificate validation and ACL’s then it should not be difficult to add this to the required class.

PCI DSS require us not to store raw Personal Account numbers in the database so we should in fact use a hash function.

A simple method for doing this is the following:

return pan[:6] + ("*" * (len(pan)-10)) + pan[-4:]

Final Product

The final product is a python implementation of a Transactional Middleware. Allot of changes will be required to make the project work for your environment, but here are basic instructions to make it work for you.

  1. Get a clean Linux installation
  2. Install the LAMP Stack
  3. run the SQL file in the project
  4. copy the project directory to the server
  5. app_get all the project dependencies. (pymysql ect.)
  6. change the config file to point to your database and your switching provider (Middleware.ini)
  7. create a screen session (command: screen -S TransactionServer )
  8. start the Server in the screen session (sudo python pyMiddlewareServer foreground)

When starting it up you should see the following:

2014-05-24 17:53:38,177 MyDaemon INFO run() Start
2014-05-24 17:53:38,180 Pinhole INFO Redirecting: localhost:9100 -> xxx.xxx.xxx.xx:xxxxx
2014-05-24 17:53:38,263 QueueLogger INFO Connected to Database <_mysql.connection open to '127.0.0.1' at 100837020>
2014-05-24 17:53:45,704 Pinhole INFO 8850226b-e318-11e3-8a9a-28cfe91efd87: NN New Session from ('127.0.0.1', 49660)
2014-05-24 17:53:45,817 Pinhole INFO SSL Handshake Completed
2014-05-24 17:53:45,832 Request INFO 8850226b-e318-11e3-8a9a-28cfe91efd87: > AtmID=[9VDD90022000002], Type=[Authorization from Cheque Account], Operation=[CW]
2014-05-24 17:53:45,832 Request INFO 8850226b-e318-11e3-8a9a-28cfe91efd87: > PAN=[216460000000008]
2014-05-24 17:53:46,072 Request INFO 8850226b-e318-11e3-8a9a-28cfe91efd87: > AtmID=[9VDD90022000002], Type=[Authorization from Cheque Account], Operation=[CW]
2014-05-24 17:53:46,072 Request INFO 8850226b-e318-11e3-8a9a-28cfe91efd87: > PAN=[216460000000008]

Source code: https://github.com/Arthurvdmerwe/ATM-Transaction-Trace.git

Leave a Comment