Last active
June 30, 2022 17:49
-
-
Save staaldraad/d78f5e6b0b6a92d0f08b to your computer and use it in GitHub Desktop.
Fuzz Verifone PoS terminals through exposed port
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/env/python | |
''' | |
Script for fuzzing verifone terminal/pos devices. This is a bad reverse-engineer and implementation of the official protocol: http://web.archive.org/web/20120603221525/http://www.verifone.com/PDF/guides/tcl_ref.pdf | |
Should work fine. Official docs were only found after the initial implementation. Not fully tested with CRC-16 checksum correctly implemented. | |
Author: [email protected] | |
Version: 1.0 | |
License: GNU GENERAL PUBLIC LICENSE (GNU) Version 2 | |
''' | |
import usb.core #import the pyusb wrapper for libusb | |
from usb.util import * | |
from usb.control import * | |
import serial | |
import sys,random | |
import binascii | |
import time | |
import array | |
vendor = 0x11ca #Vendor code for Verifone pos | |
product = 0x022a #Product -- Trident USB Device | |
''' | |
crc-table used for calculating the correct checksum. | |
Generated using pycrc : http://sourceforge.net/projects/pycrc | |
* by pycrc v0.8.2, http://www.tty1.net/pycrc/ | |
* using the configuration: | |
* Width = 16 | |
* Poly = 0x8005 | |
* XorIn = 0x0000 | |
* ReflectIn = False | |
* XorOut = 0x0000 | |
* ReflectOut = False | |
* Algorithm = table-driven | |
Values taken from: http://www.lammertbies.nl/forum/viewtopic.php?t=530 | |
''' | |
crc_table= [ 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, | |
0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, | |
0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, | |
0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, | |
0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, | |
0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, | |
0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, | |
0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, | |
0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, | |
0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, | |
0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, | |
0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, | |
0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, | |
0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, | |
0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, | |
0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, | |
0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, | |
0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, | |
0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, | |
0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, | |
0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, | |
0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, | |
0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, | |
0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, | |
0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, | |
0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, | |
0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, | |
0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, | |
0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, | |
0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, | |
0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, | |
0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202] | |
#we need to calculate the CRC as according to the spec | |
def calcCrc(data): | |
table = crc_table | |
crc = 0 | |
for byte in data: | |
crc = ((crc<<8)&0xff00) ^ table[((crc>>8)&0xff)^ord(byte)] | |
return crc & 0xffff | |
def mkhex(st): | |
st = binascii.hexlify(st) | |
return ''.join([('\\x%s'%st[i:i+2]) for i in range(0,len(st),2)]) | |
def attach(): | |
dev = None | |
while not dev: | |
try: | |
dev = usb.core.find(idVendor=vendor, idProduct=product) #get PoS device | |
except: | |
pass | |
while dev.is_kernel_driver_active(0): | |
time.sleep(7) | |
if not dev.is_kernel_driver_active(0): | |
break | |
try: | |
print '[/] Kernel driver active Attempting to get exclusive lock' | |
dev.detach_kernel_driver(0) #detach from kernel so we can grab exclusive hold | |
usb.util.claim_interface(dev,0) #claim ownership of the device | |
except usb.core.USBError: | |
print '[x] Unable to detach kernel lock' | |
sys.exit(1) | |
conf = dev.get_active_configuration() #get the device configuration such as EndPoints | |
iface = conf[(1,0)] | |
endpoints = iface.endpoints() | |
out = endpoints[1].bEndpointAddress | |
inu = endpoints[0].bEndpointAddress | |
return dev,out,inu | |
def writeval(vo): | |
global out | |
device.write(out,vo) | |
try: | |
device.read(0x81,2,100) | |
except: | |
pass | |
def initCom(): | |
writeval('\x05') | |
writeval('\x05') | |
writeval('\x05') | |
writeval('\x05') | |
writeval('\x05') | |
writeval('\x06') | |
def sendCmd(cmd): | |
writeval('\x02') | |
if len(cmd)>64: #we have to split into max 64byte chunks | |
for x in range(0,len(cmd),64): | |
if x + 64 > len(cmd): | |
writeval(cmd[x:]) | |
else: | |
writeval(cmd[x:x+64]) | |
else: | |
writeval(cmd) | |
writeval('\x03') | |
writeval(calcCrc(payload+'\x03')) | |
def closeCom(): | |
sendCmd('\x53') | |
writeval('\x04') | |
dldfile = 'LoadKey.dld' | |
mode = 'M--DOWNLOADING--' | |
payload = 'T20150427082911' #Setting the time | |
mdone = 'MDOWNLOAD DONE C' | |
upfile = 'OD000001B70000000020150427085158XXXXXXXX%s'%dldfile #Enter file upload mode | |
startUpload = 'M----------' | |
finishUpload = 'M**********' | |
#payload = 'R*/*:*' #Remove files | |
device = None | |
def mutate(p): | |
r = random.randint(1,5) | |
if r == 1: | |
i = random.randint(1,len(p)-1) | |
c = p[i] | |
v = p.replace(c,chr(random.randint(1,255))) | |
if r == 2: | |
v = ('%s'%p)*random.randint(2,20) | |
if r == 3: | |
v = chr(random.randint(1,255))*random.randint(100,1000) | |
if r == 4: | |
v = '' | |
for i in range(random.randint(255,1025)): | |
v += chr(random.randint(1,255)) | |
if r == 5: | |
v = chr(random.randint(32,125))+chr(random.randint(1,255))*random.randint(100,1000) | |
return v | |
def mutateFile(): #Mutate the dld file here. | |
''' | |
TODO | |
''' | |
fin = ''.join(open(dldfile,'r').readlines()) | |
return fin | |
def genPayloads(limit): | |
p = 'T20150427082911' | |
payloads = [] | |
for i in range(limit): | |
payloads.append(mutate(p)) | |
return payloads | |
def fuzz(limit): | |
payloads = genPayloads(limit) #gen our payloads to send | |
fout = open('payloads.txt','w') | |
for p in range(len(payloads)): | |
fout.write('Fuzz %i -- %s\n\n'%(p,payloads[p])) | |
fout.close() | |
global device,out,inu | |
for i in range(limit): | |
print 'Fuzz %i'%i | |
err = True | |
while err: | |
try: | |
device,out,inu = attach() | |
initCom() #Start communicating with device -- basically ping and wake | |
sendCmd(mode) | |
#normal payloads, set time ect | |
sendCmd(payloads[i]) | |
#upload dld file to fuzz | |
sendCmd(upfile) | |
sendCmd(startUpload) | |
sendCmd(mutateFile()) | |
sendCmd(finishUpload) | |
sendCmd(mdone) | |
closeCom() #Stop and restart the device | |
if device: | |
device.clear_halt(out) | |
device.reset() | |
usb.util.dispose_resources(device) | |
usb.util.release_interface(device,0) | |
time.sleep(2) | |
err = False | |
except usb.core.USBError as er: | |
pass | |
def simulate(): | |
global device,out,inu | |
device,out,inu = attach() | |
initCom() #Start communicating with device -- basically ping and wake | |
sendCmd(mode) | |
sendCmd(payload) | |
sendCmd(startUpload) | |
sendCmd(finishUpload) | |
sendCmd(mdone) | |
closeCom() #Stop and restart the device | |
if __name__ == "__main__": | |
print '[/] Attempting to attach to Device' | |
dev,a,b = attach() | |
if dev is None: | |
print '[-] Failed to find device' | |
sys.exit(1) | |
print '[+] Device is attached...' | |
#Lets print some info about the device - we want to look fancy | |
print '[+] Device information retrieved' | |
print 'Device class: %s\nManufacturer: %s\nSerial #: %s'%(dev.bDeviceClass,dev.manufacturer,dev.serial_number) | |
if len(sys.argv)>=2 and sys.argv[1] == "-f": | |
if len(sys.argv)>=3: | |
fuzz(int(sys.argv[2])) | |
else: | |
fuzz(5) | |
else: | |
print '[+] No options specified, doing a simulated fuzz' | |
print '[+] To fuzz the terminal: python %s -f 100'%sys.argv[0] | |
simulate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello Staaldraad,
I am new to POS devices. Do you think this program will be able capture the data from Verifone Ruby Supersystem? By data I mean that REPORTS and stuff? Or this is program is just for writing/fuzzing back to POS?
Thank you,
Jaskaran Chahal