Age | Commit message (Collapse) | Author | Files | Lines |
|
This is done to help clean the top-level "sploit" package. Furthermore,
there is some planned future work to refactor comm into multiple
modules, so this lays some groundwork for that.
Signed-off-by: Malfurious <m@lfurio.us>
|
|
Signed-off-by: Malfurious <m@lfurio.us>
|
|
We should strip the newline from the data after checking if we got an
empty string returned.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
A read of 0 isn't particularly useful, but it is weird that it will
cause a BrokenPipeError. Instead, it makes more sense to just return an
empty string.
A read of <0 would normally read until EOF, but we already have that
feature in readall() and it wouldn't be particularly useful here. A
similar functionality of reading the entire current contents of the
buffer is useful, though. This is already implemented in
readall_nonblock() and this would be a nice user-facing way of calling
that.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
In interact(), we set stdin to be nonblocking for the duration of the
function. As an unexpected side-effect, this was setting stdout to be
nonblocking as well. This has caused at least one crash in the past.
Localizing the nonblock to just when we're reading from stdin should
solve this.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
We had originally decided to use the os.read() function instead of the
actual buffered file object's read function. This was due to the
blocking behavior or os.read() being closer to POSIX read than the other
function.
As it turns out, os.read() is an unbuffered read. Every other read call
in this interface is buffered. This causes some undefined behavior in
certain cases and leads to some really confusing bugs.
After some discussion, we've decided that, in this application's domain,
the blocking behavior of the buffered file object's read is actually
often more useful anyways. Changing this call will deal with both
issues.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
This behavior was accidentally removed in dcba5f2
interact mode works by polling for IO events, but it will miss any
unread data already in the buffer when it is first entered. We can
ensure this gets caught by just doing a read once at the beginning.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Line-oriented reads now strip the newline from the end of their returned
string. Additionally, readall() strips the newline, but only from the
string that gets logged to the user's terminal (goodbye to all the "\n"
printed at the end of each line).
Of course, these functions are called by other parts of the read API and
have downstream effects. Consideration was given to the entire API with
these rules in mind:
- Raw reads (or non-line-oriented reads) will not filter ANY of
their read content. They are logged to the screen as one "line"
of log text with \n characters shown in-place (not actually
resetting the terminal cursor). If reading binary, these bytes
dont actually mean line termination anyway.
functions: read, readall(_nonblock) *, readuntil
- Line-oriented reads will strip the terminating \n, log the single
line to the screen, and return it.
functions: readline, readlineuntil **
* readall(_nonblock) functions turn out to be a special case. They will
operate as raw reads, returning a blob of content. However, we
generally want to run them on line-oriented input, so they log according
to the line-oriented rules.
** Although content returned from readlineuntil will have \n's stripped,
the lines are returned in an array, so we can still distinguish them.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
The writeline function will now default to send an empty line when
called without an argument. I don't believe any such default makes
sense for the plain write function, as writing nothing should have no
effect.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
This is normally not an issue, since logonread defaults to True.
However, if the user disables this setting, interact() becomes a lot
less useful. logonread is now forced on during io.interact(), but
respected through the rest of the API.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Failure to close target stdout is not interesting. Furthermore, if
sploit ever gets into this situation, the user script has likely already
raised a more useful error/backtrace. Handling this exception typically
results in a duplicate error.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Due to line buffering, we may often trigger a burst of data to be sent
by the target, but resolve the non-blocking read only after the first
line is received. We would like to wait just a little longer to receive
the entire burst instead.
readall_nonblock() will now reset its timeout period whenever any data
becomes readable and will not return until we go an entire period of
silence. Under normal conditions, the full duration of readall_nonblock
should barely be any longer than the defined period itself.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
select's poll.poll() function expects its timeout argument to be in
milliseconds. This is an artifact from earlier developent where we were
using the higher-level 'selectors' API, which never got merged.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
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 <howcansocksbereal@gmail.com>
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
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 <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
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 <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
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 <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
A new function, Comm.shutdown(), is added. It will close only the
stdout stream of the communications backend, potentially making the
termination of the target program more fluid.
The name 'shutdown' is chosen to emulate shutdown(2) from the low-level
socket api, which is used to close just part of a full-duplex file
descriptor. This is in contrast to 'close', which I would expect to
completely terminate the given object IO.
comm.shutdown() is now called by main.py, after the user script returns,
to ensure that the subsequent readall() doesn't get stuck because our
target is blocked reading its stdin.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
If execution is stuck inside readall() (for example, due to blocked IO),
handling KeyboardInterrupt allows the user a way to get out, without
exiting the active script early or losing the data read so far.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
This function has a momentary side-effect of switching self.logonread to
False. This patch ensures its original value is always restored, even
if an exception is raised.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
If enabled, data sent to the target will be printed/logged as alt text,
similar to data directly printed by the user. Feature is off by
default.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
This function will no longer mistakenly log data when logonread is set
to False.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
The log module is updated to support binary encodings, colors, and for
improved compatibility with Python's print() builtin.
Encoding semantics are switched up, since it seems like some of the more
interesting encoding modes (from a CTF perspective) actually use
bytes-like objects as their high-level form (that is, bytes are encoded
to another form, such as hex, then decoded back to the original form).
So the logged value is now passed to encode instead of decode, and only
if the object is of type 'bytes', as unicode strings are now considered
out-of-scope for this operation. Additionally, the bytes wrapper (b'')
is no longer visible in the logged content.
For readability, several standard colors have been defined for use
within Sploit:
- RED: Errors
- YELLOW: Warnings
- GREEN: Status messages / Startup messages
- WHITE: Target output
- GRAY: User output / Alt text
Logging functions now support an optional color option to select the
desired color, and have specific defaults based on who is invoking the
log (see below...)
Logging functions are now also fully compatible with the builtin print()
function. This is because Sploit now replaces the standard print() with
a logging function within the user's script (which is done to maintain
additional consistency of messages displayed in the console).
Function ilog (internal log) has default values tuned for the library's
convenience: Text goes to stderr, and is presented as status messages
(green).
Function elog (external log) has default values tuned for the user: Text
goes to stdout, and is presented as alt text to distinguish it from data
read from the target. Within the user context, 'print' refers to this
function.
Signed-off-by: Malfurious <m@lfurio.us>
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
logonread can enable/disable logging the result of every read
flushonwrite can enable/disable automatically flushing every write
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
readuntil() and readlineuntil() will now automatically bind() a
predicate and given arguments to produce the single function predicate
required.
The 'until' module will provide convenience utilities for use with
readuntil() and readlineuntil(). For now, it contains functools.partial
renamed as bind(), lastline() which can call a predicate with the last
element of the array of lines given from readlineuntil(), and simplified
versions of re.search and re.fullmatch renamed as contains and equals.
These allow us to write powerful and legible statements like:
comm.readlineuntil(lastline,contains,b'Enter')
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Instead of only operating on and returning the last line read,
readlineuntil() will now check the predicate against an array of all
lines read and return that array when the predicate is true.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
The BufferedReader's .read() doesn't behave as expected. It reads
EXACTLY size bytes and will block until there are enough available to
read.
os.read() does what we expect. It will read UP TO size bytes and only
block if there is nothing available to read.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
With the "read rest of output" code in the Comm destructor, it would
continue to read output even in situations where some error happened and
we expect sploit to die or when the user presses Ctrl+C to end sploit.
By moving it to the end of the script running code in main, it behaves
more intuitively.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Handle all of the edge cases when shutting down in Pipes mode.
e.g.
If the pipes are broken (tried to write after the program died)
If the fifos don't exist anymore (sometimes tempfile cleans them up
before the destructor finishes when certain errors happen)
If the object attributes for the streams and fifo paths aren't set (this
can happen if the constructor didn't finish. e.g. the user cancels while
waiting on a connection)
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
If we need to wait on the target program to die, we don't want to just
wait forever with no indication to the user. Instead, only call wait if
the program is still alive, inform the user that we are doing this, and
give them the ability to forcefully kill the target program with Ctrl+C.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Previously, you could specify a directory which must exist under /tmp.
Now, you can give the full path to a directory to be used by Pipes.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
comm.interact() will drop the user into an "interactive" mode where they
can directly control what is sent. A SIGINT (Ctrl+C) will drop the
script out of interactive mode and continue executing the rest of the
script. If the output of the program (input into our script) goes into
a broken state (such as when the target program exits), interactive mode
will automatically exit.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
Both new functions check the input for a predicate and keep reading
until the predicate is true.
readuntil() will consume input byte by byte and use the entire string
read to check the predicate. It will then return that entire string.
readlineuntil() consumes input line by line and only uses the last line
to check the predicate. The line that satisfies the predicate is all
that is returned.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|
|
First part of the MVP for the larger Sploit rework effort.
Add project structure, python packaging, basic comms, and "log" hook.
From in or out of the sploit directory, you can run the "sploit.py"
script, run python -m sploit, or import the sploit modules from the
python3 shell.
You can also pip install Sploit and from anywhere you can run the sploit
command, run python -m sploit, or import the sploit modules from the
python3 shell.
Running as a standalone application, Sploit can run in a "target" mode,
a "pipe" mode, and a "pipe daemon" mode. In "target" mode, Sploit will
launch a target program as a subprocess and run an exploit script
against its I/O. In "pipe" mode, Sploit will create named fifos and
wait for a program to connect to them to run an exploit script against
them. In "pipe daemon" mode, Sploit will run similar to the "pipe" mode,
but automatically recreate the fifos with the same name after each
execution.
Basic comm operations of read, readline, write, and writeline are
available to the exploit script.
A "log" hook is executed whenever data is read in from the target
program. This will just print the data out, but it can be configured to
decode it with a specific encoding or you could replace the function for
different behavior.
Signed-off-by: dusoleil <howcansocksbereal@gmail.com>
|