#!/usr/bin/env python3
import sys
import socket
import struct
from yaml import safe_load
from typing import List, Tuple
from secrets import token_bytes
from binascii import hexlify
from optparse import OptionParser, OptionGroup
def banner():
banner = """
_____ _____ _ _ _____
/ ___/ ___| | | / ___|
\ `--.\ `--.| |_| \ `--. ___ __ _ _ __
`--. \`--. | _ |`--. \/ __/ _` | '_ \\
/\__/ /\__/ | | | /\__/ | (_| (_| | | | |
\____/\____/\_| |_\____/ \___\__,_|_| |_|
evict
"""
return banner
def print_columns(cipherlist):
# adjust the amount of columns to display
cols = 2
while len(cipherlist) % cols != 0:
cipherlist.append("")
else:
split = [
cipherlist[i : i + int(len(cipherlist) / cols)]
for i in range(0, len(cipherlist), int(len(cipherlist) / cols))
]
for row in zip(*split):
print(" " + "".join(str.ljust(c, 37) for c in row))
print("\n")
def return_diff_list(detected, strong):
results = []
for item in detected:
if item not in strong:
results.append(item)
def parse_results(version, kex, salg, enc, mac, cmpv):
version = version.decode("utf-8").rstrip()
kex = kex.decode("utf-8").split(",")
salg = salg.decode("utf-8").split(",")
enc = enc.decode("utf-8").split(",")
mac = mac.decode("utf-8").split(",")
cmpv = cmpv.decode("utf-8").split(",")
with open("config.yml") as fd:
config = safe_load(fd)
weak_ciphers = return_diff_list(enc, config["ciphers"])
weak_macs = return_diff_list(mac, config["macs"])
weak_kex = return_diff_list(kex, config["kex"])
weak_hka = return_diff_list(salg, config["hka"])
compression = True if "zlib@openssh.com" in cmpv else False
print(" [+] Detected the following ciphers: ")
print_columns(enc)
print(" [+] Detected the following KEX algorithms: ")
print_columns(kex)
print(" [+] Detected the following MACs: ")
print_columns(mac)
print(" [+] Detected the following HostKey algorithms: ")
print_columns(salg)
print(" [+] Target SSH version is: %s" % version)
print(" [+] Retrieving ciphers...")
if weak_ciphers:
print(" [+] Detected the following weak ciphers: ")
print_columns(weak_ciphers)
else:
print(" [+] No weak ciphers detected!")
if weak_kex:
print(" [+] Detected the following weak KEX algorithms: ")
print_columns(weak_kex)
else:
print(" [+] No weak KEX detected!")
if weak_macs:
print(" [+] Detected the following weak MACs: ")
print_columns(weak_macs)
else:
print(" [+] No weak MACs detected!")
if weak_hka:
print(" [+] Detected the following weak HostKey algorithms: ")
print_columns(weak_hka)
else:
print(" [+] No weak HostKey algorithms detected!")
if compression:
print(" [+] Compression has been enabled!")
def unpack_ssh_name_list(kex, n):
"""
Unpack the name-list from the packet
The comma separated list is preceded by an unsigned
integer which specifies the size of the list.
"""
size = struct.unpack("!I", kex[n : n + 4])[0] + 1
# jump to the name-list
n += 3
payload = struct.unpack(f"!{size}p", kex[n : n + size])[0]
# to the next integer
n += size
return payload, n
def unpack_msg_kex_init(kex):
# the MSG for KEXINIT looks as follows
# byte SSH_MSG_KEXINIT
# byte[16] cookie (random bytes)
# name-list kex_algorithms
# name-list server_host_key_algorithms
# name-list encryption_algorithms_client_to_server
# name-list encryption_algorithms_server_to_client
# name-list mac_algorithms_client_to_server
# name-list mac_algorithms_server_to_client
# name-list compression_algorithms_client_to_server
# name-list compression_algorithms_server_to_client
# name-list languages_client_to_server
# name-list languages_server_to_client
# boolean first_kex_packet_follows
# uint32 0 (reserved for future extension)
packet_size = struct.unpack("!I", kex[0:4])[0]
print(f"[*] KEX size: {packet_size}")
message = kex[5] # 20 == SSH_MSG_KEXINIT
if message != 20:
raise ValueError("did not receive SSH_MSG_KEXINIT")
cookie = struct.unpack("!16p", kex[6:22])[0]
print(f"[*] server cookie: {hexlify(cookie).decode('utf-8')}")
kex_size = struct.unpack("!I", kex[22:26])[0]
kex_size += 1
kex_algos = struct.unpack(f"!{kex_size}p", kex[25 : 25 + kex_size])[0]
n = 25 + kex_size
server_host_key_algo, n = unpack_ssh_name_list(kex, n)
enc_client_to_server, n = unpack_ssh_name_list(kex, n)
enc_server_to_client, n = unpack_ssh_name_list(kex, n)
mac_client_to_server, n = unpack_ssh_name_list(kex, n)
mac_server_to_client, n = unpack_ssh_name_list(kex, n)
cmp_client_to_server, n = unpack_ssh_name_list(kex, n)
cmp_server_to_client, n = unpack_ssh_name_list(kex, n)
return (
kex_algos,
server_host_key_algo,
enc_server_to_client,
mac_server_to_client,
cmp_server_to_client,
)
def pack_msg_kexinit_for_server(kex, salg, enc, mac, cmpv):
kex_fmt = f"!I{len(kex)}s"
sal_fmt = f"!I{len(salg)}s"
enc_fmt = f"!I{len(enc)}s"
mac_fmt = f"!I{len(mac)}s"
cmp_fmt = f"!I{len(cmpv)}s"
kex = struct.pack(kex_fmt, len(kex), kex)
sal = struct.pack(sal_fmt, len(salg), salg)
enc = struct.pack(enc_fmt, len(enc), enc)
mac = struct.pack(mac_fmt, len(mac), mac)
cmpv = struct.pack(cmp_fmt, len(cmpv), cmpv)
# languages are not used, therefore null
# 4 bytes are reserved
remain = b"\x00\x00\x00\x00"
packet = b"\x20"
packet += token_bytes(16)
packet += kex
packet += sal
# we are lazy and have the ctos and stoc options same.
# this should not be the case
packet += enc
packet += enc
packet += mac
packet += mac
packet += cmpv
packet += cmpv
packet += b"\x00"
packet += remain
packet += b"\x00" * 8
# + unsigned int + header
size = len(packet) + 4 + 2
# properly calculate the padding with length % 8
padding_len = size % 8
if padding_len < 4:
padding_len = 4
return _pack_packet(packet)
def retrieve_initial_kexinit(host: str, port: int) -> Tuple[List, List]:
s = return_socket_for_host(host, port)
version = s.recv(2048)
s.send(version)
kex_init = s.recv(4096)
s.close()
return kex_init, version
def return_socket_for_host(host, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
return s
def _pack_packet(packet):
block_size = 8
# https://github.com/paramiko/paramiko/blob/master/paramiko/packet.py#L631
padding_len = 3 + block_size - ((len(packet) + 8) % block_size) + 1
if padding_len < block_size:
padding_len = block_size
header = struct.pack(">IB", len(packet) + padding_len, padding_len)
padding = b"\x00" * padding_len
packet = header + packet + padding
return packet
def main():
print(banner())
parser = OptionParser(usage="usage %prog [options]", version="%prog 2.0")
parameters = OptionGroup(parser, "Options")
parameters.add_option(
"-t",
"--target",
type="string",
help="Specify target as 'target' or 'target:port' (port 22 is default)",
dest="target",
)
parameters.add_option(
"-l",
"--target-list",
type="string",
help="File with targets: 'target' or 'target:port' seperated by a newline (port 22 is default)",
dest="targetlist",
)
parser.add_option_group(parameters)
options, arguments = parser.parse_args()
targets = []
target = options.target
targetlist = options.targetlist
if target:
targets.append(target)
else:
if targetlist:
with open(targetlist) as fd:
for item in fd.readlines():
targets.append(item.rstrip())
else:
print("[-] No target specified!")
sys.exit(0)
# we send first packets to make sure we match keys
for target in targets:
if ":" not in target:
target += ":22"
host, port = target.split(":")
port = int(port)
try:
kex_init, version = retrieve_initial_kexinit(host, port)
except socket.timeout:
print(" [-] Timeout while connecting to %s on port %i\n" % (host, port))
except socket.error as e:
if e.errno == 61:
print(" [-] %s\n" % (e.strerror))
else:
print(
" [-] Error while connecting to %s on port %i\n" % (host, port)
)
# parse the server KEXINIT message
kex, salg, enc, mac, cmpv = unpack_msg_kex_init(kex_init)
parse_results(version, kex, salg, enc, mac, cmpv)
if __name__ == "__main__":
main()
python3 ssh端口扫码器 基于原生socket数据流解包
最新推荐文章于 2023-03-22 09:02:36 发布