diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1264ec12..867ed127 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index a9e7bb03..1e147565 100644
--- a/README.md
+++ b/README.md
@@ -134,7 +134,7 @@
-> [1] mypy type coverage (fully typed: 94.37%)
+> [1] mypy type coverage (fully typed: 94.30%)
> [2] Windows builds are currently only failing, because they are simply stuck on GitHub actions.
@@ -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: | ✔ |
@@ -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.
diff --git a/bin/pwncat b/bin/pwncat
index 2f9dd699..3c002167 100755
--- a/bin/pwncat
+++ b/bin/pwncat
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
)
@@ -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
@@ -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
@@ -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(
@@ -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
@@ -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
@@ -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
@@ -1593,7 +1641,7 @@ 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:
@@ -1601,16 +1649,26 @@ class Sock(object):
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
@@ -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])
@@ -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,
)
@@ -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,
)
@@ -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()
@@ -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",
@@ -3793,6 +3895,8 @@ def main():
args.nodns,
args.ipv4,
args.ipv6,
+ args.source_addr,
+ args.source_port,
args.udp,
args.tos,
args.info,
diff --git a/docs/index.html b/docs/index.html
index 159445fc..ffacedfc 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -309,6 +309,8 @@ Usage
-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.
diff --git a/docs/pwncat.api.html b/docs/pwncat.api.html
index ecf952f6..42eec857 100644
--- a/docs/pwncat.api.html
+++ b/docs/pwncat.api.html
@@ -139,7 +139,7 @@ Module pwncat
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
@@ -332,6 +332,18 @@ Module pwncat
"""`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
@@ -353,14 +365,29 @@ Module pwncat
# --------------------------------------------------------------------------
# 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
@@ -369,6 +396,8 @@ Module pwncat
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
@@ -401,6 +430,8 @@ Module pwncat
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
@@ -409,7 +440,7 @@ Module pwncat
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
)
@@ -1110,7 +1141,7 @@ Module pwncat
"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
@@ -1119,7 +1150,7 @@ Module pwncat
"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
@@ -1171,21 +1202,26 @@ Module pwncat
# [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(
@@ -1196,13 +1232,15 @@ Module pwncat
# 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
@@ -1248,28 +1286,38 @@ Module pwncat
# [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
@@ -1535,12 +1583,12 @@ Module pwncat
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
@@ -1620,7 +1668,7 @@ Module pwncat
# 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:
@@ -1628,16 +1676,26 @@ Module pwncat
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
@@ -1686,7 +1744,7 @@ Module pwncat
"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])
@@ -1704,15 +1762,22 @@ Module pwncat
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,
)
@@ -1739,11 +1804,15 @@ Module pwncat
)
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,
)
@@ -3223,6 +3292,23 @@ Module pwncat
)
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()
@@ -3447,6 +3533,22 @@ Module pwncat
'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",
@@ -3820,6 +3922,8 @@ Module pwncat
args.nodns,
args.ipv4,
args.ipv6,
+ args.source_addr,
+ args.source_port,
args.udp,
args.tos,
args.info,
@@ -4201,6 +4305,22 @@
'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",
@@ -4589,6 +4709,8 @@
args.nodns,
args.ipv4,
args.ipv6,
+ args.source_addr,
+ args.source_port,
args.udp,
args.tos,
args.info,
@@ -5704,7 +5826,7 @@ Instance variables
class DsIONetworkSock
-(bufsize, backlog, recv_timeout, recv_timeout_retry, nodns, ipv4, ipv6, udp, ip_tos, info)
+(bufsize, backlog, recv_timeout, recv_timeout_retry, nodns, ipv4, ipv6, src_addr, src_port, udp, ip_tos, info)
A type-safe data structure for IONetwork socket options.
@@ -5736,6 +5858,8 @@ Instance variables
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
@@ -5744,7 +5868,7 @@ Instance variables
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
)
Ancestors
@@ -5780,6 +5904,8 @@ Inherited members
ipv6
nodns
recv_timeout
+src_addr
+src_port
udp
@@ -6257,7 +6383,7 @@ Instance variables
class DsSock
-(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)
A type-safe data structure for DsSock options.
@@ -6307,6 +6433,18 @@ Instance variables
"""`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
@@ -6328,14 +6466,29 @@ Instance variables
# --------------------------------------------------------------------------
# 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
@@ -6344,6 +6497,8 @@ Instance variables
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
@@ -6466,6 +6621,34 @@ Instance variables
return self.__recv_timeout
+var src_addr
+
+bool
: Custom source address for connect mode.
+
+
+Expand source code
+
+@property
+def src_addr(self):
+ # type: () -> Optional[str]
+ """`bool`: Custom source address for connect mode."""
+ return self.__src_addr
+
+
+var src_port
+
+bool
: Custom source port for connect mode.
+
+
+Expand source code
+
+@property
+def src_port(self):
+ # type: () -> Optional[int]
+ """`bool`: Custom source port for connect mode."""
+ return self.__src_port
+
+
var udp
bool
: Determines if we use TCP or UDP.
@@ -8501,7 +8684,7 @@ Args
"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
@@ -8510,7 +8693,7 @@ Args
"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
@@ -8562,21 +8745,26 @@ Args
# [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(
@@ -8587,13 +8775,15 @@ Args
# 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
@@ -8639,28 +8829,38 @@ Args
# [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
@@ -8926,12 +9126,12 @@ Args
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
@@ -9011,7 +9211,7 @@ Args
# 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:
@@ -9019,16 +9219,26 @@ Args
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
@@ -9077,7 +9287,7 @@ Args
"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])
@@ -9095,15 +9305,22 @@ Args
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,
)
@@ -9130,11 +9347,15 @@ Args
)
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,
)
@@ -9384,7 +9605,7 @@ Raises
"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
@@ -9393,7 +9614,7 @@ Raises
"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
@@ -9464,21 +9685,26 @@ Returns
# [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(
@@ -9489,13 +9715,15 @@ Returns
# 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
@@ -9563,28 +9791,38 @@ Returns
# [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
@@ -10403,6 +10641,8 @@
ipv6
nodns
recv_timeout
+src_addr
+src_port
udp
diff --git a/docs/pwncat.man.html b/docs/pwncat.man.html
index afb4d452..e8636105 100644
--- a/docs/pwncat.man.html
+++ b/docs/pwncat.man.html
@@ -200,6 +200,20 @@ DESCRIPTION
’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
diff --git a/docs/pwncat.type.html b/docs/pwncat.type.html
index b60ad95c..1c7cf034 100644
--- a/docs/pwncat.type.html
+++ b/docs/pwncat.type.html
@@ -14,13 +14,13 @@ Mypy Type Check Coverage Summary
Total |
-5.63% imprecise |
-3999 LOC |
+5.70% imprecise |
+4103 LOC |
bin/pwncat |
-5.63% imprecise |
-3999 LOC |
+5.70% imprecise |
+4103 LOC |