Skip to content

Commit

Permalink
Merge pull request #76 from cytopia/release-0.0.20
Browse files Browse the repository at this point in the history
Release 0.0.20
  • Loading branch information
cytopia authored May 21, 2020
2 parents 7e63b3e + 5afe72e commit cdc067c
Show file tree
Hide file tree
Showing 10 changed files with 714 additions and 120 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
## Unreleased


## Release 0.0.20-alpha

### Added
- Feature: Be able to specify source address and port for clients: #66


## Release 0.0.19-alpha

### Added
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
</tbody>
<table>

> <sup>[1] <a href="https://cytopia.github.io/pwncat/pwncat.type.html">mypy type coverage</a> <strong>(fully typed: 94.37%)</strong></sup><br/>
> <sup>[1] <a href="https://cytopia.github.io/pwncat/pwncat.type.html">mypy type coverage</a> <strong>(fully typed: 94.30%)</strong></sup><br/>
> <sup>[2] Windows builds are currently only failing, because they are simply stuck on GitHub actions.</sup>

Expand Down Expand Up @@ -257,6 +257,7 @@ pwncat -R 10.0.0.1:4444 everythingcli.org 3306 -u
| IPv4 ||||
| IPv6 ||||
| Unix domain sockets | :x: |||
| Socket source bind ||||
| TCP ||||
| UDP ||||
| SCTP | :x: | :x: ||
Expand Down Expand Up @@ -414,6 +415,8 @@ optional arguments:
-T str, --tos str Specifies IP Type of Service (ToS) for the connection.
Valid values are the tokens 'mincost', 'lowcost',
'reliability', 'throughput' or 'lowdelay'.
--source-addr addr Specify the source IP address of the interface for connect mode.
--source-port port Specify the source port for connect mode.
-v, --verbose Be verbose and print info to stderr. Use -v, -vv, -vvv
or -vvvv for more verbosity. The server performance will
decrease drastically if you use more than three times.
Expand Down
154 changes: 129 additions & 25 deletions bin/pwncat
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ if os.environ.get("MYPY_CHECK", False):

APPNAME = "pwncat"
APPREPO = "https://github.com/cytopia/pwncat"
VERSION = "0.0.19-alpha"
VERSION = "0.0.20-alpha"

# Default timeout for timeout-based sys.stdin and socket.recv
TIMEOUT_READ_STDIN = 0.1
Expand Down Expand Up @@ -305,6 +305,18 @@ class DsSock(object):
"""`bool`: Only use IPv6 instead of both, IPv4 and IPv6."""
return self.__ipv6

@property
def src_addr(self):
# type: () -> Optional[str]
"""`bool`: Custom source address for connect mode."""
return self.__src_addr

@property
def src_port(self):
# type: () -> Optional[int]
"""`bool`: Custom source port for connect mode."""
return self.__src_port

@property
def udp(self):
# type: () -> bool
Expand All @@ -326,14 +338,29 @@ class DsSock(object):
# --------------------------------------------------------------------------
# Constructor
# --------------------------------------------------------------------------
def __init__(self, bufsize, backlog, recv_timeout, nodns, ipv4, ipv6, udp, ip_tos, info):
# type: (int, int, Optional[float], bool, bool, bool, bool, Optional[str], str) -> None
def __init__(
self,
bufsize, # type: int
backlog, # type: int
recv_timeout, # type: Optional[float]
nodns, # type: bool
ipv4, # type: bool
ipv6, # type: bool
src_addr, # type: Optional[str]
src_port, # type: Optional[int]
udp, # type: bool
ip_tos, # type: Optional[str]
info, # type: str
):
# type: (...) -> None
assert type(bufsize) is int, type(bufsize)
assert type(backlog) is int, type(backlog)
assert type(recv_timeout) is float, type(recv_timeout)
assert type(nodns) is bool, type(nodns)
assert type(ipv4) is bool, type(ipv4)
assert type(ipv6) is bool, type(ipv6)
assert type(src_addr) is str or src_addr is None, type(src_addr)
assert type(src_port) is int or src_port is None, type(src_port)
assert type(udp) is bool, type(udp)
assert type(info) is str, type(info)
self.__bufsize = bufsize
Expand All @@ -342,6 +369,8 @@ class DsSock(object):
self.__nodns = nodns
self.__ipv4 = ipv4
self.__ipv6 = ipv6
self.__src_addr = src_addr
self.__src_port = src_port
self.__udp = udp
self.__ip_tos = ip_tos
self.__info = info
Expand Down Expand Up @@ -374,6 +403,8 @@ class DsIONetworkSock(DsSock):
nodns, # type: bool
ipv4, # type: bool
ipv6, # type: bool
src_addr, # type: Optional[str]
src_port, # type: Optional[bool]
udp, # type: bool
ip_tos, # type: Optional[str]
info, # type: str
Expand All @@ -382,7 +413,7 @@ class DsIONetworkSock(DsSock):
assert type(recv_timeout_retry) is int, type(recv_timeout_retry)
self.__recv_timeout_retry = recv_timeout_retry
super(DsIONetworkSock, self).__init__(
bufsize, backlog, recv_timeout, nodns, ipv4, ipv6, udp, ip_tos, info
bufsize, backlog, recv_timeout, nodns, ipv4, ipv6, src_addr, src_port, udp, ip_tos, info
)


Expand Down Expand Up @@ -1083,7 +1114,7 @@ class Sock(object):
"Client connected: %s:%d (family %d/%s, UDP)",
addr[0],
addr[1],
conn.family,
int(conn.family),
self.get_af_name(conn.family),
)
# A different UDP client connects
Expand All @@ -1092,7 +1123,7 @@ class Sock(object):
"New client connected: %s:%d (family %d/%s, UDP)",
addr[0],
addr[1],
conn.family,
int(conn.family),
self.get_af_name(conn.family),
)
# Set currently active UDP connection socket
Expand Down Expand Up @@ -1144,21 +1175,26 @@ class Sock(object):

# [2/4] Resolve address
remove = []
errors = []
for family in conns:
try:
conns[family]["remote_host"] = host
conns[family]["remote_addr"] = self.__gethostbyname(host, family)
conns[family]["remote_port"] = port
except socket.gaierror:
except socket.gaierror as err:
remove.append(family)
errors.append(str(err))
for family in remove:
self.__close(self.get_af_name(family), conns[family]["conn"])
del conns[family]
if not conns:
for error in errors:
self.__log.error("Resolve Error: %s", error)
return False

# [3/4] Connect
remove = []
errors = []
for family in conns:
try:
self.__connect(
Expand All @@ -1169,13 +1205,15 @@ class Sock(object):
# On successful connect, we can abandon/remove all other sockets
remove = [key for key in conns if key != family]
break
except (OSError, socket.error):
# self.__log.error(error)
except (OSError, socket.error) as err:
remove.append(family)
errors.append(str(err))
for family in remove:
self.__close(self.get_af_name(family), conns[family]["conn"])
del conns[family]
if not conns:
for error in errors:
self.__log.error(error)
return False

# [4/4] Store connections and set active connection
Expand Down Expand Up @@ -1221,28 +1259,38 @@ class Sock(object):

# [2/4] Resolve local address
remove = []
errors = []
for family in conns:
try:
conns[family]["local_addr"] = self.__gethostbyname(host, family)
conns[family]["local_host"] = host
conns[family]["local_port"] = port
except socket.gaierror:
except socket.gaierror as err:
remove.append(family)
errors.append(str(err))
for family in remove:
self.__close(self.get_af_name(family), conns[family]["sock"])
del conns[family]
if not conns:
for error in errors:
self.__log.error("Resolve Error: %s", error)
return False

# [3/4] Bind socket
remove = []
errors = []
for family in conns:
if not self.__bind(conns[family]["sock"], conns[family]["local_addr"], port):
try:
self.__bind(conns[family]["sock"], conns[family]["local_addr"], port)
except socket.error as err:
remove.append(family)
errors.append(str(err))
for family in remove:
self.__close(self.get_af_name(family), conns[family]["sock"])
del conns[family]
if not conns:
for error in errors:
self.__log.error(error)
return False

# [UDP 4/4] There is no listen or accept for UDP
Expand Down Expand Up @@ -1508,12 +1556,12 @@ class Sock(object):

if self.__options.nodns:
flags = socket.AI_NUMERICHOST

self.__log.debug("Resolving hostname: %s", host)
try:
self.__log.debug("Resolving hostname: %s", host)
infos = socket.getaddrinfo(host, port, family, socktype, proto, flags)
addr = str(infos[0][4][0])
except (AttributeError, socket.gaierror) as error:
self.__log.error("Resolve Error: %s", error)
raise socket.gaierror(error) # type: ignore
self.__log.debug("Resolved hostname: %s", addr)
return addr
Expand Down Expand Up @@ -1593,24 +1641,34 @@ class Sock(object):
# Private Functions (server)
# --------------------------------------------------------------------------
def __bind(self, sock, addr, port):
# type: (socket.socket, str, int) -> bool
# type: (socket.socket, str, int) -> None
"""Bind the socket to an address.
Args:
sock (socket.socket): The socket to bind.
addr (str): The numerical IP address to bind to.
port (int): The port to bind to.
Returns:
bool: Returns `True` on success and `False` on Failure.
Raises:
socket.error if socket cannot be bound.
"""
sock_family_name = self.get_af_name(sock.family)
sock_type_name = self.get_st_name(sock.type)
self.__log.debug(
"Binding (family %d/%s, %s) socket to %s:%d",
int(sock.family),
sock_family_name,
sock_type_name,
addr,
port,
)
try:
self.__log.debug("Binding socket to %s:%d", addr, port)
sock.bind((addr, port))
return True
except (OverflowError, OSError, socket.error) as error:
self.__log.error("Binding socket to %s:%d failed: %s", addr, port, error)
return False
except (OverflowError, OSError, socket.gaierror, socket.error) as error:
msg = "Binding (family {}/{}, {}) socket to {}:{} failed: {}".format(
sock.family, sock_family_name, sock_type_name, addr, port, error
)
raise socket.error(msg)

def __listen(self, sock):
# type: (socket.socket) -> bool
Expand Down Expand Up @@ -1659,7 +1717,7 @@ class Sock(object):
"Client connected from %s:%d (family %d/%s, TCP)",
addr[0],
addr[1],
conn.family,
int(conn.family),
self.get_af_name(conn.family),
)
return conn, (addr[0], addr[1])
Expand All @@ -1677,15 +1735,22 @@ class Sock(object):
port (int): Port of server to connect to.
Raises:
socker.error: If client cannot connect to remote peer.
socker.error: If client cannot connect to remote peer or custom bind did not succeed.
"""
sock_family_name = self.get_af_name(sock.family)
sock_type_name = self.get_st_name(sock.type)
# Bind to a custom addr/port
if self.__options.src_addr is not None and self.__options.src_port is not None:
try:
self.__bind(sock, self.__options.src_addr, self.__options.src_port)
except socket.error as error:
raise socket.error(error)

self.__log.debug(
"Connecting to %s:%d (family %d/%s, %s)",
addr,
port,
sock.family,
int(sock.family),
sock_family_name,
sock_type_name,
)
Expand All @@ -1712,11 +1777,15 @@ class Sock(object):
)
raise socket.error(msg)

local = sock.getsockname()
self.__log.debug(
"Connected from %s:%d", local[0], local[1],
)
self.__log.info(
"Connected to %s:%d (family %d/%s, %s)",
addr,
port,
sock.family,
int(sock.family),
sock_family_name,
sock_type_name,
)
Expand Down Expand Up @@ -3196,6 +3265,23 @@ def _args_check_mutually_exclusive(parser, args):
)
sys.exit(1)

# [OPTIONS] -s/-p
if not connect_mode and (args.source_port or args.source_addr):
print(
"%s: error: --source-addr and --source-port can only be used in connect mode."
% (APPNAME),
file=sys.stderr,
)
sys.exit(1)

# [OPTIONS] -s/-p
if (args.source_port and not args.source_addr) or (not args.source_port and args.source_addr):
print(
"%s: error: --source-addr and --source-port are both required." % (APPNAME),
file=sys.stderr,
)
sys.exit(1)

# [ADVANCED] --http
if args.http and (args.https or args.udp or args.zero):
parser.print_usage()
Expand Down Expand Up @@ -3420,6 +3506,22 @@ Valid values are the tokens 'mincost', 'lowcost',
'reliability', 'throughput' or 'lowdelay'.
""",
)
optional.add_argument(
"--source-addr",
metavar="addr",
dest="source_addr",
type=str,
default=None,
help="Specify the source IP address of the interface for connect mode.",
)
optional.add_argument(
"--source-port",
metavar="port",
dest="source_port",
type=_args_check_port,
default=None,
help="Specify the source port for connect mode.",
)
optional.add_argument(
"-v",
"--verbose",
Expand Down Expand Up @@ -3793,6 +3895,8 @@ def main():
args.nodns,
args.ipv4,
args.ipv6,
args.source_addr,
args.source_port,
args.udp,
args.tos,
args.info,
Expand Down
Loading

0 comments on commit cdc067c

Please sign in to comment.