From dd93525a17b9b284ee2f89e9a813df609462adb1 Mon Sep 17 00:00:00 2001 From: singe Date: Tue, 22 Jul 2014 12:24:41 +0200 Subject: [PATCH 1/4] Updated mainframe_bruter to new py3270 wrapper --- mainframe_bruter.py | 156 ++++++++++++-------------------------------- py3270wrapper.py | 88 +++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 114 deletions(-) create mode 100755 py3270wrapper.py diff --git a/mainframe_bruter.py b/mainframe_bruter.py index 014a2e3..23b5771 100755 --- a/mainframe_bruter.py +++ b/mainframe_bruter.py @@ -12,7 +12,7 @@ # The original was GPL'ed and hence so is this # Dominic White @singe dominic () sensepost.com -from py3270 import EmulatorBase,CommandError,FieldTruncateError +from py3270wrapper import WrappedEmulator import time import sys import argparse @@ -35,85 +35,13 @@ def whine(text, kind='clear', level=0): elif level == 3: lvldisp = "\t\t\t" print lvldisp+typdisp+text -# Override some behaviour of py3270 library -class EmulatorIntermediate(EmulatorBase): - def send_enter(self): #Allow a delay to be configured - self.exec_command('Enter') - if results.sleep > 0: - time.sleep(results.sleep) - - def screen_get(self): - response = self.exec_command('Ascii()') - return response.data - -# Set the emulator intelligently based on your platform -# Mainframed helpfully provided binaries for you, you can get them from -# https://github.com/mainframed/TSO-Brute -# I'm going to assume you've masters "installing things", do so, then update -# the relevant path below for your OS -if platform.system() == 'Darwin': - class Emulator(EmulatorIntermediate): - x3270_executable = '/usr/local/bin/x3270' #'MAC_Binaries/x3270' - s3270_executable = '/usr/local/bin/s3270' #'MAC_Binaries/s3270' -elif platform.system() == 'Linux': - class Emulator(EmulatorIntermediate): - x3270_executable = '/usr/bin/x3270' #comment this line if you do not wish to use x3270 on Linux - s3270_executable = '/usr/bin/s3270' -elif platform.system() == 'Windows': - class Emulator(EmulatorIntermediate): - #x3270_executable = 'Windows_Binaries/wc3270.exe' - s3270_executable = 'Windows_Binaries/ws3270.exe' -else: - whine('Your Platform:', platform.system(), 'is not supported at this time.',kind='err') - sys.exit(1) - -# Send text without triggering field protection -def safe_send(em, text): - for i in xrange(0,len(text)): - em.send_string(text[i]) - if em.status.field_protection == 'P': - return False #We triggered field protection, stop - return True #Safe - -def safe_fieldfill(em, ypos, xpos, tosend, length): - if length - len(tosend) < 0: - raise FieldTruncateError('length limit %d, but got "%s"' % (length, tosend)) - if xpos is not None and ypos is not None: - em.move_to(ypos, xpos) - try: - em.delete_field() - if safe_send(em, tosend): - return True #Hah, we win, take that mainframe - else: - return False #we entered what we could, bailing - except CommandError, e: - # We hit an error, get mad - return False - #if str(e) == 'Keyboard locked': - -# Search the screen for text when we don't know exactly where it is, checking for read errors -def find_response(em, response): - for rows in xrange(1,int(em.status.row_number)+1): - for cols in xrange(1,int(em.status.col_number)+1-len(response)): - try: - if em.string_found(rows, cols, response): - return True - except CommandError, e: - # We hit a read error, usually because the screen hasn't returned - # increasing the delay works - time.sleep(results.sleep) - results.sleep += 1 - whine('Read error encountered, assuming host is slow, increasing delay by 1s to: ' + str(results.sleep),kind='warn') - return False - return False - def check_CICS(em): whine('Checking if in CICS',kind='info') em.delete_field() em.send_string('CESF') #CICS CESF is the Signoff command em.send_enter() - if find_response(em, 'Sign-off is complete'): + if em.find_response( 'Sign-off is complete'): whine('Mainframe is in CICS',kind='good') return True else: @@ -122,7 +50,7 @@ def check_CICS(em): em.send_string('CESF') #CICS signoff command em.send_enter() - if find_response(em, 'Sign-off is complete'): + if em.find_response( 'Sign-off is complete'): whine('Mainframe is in CICS',kind='good') return True else: @@ -150,17 +78,17 @@ def check_VTAM(em): #Test command enabled in the session-level USS table ISTINCDT, should always work em.send_string('IBMTEST') em.send_enter() - if not find_response(em, 'IBMECHO ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): + if not em.find_response( 'IBMECHO ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): for i in xrange(1,3): time.sleep(results.sleep+i) - if find_response(em, 'IBMECHO ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): + if em.find_response( 'IBMECHO ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): results.sleep += 1 whine('The host is slow, increasing delay by 1s to: ' + str(results.sleep),kind='warn') return True whine('Mainframe not in VTAM, aborting',kind='err') check_CICS(em) #All may not be lost return False - elif find_response(em, 'REQSESS error'): + elif em.find_response( 'REQSESS error'): whine('Mainframe may be in a weird VTAM, continuing reluctantly',kind='warn') else: return True @@ -177,7 +105,7 @@ def enter_TSOPanel(em): em.send_string('TSOFAKE') em.send_enter() - if not find_response(em, 'TSO/E LOGON'): + if not em.find_response( 'TSO/E LOGON'): whine('Not at TSO/E Logon screen. Aborting.',kind='err') return False whine('At TSO/E Logon Panel',kind='info',level=1) @@ -212,23 +140,23 @@ def brute_TSO(em,results,userfile,passfile=''): continue # Enter the username - safe_fieldfill(em,06, 20, username.strip(), 7) + em.safe_fieldfill(06, 20, username.strip(), 7) em.move_to(1,1) em.send_enter() print em.screen_get() gooduser = False # We've tried too many attempts and need to reconnect - if find_response(em, 'LOGON REJECTED, TOO MANY ATTEMPTS'): + if em.find_response( 'LOGON REJECTED, TOO MANY ATTEMPTS'): em.reconnect() enter_TSOPanel() - safe_fieldfill(em,06, 20, username.strip(), 7) + em.safe_fieldfill(06, 20, username.strip(), 7) em.send_enter() # Look for output showing the user exists - if find_response(em, 'Enter current password for'): + if em.find_response( 'Enter current password for'): gooduser = True - elif find_response(em, 'LOGON REJECTED, RACF TEMPORARILY REVOKING USER'): + elif em.find_response( 'LOGON REJECTED, RACF TEMPORARILY REVOKING USER'): gooduser = True # Allow for additional responses to be defined @@ -249,21 +177,21 @@ def brute_TSO(em,results,userfile,passfile=''): continue # Enter the password - safe_fieldfill(em,8, 20, password.strip(),8) + em.safe_fieldfill(8, 20, password.strip(),8) em.send_enter() # We've tried too many attempts and need to reconnect - if find_response(em, 'LOGON REJECTED, TOO MANY ATTEMPTS'): + if em.find_response( 'LOGON REJECTED, TOO MANY ATTEMPTS'): em.reconnect() enter_TSOPanel(em) - safe_fieldfill(em,06, 20, username.strip(), 7) - safe_fieldfill(em,8, 20, password.strip(),8) + em.safe_fieldfill(06, 20, username.strip(), 7) + em.safe_fieldfill(8, 20, password.strip(),8) em.send_enter() # Check the responses - if find_response(em, 'PASSWORD NOT AUTHORIZED FOR USERID'): + if em.find_response( 'PASSWORD NOT AUTHORIZED FOR USERID'): validpass = False - elif find_response(em, 'LOGON REJECTED'): + elif em.find_response( 'LOGON REJECTED'): validpass = False # todo: add code to deal with a locked out account @@ -311,9 +239,9 @@ def brute_APPLID(em,results,appfile): if cmdtype == 'APPLID' and validate_text(applid,8,cmdtype): if noapplid: #LOGON launches TSO, so skips APPLID checks break - safe_send(em,'LOGON APPLID('+applid.strip()+')') + em.safe_send('LOGON APPLID('+applid.strip()+')') elif cmdtype == "MACRO": - safe_send(em,applid.strip()) + em.safe_send(applid.strip()) elif cmdtype == "PF": em.exec_command(applid.strip()) screenshot_before = em.screen_get() @@ -326,35 +254,35 @@ def brute_APPLID(em,results,appfile): badcmd = False if em.is_connected(): - if find_response(em, 'COMMAND UNRECOGNIZED'): + if em.find_response( 'COMMAND UNRECOGNIZED'): badcmd = True - elif find_response(em, 'SESSION NOT BOUND'): + elif em.find_response( 'SESSION NOT BOUND'): badcmd = True - elif find_response(em, 'INVALID COMMAND'): + elif em.find_response( 'INVALID COMMAND'): badcmd = True - elif find_response(em, 'PARAMETER OMITTED'): + elif em.find_response( 'PARAMETER OMITTED'): badcmd = True - elif find_response(em, 'REQUERIDO PARAMETRO PERDIDO'): + elif em.find_response( 'REQUERIDO PARAMETRO PERDIDO'): badcmd = True - elif find_response(em, 'Your command is unrecognized'): + elif em.find_response( 'Your command is unrecognized'): badcmd = True - elif find_response(em, 'invalid command or syntax'): + elif em.find_response( 'invalid command or syntax'): badcmd = True - elif find_response(em, 'UNABLE TO ESTABLISH SESSION'): + elif em.find_response( 'UNABLE TO ESTABLISH SESSION'): badcmd = True - elif find_response(em, 'UNSUPPORTED FUNCTION'): + elif em.find_response( 'UNSUPPORTED FUNCTION'): badcmd = True - elif find_response(em, 'REQSESS error'): + elif em.find_response( 'REQSESS error'): badcmd = True - elif find_response(em, 'syntax invalid'): + elif em.find_response( 'syntax invalid'): badcmd = True - elif find_response(em, 'INVALID SYSTEM'): + elif em.find_response( 'INVALID SYSTEM'): badcmd = True - elif find_response(em, 'NOT VALID'): + elif em.find_response( 'NOT VALID'): badcmd = True - elif find_response(em, 'COMMAND UNRECOGNIZED'): + elif em.find_response( 'COMMAND UNRECOGNIZED'): badcmd = True - elif find_response(em, 'INVALID USERID, APPLID'): + elif em.find_response( 'INVALID USERID, APPLID'): badcmd = False noapplid = True whine('LOGON launches TSO, skipping further APPLIDs',kind='warn',level=1) @@ -385,7 +313,7 @@ def brute_CICS(em,results,transfile): for transid in transfile: em.delete_field() #Some inputs are dumb, and we need to blank the previous command if validate_text(transid,10,'TRANSID'): #todo, are there length restrictions? - safe_send(em,transid.strip()) + em.safe_send(transid.strip()) screenshot_before = em.screen_get() if screenshot_before == screenshot_after: # Check if the screen has been locked and our input is being ignored @@ -397,15 +325,15 @@ def brute_CICS(em,results,transfile): badcmd = False if em.is_connected(): - if find_response(em, 'is not recognized'): + if em.find_response( 'is not recognized'): badcmd = True - elif find_response(em, 'sign on or have the right security'): + elif em.find_response( 'sign on or have the right security'): badcmd = True elif screenshot_before == screenshot_after: badcmd = True em.reconnect() #We need to close and reconnect to exit the app enter_CICS(em) - elif find_response(em, 'Sign-off is complete'): + elif em.find_response( 'Sign-off is complete'): badcmd = False em.reconnect() enter_CICS(em) @@ -483,13 +411,13 @@ def brute_CICS(em,results,transfile): if results.movie_mode and not platform.system() == 'Windows': whine('ULTRA Hacker Movie Mode\t: Enabled',kind='info') #Enables Movie Mode which uses x3270 so it looks all movie like - em = Emulator(visible=True) + em = WrappedEmulator(visible=True,delay=results.sleep) elif results.movie_mode and platform.system() == 'Windows': whine('ULTRA Hacker Movie Mode not supported on Windows',kind='warn') - em = Emulator() + em = WrappedEmulator(delay=results.sleep) else: whine('ULTRA Hacker Movie Mode\t: Disabled',kind='info') - em = Emulator() + em = WrappedEmulator(delay=results.sleep) if results.quiet: whine('Quiet Mode Enabled\t: Shhhhhhhhh!',type='warn') diff --git a/py3270wrapper.py b/py3270wrapper.py new file mode 100755 index 0000000..64f92b4 --- /dev/null +++ b/py3270wrapper.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +from py3270 import Emulator,CommandError,FieldTruncateError,TerminatedError,WaitError,KeyboardStateError,FieldTruncateError,x3270App +import platform +from time import sleep + +# Override some behaviour of py3270 library +class EmulatorIntermediate(Emulator): + def __init__(self, visible=True, delay=0): + Emulator.__init__(self, visible) + self.delay = delay + + def send_enter(self): # Allow a delay to be configured + self.exec_command('Enter') + if self.delay > 0: + sleep(self.delay) + + def screen_get(self): + response = self.exec_command('Ascii()') + return response.data + + # Send text without triggering field protection + def safe_send(self, text): + for i in xrange(0,len(text)): + self.send_string(text[i]) + if self.status.field_protection == 'P': + return False # We triggered field protection, stop + return True # Safe + + # Fill fields in carefully, checking for triggering field protections + def safe_fieldfill(self, ypos, xpos, tosend, length): + if length - len(tosend) < 0: + raise FieldTruncateError('length limit %d, but got "%s"' % (length, tosend)) + if xpos is not None and ypos is not None: + self.move_to(ypos, xpos) + try: + self.delete_field() + if safe_send(self, tosend): + return True # Hah, we win, take that mainframe + else: + return False # we entered what we could, bailing + except CommandError, e: + # We hit an error, get mad + return False + # if str(e) == 'Keyboard locked': + + # Search the screen for text when we don't know exactly where it is, checking for read errors + def find_response(self, response): + for rows in xrange(1,int(self.status.row_number)+1): + for cols in xrange(1,int(self.status.col_number)+1-len(response)): + try: + if self.string_found(rows, cols, response): + return True + except CommandError, e: + # We hit a read error, usually because the screen hasn't returned + # increasing the delay works + sleep(self.delay) + self.delay += 1 + whine('Read error encountered, assuming host is slow, increasing delay by 1s to: ' + str(self.delay),kind='warn') + return False + return False + + # Get the current x3270 cursor position + def get_pos(self): + results = self.exec_command('Query(Cursor)') + row = int(results.data[0].split(' ')[0]) + col = int(results.data[0].split(' ')[1]) + return (row,col) + + def get_hostinfo(self): + return self.exec_command('Query(Host)').data[0].split(' ') + +# Set the emulator intelligently based on your platform +if platform.system() == 'Darwin': + class WrappedEmulator(EmulatorIntermediate): + #x3270_executable = './x3270' + x3270App.executable = 'x3270' +elif platform.system() == 'Linux': + class WrappedEmulator(EmulatorIntermediate): + #x3270_executable = './x3270' + x3270App.executable = 'x3270' +elif platform.system() == 'Windows': + class WrappedEmulator(EmulatorIntermediate): + #x3270_executable = 'Windows_Binaries/wc3270.exe' + x3270App.executable = 'wc3270.exe' +else: + logger('Your Platform:', platform.system(), 'is not supported at this time.',kind='err') + sys.exit(1) From 7976eec6000d9d877ff91f70167e115b254762ef Mon Sep 17 00:00:00 2001 From: singe Date: Tue, 22 Jul 2014 12:36:58 +0200 Subject: [PATCH 2/4] updates to py3270 and conversion of screenshotter to use it --- py3270wrapper.py | 6 +++--- screenshotter.py | 36 ++++-------------------------------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/py3270wrapper.py b/py3270wrapper.py index 64f92b4..61aa572 100755 --- a/py3270wrapper.py +++ b/py3270wrapper.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from py3270 import Emulator,CommandError,FieldTruncateError,TerminatedError,WaitError,KeyboardStateError,FieldTruncateError,x3270App +from py3270 import Emulator,CommandError,FieldTruncateError,TerminatedError,WaitError,KeyboardStateError,FieldTruncateError,x3270App,s3270App import platform from time import sleep @@ -73,12 +73,12 @@ def get_hostinfo(self): # Set the emulator intelligently based on your platform if platform.system() == 'Darwin': class WrappedEmulator(EmulatorIntermediate): - #x3270_executable = './x3270' x3270App.executable = 'x3270' + s3270App.executable = 's3270' elif platform.system() == 'Linux': class WrappedEmulator(EmulatorIntermediate): - #x3270_executable = './x3270' x3270App.executable = 'x3270' + s3270App.executable = 's3270' elif platform.system() == 'Windows': class WrappedEmulator(EmulatorIntermediate): #x3270_executable = 'Windows_Binaries/wc3270.exe' diff --git a/screenshotter.py b/screenshotter.py index b4f1103..b7a82cd 100755 --- a/screenshotter.py +++ b/screenshotter.py @@ -11,7 +11,7 @@ # Original credit goes to Mainframed and TSO-Brute https://github.com/mainframed/TSO-Brute # Actually, he has a NMAP script to do this for you https://github.com/mainframed/NMAP/blob/master/3270_screen_grab.nse -from py3270 import EmulatorBase,CommandError,FieldTruncateError +from py3270wrapper import WrappedEmulator import time import sys import argparse @@ -34,34 +34,6 @@ def whine(text, kind='clear', level=0): elif level == 3: lvldisp = "\t\t\t" print lvldisp+typdisp+text -# Override some behaviour of py3270 library -class EmulatorIntermediate(EmulatorBase): - def send_enter(self): #Allow a delay to be configured - self.exec_command('Enter') - if results.sleep > 0: - time.sleep(results.sleep) - - def screen_get(self): - response = self.exec_command('Ascii()') - return response.data - -# Set the emulator intelligently based on your platform -if platform.system() == 'Darwin': - class Emulator(EmulatorIntermediate): - x3270_executable = '/usr/local/bin/x3270' #'MAC_Binaries/x3270' - s3270_executable = '/usr/local/bin/s3270' #'MAC_Binaries/s3270' -elif platform.system() == 'Linux': - class Emulator(EmulatorIntermediate): - x3270_executable = '/usr/bin/x3270' #comment this line if you do not wish to use x3270 on Linux - s3270_executable = '/usr/bin/s3270' -elif platform.system() == 'Windows': - class Emulator(EmulatorIntermediate): - #x3270_executable = 'Windows_Binaries/wc3270.exe' - s3270_executable = 'Windows_Binaries/ws3270.exe' -else: - whine('Your Platform:', platform.system(), 'is not supported at this time.',kind='err') - sys.exit(1) - def connect_zOS(em, target): whine('Connecting to ' + results.target,kind='info') em.connect(target) @@ -87,13 +59,13 @@ def connect_zOS(em, target): if results.movie_mode and not platform.system() == 'Windows': whine('ULTRA Hacker Movie Mode\t: Enabled',kind='info') #Enables Movie Mode which uses x3270 so it looks all movie like 'n shit - em = Emulator(visible=True) + em = WrappedEmulator(visible=True) elif results.movie_mode and platform.system() == 'Windows': whine('ULTRA Hacker Movie Mode not supported on Windows',kind='warn') - em = Emulator() + em = WrappedEmulator() else: whine('ULTRA Hacker Movie Mode\t: Disabled',kind='info') - em = Emulator() + em = WrappedEmulator(visible=False) if results.quiet: whine('Quiet Mode Enabled\t: Shhhhhhhhh!',type='warn') From 05926b31c2af6c26e997f6df684661a6a120ba2a Mon Sep 17 00:00:00 2001 From: singe Date: Sun, 28 Sep 2014 13:49:25 +0200 Subject: [PATCH 3/4] Fixed issue with new py3270 inputting data to quickly for some hosts --- mainframe_bruter.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mainframe_bruter.py b/mainframe_bruter.py index 23b5771..0074875 100755 --- a/mainframe_bruter.py +++ b/mainframe_bruter.py @@ -18,6 +18,7 @@ import argparse import re import platform +from IPython import embed # Print output that can be surpressed by a CLI opt def whine(text, kind='clear', level=0): @@ -78,13 +79,17 @@ def check_VTAM(em): #Test command enabled in the session-level USS table ISTINCDT, should always work em.send_string('IBMTEST') em.send_enter() + time.sleep(results.sleep) if not em.find_response( 'IBMECHO ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): - for i in xrange(1,3): - time.sleep(results.sleep+i) + for i in xrange(1,5): + time.sleep(results.sleep+(i/10)) if em.find_response( 'IBMECHO ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): - results.sleep += 1 - whine('The host is slow, increasing delay by 1s to: ' + str(results.sleep),kind='warn') + results.sleep += 0.1 + whine('The host is slow, increasing delay by 0.1s to: ' + str(results.sleep),kind='warn') return True + else: + em.send_string('IBMTEST') + em.send_enter() whine('Mainframe not in VTAM, aborting',kind='err') check_CICS(em) #All may not be lost return False @@ -414,10 +419,10 @@ def brute_CICS(em,results,transfile): em = WrappedEmulator(visible=True,delay=results.sleep) elif results.movie_mode and platform.system() == 'Windows': whine('ULTRA Hacker Movie Mode not supported on Windows',kind='warn') - em = WrappedEmulator(delay=results.sleep) + em = WrappedEmulator(visible=False,delay=results.sleep) else: whine('ULTRA Hacker Movie Mode\t: Disabled',kind='info') - em = WrappedEmulator(delay=results.sleep) + em = WrappedEmulator(visible=False,delay=results.sleep) if results.quiet: whine('Quiet Mode Enabled\t: Shhhhhhhhh!',type='warn') From 89918344e1e809e3f1e65cfd1e17f662b969940a Mon Sep 17 00:00:00 2001 From: singe Date: Sun, 28 Sep 2014 13:52:15 +0200 Subject: [PATCH 4/4] Fixed an issue with new py3270 inputting data too quickly for some hosts --- screenshotter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screenshotter.py b/screenshotter.py index b7a82cd..01a361c 100755 --- a/screenshotter.py +++ b/screenshotter.py @@ -45,7 +45,7 @@ def connect_zOS(em, target): # Define and fetch commandline arguments parser = argparse.ArgumentParser(description='z/OS Mainframe Screenshotter', epilog='Get to it!') parser.add_argument('-t', '--target', help='Target IP address or Hostname and port: TARGET[:PORT] default port is 23', required=True, dest='target') -parser.add_argument('-s', '--sleep', help='Seconds to sleep between actions (increase on slower systems). The default is 0 seconds.', default=0, type=float, dest='sleep') +parser.add_argument('-s', '--sleep', help='Seconds to sleep between actions (increase on slower systems). The default is 0 seconds.', default=0.1, type=float, dest='sleep') parser.add_argument('-m', '--moviemode', help='Enables ULTRA AWESOME Movie Mode. Watch the system get hacked in real time!', default=False, dest='movie_mode', action='store_true') parser.add_argument('-q', '--quiet', help='Be more quieter', default=False, dest='quiet', action='store_true') results = parser.parse_args()