Files
fortra-impacket/examples/wmipersist.py
T
Gabriel Gonzalez ae0ec300f8 Techdebt examples bootstrapping v2 (#1928)
* 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
2025-03-26 22:58:39 -03:00

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)