commit 0014f8210b9eedadec1f7b5263dcfc1c7ba4ff37 Author: dash Date: Fri Jan 23 20:47:54 2026 +0100 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..736204f --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# slpscan - service location protocol scanner + +## intro + +This is a research tool, created for looking deeper into SLP at the internet. +Due current events we decided to publish a version of it, to support defenders and researchers tackle the current ESXi exploitation scheme. + +## usage +Show all supported functions +``` +./slpscan.py -m ? +---------------------------------------------------------------------- +SLPv1 Modes Operation Description +---------------------------------------------------------------------- +svc_req_v1 1 +svc_reply_v1 2 +svc_attr_req_v1 6 +svc_attr_reply_v1 7 +svc_type_req_v1 9 +svc_type_reply_v1 10 +---------------------------------------------------------------------- +SLPv2 Modes Operation Description +---------------------------------------------------------------------- +svc_req_v2 1 +svc_reply_v2 2 +svc_attr_req_v2 6 +svc_attr_reply_v2 7 +svc_type_req_v2 9 +svc_type_reply_v2 10 +``` + +Do slp svc req v2 +`./slpscan.py -l 192.168.170.50 -m svc_req_v2` + +Do slp svc type req v1 +`./slpscan.py -l 192.168.170.50 -m svc_type_req_v1` + + +Do slp attribute req for vmware v2 +`./slpscan.py -l 192.168.170.50 -m svc_attr_req_v2` + +Show supported probes +``` +./slpscan.py -P? + +SLP Request | Brief | Devices +------------------------------------------------------------------------------- +svc_type_req_holder_v1 | example pkt, svc_type_req_v1 | +svc_attr_req_holder_v1 | example request, svc_attr_req_v1 | +svc_req_holder_v2 | example pkt, svc_req_v2 | +svc_type_req_holder_v2 | example pkt, svc_type_req_v2 | +svc_attr_req_holder_v2 | example pkt, svc_attr_req_v2 | +VMWARE_SVC_Request_https | service:https | + +``` + +For SLP identification against ESXi Hosts use the probe published within the release: + +``` +./slpscan.py -l -P VMWARE_SVC_Request_https +``` + +If you have a datacenter and need to check a big list of hosts use the -L option. + +If you have a specific probe you can easily add it to the probe json file in the libs directory. There are already several examples to do so. + +General help: + +``` +usage: slpscan.py [-h] [-l HOST] [-L HOSTLIST] [-p PORT] [-t THRCNT] [-m SLP_MODE] + [-P PROBE_MODE] [-d PKT_DELAY] [-T TIMEOUT] [-o OUTFILE] [-oj OUTFILE_JSON] + [-r UNRANDOM] [-R RANDOMIP] + +service location protocol 0.3.7 by dash in published 2023 + +options: + -h, --help show this help message and exit + -l HOST, --host HOST host to check version + -L HOSTLIST, --hostlist HOSTLIST + hostlist to check + -p PORT, --port PORT slp port (default:427) + -t THRCNT, --threads THRCNT + how many threads + -m SLP_MODE, --slp-mode SLP_MODE + what attack mode to choose, ? for list + -P PROBE_MODE, --probe-mode PROBE_MODE + what probe to send, ? for list + -d PKT_DELAY, --packet-delay PKT_DELAY + set the delay(in seconds) a packet is sent, delay is per thread (1s and + 10 threads, each second 10 threads are working) + -T TIMEOUT, --timeout TIMEOUT + timeout of socket recv + -o OUTFILE, --outfile OUTFILE + outfile in txt format + -oj OUTFILE_JSON, --outfile-json OUTFILE_JSON + outfile in json format + -r UNRANDOM, --unrandom UNRANDOM + disable random targetlist + -R RANDOMIP, --randomIP RANDOMIP + generate random ips on the fly +``` + +# outro + +This tool is part of an ongoing research conducted by Marco Lux (ping@curesec.com) and Pedro Umbelino (pedro.umbelino@bitsight.com). diff --git a/libs/__init__.py b/libs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/srvloc_fortune.py b/libs/srvloc_fortune.py new file mode 100644 index 0000000..0c51309 --- /dev/null +++ b/libs/srvloc_fortune.py @@ -0,0 +1,16 @@ +# fortune cookies + +import random + +fck = ['2021 will **** less...', + '"Youre not allowed to say this, but...", is usually the preamble for something pretty pretty stupid which is coming next.', + '...', + 'the plot thickens', + 'just grabbing some b33rs' + 'it just had to happen in 2023' + ] + + +def rnd_fck(): + nxt_fck = random.choice(fck) + return nxt_fck diff --git a/libs/srvloc_globals.py b/libs/srvloc_globals.py new file mode 100644 index 0000000..2d91e84 --- /dev/null +++ b/libs/srvloc_globals.py @@ -0,0 +1,42 @@ +import queue + +global jout_Queue +jout_Queue = queue.Queue() + +global rQ +rQ = queue.Queue() + +global q +q = queue.Queue() + +__tool_version__ = '0.3.7' +__tool_author__ = 'Marco Lux' +__tool_date__ = 'published 2023' + +SLP_SVC_REQ = 0x1 +SLP_SVC_REPLY = 0x2 +SLP_ATTR_REQ = 0x6 +SLP_ATTR_REPLY = 0x7 +SLP_SVC_TYPE_REQ = 0x9 +SLP_SVC_TYPE_REPLY = 0xa + + +# basic v1 pkt +req_dict_v1 = {'slp_ver': 1, + 'slp_func': SLP_ATTR_REQ, + 'slp_pkt_len': 0, + 'slp_flags': 0, + 'slp_dialect': 0, + 'slp_lang': 0x656e, + 'slp_enc': 3, + 'slp_transx': 0x29A} + +# basic v2 pkt +req_dict_v2 = {'slp_ver': 2, + 'slp_func': SLP_SVC_TYPE_REQ, + 'slp_pkt_len': 0, + 'slp_flags': 0, + 'slp_next_offset': 0, + 'slp_xid': 0x666, + 'slpintroduction_lang_tag_len': 2, + 'slp_lang_tag': 0x656e} diff --git a/libs/srvloc_helper.py b/libs/srvloc_helper.py new file mode 100644 index 0000000..5ff6f1a --- /dev/null +++ b/libs/srvloc_helper.py @@ -0,0 +1,176 @@ +import os +import sys +import pytz +import base64 +import datetime +import random +from random import randrange +from libs.srvloc_globals import * +from libs.srvloc_log import printd, printe + + +def randomizeIP(iplist): + ''' function to randomize ips to scan''' +# orig_list = iplist + # run 1 + random.shuffle(iplist) + + # run 2 + random.shuffle(iplist) + return iplist + + +def generateIP(): + blockOne = randrange(0, 255, 1) + blockTwo = randrange(0, 255, 1) + blockThree = randrange(0, 255, 1) + blockFour = randrange(0, 255, 1) + if blockOne == 10: + return generateIP() + elif blockOne == 172: + return generateIP() + elif blockOne == 192: + return generateIP() + else: + d = str(blockOne) + '.' + str(blockTwo) + '.' + \ + str(blockThree) + '.' + str(blockFour) + + return d + + +def console_size(): + height, witdh = os.popen('stty size', 'r').read().split() + + return (int(witdh), int(height)) + + +def generate_randomIP(q, count): + i = 0 + count = int(count) + # print 'cnt', count + while i != count: + ip = generateIP() + check_ip = [ip] + ip = check_blacklist(check_ip) + if len(ip) > 0: + q.put(ip[0]) + i += 1 + else: + print('{0} BLACKLISTED.'.format(ip)) + + print('gen %d' % (q.qsize())) + return + + +def timefield_rfc3339(): + ''' + implementing timestamp like rfc3339 + ''' + d = datetime.datetime.utcnow() + d_with_timezone = d.replace(tzinfo=pytz.UTC) + timestamp = d_with_timezone.isoformat() + return timestamp + + +def timedict_rfc3339(): + ''' + implementing timestamp like rfc3339 + ''' + d = datetime.datetime.utcnow() + d_with_timezone = d.replace(tzinfo=pytz.UTC) + timestamp = d_with_timezone.isoformat() + return {'timestamp': timestamp} + + +def ascii_check(rec): + '''check if banner has non-ascii values + ''' + # yes, this is a bool now + ascii_bool = False + + data = rec + # print(rec) + # poor mans clause for checking if ascii or not + try: + if type(rec) != int: + ascii_test = data.decode('ascii') + ascii_bool = True + else: + return rec + + except UnicodeDecodeError as e: + ascii_bool = False + + # its not ascii, so base64 encoding + if ascii_bool == False: + rec = base64.b64encode(data) + + return(rec) + + +def clean_line(line): + line = line.rstrip('\r') + line = line.rstrip('\n') + return line + + +def check_file_exists(fname): + + try: + stat = os.stat(fname) + + except FileNotFoundError as e: + print(repr(e)) + return False + + return True + + +def check_file_readable(fname): + try: + fr = open(fname, 'r', 1) + + except PermissionError as e: + print(repr(e)) + return False + except Exception as e: + print(repr(e)) + return False + + return True + + +def create_hostlist(args): + #ret = check_file_exists(args.hostlist) + + # if not ret: + # return False + fr = open(args.hostlist, 'r', 1) + + for line in fr.readlines(): + cline = clean_line(line) + rQ.put(cline) + rQ.put('') + # req_queue.put('EOF') + + +def check_blacklist(whites): + # def check_blacklist(blacks, whites): + ''' + ''' + fr = open('supply/blacklist.txt', 'r') + bips = fr.readlines() + white_len = len(whites) + blacklisted = [] + for black in bips: + black = clean_line(black) + try: + whites.index(black) + whites.remove(black) + blacklisted.append(black) + print('BLACKLISTED {0}'.format(black)) + except ValueError as e: + pass + white_len_after = len(whites) + #print('Whitelist: {0}/{1}'.format(white_len, white_len_after)) + return whites diff --git a/libs/srvloc_log.py b/libs/srvloc_log.py new file mode 100644 index 0000000..5f4ab41 --- /dev/null +++ b/libs/srvloc_log.py @@ -0,0 +1,9 @@ + +def printd(data): + # if DEBUG_PROTO: + # print(data) + pass + + +def printe(data): + pass diff --git a/libs/srvloc_main.py b/libs/srvloc_main.py new file mode 100644 index 0000000..3098bbd --- /dev/null +++ b/libs/srvloc_main.py @@ -0,0 +1,267 @@ +import os +import sys +import json +import time +import socket +import threading +import argparse + +from libs.srvloc_globals import q, rQ, __tool_author__, __tool_version__, __tool_date__, jout_Queue +from libs.srvloc_proto_v1 import * +from libs.srvloc_proto_v2 import * + +from libs.srvloc_helper import randomizeIP, generate_randomIP, clean_line, check_blacklist +from libs.srvloc_log import printd, printe + +from libs.srvloc_fortune import rnd_fck + + +def parser_main(): + parser_desc = 'service location protocol {0} by {1} in {2}'.format( + __tool_version__, __tool_author__, __tool_date__) + prog_desc = 'slpscan.py' + parser = argparse.ArgumentParser(prog=prog_desc, description=parser_desc) + parser.add_argument("-l", "--host", action="store", + required=False, help='host to check version', dest='host') + parser.add_argument("-L", "--hostlist", action="store", + required=False, help='hostlist to check', dest='hostlist') + parser.add_argument("-p", "--port", action="store", required=False, + default=427, help='slp port (default:427)', dest='port') + parser.add_argument("-t", "--threads", action="store", required=False, + default=50, help='how many threads', dest='thrCnt', type=int) + parser.add_argument("-m", "--slp-mode", action="store", required=False, default='', + help='what attack mode to choose, ? for list', dest='slp_mode') + parser.add_argument("-P", "--probe-mode", action="store", required=False, default=False, + help='what probe to send, ? for list', dest='probe_mode') + parser.add_argument("-d", "--packet-delay", action="store", required=False, type=float, + help='set the delay(in seconds) a packet is sent, delay is per thread (1s and 10 threads, each second 10 threads are working)', + dest='pkt_delay') + + parser.add_argument("-T", "--timeout", action="store", required=False, + default=5, help='timeout of socket recv', dest='timeout') + parser.add_argument("-o", "--outfile", action="store", + required=False, help='outfile in txt format', dest='outfile') + parser.add_argument("-oj", "--outfile-json", action="store", + required=False, help='outfile in json format', dest='outfile_json') + + parser.add_argument("-r", "--unrandom", action="store", required=False, + help='disable random targetlist', dest='unrandom') + parser.add_argument("-R", "--randomIP", action="store", required=False, + help='generate random ips on the fly', dest='randomip') + + args = parser.parse_args() + return args + + +SLP_SVC_REQ = 0x1 +SLP_SVC_REPLY = 0x2 +SLP_ATTR_REQ = 0x6 +SLP_ATTR_REPLY = 0x7 +SLP_SVC_TYPE_REQ = 0x9 +SLP_SVC_TYPE_REPLY = 0xa + +# FIXME build other structure for modes + + +def choose_slp_mode(args): + lsize = 70 + modes = {1: {'name': '{0:<30} {1:<15} {2:<30}'.format('SLPv1 Modes', 'Operation', 'Description'), 'Operation': '', 'Description': '', 'Method': ''}, + # 2: {'name': '-'*lsize, 'Operation': '', 'Description': '', 'Method': ''}, + # !!!!!! + 21: {'name': 'svc_req_v1', 'Operation': SLP_SVC_REQ, 'Description': '', 'Method': build_slp_svc_req_v1()}, + 22: {'name': 'svc_reply_v1', 'Operation': SLP_SVC_REPLY, 'Description': '', 'Method': build_slp_reply_v1()}, + 26: {'name': 'svc_attr_req_v1', 'Operation': SLP_ATTR_REQ, 'Description': '', 'Method': build_svc_attr_req_v1()}, + 27: {'name': 'svc_attr_reply_v1', 'Operation': SLP_ATTR_REPLY, 'Description': '', 'Method': build_slp_attr_reply_v1()}, + 29: {'name': 'svc_type_req_v1', 'Operation': SLP_SVC_TYPE_REQ, 'Description': '', 'Method': build_slp_svc_type_req_v1()}, + 30: {'name': 'svc_type_reply_v1', 'Operation': SLP_SVC_TYPE_REPLY, 'Description': '', 'Method': build_slp_type_reply_v1()}, + + 38: {'name': '{0:<30} {1:<15} {2:<30}'.format('SLPv2 Modes', 'Operation', 'Description'), 'Operation': '', 'Description': '', 'Method': ''}, + + 40: {'name': 'svc_req_v2', 'Operation': SLP_SVC_REQ, 'Description': '', 'Method': build_slp_svc_req_v2()}, + 41: {'name': 'svc_reply_v2', 'Operation': SLP_SVC_REPLY, 'Description': '', 'Method': build_slp_reply_v2()}, + 45: {'name': 'svc_attr_req_v2', 'Operation': SLP_ATTR_REQ, 'Description': '', 'Method': build_slp_attr_req_v2()}, + 46: {'name': 'svc_attr_reply_v2', 'Operation': SLP_ATTR_REPLY, 'Description': '', 'Method': build_slp_attr_reply_v2()}, + 48: {'name': 'svc_type_req_v2', 'Operation': SLP_SVC_TYPE_REQ, 'Description': '', 'Method': build_slp_svc_type_req_v2()}, + 49: {'name': 'svc_type_reply_v2', 'Operation': SLP_SVC_TYPE_REPLY, 'Description': '', 'Method': build_slp_type_reply_v2()}, + } + + slp_mode = args.slp_mode + + if slp_mode == '?': + print() + for k in modes.keys(): + # print(modes[k]) + name = modes[k]['name'] + operation = modes[k]['Operation'] + desc = modes[k]['Description'] + if name.startswith('SLPv'): + print('-'*lsize) + print('{0:<30} {1:<15} {2:<30}'.format(name, operation, desc)) + if name.startswith('SLPv'): + print('-'*lsize) + print() + sys.exit() + else: + for k in modes.keys(): + if slp_mode == modes[k]['name']: + pkt = modes[k]['Method'] + + return pkt + + print('Unknown mode use -m? for showing supported modes') + sys.exit() + + +def make_request(host, port, args, pkt): + human = [] + timeout = args.timeout + slp_mode = args.slp_mode + probe_mode = args.probe_mode + pkt_delay = args.pkt_delay + + if pkt_delay: + time.sleep(pkt_delay) + try: + # build up socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # we want to have a timeout + s.settimeout(timeout) + s.connect((host, port)) + + # send the discovery packet + s.send(pkt) + + # getting the data of the connection + rec = s.recv(4096) + data_dict = {'target': host+':' + + str(port), 'reply_pkt': rec, 'pkt': pkt} + hdata = '%s:%d' % (host, port) + hdump = '%s' % (repr(rec)) + + # place stuff in one of the queues + human.append(hdata) + human.append(hdump) + # human.append(reply_dict) + rQ.put(human) + + except socket.timeout: + printe('%s timeout' % host) + except socket.error: + printe('%s refused' % host) + + +def run_mainthreads(args, pkt): + + # wanna have a c00kie?! + fck = rnd_fck() + printd(fck) + + if args.outfile: + fw = open(args.outfile, 'w') + + if args.outfile_json: + fwj = open(args.outfile_json, 'w') + + if args.host: + host = args.host + print('Hostmode: %s' % host) + line = clean_line(host) + + bl = [host] + wh = check_blacklist(bl) + if len(wh) == 1: + q.put(line) + else: + # print('{0} Blacklisted!!!'.format(host)) + sys.exit() + + elif args.hostlist: + ipL = [] + hostlist = args.hostlist + print('Hostlistmode') + fr = open(hostlist, 'r') + rBuf = fr.readlines() + for l in rBuf: + l = clean_line(l) + ipL.append(l) + if not args.unrandom: + iplist = randomizeIP(ipL) + else: + iplist = ipL + + iplist = check_blacklist(iplist) + + list = [q.put(query) for query in iplist] + + elif args.randomip: + randIP = int(args.randomip) + print('RandomIPs: %d' % (randIP)) + + else: + print('Unknown or no mode choosen. cya') + sys.exit() + + if not args.randomip: + print('Targets: %d' % (q.qsize())) + else: + # lets start the thread for generating randomIPs + printd('Starting random thread:') + randIPThread = threading.Thread( + target=generate_randomIP, args=(q, args.randomip)) + randIPThread.daemon = True + randIPThread.start() + # FIXME + # quick fix so we do not miss the threading loop + # better would be a counter in the loop itself + time.sleep(5) + + port = int(args.port) + thrCnt = args.thrCnt + + thrList = [] + + printd('Starting loop') + while True: + if len(thrList) < thrCnt and q.qsize() > 0: + newthread = threading.Thread(target=make_request, args=( + q.get(), port, args, pkt)) + newthread.daemon = True + newthread.start() + thrList.append(newthread) + + for entry in thrList: + if entry.is_alive() == False: + entry.join() + thrList.remove(entry) + time.sleep(0.1) + + if rQ.qsize() > 0: + + pout = rQ.get() + pp = '%s' % (pout) + print('[RAW] ', pp) + print() + if args.outfile: + fw.write(pp + '\n') + + if jout_Queue.qsize() > 0: + jdata = jout_Queue.get() + print(jdata) + + if args.outfile_json: + fwj.write(jdata + '\n') + if q.qsize() == 0 and len(thrList) == 0: + break + + +def print_slp_modes(): + + data = ''' + Supported modes: + + * slp_type_request + requesting the supported ressources at the remote device + ''' + + print(data) diff --git a/libs/srvloc_parse.py b/libs/srvloc_parse.py new file mode 100644 index 0000000..40c7873 --- /dev/null +++ b/libs/srvloc_parse.py @@ -0,0 +1,71 @@ +import struct + + +def parse_slp_reply(data_dict): + ''' + Attribute Reply + Version | Function | PktLen | Flags | Dialect | Lang | Encoding | TransactionID | Error Code | Attribute List Len | Attribdata + 1b 1b 2b 1b 1b 2b 2b 2b 2b 2b + ''' + + reply_pkt = data_dict['reply_pkt'] + slp_ver, slp_func, slp_pkt_len, slp_flags, slp_dialect, slp_lang, slp_enc, slp_transx,\ + slp_err, slp_attr_list = struct.unpack( + '>BBHBBHHHHH', reply_pkt[:16]) + reply_dict = {'slp_ver': slp_ver, + 'slp_func': slp_func, + 'slp_pkt_len': slp_pkt_len, + 'slp_flags': slp_flags, + 'slp_dialect': slp_dialect, + 'slp_lang': slp_lang, + 'slp_enc': slp_enc, + 'slp_transx': slp_transx, + 'slp_err': slp_err, + 'slp_attr_list': slp_attr_list} + + # FIXME FIXME + # ... disabling parsing for now + # print(reply_dict) + attr_data = reply_pkt[16:] + # print(attr_data) + s_attr_data = attr_data.split(b'(') + print(s_attr_data) + s_attr_data.remove(b'') + hwdata = s_attr_data + hw_dict = {} + for item in hwdata: + item = item.decode() + item = item.rstrip(')') + key_a, val_a = item.split('=') + hw_dict[key_a] = val_a + + #hwdata = hwdata.decode() + shwdata = hwdata.split('(') + #hw_dict = {} + # shwdata.remove('') + print('-'*80) + print(hwdata) + print(hw_dict) + print('-'*80) + for b in shwdata: + b = b.rstrip(')') + b = b.split('=') + + key_b = b[0] + val_b = b[1] + hw_dict[key_b] = val_b + print(b) + if key_b == 'x-hp-p1': + + hw_dict[key_b] = {} + ['x-hp-p1', 'MFG:Hewlett-Packard'] + + s_attr_data.remove(b')') + print(s_attr_data) + for entry in s_attr_data: + ee = entry.decode() + ee = ee.split(':') +# print(ee) + hw_dict[ee[0]] = ee[1] + + return hw_dict diff --git a/libs/srvloc_probes.json b/libs/srvloc_probes.json new file mode 100644 index 0000000..e2b8535 --- /dev/null +++ b/libs/srvloc_probes.json @@ -0,0 +1,151 @@ +{ + "1": { + "name": "svc_type_req_holder_v1", + "brief": "example pkt, svc_type_req_v1", + "devices": [], + "description": "example pkt, svc_type_req_v1", + "probe": { + "base": { + "slp_ver": 1, + "slp_func": 9, + "slp_pkt_len": 0, + "slp_flags": 0, + "slp_dialect": 0, + "slp_lang": 25966, + "slp_enc": 3, + "slp_transx": 666 + }, + "data": { + "slp_prev_res_list": 0, + "slp_all": 65535, + "slp_scope": "default" + } + } + }, + "2": { + "name": "svc_attr_req_holder_v1", + "brief": "example request, svc_attr_req_v1", + "devices": [], + "description": "example request, svc_attr_req_v1", + "probe": { + "base": { + "slp_ver": 1, + "slp_func": 6, + "slp_pkt_len": 0, + "slp_flags": 0, + "slp_dialect": 0, + "slp_lang": 25966, + "slp_enc": 3, + "slp_transx": 667 + }, + "data": { + "slp_prev_res_list": 0, + "slp_svc_len": 8, + "slp_svc_url": "service:", + "slp_scope_len": 0, + "slp_attr_len": 0 + } + } + }, + "3": { + "name": "svc_req_holder_v2", + "brief": "example pkt, svc_req_v2", + "devices": [], + "description": "example pkt, svc_req_v2", + "probe": { + "base": { + "slp_ver": 2, + "slp_func": 1, + "slp_pkt_len": 0, + "slp_flags": 0, + "slp_next_offset": 0, + "slp_xid": 12345, + "slp_ltag_len": 2, + "slp_ltag": 25966 + }, + "data": { + "slp_prev_res_list": 0, + "slp_svc_type_len": 0, + "slp_svc_type": "service:https", + "slp_scope_len": 13, + "slp_scope": "default" + } + } + }, + "4": { + "name": "svc_type_req_holder_v2", + "brief": "example pkt, svc_type_req_v2", + "devices": [], + "description": "example pkt, svc_type_req_v2", + "probe": { + "base": { + "slp_ver": 2, + "slp_func": 9, + "slp_pkt_len": 0, + "slp_flags": 0, + "slp_next_offset": 0, + "slp_xid": 45267, + "slp_ltag_len": 2, + "slp_ltag": 25966 + }, + "data": { + "slp_prev_res_list": 0, + "slp_all": 65535, + "slp_scope": "default", + "slp_scope_len": 7 + } + } + }, + "5": { + "name": "svc_attr_req_holder_v2", + "brief": "example pkt, svc_attr_req_v2", + "devices": [], + "description": "example pkt, svc_attr_req_v2", + "probe": { + "base": { + "slp_ver": 2, + "slp_func": 6, + "slp_pkt_len": 0, + "slp_flags": 0, + "slp_next_offset": 0, + "slp_xid": 19121, + "slp_ltag_len": 2, + "slp_ltag": 25966 + }, + "data": { + "slp_prev_res_list": 0, + "slp_svc_url_len": 13, + "slp_svc_url": "service:https", + "slp_scope_len": 0, + "slp_scope": "", + "slp_tag_len": 0, + "slp_tag": "" + } + } + }, + "6": { + "name": "VMWARE_SVC_Request_https", + "brief": "service:https", + "devices": [], + "description": "", + "probe": { + "base": { + "slp_ver": 2, + "slp_func": 1, + "slp_pkt_len": 0, + "slp_flags": 0, + "slp_next_offset": 0, + "slp_xid": 23152, + "slp_ltag_len": 2, + "slp_ltag": 25966 + }, + "data": { + "slp_prev_res_list": 0, + "slp_svc_type_len": 13, + "slp_svc_type": "service:https", + "slp_scope_len": 7, + "slp_scope": "default" + } + } + } +} diff --git a/libs/srvloc_probes.py b/libs/srvloc_probes.py new file mode 100644 index 0000000..9588b11 --- /dev/null +++ b/libs/srvloc_probes.py @@ -0,0 +1,184 @@ +import json +from libs.srvloc_proto_v1 import build_slp_base_v1, compute_len_v1, _slp_attr_req_v1, _slp_svc_req_v1, \ + _slp_svc_reply_v1, _slp_attr_reply_v1, _slp_type_reply_v1, _slp_svc_type_req_v1 + + +from libs.srvloc_proto_v2 import _slp_svc_req_v2, _slp_svc_reply_v2, _slp_svc_type_req_v2, _slp_attr_req_v2,_slp_attr_reply_v2, _slp_type_reply_v2, compute_len_v2, build_slp_base_v2 + + +from libs.srvloc_helper import console_size + +fname = 'srvloc_probes.json' + +SLP_SVC_REQ = 0x1 +SLP_SVC_REPLY = 0x2 +SLP_ATTR_REQ = 0x6 +SLP_ATTR_REPLY = 0x7 +SLP_SVC_TYPE_REQ = 0x9 +SLP_SVC_TYPE_REPLY = 0xa + + +def open_probe_file(fname): + fr = open(fname, 'r') + jprobes = json.loads(fr.read()) + fr.close() + return jprobes + + +def print_probes(jprobes): + width, heigth = console_size() + # 30% name + # 40% brief + # 15% devices + # yeah i need to substract max width len as well ... + name_res = 0.30 + brief_res = 0.40 + dev_res = 0.15 + tab_vert_res = 0.90 + width = width * 0.90 + name_space = int(width * name_res) + brief_space = int(width * brief_res) + dev_space = int(width * dev_res) + max_table_vert = int(width * tab_vert_res) + + print('{0: <{1}}| {2: <{3}}| {4: <{5}}'.format( + 'SLP Request', name_space, 'Brief', brief_space, 'Devices', dev_space)) + + print('-'*(max_table_vert)) + for k in jprobes.keys(): + name = jprobes[k]['name'] + desc = jprobes[k]['brief'] + devices = jprobes[k]['devices'] + str_devices = ",".join(devices) + print('{0: <{1}}| {2: <{3}}| {4: <{5}}'.format( + name, name_space, desc, brief_space, str_devices, dev_space)) + + +def probe_packet(jprobes, pname): + # print('123') + for k in jprobes.keys(): + name = jprobes[k]['name'] + if pname == name: +# print('Found probe') + base = jprobes[k]['probe']['base'] + data = jprobes[k]['probe']['data'] + slp_ver = jprobes[k]['probe']['base']['slp_ver'] + slp_func = jprobes[k]['probe']['base']['slp_func'] + + if slp_ver == 1: + slp_ver, slp_func, slp_pkt_len, slp_flags, slp_dialect, slp_lang, slp_enc, slp_transx = jprobes[k]['probe']['base'].values( + ) + pkt1 = build_slp_base_v1( + slp_ver, slp_func, slp_pkt_len, slp_flags, slp_dialect, slp_lang, slp_enc, slp_transx) + + if slp_func == SLP_SVC_REQ: + slp_prev_res_list_len, slp_rest_list, slp_pred_len, slp_pred = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_svc_req_v1( + slp_prev_res_list_len, slp_rest_list.encode(), slp_pred_len, slp_pred.encode()) + + elif slp_func == SLP_SVC_REPLY: + err_code, num_urls, url_lifetime, url_len, urls, num_auths = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_svc_reply_v1( + err_code, num_urls, url_lifetime, url_len, urls.encode(), num_auths) + + elif slp_func == SLP_ATTR_REQ: + slp_prev_res_list, slp_svc_len, slp_svc_url, slp_scope_len, slp_attr_len = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_attr_req_v1( + slp_prev_res_list, slp_svc_len, slp_svc_url.encode(), slp_scope_len, slp_attr_len) + + elif slp_func == SLP_ATTR_REPLY: + err_code, attr_list_len, attr_list, attr_auths = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_attr_reply_v1( + err_code, attr_list_len, attr_list.encode(), attr_auths) + elif slp_func == SLP_SVC_TYPE_REQ: + slp_prev_res_list, slp_all, slp_scope = jprobes[k]['probe']['data'].values( + ) + + pkt2 = _slp_svc_type_req_v1( + slp_prev_res_list, slp_all, slp_scope.encode()) + + elif slp_func == SLP_SVC_TYPE_REPLY: + err_code, svc_type_count, svc_type_list_len, svc_type_list = jprobes[k]['probe']['data'].values( + ) + + pkt2 = _slp_type_reply_v1( + err_code, svc_type_count, svc_type_list_len, svc_type_list.encode()) + + else: + + print('{0} function not yet supported.'.format(slp_func)) + + return False + + pkt = pkt1 + pkt2 + pkt_rdy = compute_len_v1(pkt) + + elif slp_ver == 2: + slp_ver, slp_func, slp_pkt_len, slp_flags, slp_next_offset, slp_xid, slp_ltag_len, slp_ltag = jprobes[k]['probe']['base'].values( + ) + + pkt1 = build_slp_base_v2( + slp_ver, slp_func, slp_pkt_len, slp_flags, slp_next_offset, slp_xid, slp_ltag_len, slp_ltag) + + if slp_func == SLP_SVC_REQ: + slp_prev_res_list, slp_svc_type_len, slp_svc_type, slp_scope_len, slp_scope = jprobes[k]['probe']['data'].values( + ) + + pkt2 = _slp_svc_req_v2( + slp_prev_res_list, slp_svc_type_len, slp_svc_type.encode(), slp_scope_len, slp_scope.encode()) + + elif slp_func == SLP_SVC_REPLY: + err_code, num_urls, reserved, url_lifetime, url_len, urls, num_auths = jprobes[k]['probe']['data'].values( + ) + + pkt2 = _slp_svc_reply_v2( + err_code, num_urls, reserved, url_lifetime, url_len, urls.encode(), num_auths) + + elif slp_func == SLP_SVC_ACK: + err_code = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_svc_ack_v2( + err_code) + + elif slp_func == SLP_ATTR_REQ: + slp_prev_res_list, slp_svc_url_len, slp_svc_url, slp_scope_len, slp_scope, slp_tag_len, slp_tag = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_attr_req_v2( + slp_prev_res_list, slp_svc_url_len, slp_svc_url.encode(), slp_scope_len, slp_scope.encode(), slp_tag_len, slp_tag.encode()) + + elif slp_func == SLP_ATTR_REPLY: + err_code, attr_list_len, attr_list, attr_auths = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_attr_reply_v2( + err_code, attr_list_len, attr_list.encode(), attr_auths) + elif slp_func == SLP_SVC_TYPE_REQ: + slp_prev_res_list, slp_all, slp_scope, slp_scope_len = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_svc_type_req_v2( + slp_prev_res_list, slp_all, slp_scope.encode(), slp_scope_len) + + elif slp_func == SLP_SVC_TYPE_REPLY: + err_code, svc_type_list_len, svc_type_list = jprobes[k]['probe']['data'].values( + ) + pkt2 = _slp_type_reply_v2( + err_code, svc_type_list_len, svc_type_list.encode()) + + else: + print('{0} function not yet supported.'.format(slp_func)) + + pkt = pkt1 + pkt2 + pkt_rdy = compute_len_v2(pkt) +# print('* SLP_SVC_REQ implemented') + + else: + print('Not supported version {0}'.format(slp_ver)) + return False + + print(base) + print(data) + + return pkt_rdy diff --git a/libs/srvloc_proto_v1.py b/libs/srvloc_proto_v1.py new file mode 100644 index 0000000..6090614 --- /dev/null +++ b/libs/srvloc_proto_v1.py @@ -0,0 +1,195 @@ +import struct +import random +from libs.srvloc_log import printd + +SLP_SVC_REQ = 0x1 +SLP_SVC_REPLY = 0x2 +SLP_ATTR_REQ = 0x6 +SLP_ATTR_REPLY = 0x7 +SLP_SVC_TYPE_REQ = 0x9 +SLP_SVC_TYPE_REPLY = 0xa + +SLP_TRANSX_RAND = True +SLP_XID_RAND = True +CRAFT_AUTO_LEN = True +DEBUG_PROTO = True + +##################### +###### SLP v1 ####### +##################### + + +def build_slp_base_v1(slp_ver=1, slp_func=0, slp_pkt_len=0, slp_flags=0, slp_dialect=0, slp_lang=0x656e, slp_enc=3, slp_transx=0x29A): + ''' + this method is used for building up what i call the *base* part of the SLPv1 specification. Base parameters are all parameters part + of every valid SLPv1 packet. + + Returns: pkt - a ready base packet, WARNING this packet is missing the function part and also pkt_len is not computed yet + ''' + # print(slp_ver) + if SLP_TRANSX_RAND: + slp_transx = random.randint(1, 65535) + + # basic pkt structure v1 + pkt = struct.pack('>BBHBBHHH', slp_ver, slp_func, slp_pkt_len, + slp_flags, slp_dialect, slp_lang, slp_enc, slp_transx) + + printd(pkt) + return pkt + + +def compute_len_v1(pkt): + ''' + This method is used for computing the overall length of a complete SLPv1 packet. It's default usage is being called at the end of + packet building process. + + Params: pkt - a ready pkt without correct pkt_len, usually 0 (zero) + Returns: pkt - a ready pkt with correct pkt_len + ''' + pkt_len = len(pkt) + + pkt_byte_len = struct.pack('>H', pkt_len) + pkt = pkt[:2]+pkt_byte_len+pkt[4:] + return pkt + + +########SLP_SVC_REQ = 0x1 +def build_slp_svc_req_v1(): + pkt1 = build_slp_base_v1(slp_func=SLP_SVC_REQ) + pkt2 = _slp_svc_req_v1() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v1(pkt) + + return pkt_rdy + + +def _slp_svc_req_v1(slp_prev_res_list_len=1, slp_resp_list=b'A', slp_pred_len=1, slp_pred=b'B'): + ''' + ''' + + pkt2 = struct.pack('>H' + str(slp_prev_res_list_len) + 'sH' + str(slp_pred_len) + 's', + slp_prev_res_list_len, + slp_resp_list, + slp_pred_len, + slp_pred + ) + + return pkt2 + +######################################################################################################################################## + +###### SLP_SVC_REPLY = 0x2 + + +def build_slp_reply_v1(): + pkt1 = build_slp_base_v1(slp_func=SLP_SVC_REPLY) + pkt2 = _slp_svc_reply_v1() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v1(pkt) + + return pkt_rdy + + +def _slp_svc_reply_v1(err_code=0, num_urls=1, url_lifetime=667, url_len=12, urls=b'service:wbem', num_auths=0): + ''' + ''' + + #url_len = len(urls) + pkt2 = struct.pack('>HHHH'+str(url_len)+'sB', err_code, + num_urls, url_lifetime, url_len, urls, 0) + + return pkt2 +######################################################################################################################################## + +#############SLP_ATTR_REQ = 0x6 +def build_svc_attr_req_v1(): + pkt1 = build_slp_base_v1(slp_func=SLP_ATTR_REQ) + pkt2 = _slp_attr_req_v1() + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v1(pkt) + + return pkt_rdy + + +def _slp_attr_req_v1(slp_prev_res_list=0, slp_url_len=8, slp_svc_url=b'service:', slp_scope_len=0, slp_attr_len=0): + + # attr request structure + pkt2 = struct.pack('>HH'+str(slp_url_len)+'sHH', slp_prev_res_list, + slp_url_len, slp_svc_url, slp_scope_len, slp_attr_len) + + return pkt2 + +######################################################################################################################################## + +#########SLP_ATTR_REPLY = 0x7 +def build_slp_attr_reply_v1(): + pkt1 = build_slp_base_v1(slp_func=SLP_ATTR_REPLY) + pkt2 = _slp_attr_reply_v1() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v1(pkt) + + return pkt_rdy + + +def _slp_attr_reply_v1(err_code=0, attr_list_len=9, attr_list=b'attribute', attr_auths=0): + + pkt2 = struct.pack('>HH' + str(attr_list_len) + 'sB', + err_code, + attr_list_len, + attr_list, + attr_auths + ) + + return pkt2 +######################################################################################################################################## + + +##################SLP_SVC_TYPE_REQ = 0x9 + + +def build_slp_svc_type_req_v1(): + pkt1 = build_slp_base_v1(slp_func=SLP_SVC_TYPE_REQ) + pkt2 = _slp_svc_type_req_v1() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v1(pkt) + + return pkt_rdy + + +def _slp_svc_type_req_v1(slp_prev_res_list=0, slp_all=65535, slp_scope=b'default'): + + slp_scope_len = len(slp_scope) + pkt2 = struct.pack('>HHH'+str(slp_scope_len)+'s', slp_prev_res_list, + slp_all, slp_scope_len, slp_scope) + + return pkt2 +######################################################################################################################################## + +########SLP_SVC_TYPE_REPLY = 0xa + + +def build_slp_type_reply_v1(): + pkt1 = build_slp_base_v1(slp_func=SLP_SVC_TYPE_REPLY) + pkt2 = _slp_type_reply_v1() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v1(pkt) + + return pkt_rdy + + +def _slp_type_reply_v1(err_code=0, svc_type_count=1, svc_type_list_len=31, svc_type_list=b'service:Windows:wbem:http:https'): + + pkt2 = struct.pack('>HHH' + str(svc_type_list_len) + 's', + err_code, + svc_type_count, + svc_type_list_len, + svc_type_list + ) + + return pkt2 +######################################################################################################################################## diff --git a/libs/srvloc_proto_v2.py b/libs/srvloc_proto_v2.py new file mode 100644 index 0000000..c511a8a --- /dev/null +++ b/libs/srvloc_proto_v2.py @@ -0,0 +1,172 @@ +import struct +import random +from libs.srvloc_log import printd + +SLP_SVC_REQ = 0x1 +SLP_SVC_REPLY = 0x2 +SLP_ATTR_REQ = 0x6 +SLP_ATTR_REPLY = 0x7 +SLP_SVC_TYPE_REQ = 0x9 +SLP_SVC_TYPE_REPLY = 0xa + +SLP_TRANSX_RAND = True +SLP_XID_RAND = True +CRAFT_AUTO_LEN = True +DEBUG_PROTO = True + +##################### +###### SLP v2 ####### +##################### + + +def build_slp_base_v2(slp_ver=2, slp_func=0, slp_pkt_len=0, slp_flags=0, slp_next_offset=0, slp_xid=0x299, slp_ltag_len=2, slp_ltag=0x656e): + + if SLP_XID_RAND: + slp_xid = random.randint(1, 65535) + + # basic pkt structure v2 + pkt = struct.pack('>BBBHHBHHHH', slp_ver, slp_func, 0, slp_pkt_len, + slp_flags, 0, slp_next_offset, slp_xid, slp_ltag_len, slp_ltag) + + return pkt + + +def compute_len_v2(pkt): + + pkt_len = len(pkt) + + pkt_byte_len = struct.pack('>bH', 0, pkt_len) + pkt = pkt[:2] + pkt_byte_len + pkt[5:] + + return pkt + + +#########SLP_SVC_REQ = 0x1 +def build_slp_svc_req_v2(): + pkt1 = build_slp_base_v2(slp_func=SLP_SVC_REQ) + pkt2 = _slp_svc_req_v2() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v2(pkt) + + return pkt_rdy + + +def _slp_svc_req_v2(slp_prev_res_list=0, slp_svc_type_len=0, slp_svc_type=b'service:wbem', slp_scope_len=7, slp_scope=b'default'): + ''' + ''' + + pkt2 = struct.pack('>HH'+str(slp_svc_type_len)+'sH'+str(slp_scope_len)+'sHH', slp_prev_res_list, + slp_svc_type_len, slp_svc_type, slp_scope_len, slp_scope, 0, 0) + + return pkt2 +########## + +#######SLP_SVC_REPLY = 0x2 + + +def build_slp_reply_v2(): + pkt1 = build_slp_base_v2(slp_func=SLP_SVC_REPLY) + pkt2 = _slp_svc_reply_v2() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v2(pkt) + + return pkt_rdy + + +def _slp_svc_reply_v2(err_code=0, num_urls=1, reserved=0, url_lifetime=667, url_len=12, urls=b'service:wbem', num_auths=0): + ''' + ''' + + pkt2 = struct.pack('>HHBHH'+str(url_len)+'sB', err_code, + num_urls, reserved, url_lifetime, url_len, urls, 0) + + return pkt2 + +######SLP_ATTR_REQ = 0x6 + + +def build_slp_attr_req_v2(): + pkt1 = build_slp_base_v2(slp_func=SLP_ATTR_REQ) + pkt2 = _slp_attr_req_v2() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v2(pkt) + + return pkt_rdy + + +def _slp_attr_req_v2(slp_prev_res_list=0, slp_svc_url_len=12, slp_svc_url=b'service:wbem', slp_scope_len=0, slp_scope=b'', slp_tag_len=0, slp_tag=b''): + + pkt2 = struct.pack('>HH'+str(slp_svc_url_len)+'sH'+str(slp_scope_len)+'sH'+str(slp_tag_len)+'sH', slp_prev_res_list, + slp_svc_url_len, slp_svc_url, slp_scope_len, slp_scope, slp_tag_len, slp_tag, 0) + + return pkt2 +####################### + + +######SLP_ATTR_REPLY = 0x7 +def build_slp_attr_reply_v2(): + pkt1 = build_slp_base_v2(slp_func=SLP_ATTR_REPLY) + pkt2 = _slp_attr_reply_v2() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v2(pkt) + + return pkt_rdy + + +def _slp_attr_reply_v2(err_code=4, attr_list_len=0, attr_list=b'', attr_auths=4): + + pkt2 = struct.pack('>HH' + str(attr_list_len) + 'sB', + err_code, + attr_list_len, + attr_list, + attr_auths + ) + + return pkt2 +########## + +#####SLP_SVC_TYPE_REQ = 0x9 +def build_slp_svc_type_req_v2(): + pkt1 = build_slp_base_v2(slp_func=SLP_SVC_TYPE_REQ) + pkt2 = _slp_svc_type_req_v2() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v2(pkt) + + return pkt_rdy + + +def _slp_svc_type_req_v2(slp_prev_res_list=0, slp_all=65535, slp_scope=b'default', slp_scope_len=7): + + pkt2 = struct.pack('>HHH'+str(slp_scope_len)+'s', slp_prev_res_list, + slp_all, slp_scope_len, slp_scope) + + return pkt2 +######################## + + +#####SLP_SVC_TYPE_REPLY = 0xa +def build_slp_type_reply_v2(): + pkt1 = build_slp_base_v2(slp_func=SLP_SVC_TYPE_REPLY) + pkt2 = _slp_type_reply_v2() + + pkt = pkt1+pkt2 + pkt_rdy = compute_len_v2(pkt) + + return pkt_rdy + + +def _slp_type_reply_v2(err_code=0, svc_type_list_len=31, svc_type_list=b'service:Windows:wbem:http:https'): + + pkt2 = struct.pack('>HH' + str(svc_type_list_len) + 's', + err_code, + svc_type_list_len, + svc_type_list + ) + + return pkt2 +########################### diff --git a/slpscan.py b/slpscan.py new file mode 100755 index 0000000..7f9e19e --- /dev/null +++ b/slpscan.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +import os +import sys +import time + +from libs.srvloc_proto_v1 import CRAFT_AUTO_LEN +from libs.srvloc_main import print_slp_modes, parser_main, run_mainthreads, choose_slp_mode +from libs.srvloc_globals import * +from libs.srvloc_log import printe, printd +from libs.srvloc_probes import open_probe_file, print_probes, probe_packet + + +def run(args): + + fname = 'libs/srvloc_probes.json' + slp_mode = args.slp_mode + global CRAFT_AUTO_LEN + + if args.probe_mode: + jprobes = open_probe_file(fname) + if args.probe_mode == '?' or args.probe_mode == 'help': + print_probes(jprobes) + sys.exit() + + else: + pkt = probe_packet(jprobes, args.probe_mode) + + else: + pkt = choose_slp_mode(args) + + print('PKT: ', pkt) + run_mainthreads(args, pkt) + + +def main(): + args = parser_main() + run(args) + +if __name__ == "__main__": + main() diff --git a/supply/blacklist.txt b/supply/blacklist.txt new file mode 100644 index 0000000..588d1ba --- /dev/null +++ b/supply/blacklist.txt @@ -0,0 +1 @@ +127.0.0.2