mirror of
https://github.com/Gallopsled/pwntools.git
synced 2026-10-12 02:02:27 +00:00
* Add Type Annotations for parts of pwnlib/util * fix: use Callable in pwnlib/util/lists.py * fix: Import Errors * fix: use getLogger in pwnlib/util/net * feat: use collections.abc.Buffer where appropriate * fix: remove collections.abc.Buffer to use Sequence * add CHANGELOG.md entry * Improve packing and fiddling types * Introduce own `BytesLike` type instead of 3.12 Buffer * Improve web.wget types * Use TypedDict in util.net Specify generics properly. * Improve pwnlib.proc types * Improve pwnlib.util.lists types * Fix util.splash types * Make TypeVars private * Improve util.hexdump types * Remove partial types from packing.fit/flat Those require more work to be useful. * Fix implicit wget return * Fix term.text type export * Fix js_escape return type * Fix sh_string doctest types * Fix usage of ssh_channel as ssh_process We access the pid, so a plain ssh_channel is not enough. * Improve util.lists types * Improve packing.dd types * Fix relying on socket.AddressFamily type at runtime The type is only in typeshed and not the real socket module. Don't access it at runtime, only at type checking. * Fix lists.findall with string needle * Fix dd on two files * Update mypy-baseline.txt * Remove testing artifacts from mypy-baseline.txt --------- Co-authored-by: peace-maker <[email protected]>
298 lines
11 KiB
Python
298 lines
11 KiB
Python
from __future__ import annotations
|
|
import ctypes
|
|
import ctypes.util
|
|
import socket
|
|
from typing import TYPE_CHECKING, Callable, Literal, TypeAlias, TypedDict
|
|
|
|
if TYPE_CHECKING:
|
|
import psutil
|
|
from socket import AddressFamily, SocketKind
|
|
|
|
from pwnlib.log import getLogger
|
|
from pwnlib.util.packing import p16
|
|
from pwnlib.util.packing import p32
|
|
from pwnlib.util.packing import pack
|
|
|
|
log = getLogger(__name__)
|
|
|
|
__all__ = ['getifaddrs', 'interfaces', 'interfaces4', 'interfaces6', 'sockaddr']
|
|
|
|
# /usr/src/linux-headers-3.12-1-common/include/uapi/linux/socket.h
|
|
sa_family_t = ctypes.c_ushort
|
|
|
|
# /usr/src/linux-headers-3.12-1-common/include/linux/socket.h
|
|
class struct_sockaddr(ctypes.Structure):
|
|
_fields_ = [
|
|
('sa_family', sa_family_t) ,
|
|
('sa_data' , ctypes.c_char * 14),
|
|
]
|
|
|
|
# /usr/src/linux-headers-3.12-1-common/include/uapi/linux/in.h
|
|
struct_in_addr = ctypes.c_uint8 * 4
|
|
class struct_sockaddr_in(ctypes.Structure):
|
|
_fields_ = [
|
|
('sin_family', sa_family_t) ,
|
|
('sin_port' , ctypes.c_uint16),
|
|
('sin_addr' , struct_in_addr) ,
|
|
]
|
|
|
|
# /usr/src/linux-headers-3.12-1-common/include/uapi/linux/in6.h
|
|
struct_in6_addr = ctypes.c_uint8 * 16
|
|
class struct_sockaddr_in6(ctypes.Structure):
|
|
_fields_ = [
|
|
('sin6_family' , ctypes.c_ushort),
|
|
('sin6_port' , ctypes.c_uint16),
|
|
('sin6_flowinfo', ctypes.c_uint32),
|
|
('sin6_addr' , struct_in6_addr),
|
|
('sin6_scope_id', ctypes.c_uint32),
|
|
]
|
|
|
|
# /usr/include/ifaddrs.h
|
|
class union_ifa_ifu(ctypes.Union):
|
|
_fields_ = [
|
|
('ifu_broadaddr', ctypes.POINTER(struct_sockaddr)),
|
|
('ifu_dstaddr' , ctypes.POINTER(struct_sockaddr)),
|
|
]
|
|
class struct_ifaddrs(ctypes.Structure):
|
|
pass # recursively defined
|
|
struct_ifaddrs._fields_ = [
|
|
('ifa_next' , ctypes.POINTER(struct_ifaddrs)) ,
|
|
('ifa_name' , ctypes.c_char_p) ,
|
|
('ifa_flags' , ctypes.c_uint) ,
|
|
('ifa_addr' , ctypes.POINTER(struct_sockaddr)),
|
|
('ifa_netmask', ctypes.POINTER(struct_sockaddr)),
|
|
('ifa_ifu' , union_ifa_ifu) ,
|
|
('ifa_data' , ctypes.c_void_p) ,
|
|
]
|
|
|
|
class sockaddr_fix(TypedDict, total=False):
|
|
port: int
|
|
addr: str
|
|
flowinfo: int | None
|
|
scope_id: int | None
|
|
|
|
def sockaddr_fixup(saptr: ctypes._Pointer[struct_sockaddr]) -> tuple[AddressFamily, sockaddr_fix]:
|
|
family: AddressFamily = saptr.contents.sa_family
|
|
addr: sockaddr_fix = {}
|
|
if family == socket.AF_INET:
|
|
sa = ctypes.cast(saptr, ctypes.POINTER(struct_sockaddr_in)).contents
|
|
addr['port'] = socket.ntohs(sa.sin_port)
|
|
addr['addr'] = socket.inet_ntop(family, sa.sin_addr)
|
|
elif family == socket.AF_INET6:
|
|
sa = ctypes.cast(saptr, ctypes.POINTER(struct_sockaddr_in6)).contents
|
|
addr['port'] = socket.ntohs(sa.sin6_port)
|
|
addr['flowinfo'] = socket.ntohl(sa.sin6_flowinfo)
|
|
addr['addr'] = socket.inet_ntop(family, sa.sin6_addr)
|
|
addr['scope_id'] = sa.sin6_scope_id
|
|
return family, addr
|
|
|
|
class ifaddrs(TypedDict, total=False):
|
|
name: bytes
|
|
flags: int
|
|
family: AddressFamily | None
|
|
addr: sockaddr_fix | None
|
|
netmask: sockaddr_fix | None
|
|
|
|
def getifaddrs() -> list[ifaddrs]:
|
|
"""getifaddrs() -> dict list
|
|
|
|
A wrapper for libc's ``getifaddrs``.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Returns:
|
|
list of dictionaries each representing a `struct ifaddrs`. The
|
|
dictionaries have the fields `name`, `flags`, `family`, `addr` and
|
|
`netmask`. Refer to `getifaddrs(3)` for details. The fields `addr` and
|
|
`netmask` are themselves dictionaries. Their structure depend on
|
|
`family`. If `family` is not :const:`socket.AF_INET` or
|
|
:const:`socket.AF_INET6` they will be empty.
|
|
"""
|
|
libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
|
getifaddrs = libc.getifaddrs
|
|
getifaddrs.restype = ctypes.c_int
|
|
getifaddrs.argtypes = [ctypes.POINTER(ctypes.POINTER(struct_ifaddrs))]
|
|
freeifaddrs = libc.freeifaddrs
|
|
freeifaddrs.restype = None
|
|
freeifaddrs.argtypes = [ctypes.POINTER(struct_ifaddrs)]
|
|
ifaptr = ctypes.POINTER(struct_ifaddrs)()
|
|
result = getifaddrs(ctypes.pointer(ifaptr))
|
|
if result == -1:
|
|
raise OSError(ctypes.get_errno())
|
|
del result
|
|
try:
|
|
ifas = []
|
|
while ifaptr:
|
|
ifac = ifaptr.contents
|
|
ifa: ifaddrs = {'name' : ifac.ifa_name,
|
|
'flags': ifac.ifa_flags,
|
|
}
|
|
if ifac.ifa_addr:
|
|
ifa['family'], ifa['addr'] = sockaddr_fixup(ifac.ifa_addr)
|
|
else:
|
|
ifa['family'], ifa['addr'] = None, None
|
|
if ifac.ifa_netmask:
|
|
_, ifa['netmask'] = sockaddr_fixup(ifac.ifa_netmask)
|
|
else:
|
|
ifa['netmask'] = None
|
|
ifas.append(ifa)
|
|
ifaptr = ifac.ifa_next
|
|
return ifas
|
|
finally:
|
|
freeifaddrs(ifaptr)
|
|
|
|
def interfaces(all: bool = False) -> dict[bytes, list[tuple[AddressFamily, str]]]:
|
|
"""interfaces(all = False) -> dict
|
|
|
|
Arguments:
|
|
all (bool): Whether to include interfaces with not associated address.
|
|
Default: :const:`False`.
|
|
|
|
Returns:
|
|
A dictionary mapping each of the hosts interfaces to a list of it's
|
|
addresses. Each entry in the list is a tuple ``(family, addr)``, and
|
|
`family` is either :const:`socket.AF_INET` or :const:`socket.AF_INET6`.
|
|
"""
|
|
out: dict[bytes, list[tuple[AddressFamily, str]]] = {}
|
|
for ifa in getifaddrs():
|
|
name = ifa['name']
|
|
if name not in out:
|
|
out[name] = []
|
|
if not ifa['addr'] or not ifa['family']:
|
|
continue
|
|
family = ifa['family']
|
|
addr = ifa['addr']['addr']
|
|
out[name].append((family, addr))
|
|
if not all:
|
|
out = {k: v for k, v in out.items() if v}
|
|
return out
|
|
|
|
def interfaces4(all: bool = False) -> dict[bytes, list[str]]:
|
|
"""interfaces4(all = False) -> dict
|
|
|
|
As :func:`interfaces` but only includes IPv4 addresses and the lists in the
|
|
dictionary only contains the addresses not the family.
|
|
|
|
Arguments:
|
|
all (bool): Whether to include interfaces with not associated address.
|
|
Default: :const:`False`.
|
|
|
|
Returns:
|
|
A dictionary mapping each of the hosts interfaces to a list of it's
|
|
IPv4 addresses.
|
|
|
|
Examples:
|
|
|
|
>>> interfaces4(all=True) # doctest: +ELLIPSIS
|
|
{...'127.0.0.1'...}
|
|
"""
|
|
out: dict[bytes, list[str]] = {}
|
|
for name, addrs in interfaces(all = all).items():
|
|
addrs = [addr for fam, addr in addrs if fam == socket.AF_INET]
|
|
if addrs or all:
|
|
out[name] = addrs
|
|
return out
|
|
|
|
def interfaces6(all: bool = False) -> dict[bytes, list[str]]:
|
|
"""interfaces6(all = False) -> dict
|
|
|
|
As :func:`interfaces` but only includes IPv6 addresses and the lists in the
|
|
dictionary only contains the addresses not the family.
|
|
|
|
Arguments:
|
|
all (bool): Whether to include interfaces with not associated address.
|
|
Default: :const:`False`.
|
|
|
|
Returns:
|
|
A dictionary mapping each of the hosts interfaces to a list of it's
|
|
IPv6 addresses.
|
|
|
|
Examples:
|
|
|
|
>>> interfaces6() # doctest: +ELLIPSIS
|
|
{...'::1'...}
|
|
"""
|
|
out: dict[bytes, list[str]] = {}
|
|
for name, addrs in interfaces(all = all).items():
|
|
addrs = [addr for fam, addr in addrs if fam == socket.AF_INET6]
|
|
if addrs or all:
|
|
out[name] = addrs
|
|
return out
|
|
|
|
def sockaddr(host: str, port: int, network: str = 'ipv4') -> tuple[bytes, int, int]:
|
|
"""sockaddr(host, port, network = 'ipv4') -> (data, length, family)
|
|
|
|
Creates a sockaddr_in or sockaddr_in6 memory buffer for use in shellcode.
|
|
|
|
Arguments:
|
|
host (str): Either an IP address or a hostname to be looked up.
|
|
port (int): TCP/UDP port.
|
|
network (str): Either 'ipv4' or 'ipv6'.
|
|
|
|
Returns:
|
|
A tuple containing the sockaddr buffer, length, and the address family.
|
|
"""
|
|
address_family = {'ipv4':socket.AF_INET,'ipv6':socket.AF_INET6}[network]
|
|
|
|
for family, _, _, _, ip_tuple in socket.getaddrinfo(host, None, address_family):
|
|
if not isinstance(ip_tuple[0], str):
|
|
continue
|
|
ip = ip_tuple[0]
|
|
if family == address_family:
|
|
break
|
|
else:
|
|
log.error("Could not find %s address for %r" % (network, host))
|
|
|
|
host = socket.inet_pton(address_family, ip)
|
|
sockaddr = p16(address_family)
|
|
sockaddr += pack(port, word_size = 16, endianness = 'big') # Port should be big endian = network byte order
|
|
length = 0
|
|
|
|
if network == 'ipv4':
|
|
sockaddr += host
|
|
length = 16 # Save ten bytes by skipping two 'push 0'
|
|
else:
|
|
sockaddr += p32(0xffffffff) # Save three bytes 'push -1' vs 'push 0'
|
|
sockaddr += host
|
|
length = len(sockaddr) + 4 # Save five bytes 'push 0'
|
|
return (sockaddr, length, getattr(address_family, "name", address_family))
|
|
|
|
# IPv4, IPv6, and UNIX socket address tuples returned by socket.getaddrinfo
|
|
if TYPE_CHECKING:
|
|
_AddrInfoTuples: TypeAlias = tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes]
|
|
_AddrInfo: TypeAlias = tuple[AddressFamily, SocketKind, int, str, _AddrInfoTuples]
|
|
def sock_match(local: psutil._ntp.addr, remote: psutil._ntp.addr | None, fam: AddressFamily =socket.AF_UNSPEC, typ: SocketKind | Literal[0] = 0) -> Callable[[psutil._ntp.sconn], bool]:
|
|
"""
|
|
Given two addresses, returns a function comparing address pairs from
|
|
psutil library against these two. Useful for filtering done in
|
|
:func:`pwnlib.util.proc.pidof`.
|
|
"""
|
|
def sockinfos(addr: psutil._ntp.addr | tuple[()], f: AddressFamily, t: SocketKind | Literal[0]) -> set[_AddrInfo] | set[psutil._ntp.addr]:
|
|
if not addr:
|
|
return set()
|
|
if f not in (socket.AF_UNSPEC, socket.AF_INET, socket.AF_INET6):
|
|
return {addr}
|
|
infos: set[_AddrInfo] = set(socket.getaddrinfo(addr[0], addr[1], f, t))
|
|
|
|
# handle mixed IPv4-to-IPv6 and the other way round connections
|
|
for f, t, proto, _canonname, sockaddr in tuple(infos):
|
|
if f == socket.AF_INET and t != socket.SOCK_RAW and isinstance(sockaddr[0], str):
|
|
infos |= set(socket.getaddrinfo(sockaddr[0], sockaddr[1], socket.AF_INET6, t, proto, socket.AI_V4MAPPED))
|
|
return infos
|
|
|
|
local = sockinfos(local, fam, typ)
|
|
if remote is not None:
|
|
remote = sockinfos(remote, fam, typ)
|
|
|
|
def match(c: psutil._ntp.sconn) -> bool:
|
|
laddrs = sockinfos(c.laddr, c.family, c.type)
|
|
raddrs = sockinfos(c.raddr, c.family, c.type)
|
|
if not (laddrs & local):
|
|
return False
|
|
if remote is None:
|
|
return True
|
|
return bool(raddrs & remote)
|
|
|
|
return match
|