From f013d802088ede38389705c8b8591e8b26de221e Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 10 Mar 2022 04:47:04 -0500 Subject: sploit: Add function Comm.readall_nonblock() Function should consume all available incoming data from target and return it, however will return 'immediately' (according to a configurable timeout) if the pipe is empty. Signed-off-by: Malfurious Signed-off-by: dusoleil --- tools/sploit/sploit/comm.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/sploit/sploit/comm.py b/tools/sploit/sploit/comm.py index 604045c..4b1d487 100644 --- a/tools/sploit/sploit/comm.py +++ b/tools/sploit/sploit/comm.py @@ -11,6 +11,7 @@ class Comm: logonread = True logonwrite = False flushonwrite = True + timeout = 0.25 # seconds def __init__(self, backend): self.back = backend @@ -42,6 +43,16 @@ class Comm: pass return data + def readall_nonblock(self): + try: + os.set_blocking(self.back.stdin.fileno(), False) + poll = select.poll() + poll.register(self.back.stdin, select.POLLIN) + poll.poll(self.timeout) + return self.readall() + finally: + os.set_blocking(self.back.stdin.fileno(), True) + def readuntil(self, pred, /, *args, **kwargs): data = b'' pred = bind(pred, *args, **kwargs) -- cgit v1.2.3 From 554e159ab80dfe4f8e504209de3c95f4929aa5aa Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 10 Mar 2022 04:56:04 -0500 Subject: sploit: Add Comm property 'readonwrite' If readonwrite is set to True (default False), Sploit will catch up and read all available stdin data from the target in a non-blocking fashion. If logonread is also set to True, this data will immediately be presented to the user whenever data is sent, but is otherwise lost (not returned). This mode is primarily intended for use in the interactive Python interpreter, where it can be cumbersome to keep alternating read and write calls when one does not care to actually record the read values. Signed-off-by: Malfurious Signed-off-by: dusoleil --- tools/sploit/sploit/comm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/sploit/sploit/comm.py b/tools/sploit/sploit/comm.py index 4b1d487..c109ec4 100644 --- a/tools/sploit/sploit/comm.py +++ b/tools/sploit/sploit/comm.py @@ -11,6 +11,7 @@ class Comm: logonread = True logonwrite = False flushonwrite = True + readonwrite = False timeout = 0.25 # seconds def __init__(self, backend): @@ -81,6 +82,7 @@ class Comm: self.back.stdout.write(data) if self.flushonwrite : self.back.stdout.flush() if self.logonwrite : ilog(data, file=sys.stdout, color=ALT) + if self.readonwrite : self.readall_nonblock() def writeline(self, data): self.write(data + b'\n') -- cgit v1.2.3 From f62515b19c848d5020e4e0e06c13c33e0a1af5bb Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 10 Mar 2022 05:13:12 -0500 Subject: sploit: Add function popen() This is a free-function in the comm module, intended to help setup Sploit plumbing when working in the Python interactive interpreter. At the moment, the intended user experience in the interpreter is to err on the side of being interactive/responsive. As such, the Comm object returned from popen() is initialized with overridden IO settings to prefer 'readonwrite' by default. Addtionally, any early output from the target is also read, so that it may be immediately visible. A consequence of this configuration is that, until readonwrite is set False, most target output will be consumed before any .read* function has a chance to return it. While that would be a hard showstopper for any Sploit script, an interactive user can simply copy/paste any important data that is produced. Given that the interpreter workflow is likely going to be most useful for quick prototyping and recon with the proposed rev module, I consider this tradeoff appropriate at the moment, but will consider revisiting this if its usage is problematic. Signed-off-by: Malfurious Signed-off-by: dusoleil --- tools/sploit/sploit/comm.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/sploit/sploit/comm.py b/tools/sploit/sploit/comm.py index c109ec4..3972f16 100644 --- a/tools/sploit/sploit/comm.py +++ b/tools/sploit/sploit/comm.py @@ -124,6 +124,12 @@ class Comm: os.set_blocking(stdin.fileno(), True) ilog("<--Interact Mode Done-->") +def popen(cmdline=''): + io = Comm((Process(cmdline.split()) if len(cmdline) > 0 else Pipes())) + io.readall_nonblock() + io.readonwrite = True + return io + class Process: def __init__(self, args): ilog(f"Running: {' '.join(args)}") -- cgit v1.2.3 From 38a8b3f528d9e02fd08d7d98a5c4beb530700cc2 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 10 Mar 2022 05:23:00 -0500 Subject: sploit: Clean up function Comm.interact() The previous patches in this series have needed to utilize similar logic as Comm.interact() throughout other parts of the Comm class. This patch just revisits .interact() to clean up redundant code. Co-authored-by: dusoleil Signed-off-by: Malfurious Signed-off-by: dusoleil --- tools/sploit/sploit/comm.py | 58 ++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/tools/sploit/sploit/comm.py b/tools/sploit/sploit/comm.py index 3972f16..265ab96 100644 --- a/tools/sploit/sploit/comm.py +++ b/tools/sploit/sploit/comm.py @@ -88,41 +88,35 @@ class Comm: self.write(data + b'\n') def interact(self): - ilog("<--Interact Mode-->") stdin = sys.stdin.buffer - os.set_blocking(self.back.stdin.fileno(), False) - os.set_blocking(stdin.fileno(), False) - poll = select.poll() - poll.register(self.back.stdin, select.POLLIN) - poll.register(stdin, select.POLLIN) - brk = False - def readall(read, write): - while(True): - data = read() - if(data == b''): - break - write(data) - def writeinput(write): - ilog(write, file=sys.stdout, color=NORMAL) + event = select.POLLIN + + def readall_stdin(): + for line in stdin: + self.write(line) + readtable = { - stdin.fileno() : lambda : readall(stdin.readline, self.write), - self.back.stdin.fileno() : lambda : readall(self.back.stdin.readline, writeinput) + self.back.stdin.fileno(): self.readall_nonblock, + stdin.fileno(): readall_stdin, } - readtable[self.back.stdin.fileno()]() - while(not brk): - try: - ioevents = poll.poll(100) - for ev in ioevents: - if(ev[1] & select.POLLIN): - readtable[ev[0]]() - else: - brk = True - break - except KeyboardInterrupt: - break - os.set_blocking(self.back.stdin.fileno(), True) - os.set_blocking(stdin.fileno(), True) - ilog("<--Interact Mode Done-->") + + try: + ilog("<--Interact Mode-->") + os.set_blocking(stdin.fileno(), False) + + poll = select.poll() + poll.register(self.back.stdin, event) + poll.register(stdin, event) + + while True: + for fd, e in poll.poll(self.timeout): + if not e & event: return + readtable[fd]() + except KeyboardInterrupt: + pass + finally: + os.set_blocking(stdin.fileno(), True) + ilog("<--Interact Mode Done-->") def popen(cmdline=''): io = Comm((Process(cmdline.split()) if len(cmdline) > 0 else Pipes())) -- cgit v1.2.3