def run_script(self, script, quiet=False): log('executing script {}'.format(script)) self.inject() try: fname = '{}/{}'.format(self.script_dir, script) with open(fname) as fd: src = fd.read() except: log_traceback() if not quiet: self.print_message('Unable to load {}'.format(fname), color=palette.ERROR) return try: result = self.injection_command(src=src) except Exception as e: result = e if not isinstance(result, Exception): if result is None: msg = '{} executed'.format(script) else: msg = '{}: {}'.format(script, result) if not quiet: self.print_message(msg, color=palette.GREEN_BOLD) else: if not quiet: self.print_message('{}: {}'.format(script, result), color=palette.ERROR)
def autostart_plugins(): for plugin in plugins_autostart: if plugin['p'] is not _d.current_plugin.get('p'): log('autostarting {}'.format(plugin['m'])) inject_plugin(plugin) p = plugin['p'] if p.background: p.start()
def inject_plugin(plugin): if plugin['p'].injected is False: log('injecting plugin {}'.format(plugin['p'].name)) plugin['p'].injected = True try: command('.inject', plugin['i']) return True except: print_message('Plugin injection failed', color=palette.ERROR) return False
def format_key(k): if len(k) == 1: z = ord(k) if z == 10: k = 'ENTER' elif z == 27: k = 'ESC' elif z < 27: k = 'CTRL_' + chr(z + 64) log('key pressed: {}'.format(k if len(k) > 1 else (( 'ord=' + str(ord(k))) if ord(k) < 32 else '"{}"'.format(k)))) return k
def launch(cpid, wait=True, protocol=None): start(cpid, protocol=None, runner_mode=True) if wait is True: log('waiting for ready') while not g._runner_ready: time.sleep(0.2) elif wait > 0: log('waiting {} seconds'.format(wait)) t_end = time.time() + wait while time.time() < t_end: if g._runner_ready: break time.sleep(0.1)
def start(cpid, protocol=None, lg=None, runner_mode=False): if lg: init_logging(lg) else: stop_logging() log('starting injection server for pid {}'.format(cpid)) if protocol and protocol <= PICKLE_PROTOCOL: protocol = protocol else: protocol = PICKLE_PROTOCOL t = threading.Thread(name='__pptop_injection_{}'.format(cpid), target=loop, args=(cpid, protocol, runner_mode)) t.setDaemon(True) t.start()
def main(): import argparse ap = argparse.ArgumentParser() ap.add_argument('file', metavar='FILE', help='File to launch') ap.add_argument('cpid', metavar='PID', type=int, help='Client PID') ap.add_argument('-w', '--wait', metavar='SEC', type=float, help='Wait seconds till start') ap.add_argument('-p', '--protocol', metavar='VER', type=int, help='Pickle protocol') ap.add_argument('-a', '--args', metavar='ARGS', help='Child args (quoted)') ap.add_argument('--log', metavar='FILE', help='Send debug log to file') a = ap.parse_args() if a.protocol and a.protocol > PICKLE_PROTOCOL: raise ValueError('Protocol {} is not supported'.format(a.protocol)) if a.log: init_logging(a.log) with open(a.file) as fh: src = fh.read() sys.argv = [a.file] if a.args: import shlex sys.argv += shlex.split(a.args) log('pptop injection runner started') launch(a.cpid, wait=True if a.wait is None else a.wait) g._runner_status = 1 log('starting main code') try: code = compile(src, a.file, 'exec') launcher_g = {'__file__': a.file} exec(code, launcher_g) g._runner_status = 0 log('main code finished') except: g._runner_status = -2 log_traceback('exception in main code') e = sys.exc_info() with _g_lock: g._last_exception = (e[0].__name__, str( e[1]), ['']) # TODO: correct tb traceback.format_tb(e[2])) while not g._server_finished: time.sleep(0.2) # usually not executed as server kills process log('pptop injection runner stopped')
def on_load(self): self.title = 'Script runner' self.description = 'Run selected script from ~/.pptop/scripts' self.short_name = 'Script' self.sorting_rev = False self.selectable = True if 'script_dir' in self.config: self.script_dir = os.path.expanduser(self.config['script_dir']) else: self.script_dir = self.get_config_dir() + '/scripts' self.global_script_hotkeys = {} self.script_hotkey_help = {} script_keys = self.config.get('script-keys') if not script_keys: script_keys = {} for i, v in script_keys.items(): for k in v if isinstance(v, list) else [v]: self.global_script_hotkeys[str(k)] = str(i) log('script hot key {} = {}'.format(k, i)) if not os.path.isfile('{}/{}'.format(self.script_dir, i)): log('WARNING: script {} doesn\'t exists'.format(i)) self.script_hotkey_help[i] = ', '.join( [format_shortcut(x) for x in v]) if isinstance(v, list) else format_shortcut(v)
def command(cmd, params=None): with client_lock: _d.client_frame_id += 1 if _d.client_frame_id >= frame_counter_reset: _d.client_frame_id = 1 _d.last_frame_id = 0 try: frame = cmd.encode() if params is not None: frame += b'\xff' + pickle.dumps(params, protocol=_d.protocol) client.sendall( struct.pack('I', len(frame)) + struct.pack('I', _d.client_frame_id) + frame) time_start = time.time() data = client.recv(4) frame_id = struct.unpack('I', client.recv(4))[0] except: log_traceback() raise CriticalException('Injector is gone') if not data: log('critical: no data from injector') raise CriticalException('Injector error') l = struct.unpack('I', data)[0] data = b'' while len(data) != l: data += client.recv(socket_buf) if time.time() > time_start + socket_timeout: raise CriticalException('Socket timeout') if frame_id != _d.client_frame_id: log('critical: got wrong frame, channel is broken') raise CriticalException('Wrong frame') _d.last_frame_id += 1 with ifoctets_lock: _d.ifoctets += len(data) + 8 if _d.ifoctets > 1000000000: _d.ifoctets = _d.ifoctets - 1000000000 if data[0] != 0: log('injector command error, code: {}'.format(data[0])) raise RuntimeError('Injector command error') return pickle.loads(data[1:]) if len(data) > 1 else True
def inject_server(gdb, p): cmds = [] pid = p.pid libpath = os.path.abspath(os.path.dirname(__file__) + '/..') if _d.inject_method in ['native', 'loadcffi']: cmds.append('call (void)dlopen("{}", 2)'.format(_d.inject_lib)) if _d.inject_method == 'native': cmds.append( 'call (int)__pptop_start_injection("{}",{},{},"{}")'.format( libpath, os.getpid(), _d.protocol, log_config.fname if log_config.fname else '')) else: cmds += [ 'call (PyGILState_STATE)PyGILState_Ensure()', ('call (int)PyRun_SimpleString("' + 'import sys\\nif \\"{path}\\" not in sys.path: ' + 'sys.path.insert(0,\\"{path}\\")\\n' + 'import pptop.injection;pptop.injection.start(' + '{mypid},{protocol}{lg})")').format( path=libpath, mypid=os.getpid(), lg='' if not log_config.fname else ',lg=\\"{}\\"'.format( log_config.fname), protocol=_d.protocol), ' call (void)PyGILState_Release($1)' ] args = [gdb, '-p', str(pid), '--batch' ] + ['--eval-command={}'.format(c) for c in cmds] log(args) p = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() log(out) log(err) if p.returncode: raise RuntimeError(err)
def resize_handler(**kwargs): log('resize event') with scr.lock: resize_term()
def loop(cpid, protocol, runner_mode=False): class STD: pass class ppStdout(object): write_through = False mode = 'w' def __init__(self, name, real, std): self.name = '<{}>'.format(name) self.real = real self.std = std try: self.encoding = real.encoding except: self.encoding = 'UTF-8' self.flush = real.flush self.isatty = real.isatty def writable(self): return True def write(self, text): with self.std.lock: self.std.buf += text return self.real.write(text) def writelines(self, lines): with self.std.lock: for l in lines: self.std.buf += l return self.real.writelines(lines) def send_frame(conn, frame_id, data): conn.sendall( struct.pack('I', len(data)) + struct.pack('I', frame_id) + data) # log('{}: frame {}, {} bytes sent'.format(cpid, frame_id, len(data))) def send_serialized(conn, frame_id, data): send_frame(conn, frame_id, b'\x00' + pickle.dumps(data, protocol=protocol)) def send_ok(conn, frame_id): send_frame(conn, frame_id, b'\x00') def format_injection_unload_code(injection_id, src): return compile(u + '\ninjection_unload()', '__pptop_injection_unload_' + injection_id, 'exec') server_address = '/tmp/.pptop.{}'.format(cpid) try: os.unlink(server_address) except: pass server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) server.bind(server_address) os.chmod(server_address, 0o600) server.listen(0) server.settimeout(socket_timeout) server.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, socket_buf) server.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, socket_buf) injections = {} real_stdout = None real_stderr = None log('Pickle protocol: {}'.format(protocol)) log('listening') try: connection, client_address = server.accept() log('connected') connection.sendall(struct.pack('b', protocol)) with _g_lock: g.clients += 1 connection.settimeout(socket_timeout) exec_globals = {} while True: try: time_start = time.time() data = connection.recv(4) frame_id = struct.unpack('I', connection.recv(4))[0] if data: l = struct.unpack('I', data)[0] frame = b'' while len(frame) != l: if time.time() > time_start + socket_timeout: raise TimeoutError frame += connection.recv(socket_buf) else: break except: log_traceback('invalid data received or client is gone') break if frame: try: cmd, params = frame.split(b'\xff', 1) cmd = cmd.decode() params = pickle.loads(params) except: cmd = frame.decode() params = {} try: if cmd == '.test': send_ok(connection, frame_id) elif cmd == '.bye': break elif cmd == '.gs': if real_stdout is None: std = STD() std.lock = threading.Lock() std.buf = '' with std.lock: real_stdout = sys.stdout real_stderr = sys.stderr sys.stdout = ppStdout('stdout', real_stdout, std) sys.stderr = ppStdout('stderr', real_stderr, std) buf = '' else: with std.lock: buf = std.buf std.buf = '' send_serialized(connection, frame_id, buf) elif cmd == '.status': send_serialized(connection, frame_id, g._runner_status if runner_mode else 1) elif cmd == '.path': send_serialized(connection, frame_id, sys.path) elif cmd == '.x': x = {} try: exec(params, x) result = (0, x.get('out')) except: log_traceback() e = sys.exc_info() result = (1, e[0].__name__, str(e[1])) send_serialized(connection, frame_id, result) elif cmd == '.exec': try: if params.startswith('help'): raise RuntimeError( 'Help on remote is not supported') if params.startswith('try: __result '): src = params else: p1 = params.split(' ', 1)[0] prfunc = '_print' if sys.version_info < ( 3, 0) else 'print' if p1 in [ 'import', 'def', 'class', 'for', 'while', 'raise', 'if', 'with', 'from', 'try:' ]: src = ( 'def {}(*args):\n' + ' __resultl.append(\' \'.join(str(a) ' + 'for a in args))\n__resultl=[]\n{}' + '\n__result = \'\\n\'.join(__resultl) ' + 'if __resultl else None').format( prfunc, params) else: src = ('def {}(*args): ' + 'return \' \'.join(str(a) ' + 'for a in args)\n' + '__result = {}').format( prfunc, params) exec(src, exec_globals) result = exec_globals.get('__result') try: data = pickle.dumps( (0, safe_serialize(result)), protocol=protocol) except: log_traceback() data = pickle.dumps((0, str(result)), protocol=protocol) send_frame(connection, frame_id, b'\x00' + data) except: log_traceback() e = sys.exc_info() with _g_lock: g._last_exception = (e[0].__name__, str(e[1]), ['']) send_serialized(connection, frame_id, (-1, e[0].__name__, str(e[1]))) elif cmd == '.le': with _g_lock: send_serialized(connection, frame_id, g._last_exception) elif cmd == '.ready': g._runner_ready = True send_ok(connection, frame_id) elif cmd == '.inject': log(params) injection_id = params['id'] if injection_id in injections: u = injections[injection_id].get('u') if u: try: code = format_injection_unload_code( injection_id, u) exec(code, injections[injection_id]['g']) log('injection removed: {}'.format( injection_id)) except: log_traceback() injections[injection_id] = { 'g': { 'g': SimpleNamespace(), 'mg': g }, 'u': params.get('u') } if 'l' in params: code = compile( params['l'] + '\ninjection_load(**load_kw)', '__pptop_injection_load_' + injection_id, 'exec') injections[injection_id]['g'][ 'load_kw'] = params.get('lkw', {}) exec(code, injections[injection_id]['g']) if 'i' in params: src = params['i'] + '\n_r = injection(**kw)' else: src = '_r = None' injections[injection_id]['i'] = compile( src, '__pptop_injection_' + injection_id, 'exec') log('injection completed: {}'.format(injection_id)) send_ok(connection, frame_id) elif cmd in injections: log('command {}, data: {}'.format(cmd, params)) gl = injections[cmd]['g'] gl['kw'] = params exec(injections[cmd]['i'], gl) send_serialized(connection, frame_id, gl['_r']) else: send_frame(connection, frame_id, b'\x01') except: log_traceback() send_frame(connection, frame_id, b'\x02') else: break except Exception as e: log_traceback() for i, v in injections.items(): u = v.get('u') if u: try: code = format_injection_unload_code(i, u) exec(code, v['g']) log('injection removed: {}'.format(i)) except: log_traceback() try: server.close() except: pass try: with _g_lock: g.clients -= 1 except: pass if real_stdout is not None: sys.stdout = real_stdout if real_stderr is not None: sys.stderr = real_stderr try: os.unlink(server_address) except: pass log('finished') if runner_mode: g._server_finished = True os._exit(0)
def start(): def format_plugin_option(dct, o, v): if o.find('.') != -1: x, y = o.split('.', 1) dct[x] = {} format_plugin_option(dct[x], y, v) else: dct[o] = v _me = 'ppTOP version %s' % __version__ ap = argparse.ArgumentParser(description=_me) ap.add_argument('-V', '--version', help='Print version and exit', action='store_true') ap.add_argument('-R', '--raw', help='Raw mode (disable colors and unicode glyphs)', action='store_true') ap.add_argument('--disable-glyphs', help='disable unicode glyphs', action='store_true') ap.add_argument('file', nargs='?', help='File, PID file or PID', metavar='FILE/PID') ap.add_argument('-a', '--args', metavar='ARGS', help='Child args (quoted)') ap.add_argument('--python', metavar='FILE', help='Python interpreter to launch file') ap.add_argument('--gdb', metavar='FILE', help='Path to gdb') ap.add_argument( '-p', '--protocol', metavar='VER', type=int, help=textwrap.dedent('''Pickle protocol, default is highest. 4: Python 3.4+, 3: Python 3.0+, 2: Python 2.3+, 1: vintage''')) ap.add_argument('--inject-method', choices=['auto', 'native', 'loadcffi', 'unsafe'], help='Inject method') ap.add_argument('-g', '--grab-stdout', help='Grab stdout/stderr of injected process', action='store_true') ap.add_argument( '-w', '--wait', metavar='SEC', type=float, help='If file is specified, wait seconds to start main code') ap.add_argument( '-f', '--config-file', help='Alternative config file (default: ~/.pptop/pptop.yml)', metavar='CONFIG', dest='config') ap.add_argument('-d', '--default', help='Default plugin to launch', metavar='PLUGIN', dest='plugin') ap.add_argument( '-o', '--plugin-option', help='Override plugin config option, e.g. threads.filter=mythread', metavar='NAME=VALUE', action='append', dest='plugin_options') ap.add_argument('--log', metavar='FILE', help='Send debug log to file') ap.add_argument('-x', '--exec', help='Exec code from a file ("-" for stdin) and exit ' ' (the code can put result to "out" var)', metavar='FILE', dest='_exec') ap.add_argument('-J', '--json', help='Output exec result as JSON', action='store_true') try: import argcomplete argcomplete.autocomplete(ap) except: pass a = ap.parse_args() if a.log: log_config.fname = a.log log_config.name = 'client:{}'.format(os.getpid()) logging.getLogger('asyncio').setLevel(logging.DEBUG) logging.getLogger('neotasker').setLevel(logging.DEBUG) logging.basicConfig(level=logging.DEBUG) le = logging.getLogger() le.addHandler(ppLoghandler()) list(map(le.removeHandler, le.handlers)) neotasker.set_debug(True) init_logging() if a.version: print(_me) exit() log('initializing') if a.file: try: # pid? _d.work_pid = int(a.file) except: # probably pid file try: with open(a.file) as fh: _d.work_pid = int(fh.read(128)) except: # okay, program to launch _d.child_cmd = os.path.abspath(a.file) _d.pptop_dir = os.path.expanduser('~/.pptop') if a.config: config_file = a.config use_default_config = False else: config_file = _d.pptop_dir + '/pptop.yml' use_default_config = True sys.path.append(_d.pptop_dir + '/lib') config.clear() if use_default_config and not os.path.isfile(config_file): log('no user config, setting default') try: os.mkdir(_d.pptop_dir) except: pass if not os.path.isdir(_d.pptop_dir + '/scripts'): shutil.copytree(dir_me + '/config/scripts', _d.pptop_dir + '/scripts') shutil.copy(dir_me + '/config/pptop.yml', _d.pptop_dir + '/pptop.yml') if not os.path.isdir(_d.pptop_dir + '/lib'): os.mkdir(_d.pptop_dir + '/lib') with open(config_file) as fh: config.update(yaml.load(fh.read())) console = config.get('console') if console is None: console = {} _d.console_json_mode = console.get('json-mode') _d.inject_method = a.inject_method if a.inject_method else config.get( 'inject-method') if config.get('display') is None: config['display'] = {} if a.raw: config['display']['colors'] = False if a.grab_stdout: _d.grab_stdout = True if a.raw or a.disable_glyphs: config['display']['glyphs'] = False if a._exec: if a._exec == '-': _d.exec_code = sys.stdin.read() else: with open(a._exec) as fd: _d.exec_code = fd.read() _d.output_as_json = a.json else: ebk = {} global_keys = config.get('keys') if global_keys: for event, keys in global_keys.items(): for k, v in events_by_key.copy().items(): if event == v: del events_by_key[k] if keys is not None: for k in keys if isinstance(keys, list) else [keys]: ebk[str(k)] = str(event) events_by_key.update(ebk) plugin_options = {} for x in a.plugin_options or []: try: o, v = x.split('=', 1) except: o = x v = None format_plugin_option(plugin_options, o, v) if plugin_options: config.update(merge_dict(config, {'plugins': plugin_options})) log('loading plugins') try: plugins.clear() for i, v in config.get('plugins', {}).items(): try: log('+ plugin ' + i) if v is None: v = {} try: mod = importlib.import_module('pptop.plugins.' + i) mod.__version__ = 'built-in' except ModuleNotFoundError: mod = importlib.import_module('pptopcontrib.' + i) try: mod.__version__ except: raise RuntimeError( 'Please specify __version__ in plugin file') plugin = {'m': mod} plugins[i] = plugin p = mod.Plugin(interval=float( v.get('interval', mod.Plugin.default_interval))) p.command = command p.get_plugins = get_plugins p.get_plugin = get_plugin p.get_config_dir = get_config_dir p.switch_plugin = switch_plugin p.get_process = get_process p.get_process_path = get_process_path p.global_config = config plugin['p'] = p plugin['id'] = i p._inject = partial(inject_plugin, plugin=plugin) injection = {'id': i} need_inject = False try: injection['l'] = inspect.getsource(mod.injection_load) need_inject = True except: pass try: injection['i'] = inspect.getsource(mod.injection) need_inject = True except: pass try: injection['u'] = inspect.getsource( mod.injection_unload) need_inject = True except: pass if need_inject: p.injected = False plugin['i'] = injection else: p.injected = None if not _d.default_plugin or val_to_boolean( v.get('default')) or i == a.plugin: _d.default_plugin = plugin p_cfg = v.get('config') p.config = {} if p_cfg is None else p_cfg p.on_load() p._on_load() if 'l' in injection: injection['lkw'] = p.get_injection_load_params() if 'shortcut' in v: sh = v['shortcut'] plugin['shortcut'] = sh plugin_shortcuts[sh] = plugin if sh.startswith('KEY_F('): try: f = int(sh[6:-1]) if f <= 10: bottom_bar_help[f] = p.short_name except: pass else: plugin['shortcut'] = '' if 'filter' in v: p.filter = str(v['filter']) if 'cursor' in v: p._cursor_enabled_by_user = val_to_boolean(v['cursor']) if val_to_boolean(v.get('autostart')): plugins_autostart.append(plugin) except Exception as e: raise RuntimeError('plugin {}: {}'.format(i, e)) except: log_traceback() raise neotasker.task_supervisor.start() neotasker.task_supervisor.create_aloop('pptop', default=True, daemon=True) neotasker.task_supervisor.create_aloop('service', daemon=True) try: if a.file and not _d.work_pid: # launch file _d.need_inject_server = False if a.python: python_path = a.python else: python_path = shutil.which('python3') if not python_path: raise RuntimeError( 'python3 not found in path, please specify manually') args = (python_path, '-m', 'pptop.injection', a.file, str(os.getpid())) if a.wait is not None: args += ('-w', str(a.wait)) if a.protocol is not None: args += ('-p', str(a.protocol)) if a.args: args += ('-a', a.args) if log_config.fname: args += ('--log', log_config.fname) log('starting child process') _d.child = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _d.work_pid = _d.child.pid _d.protocol = pickle.HIGHEST_PROTOCOL else: if a.gdb: _d.gdb = a.gdb else: _d.gdb = shutil.which('gdb') if not _d.gdb or not os.path.isfile(_d.gdb): raise RuntimeError('gdb not found') # check yama ptrace scope try: with open('/proc/sys/kernel/yama/ptrace_scope') as fd: yps = int(fd.read().strip()) except: yps = None if yps: raise RuntimeError( 'yama ptrace scope is on. ' + 'disable with "sudo sysctl -w kernel.yama.ptrace_scope=0"') init_inject() log('inject method: {}'.format(_d.inject_method)) log('inject library: {}'.format(_d.inject_lib)) if a.protocol is not None: if a.protocol > pickle.HIGHEST_PROTOCOL or a.protocol < 1: raise ValueError('Protocol {} is not supported'.format( a.protocol)) _d.protocol = a.protocol _d.force_protocol = a.protocol else: _d.protocol = pickle.HIGHEST_PROTOCOL log('Pickle protocol: {}'.format(_d.protocol)) run() log('terminating') for p, v in plugins.items(): v['p'].on_unload() except Exception as e: log_traceback() raise finally: try: client.close() except: pass neotasker.task_supervisor.stop(wait=False, cancel_tasks=True) return 0
def run(): def autostart_plugins(): for plugin in plugins_autostart: if plugin['p'] is not _d.current_plugin.get('p'): log('autostarting {}'.format(plugin['m'])) inject_plugin(plugin) p = plugin['p'] if p.background: p.start() try: if not _d.work_pid: init_curses(initial=True, after_resize=after_resize, colors=config['display'].get('colors'), glyphs=config['display'].get('glyphs')) p = select_process() else: p = psutil.Process(_d.work_pid) if not p: return _d.process = p client.settimeout(socket_timeout) client.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, socket_buf) client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, socket_buf) if _d.need_inject_server: inject_server(_d.gdb, p) log('server injected') sock_path = '/tmp/.pptop.{}'.format(os.getpid()) for i in range(injection_timeout * 10): if os.path.exists(sock_path): break time.sleep(0.1) try: client.connect(sock_path) except: log_traceback() raise RuntimeError('Unable to connect to process') log('connected') frame = b'' with client_lock: time_start = time.time() while len(frame) < 1: data = client.recv(1) if data: frame += data if time.time() > time_start + socket_timeout: raise CriticalException('Socket timeout') server_protocol = struct.unpack('b', frame)[0] if server_protocol < _d.protocol: if _d.force_protocol: raise RuntimeError( 'Process doesn\'t support protocol {}'.format(_d.protocol)) else: _d.protocol = server_protocol log('Falling back to protocol {}'.format(_d.protocol)) if _d.exec_code: end_curses() result = command('.x', _d.exec_code) if result[0] == 0: if _d.output_as_json: print_json(result[1]) else: print(result[1] if result[1] else '') else: print(err('{}: {}'.format(result[1], result[2]))) return init_curses(initial=True, after_resize=after_resize, colors=config['display'].get('colors'), glyphs=config['display'].get('glyphs')) signal.signal(signal.SIGWINCH, sigwinch_handler) calc_bw.start() update_status.start() if _d.grab_stdout: grab_stdout.start() _d.process_path.clear() plugin_process_path.clear() if _d.grab_stdout: try: command('.gs') except: raise RuntimeError('Unable to set stdout grabber') ppath = [] for i in command('.path'): ppath.append(os.path.abspath(i)) _d.process_path.extend(sorted(ppath, reverse=True)) plugin_process_path.extend(_d.process_path) log('process path: {}'.format(_d.process_path)) switch_plugin(_d.default_plugin) recalc_info_col_pos() show_process_info.start(p=p) show_bottom_bar.start() neotasker.spawn(autostart_plugins) log('main loop started') while True: try: try: k = format_key(scr.stdscr.getkey()) event = get_key_event(k) except KeyboardInterrupt: return except curses.error: resize_handler.trigger_threadsafe(force=True) continue if show_process_info.is_stopped(): return elif k in plugin_shortcuts: switch_plugin(plugin_shortcuts[k]) elif event == 'ready': try: result = command('.ready') except: result = None with scr.lock: if result: print_message('Ready event sent', color=palette.OK) else: print_message('Command failed', color=palette.ERROR) elif event == 'reinject' and \ _d.current_plugin['p'].injected is not None: try: result = command('.inject', _d.current_plugin['i']) except: result = None with scr.lock: if result: print_message('Plugin re-injected', color=palette.OK) else: print_message('Plugin re-injection failed', color=palette.ERROR) elif event == 'quit': _d.current_plugin['p'].stop(wait=False) show_process_info.stop(wait=False) show_bottom_bar.stop(wait=False) return elif event == 'console': with scr.lock: end_curses() if _d.grab_stdout: print_stdout.start() cli_mode() if _d.grab_stdout: print_stdout.stop() init_curses(after_resize=after_resize) resize_term() elif event == 'show-console': with scr.lock: end_curses() hide_cursor() if _d.grab_stdout: print_stdout.start() try: wait_key() except KeyboardInterrupt: pass if _d.grab_stdout: print_stdout.stop() init_curses(after_resize=after_resize) resize_term() elif event == 'filter': apply_filter(_d.current_plugin['p']) elif event == 'interval': apply_interval(_d.current_plugin['p']) elif event == 'pause': with scr.lock: _d.current_plugin['p'].toggle_pause() elif event in _d.current_plugin['p'].inputs: with scr.lock: try: prev_value = _d.current_plugin['p'].get_input( event) except ValueError: continue value = prompt( ps=_d.current_plugin['p'].get_input_prompt(event), value=prev_value if prev_value is not None else '') _d.current_plugin['p'].inputs[event] = value try: _d.current_plugin['p'].handle_input( event, value, prev_value) except: pass else: for i, plugin in plugins.items(): try: plugin['p'].handle_key_global_event(event, k) except: log_traceback() with scr.lock: _d.current_plugin['p'].key_code = k _d.current_plugin['p'].key_event = event _d.current_plugin['p'].trigger_threadsafe() except: log_traceback() return except: log_traceback() raise finally: end_curses()
def init_color_palette(force256=False): if term.endswith('256color') or force256: log('initializing terminal palette with 256 colors') palette.DARKGREY = curses.color_pair(238) palette.DARKGREY_BOLD = curses.color_pair(238) | curses.A_BOLD palette.DEBUG = curses.color_pair(244) palette.WARNING = curses.color_pair(187) | curses.A_BOLD palette.ERROR = curses.color_pair(198) | curses.A_BOLD palette.CRITICAL = curses.color_pair(197) | curses.A_BOLD palette.HEADER = curses.color_pair(37) | curses.A_REVERSE palette.CURSOR = curses.color_pair(32) | curses.A_REVERSE palette.BAR = curses.color_pair(37) | curses.A_REVERSE palette.BAR_OK = curses.color_pair(29) | curses.A_REVERSE palette.BAR_WARNING = curses.color_pair(4) | curses.A_REVERSE palette.BAR_ERROR = curses.color_pair(198) | curses.A_REVERSE palette.GREY = curses.color_pair(244) palette.GREY_BOLD = curses.color_pair(244) | curses.A_BOLD palette.GREEN = curses.color_pair(41) palette.GREEN_BOLD = curses.color_pair(41) | curses.A_BOLD palette.OK = curses.color_pair(121) | curses.A_BOLD palette.BLUE = curses.color_pair(40) palette.BLUE_BOLD = curses.color_pair(40) | curses.A_BOLD palette.RED = curses.color_pair(198) palette.RED_BOLD = curses.color_pair(198) | curses.A_BOLD palette.CYAN = curses.color_pair(51) palette.CYAN_BOLD = curses.color_pair(51) | curses.A_BOLD palette.MAGENTA = curses.color_pair(208) palette.MAGENTA_BOLD = curses.color_pair(208) | curses.A_BOLD palette.YELLOW = curses.color_pair(187) palette.YELLOW_BOLD = curses.color_pair(187) | curses.A_BOLD palette.WHITE_BOLD = curses.color_pair(231) | curses.A_BOLD palette.PROMPT = curses.color_pair(76) | curses.A_BOLD else: log('initializing terminal palette with 16 colors') palette.DARKGREY = curses.color_pair(1) palette.DARKGREY_BOLD = curses.color_pair(1) | curses.A_BOLD palette.DEBUG = curses.color_pair(1) | curses.A_BOLD palette.WARNING = curses.color_pair(4) | curses.A_BOLD palette.ERROR = curses.color_pair(2) | curses.A_BOLD palette.CRITICAL = curses.color_pair(2) | curses.A_BOLD palette.HEADER = curses.color_pair(3) | curses.A_REVERSE palette.CURSOR = curses.color_pair(7) | curses.A_REVERSE palette.BAR = curses.color_pair(7) | curses.A_REVERSE palette.BAR_OK = curses.color_pair(3) | curses.A_REVERSE palette.BAR_WARNING = curses.color_pair(4) | curses.A_REVERSE palette.BAR_ERROR = curses.color_pair(2) | curses.A_REVERSE palette.GREY = curses.color_pair(1) palette.GREY_BOLD = curses.color_pair(1) | curses.A_BOLD palette.GREEN = curses.color_pair(3) palette.GREEN_BOLD = curses.color_pair(3) | curses.A_BOLD palette.OK = curses.color_pair(3) | curses.A_BOLD palette.BLUE = curses.color_pair(5) palette.BLUE_BOLD = curses.color_pair(5) | curses.A_BOLD palette.RED = curses.color_pair(2) palette.RED_BOLD = curses.color_pair(2) | curses.A_BOLD palette.CYAN = curses.color_pair(7) palette.CYAN_BOLD = curses.color_pair(7) | curses.A_BOLD palette.MAGENTA = curses.color_pair(6) palette.MAGENTA_BOLD = curses.color_pair(6) | curses.A_BOLD palette.YELLOW = curses.color_pair(4) palette.YELLOW_BOLD = curses.color_pair(4) | curses.A_BOLD palette.WHITE_BOLD = curses.color_pair(8) | curses.A_BOLD palette.PROMPT = curses.color_pair(3) | curses.A_BOLD
def cli_mode(): def compl(text, state): if not text or text.find('.') == -1: return None o = text.rsplit('.', 1)[0] src = 'try: __result = dir({})\nexcept: pass'.format(o) result = command('.exec', src) if not result or result[0] or not result[1]: return None matches = [ s for s in result[1] if ('{}.{}'.format(o, s)).startswith(text) ] try: return '{}.{}'.format(o, matches[state]) except IndexError: return None log('cli mode started') if _d.cli_first_time: # os.system('clear') print( colored('Console mode, process {} connected'.format( _d.process.pid), color='green', attrs=['bold'])) print( colored(format_cmdline(_d.process, _d.need_inject_server), color='yellow')) print( colored( 'Enter any Python command, press Ctrl-D or type "exit" to quit' )) print(colored('To toggle between JSON and normal mode, type ".j"')) if _d.grab_stdout: print(colored('To toggle stdout/stderr output, type ".p"')) print( colored( 'To execute multiple commands from file, type "< filename"')) print( colored( 'To explore object, type "obj?" (transformed to "dir(obj)")')) if _d.protocol < 3: print( colored('For Python 2 use \'_print\' instead of \'print\'', color='yellow', attrs=['bold'])) print() _d.cli_first_time = False readline.set_history_length(100) readline.set_completer_delims('') readline.set_completer(compl) readline.parse_and_bind('tab: complete') try: readline.read_history_file('{}/console.history'.format(_d.pptop_dir)) except: pass try: while True: try: cmd = input('>>> ').strip() if cmd == '': continue elif cmd == 'exit': raise EOFError elif _d.grab_stdout and cmd == '.p': if print_stdout.is_active(): print_stdout.stop() else: print_stdout.start() elif cmd == '.j': _d.console_json_mode = not _d.console_json_mode print('JSON mode ' + ('on' if _d.console_json_mode else 'off')) else: if cmd.startswith('<'): with open(os.path.expanduser(cmd[1:].strip())) as fh: cmds = filter(None, [x.strip() for x in fh.readlines()]) elif cmd.endswith('?'): cmds = ['dir({})'.format(cmd[:-1]).strip()] else: cmds = [cmd] for cmd in cmds: r = command('.exec', cmd) if r[0] == -1: print(err('{}: {}'.format(r[1], r[2]))) else: if r[1] is not None: if _d.console_json_mode and \ (isinstance(r[1], dict) or \ isinstance(r[1], list)): print_json(r[1]) else: print(r[1]) except EOFError: return except KeyboardInterrupt: print() continue except Exception as e: log_traceback() print(err(e)) finally: log('cli mode completed') try: readline.write_history_file('{}/console.history'.format( _d.pptop_dir)) except: log_traceback()
def get_key_event(k): event = events_by_key.get(k, k) log('key event: {}'.format(event)) return event
def emit(self, record): log(super().format(record))