mirror of
https://github.com/fortra/impacket
synced 2026-06-08 14:15:13 +00:00
ae0ec300f8
* Standardized examples logger init method * Fixing missing import * Standardized examples init_ldap_session function #1 * Calling new shared function _ldap3_kerberos_login from example addcomputer * Removed considering -debug in each example. Now it's handled in utils. * Standardized EMPTY_LM_HASH in impacket.examples.utils * Unify parse_identity function (phase 1) * Standardized parse_identity * Renamed "_ldap3_kerberos_login" to "ldap3_kerberos_login" (as it is called from outside) * Standardize "logger" init in regsecrets.py * Standardized LDAP login into a single function in utils.py:ldap_login
249 lines
12 KiB
Python
Executable File
249 lines
12 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Impacket - Collection of Python classes for working with network protocols.
|
|
#
|
|
# Copyright Fortra, LLC and its affiliated companies
|
|
#
|
|
# All rights reserved.
|
|
#
|
|
# This software is provided under a slightly modified version
|
|
# of the Apache Software License. See the accompanying LICENSE file
|
|
# for more information.
|
|
#
|
|
# Description:
|
|
# This script creates/removes a WMI Event Consumer/Filter and link
|
|
# between both to execute Visual Basic based on the WQL filter
|
|
# or timer specified.
|
|
#
|
|
# Example:
|
|
#
|
|
# write a file toexec.vbs the following:
|
|
# Dim objFS, objFile
|
|
# Set objFS = CreateObject("Scripting.FileSystemObject")
|
|
# Set objFile = objFS.OpenTextFile("C:\ASEC.log", 8, true)
|
|
# objFile.WriteLine "Hey There!"
|
|
# objFile.Close
|
|
#
|
|
# then execute this script this way, VBS will be triggered once
|
|
# somebody opens calc.exe:
|
|
#
|
|
# wmipersist.py domain.net/adminuser:mypwd@targetHost install -name ASEC
|
|
# -vbs toexec.vbs
|
|
# -filter 'SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance
|
|
# ISA "Win32_Process" AND TargetInstance.Name = "calc.exe"'
|
|
#
|
|
# or, if you just want to execute the VBS every XXX milliseconds:
|
|
#
|
|
# wmipersist.py domain.net/adminuser:mypwd@targetHost install -name ASEC
|
|
# -vbs toexec.vbs -timer XXX
|
|
#
|
|
# to remove the event:
|
|
# wmipersist.py domain.net/adminuser:mypwd@targetHost remove -name ASEC
|
|
#
|
|
# if you don't specify the password, it will be asked by the script.
|
|
# domain is optional.
|
|
#
|
|
# Author:
|
|
# beto (@agsolino)
|
|
#
|
|
# Reference for:
|
|
# DCOM/WMI
|
|
#
|
|
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
import sys
|
|
import argparse
|
|
import logging
|
|
|
|
from impacket.examples import logger
|
|
from impacket.examples.utils import parse_target
|
|
from impacket import version
|
|
from impacket.dcerpc.v5.dcomrt import DCOMConnection, COMVERSION
|
|
from impacket.dcerpc.v5.dcom import wmi
|
|
from impacket.dcerpc.v5.dtypes import NULL
|
|
|
|
|
|
class WMIPERSISTENCE:
|
|
def __init__(self, username='', password='', domain='', options=None):
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__domain = domain
|
|
self.__options = options
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
if options.hashes is not None:
|
|
self.__lmhash, self.__nthash = options.hashes.split(':')
|
|
|
|
@staticmethod
|
|
def checkError(banner, resp):
|
|
call_status = resp.GetCallStatus(0) & 0xffffffff # interpret as unsigned
|
|
if call_status != 0:
|
|
from impacket.dcerpc.v5.dcom.wmi import WBEMSTATUS
|
|
try:
|
|
error_name = WBEMSTATUS.enumItems(call_status).name
|
|
except ValueError:
|
|
error_name = 'Unknown'
|
|
logging.error('%s - ERROR: %s (0x%08x)' % (banner, error_name, call_status))
|
|
else:
|
|
logging.info('%s - OK' % banner)
|
|
|
|
def run(self, addr):
|
|
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
|
options.aesKey, oxidResolver=False, doKerberos=options.k, kdcHost=options.dc_ip)
|
|
|
|
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
|
|
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
|
iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/subscription', NULL, NULL)
|
|
iWbemLevel1Login.RemRelease()
|
|
|
|
if self.__options.action.upper() == 'REMOVE':
|
|
self.checkError('Removing ActiveScriptEventConsumer %s' % self.__options.name,
|
|
iWbemServices.DeleteInstance('ActiveScriptEventConsumer.Name="%s"' % self.__options.name))
|
|
|
|
self.checkError('Removing EventFilter EF_%s' % self.__options.name,
|
|
iWbemServices.DeleteInstance('__EventFilter.Name="EF_%s"' % self.__options.name))
|
|
|
|
self.checkError('Removing IntervalTimerInstruction TI_%s' % self.__options.name,
|
|
iWbemServices.DeleteInstance(
|
|
'__IntervalTimerInstruction.TimerId="TI_%s"' % self.__options.name))
|
|
|
|
self.checkError('Removing FilterToConsumerBinding %s' % self.__options.name,
|
|
iWbemServices.DeleteInstance(
|
|
r'__FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"%s\"",'
|
|
r'Filter="__EventFilter.Name=\"EF_%s\""' % (
|
|
self.__options.name, self.__options.name)))
|
|
else:
|
|
activeScript, _ = iWbemServices.GetObject('ActiveScriptEventConsumer')
|
|
activeScript = activeScript.SpawnInstance()
|
|
activeScript.Name = self.__options.name
|
|
activeScript.ScriptingEngine = 'VBScript'
|
|
activeScript.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
|
activeScript.ScriptText = options.vbs.read()
|
|
self.checkError('Adding ActiveScriptEventConsumer %s'% self.__options.name,
|
|
iWbemServices.PutInstance(activeScript.marshalMe()))
|
|
|
|
if options.filter is not None:
|
|
eventFilter, _ = iWbemServices.GetObject('__EventFilter')
|
|
eventFilter = eventFilter.SpawnInstance()
|
|
eventFilter.Name = 'EF_%s' % self.__options.name
|
|
eventFilter.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
|
eventFilter.Query = options.filter
|
|
eventFilter.QueryLanguage = 'WQL'
|
|
eventFilter.EventNamespace = r'root\cimv2'
|
|
self.checkError('Adding EventFilter EF_%s' % self.__options.name,
|
|
iWbemServices.PutInstance(eventFilter.marshalMe()))
|
|
|
|
else:
|
|
wmiTimer, _ = iWbemServices.GetObject('__IntervalTimerInstruction')
|
|
wmiTimer = wmiTimer.SpawnInstance()
|
|
wmiTimer.TimerId = 'TI_%s' % self.__options.name
|
|
wmiTimer.IntervalBetweenEvents = int(self.__options.timer)
|
|
#wmiTimer.SkipIfPassed = False
|
|
self.checkError('Adding IntervalTimerInstruction',
|
|
iWbemServices.PutInstance(wmiTimer.marshalMe()))
|
|
|
|
eventFilter,_ = iWbemServices.GetObject('__EventFilter')
|
|
eventFilter = eventFilter.SpawnInstance()
|
|
eventFilter.Name = 'EF_%s' % self.__options.name
|
|
eventFilter.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
|
eventFilter.Query = 'select * from __TimerEvent where TimerID = "TI_%s" ' % self.__options.name
|
|
eventFilter.QueryLanguage = 'WQL'
|
|
eventFilter.EventNamespace = r'root\subscription'
|
|
self.checkError('Adding EventFilter EF_%s' % self.__options.name,
|
|
iWbemServices.PutInstance(eventFilter.marshalMe()))
|
|
|
|
filterBinding, _ = iWbemServices.GetObject('__FilterToConsumerBinding')
|
|
filterBinding = filterBinding.SpawnInstance()
|
|
filterBinding.Filter = '__EventFilter.Name="EF_%s"' % self.__options.name
|
|
filterBinding.Consumer = 'ActiveScriptEventConsumer.Name="%s"' % self.__options.name
|
|
filterBinding.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
|
|
|
self.checkError('Adding FilterToConsumerBinding',
|
|
iWbemServices.PutInstance(filterBinding.marshalMe()))
|
|
|
|
dcom.disconnect()
|
|
|
|
|
|
# Process command-line arguments.
|
|
if __name__ == '__main__':
|
|
print(version.BANNER)
|
|
|
|
parser = argparse.ArgumentParser(add_help = True, description = "Creates/Removes a WMI Event Consumer/Filter and "
|
|
"link between both to execute Visual Basic based on the WQL filter or timer specified.")
|
|
|
|
parser.add_argument('target', action='store', help='[domain/][username[:password]@]<address>')
|
|
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
|
parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output')
|
|
parser.add_argument('-com-version', action='store', metavar = "MAJOR_VERSION:MINOR_VERSION", help='DCOM version, '
|
|
'format is MAJOR_VERSION:MINOR_VERSION e.g. 5.7')
|
|
subparsers = parser.add_subparsers(help='actions', dest='action')
|
|
|
|
# A start command
|
|
install_parser = subparsers.add_parser('install', help='installs the wmi event consumer/filter')
|
|
install_parser.add_argument('-name', action='store', required=True, help='event name')
|
|
install_parser.add_argument('-vbs', type=argparse.FileType('r'), required=True, help='VBS filename containing the '
|
|
'script you want to run')
|
|
install_parser.add_argument('-filter', action='store', required=False, help='the WQL filter string that will trigger'
|
|
' the script')
|
|
install_parser.add_argument('-timer', action='store', required=False, help='the amount of milliseconds after the'
|
|
' script will be triggered')
|
|
|
|
# A stop command
|
|
remove_parser = subparsers.add_parser('remove', help='removes the wmi event consumer/filter')
|
|
remove_parser.add_argument('-name', action='store', required=True, help='event name')
|
|
|
|
group = parser.add_argument_group('authentication')
|
|
|
|
group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
|
group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
|
|
group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
|
|
'(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
|
|
'ones specified in the command line')
|
|
group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
|
|
'(128 or 256 bits)')
|
|
group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If '
|
|
'ommited it use the domain part (FQDN) specified in the target parameter')
|
|
|
|
if len(sys.argv)==1:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
options = parser.parse_args()
|
|
# Init the example's logger theme
|
|
logger.init(options.ts, options.debug)
|
|
|
|
if options.com_version is not None:
|
|
try:
|
|
major_version, minor_version = options.com_version.split('.')
|
|
COMVERSION.set_default_version(int(major_version), int(minor_version))
|
|
except Exception:
|
|
logging.error("Wrong COMVERSION format, use dot separated integers e.g. \"5.7\"")
|
|
sys.exit(1)
|
|
|
|
if options.action.upper() == 'INSTALL':
|
|
if (options.filter is None and options.timer is None) or (options.filter is not None and options.timer is not None):
|
|
logging.error("You have to either specify -filter or -timer (and not both)")
|
|
sys.exit(1)
|
|
|
|
domain, username, password, address = parse_target(options.target)
|
|
|
|
try:
|
|
if domain is None:
|
|
domain = ''
|
|
|
|
if options.aesKey is not None:
|
|
options.k = True
|
|
|
|
if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
|
|
from getpass import getpass
|
|
password = getpass("Password:")
|
|
|
|
executer = WMIPERSISTENCE(username, password, domain, options)
|
|
executer.run(address)
|
|
except (Exception, KeyboardInterrupt) as e:
|
|
if logging.getLogger().level == logging.DEBUG:
|
|
import traceback
|
|
traceback.print_exc()
|
|
logging.error(e)
|
|
sys.exit(0)
|