2020-11-15 20:08:43 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
import socket
|
|
|
|
import errno
|
|
|
|
import fcntl
|
|
|
|
import os
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
from rich.progress import BarColumn, Progress
|
|
|
|
|
|
|
|
from pwncat.channel import Channel, ChannelError, ChannelClosed
|
|
|
|
|
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
def connect_required(method):
|
|
|
|
def _wrapper(self, *args, **kwargs):
|
|
|
|
if not self.connected:
|
|
|
|
raise ChannelError(self, "channel not connected")
|
|
|
|
return method(self, *args, **kwargs)
|
|
|
|
|
|
|
|
return _wrapper
|
|
|
|
|
|
|
|
|
2020-11-15 20:08:43 +01:00
|
|
|
class Socket(Channel):
|
|
|
|
"""
|
|
|
|
Implements a channel which rides over a shell attached
|
|
|
|
directly to a socket. This channel takes an existing
|
|
|
|
socket as an argument, and allows pwncat to reuse
|
|
|
|
an existing connection.
|
|
|
|
"""
|
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
def __init__(self, client: socket.socket = None, **kwargs):
|
2020-11-15 20:08:43 +01:00
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
if client is not None:
|
|
|
|
# Report host and port number to base channel
|
|
|
|
host, port = client.getpeername()
|
2020-11-15 20:08:43 +01:00
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
if "host" not in kwargs:
|
|
|
|
kwargs["host"] = host
|
|
|
|
if "port" not in kwargs:
|
|
|
|
kwargs["port"] = port
|
2020-11-15 20:08:43 +01:00
|
|
|
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
self._connected = False
|
2020-11-15 20:08:43 +01:00
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
if client is not None:
|
|
|
|
self._socket_connected(client)
|
2020-11-15 20:08:43 +01:00
|
|
|
|
2021-02-15 05:32:05 +01:00
|
|
|
@property
|
|
|
|
def connected(self):
|
|
|
|
return self._connected
|
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
def _socket_connected(self, client: socket.socket):
|
|
|
|
"""Notify the channel that the socket is now connected.
|
|
|
|
This is mainly used for binding sockets where the initial
|
|
|
|
socket creation is only the server, and the client is
|
|
|
|
connected during the ``connect`` method."""
|
|
|
|
|
|
|
|
self._connected = True
|
|
|
|
self.client = client
|
|
|
|
self.address = client.getpeername()
|
|
|
|
|
|
|
|
self.client.setblocking(False)
|
|
|
|
fcntl.fcntl(self.client, fcntl.F_SETFL, os.O_NONBLOCK)
|
|
|
|
|
|
|
|
@connect_required
|
2020-11-15 20:08:43 +01:00
|
|
|
def send(self, data: bytes):
|
2021-02-15 05:32:05 +01:00
|
|
|
"""Send data to the remote shell. This is a blocking call
|
|
|
|
that only returns after all data is sent."""
|
2020-11-15 20:08:43 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
written = 0
|
|
|
|
while written < len(data):
|
|
|
|
try:
|
|
|
|
written += self.client.send(data[written:])
|
|
|
|
except BlockingIOError:
|
|
|
|
pass
|
|
|
|
except BrokenPipeError as exc:
|
2021-02-15 05:32:05 +01:00
|
|
|
self._connected = False
|
2020-11-15 20:08:43 +01:00
|
|
|
raise ChannelClosed(self) from exc
|
|
|
|
|
|
|
|
return len(data)
|
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
@connect_required
|
2020-11-15 20:08:43 +01:00
|
|
|
def recv(self, count: Optional[int] = None) -> bytes:
|
2021-04-10 21:52:47 +02:00
|
|
|
"""Basic socket recv wrapper. This also uses the default
|
|
|
|
``peek`` implementation. This could be optimized to use
|
|
|
|
the socket native ``peek`` method.
|
2020-11-15 20:08:43 +01:00
|
|
|
|
|
|
|
:param count: maximum number of bytes to receive (default: unlimited)
|
|
|
|
:type count: int
|
|
|
|
:return: the data that was received
|
|
|
|
:rtype: bytes
|
|
|
|
"""
|
|
|
|
|
|
|
|
if self.peek_buffer:
|
|
|
|
data = self.peek_buffer[:count]
|
|
|
|
self.peek_buffer = self.peek_buffer[len(data) :]
|
|
|
|
count -= len(data)
|
|
|
|
else:
|
|
|
|
data = b""
|
|
|
|
|
|
|
|
try:
|
|
|
|
return data + self.client.recv(count)
|
|
|
|
except socket.error as exc:
|
|
|
|
if exc.args[0] == errno.EAGAIN or exc.args[0] == errno.EWOULDBLOCK:
|
|
|
|
return data
|
|
|
|
|
2021-02-15 05:32:05 +01:00
|
|
|
self._connected = False
|
2020-11-15 20:08:43 +01:00
|
|
|
raise ChannelClosed(self) from exc
|
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
@connect_required
|
2021-02-15 05:32:05 +01:00
|
|
|
def close(self):
|
|
|
|
self._connected = False
|
|
|
|
self.client.close()
|
|
|
|
|
2021-04-10 21:52:47 +02:00
|
|
|
@connect_required
|
2020-11-15 20:08:43 +01:00
|
|
|
def fileno(self):
|
|
|
|
return self.client.fileno()
|