A note on fgets()
: This is what I observed stepping through the debugger. When piped data is sent to fgets()
it's buffered on the heap, then it's transferred to the stack one by one until
A compare instruction returns true on a counter and the value that's passed in the RSI register to the fgets()
function; which is the string length specified by the programmer.
Or receipt of an 0x0a byte ("\n")
Once either of those are met, the function exits and STDIN is torn down. So it's buffered I/O and not actual I/O to a file descriptor that's used to communicate with the OS.
Anyways, to manage stdin like you're doing there with bash in Python, you can use Subprocess's Popen
function to connect directly to the service, but there's a catch. Python blocks SIGPIPE by default, which prevents keeping the fd open for writing. You can overcome this using the signals module and creating a patch function that overrides Subprocesses configuration to allow SIGPIP:
import signals
def restore_signals():
signals = ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ')
for sig in signals:
if hasattr(signal, sig):
signal.signal(getattr(signal, sig), signal.SIG_DFL)
Then, call your program directly (without piping) via a Subproccess shell passing the SIGPIP patch through preexec_fn
arg. I also specified bash as the shell I wanted to use, but it's not required:
myproc = Popen("./test",
stdin=PIPE,
stdout=PIPE,
shell=True,
executable='/bin/bash',
preexec_fn=**restore_signals()**)
This will keep a pipe open and you'll just have to manually close it. I assume from an exploit dev standpoint, it won't really matter for you.
To send data, don't use the .communicate()
method. You'll have to manually manage the connection sending your shellcode bytes like so:
#prep the receive buffer by clearing it first
myproc.stdout.flush()
#Send your shellcode where shellcode is a var holding your shellcode
myproc.stdin.write(shellcode+"\n")
Note the "\n" to simulate enter so fgets()
knows to end the buffer.
Then, to read output, you probably won't want to use the .readline()
method unless you know the output (like a banner) will end with a newline byte (0x0a / '\n'). You'll have to manually manage it using the .read()
method.
The thing with .read()
is you'll need to have some sense of the data that is returned. So if you're expecting a return of something like a memory leak or string and know the size, you're going to want to specify that so your script doesn't block while waiting for more data. I can't remember what the default buffer size on read is, but it won't return until that buffer is filled. .readline()
will return on a 0x0a byte. So, if you're expecting a 10 byte string:
#sleep 1 second after your shellcode send to give the process time
time.sleep(1)
output = myproc.stdout.read(10)
Keep in mind that if you're sending multiple lines, you're going to want to flush()
the stdout buffer each time, or your response will read 10 bytes from the previously received data.
Repeat as necessary. Hope that helps.